python_sql-1.8.0/sql/__init__.py0000644000000000000000000017750213615410400013564 0ustar00# This file is part of python-sql. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import numbers import string import warnings from collections import defaultdict from itertools import chain from threading import current_thread, local __version__ = '1.8.0' __all__ = [ 'Flavor', 'Table', 'Values', 'Literal', 'Column', 'Grouping', 'Conflict', 'Matched', 'MatchedUpdate', 'MatchedDelete', 'NotMatched', 'NotMatchedInsert', 'Rollup', 'Cube', 'Excluded', 'Join', 'Asc', 'Desc', 'NullsFirst', 'NullsLast', 'format2numeric'] def _escape_identifier(name): return '"%s"' % name.replace('"', '""') def alias(i, letters=string.ascii_lowercase): ''' Generate a unique alias based on integer >>> [alias(n) for n in range(6)] ['a', 'b', 'c', 'd', 'e', 'f'] >>> [alias(n) for n in range(26, 30)] ['ba', 'bb', 'bc', 'bd'] >>> [alias(26**n) for n in range(5)] ['b', 'ba', 'baa', 'baaa', 'baaaa'] ''' s = '' length = len(letters) while True: r = i % length s = letters[r] + s i //= length if i == 0: break return s class Flavor(object): ''' Contains the flavor of SQL Contains: limitstyle - state the type of pagination max_limit - limit to use if there is no limit but an offset paramstyle - state the type of parameter marker formatting ilike - support ilike extension no_as - doesn't support AS keyword for column and table no_boolean - doesn't support boolean type null_ordering - support NULL ordering function_mapping - dictionary with Function to replace filter_ - support filter on aggregate functions escape_empty - support empty escape ''' def __init__(self, limitstyle='limit', max_limit=None, paramstyle='format', ilike=False, no_as=False, no_boolean=False, null_ordering=True, function_mapping=None, filter_=False, escape_empty=False): if limitstyle not in {'fetch', 'limit', 'rownum'}: raise ValueError("unsupported limitstyle: %r" % limitstyle) self.limitstyle = limitstyle if (max_limit is not None and not isinstance(max_limit, numbers.Integral)): raise ValueError("unsupported max_limit: %r" % max_limit) self.max_limit = max_limit if paramstyle not in {'format', 'qmark'}: raise ValueError("unsupported paramstyle: %r" % paramstyle) self.paramstyle = paramstyle self.ilike = bool(ilike) self.no_as = bool(no_as) self.no_boolean = bool(no_boolean) self.null_ordering = bool(null_ordering) self.function_mapping = dict(function_mapping or {}) self.filter_ = bool(filter_) self.escape_empty = bool(escape_empty) @property def param(self): if self.paramstyle == 'format': return '%s' elif self.paramstyle == 'qmark': return '?' @staticmethod def set(flavor): '''Set this thread's flavor to flavor.''' current_thread().__sql_flavor__ = flavor @staticmethod def get(): ''' Return this thread's flavor. If this thread does not yet have a flavor, returns a new flavor and sets this thread's flavor. ''' try: return current_thread().__sql_flavor__ except AttributeError: flavor = Flavor() current_thread().__sql_flavor__ = flavor return flavor class AliasManager(object): ''' Context Manager for unique alias generation ''' __slots__ = () local = local() local.alias = None local.nested = 0 local.exclude = None def __init__(self, exclude=None): if exclude: if getattr(self.local, 'exclude', None) is None: self.local.exclude = [] self.local.exclude.extend(exclude) @classmethod def __enter__(cls): if getattr(cls.local, 'alias', None) is None: cls.local.alias = defaultdict(cls.alias_factory) cls.local.nested = 0 if getattr(cls.local, 'exclude', None) is None: cls.local.exclude = [] cls.local.nested += 1 @classmethod def __exit__(cls, type, value, traceback): cls.local.nested -= 1 if not cls.local.nested: cls.local.alias = None cls.local.exclude = None @classmethod def get(cls, from_): if getattr(cls.local, 'alias', None) is None: return '' if from_ in cls.local.exclude: return '' return cls.local.alias[id(from_)] @classmethod def contains(cls, from_): if getattr(cls.local, 'alias', None) is None: return False if from_ in cls.local.exclude: return False return id(from_) in cls.local.alias @classmethod def set(cls, from_, alias): assert cls.local.alias.get(from_) is None cls.local.alias[id(from_)] = alias @classmethod def alias_factory(cls): i = len(cls.local.alias) return alias(i) def format2numeric(query, params): ''' Convert format paramstyle query to numeric paramstyle >>> format2numeric('SELECT * FROM table WHERE col = %s', ('foo',)) ('SELECT * FROM table WHERE col = :0', ('foo',)) >>> format2numeric('SELECT * FROM table WHERE col1 = %s AND col2 = %s', ... ('foo', 'bar')) ('SELECT * FROM table WHERE col1 = :0 AND col2 = :1', ('foo', 'bar')) ''' return (query % tuple(':%i' % i for i, _ in enumerate(params)), params) class Query(object): __slots__ = ('__weakref__',) @property def params(self): return () def __iter__(self): yield str(self) yield self.params def __or__(self, other): return Union(self, other) def __and__(self, other): return Intersect(self, other) def __sub__(self, other): return Except(self, other) class WithQuery(Query): __slots__ = ('_with',) def __init__(self, **kwargs): self._with = None self.with_ = kwargs.pop('with_', None) super(Query, self).__init__(**kwargs) @property def with_(self): return self._with @with_.setter def with_(self, value): if value is not None: if isinstance(value, With): value = [value] if any(not isinstance(w, With) for w in value): raise ValueError("invalid with: %r" % value) self._with = value def _with_str(self): if not self.with_: return '' recursive = (' RECURSIVE' if any(w.recursive for w in self.with_) else '') with_ = ('WITH%s ' % recursive + ', '.join(w.statement() for w in self.with_) + ' ') return with_ def _with_params(self): if not self.with_: return () params = [] for w in self.with_: params.extend(w.statement_params()) return tuple(params) class FromItem(object): __slots__ = ('__weakref__',) @property def alias(self): return AliasManager.get(self) @property def has_alias(self): return AliasManager.contains(self) def __getattr__(self, name): if name.startswith('__'): raise AttributeError return Column(self, name) def __add__(self, other): if not isinstance(other, FromItem): return NotImplemented return From((self, other)) def select(self, *args, **kwargs): return From((self,)).select(*args, **kwargs) def join(self, right, type_='INNER', condition=None): return Join(self, right, type_=type_, condition=condition) def left_join(self, right, condition=None): return self.join(right, type_='LEFT', condition=condition) def left_outer_join(self, right, condition=None): return self.join(right, type_='LEFT OUTER', condition=condition) def right_join(self, right, condition=None): return self.join(right, type_='RIGHT', condition=condition) def right_outer_join(self, right, condition=None): return self.join(right, type_='RIGHT OUTER', condition=condition) def full_join(self, right, condition=None): return self.join(right, type_='FULL', condition=condition) def full_outer_join(self, right, condition=None): return self.join(right, type_='FULL OUTER', condition=condition) def cross_join(self, right, condition=None): return self.join(right, type_='CROSS', condition=condition) def lateral(self): return Lateral(self) class Lateral(FromItem): __slots__ = ('_from_item',) def __init__(self, from_item): self._from_item = from_item def __str__(self): template = '%s' if isinstance(self._from_item, Query): template = '(%s)' return 'LATERAL ' + template % self._from_item def __getattr__(self, name): return getattr(self._from_item, name) class With(FromItem): __slots__ = ('columns', 'query', 'recursive') def __init__(self, *columns, **kwargs): self.recursive = kwargs.pop('recursive', False) self.columns = columns self.query = kwargs.pop('query', None) super(With, self).__init__(**kwargs) def statement(self): columns = (' (%s)' % ', '.join('"%s"' % c for c in self.columns) if self.columns else '') return '"%s"%s AS (%s)' % (self.alias, columns, self.query) def statement_params(self): return self.query.params def __str__(self): return '"%s"' % self.alias @property def params(self): return tuple() class SelectQuery(WithQuery): __slots__ = ('_order_by', '_limit', '_offset') def __init__(self, *args, **kwargs): self._order_by = None self._limit = None self._offset = None self.order_by = kwargs.pop('order_by', None) self.limit = kwargs.pop('limit', None) self.offset = kwargs.pop('offset', None) super(SelectQuery, self).__init__(*args, **kwargs) @property def order_by(self): return self._order_by @order_by.setter def order_by(self, value): if value is not None: if isinstance(value, Expression): value = [value] if any(not isinstance(col, Expression) for col in value): raise ValueError("invalid order by: %r" % value) self._order_by = value @property def _order_by_str(self): order_by = '' if self.order_by: order_by = ' ORDER BY ' + ', '.join(map(str, self.order_by)) return order_by @property def limit(self): return self._limit @limit.setter def limit(self, value): if value is not None: if not isinstance(value, numbers.Integral): raise ValueError("invalid limit: %r" % value) self._limit = value @property def offset(self): return self._offset @offset.setter def offset(self, value): if value is not None: if not isinstance(value, numbers.Integral): raise ValueError("invalid offset: %r" % value) self._offset = value @property def _limit_offset_str(self): param = Flavor.get().param if Flavor.get().limitstyle == 'limit': offset = '' if self.offset: offset = ' OFFSET %s' % param limit = '' if self.limit is not None: limit = ' LIMIT %s' % param elif self.offset: max_limit = Flavor.get().max_limit if max_limit: limit = ' LIMIT %s' % max_limit return limit + offset else: offset = '' if self.offset: offset = ' OFFSET (%s) ROWS' % param fetch = '' if self.limit is not None: fetch = ' FETCH FIRST (%s) ROWS ONLY' % param return offset + fetch @property def _limit_offset_params(self): p = [] if Flavor.get().limitstyle == 'limit': if self.limit is not None: p.append(self.limit) if self.offset: p.append(self.offset) else: if self.offset: p.append(self.offset) if self.limit is not None: p.append(self.limit) return tuple(p) def as_(self, output_name): return As(self, output_name) class Select(FromItem, SelectQuery): __slots__ = ('_columns', '_where', '_group_by', '_having', '_for_', 'from_', '_distinct', '_distinct_on', '_windows') def __init__(self, columns, from_=None, where=None, group_by=None, having=None, for_=None, distinct=False, distinct_on=None, windows=None, **kwargs): self._distinct = False self._distinct_on = [] self._columns = None self._where = None self._group_by = None self._having = None self._for_ = None self._windows = [] super(Select, self).__init__(**kwargs) self.distinct = distinct self.distinct_on = distinct_on self.columns = columns self.from_ = from_ self.where = where self.group_by = group_by self.having = having self.for_ = for_ self.windows = windows @property def distinct(self): return bool(self._distinct or self._distinct_on) @distinct.setter def distinct(self, value): self._distinct = bool(value) @property def distinct_on(self): return self._distinct_on @distinct_on.setter def distinct_on(self, value): if value is not None: if isinstance(value, Expression): value = [value] if any(not isinstance(col, Expression) for col in value): raise ValueError("invalid distinct on: %r" % value) self._distinct_on = value @property def columns(self): return self._columns @columns.setter def columns(self, value): if any( not isinstance(col, (Expression, SelectQuery)) for col in value): raise ValueError("invalid columns: %r" % value) self._columns = tuple(value) @property def where(self): return self._where @where.setter def where(self, value): from sql.operators import And, Or if value is not None: if not isinstance(value, (Expression, And, Or)): raise ValueError("invalid where: %r" % value) self._where = value @property def group_by(self): return self._group_by @group_by.setter def group_by(self, value): if value is not None: if isinstance(value, Expression): value = [value] if any(not isinstance(col, Expression) for col in value): raise ValueError("invalid group by: %r" % value) self._group_by = value @property def having(self): return self._having @having.setter def having(self, value): from sql.operators import And, Or if value is not None: if not isinstance(value, (Expression, And, Or)): raise ValueError("invalid having: %r" % value) self._having = value @property def for_(self): return self._for_ @for_.setter def for_(self, value): if value is not None: if isinstance(value, For): value = [value] if any(not isinstance(f, For) for f in value): raise ValueError("invalid for: %r" % value) self._for_ = value @property def windows(self): from sql.aggregate import Aggregate from sql.functions import WindowFunction windows = set() if self._windows is not None: for window in self._windows: windows.add(window) yield window for column in self.columns: window_function = None if isinstance(column, (WindowFunction, Aggregate)): window_function = column elif (isinstance(column, As) and isinstance(column.expression, (WindowFunction, Aggregate))): window_function = column.expression if (window_function and window_function.window and window_function.window not in windows): windows.add(window_function.window) yield window_function.window @windows.setter def windows(self, value): if value is not None: if any(not isinstance(w, Window) for w in value): raise ValueError("invalid windows: %r" % value) self._windows = value @staticmethod def _format_column(column): if isinstance(column, As): if isinstance(column.expression, Select): expression = '(%s)' % column.expression else: expression = column.expression if Flavor.get().no_as: return '%s %s' % (expression, column) else: return '%s AS %s' % (expression, column) else: if isinstance(column, Select): return '(%s)' % column else: return str(column) def _rownum(self, func): aliases = [c.output_name if isinstance(c, As) else None for c in self.columns] def columns(table): if aliases and all(aliases): return [Column(table, alias) for alias in aliases] else: return [Column(table, '*')] limitselect = self.select(*columns(self)) if self.limit is not None: max_row = self.limit if self.offset is not None: max_row += self.offset limitselect.where = _rownum <= max_row if self.offset is not None: rnum = _rownum.as_('rnum') limitselect.columns += (rnum,) offsetselect = limitselect.select(*columns(limitselect), where=rnum > self.offset) query = offsetselect else: query = limitselect self.limit, limit = None, self.limit self.offset, offset = None, self.offset query.for_, self.for_ = self.for_, None try: value = func(query) finally: self.limit = limit self.offset = offset self.for_ = query.for_ return value def __str__(self): if (Flavor.get().limitstyle == 'rownum' and (self.limit is not None or self.offset is not None)): return self._rownum(str) ordinals = {} for expression in chain( self.group_by or [], self.order_by or []): if not isinstance(expression, As): continue for i, column in enumerate(self.columns, start=1): if not isinstance(column, As): continue if column.output_name != expression.output_name: continue if (str(column.expression) != str(expression.expression) or column.params != expression.params): raise ValueError("%r != %r" % (expression, column)) ordinals[column.output_name] = i def str_or_ordinal(expression): if isinstance(expression, As): expression = ordinals.get(expression.output_name, expression) return str(expression) with AliasManager(): if self.from_ is not None: from_ = ' FROM %s' % self.from_ else: from_ = '' # format window before expressions to set alias window = ', '.join( '"%s" AS (%s)' % (w.alias, w) for w in self.windows) if window: window = ' WINDOW ' + window if self.distinct: distinct = 'DISTINCT ' if self.distinct_on: distinct += ('ON (%s) ' % ', '.join(map(str, self.distinct_on))) else: distinct = '' if self.columns: columns = ', '.join(map(self._format_column, self.columns)) else: columns = '*' where = '' if self.where: where = ' WHERE ' + str(self.where) group_by = '' if self.group_by: group_by = ' GROUP BY ' + ', '.join( map(str_or_ordinal, self.group_by)) having = '' if self.having: having = ' HAVING ' + str(self.having) for_ = '' if self.for_ is not None: for_ = ' ' + ' '.join(map(str, self.for_)) return (self._with_str() + 'SELECT %s%s%s' % (distinct, columns, from_) + where + group_by + having + window + self._order_by_str + self._limit_offset_str + for_) @property def params(self): if (Flavor.get().limitstyle == 'rownum' and (self.limit is not None or self.offset is not None)): return self._rownum(lambda q: q.params) p = [] with AliasManager(): # Set alias to window function to skip their params for window in self.windows: window.alias p.extend(self._with_params()) for column in chain(self.distinct_on or (), self.columns): if isinstance(column, As): p.extend(column.expression.params) p.extend(column.params) if self.from_ is not None: p.extend(self.from_.params) if self.where: p.extend(self.where.params) if self.group_by: for expression in self.group_by: p.extend(expression.params) if self.having: p.extend(self.having.params) for window in self.windows: p.extend(window.params) if self.order_by: for expression in self.order_by: p.extend(expression.params) p.extend(self._limit_offset_params) return tuple(p) class Insert(WithQuery): __slots__ = ('_table', '_columns', '_values', '_on_conflict', '_returning') def __init__( self, table, columns=None, values=None, returning=None, on_conflict=None, **kwargs): self._table = None self._columns = None self._values = None self._on_conflict = None self._returning = None self.table = table self.columns = columns self.values = values self.on_conflict = on_conflict self.returning = returning super(Insert, self).__init__(**kwargs) @property def table(self): return self._table @table.setter def table(self, value): if not isinstance(value, Table): raise ValueError("invalid table: %r" % value) self._table = value @property def columns(self): return self._columns @columns.setter def columns(self, value): if value is not None: if any( not isinstance(col, Column) or col.table != self.table for col in value): raise ValueError("invalid columns: %r" % value) self._columns = value @property def values(self): return self._values @values.setter def values(self, value): if value is not None: if not isinstance(value, (list, Select)): raise ValueError("invalid values: %r" % value) if isinstance(value, list): value = Values(value) self._values = value @property def on_conflict(self): return self._on_conflict @on_conflict.setter def on_conflict(self, value): if value is not None: if not isinstance(value, Conflict) or value.table != self.table: raise ValueError("invalid on conflict: %r" % value) self._on_conflict = value @property def returning(self): return self._returning @returning.setter def returning(self, value): if value is not None: if not isinstance(value, list): raise ValueError("invalid returning: %r" % value) self._returning = value @staticmethod def _format(value, param=None): if param is None: param = Flavor.get().param if isinstance(value, Expression): return str(value) elif isinstance(value, Select): return '(%s)' % value else: return param def __str__(self): columns = '' if self.columns: assert all(col.table == self.table for col in self.columns) # Get columns without alias columns = ', '.join(c.column_name for c in self.columns) columns = ' (' + columns + ')' with AliasManager(): if isinstance(self.values, Query): values = ' %s' % str(self.values) # TODO manage DEFAULT elif self.values is None: values = ' DEFAULT VALUES' on_conflict = '' if self.on_conflict: on_conflict = ' %s' % self.on_conflict returning = '' if self.returning: returning = ' RETURNING ' + ', '.join( map(self._format, self.returning)) if on_conflict or returning: table = '%s AS "%s"' % (self.table, self.table.alias) else: table = str(self.table) return (self._with_str() + 'INSERT INTO %s' % table + columns + values + on_conflict + returning) @property def params(self): p = [] p.extend(self._with_params()) if isinstance(self.values, Query): p.extend(self.values.params) if self.on_conflict: p.extend(self.on_conflict.params) if self.returning: for exp in self.returning: p.extend(exp.params) return tuple(p) class Conflict(object): __slots__ = ( '_table', '_indexed_columns', '_index_where', '_columns', '_values', '_where') def __init__( self, table, indexed_columns=None, index_where=None, columns=None, values=None, where=None): self._table = None self._indexed_columns = None self._index_where = None self._columns = None self._values = None self._where = None self.table = table self.indexed_columns = indexed_columns self.index_where = index_where self.columns = columns self.values = values self.where = where @property def table(self): return self._table @table.setter def table(self, value): if not isinstance(value, Table): raise ValueError("invalid table: %r" % value) self._table = value @property def indexed_columns(self): return self._indexed_columns @indexed_columns.setter def indexed_columns(self, value): if value is not None: if any( not isinstance(col, Column) or col.table != self.table for col in value): raise ValueError("invalid indexed columns: %r" % value) self._indexed_columns = value @property def index_where(self): return self._index_where @index_where.setter def index_where(self, value): from sql.operators import And, Or if value is not None: if not isinstance(value, (Expression, And, Or)): raise ValueError("invalid index where: %r" % value) self._index_where = value @property def columns(self): return self._columns @columns.setter def columns(self, value): if value is not None: if any( not isinstance(col, Column) or col.table != self.table for col in value): raise ValueError("invalid columns: %r" % value) self._columns = value @property def values(self): return self._values @values.setter def values(self, value): if value is not None: if not isinstance(value, (list, Select)): raise ValueError("invalid values: %r" % value) if isinstance(value, list): value = Values([value]) self._values = value @property def where(self): return self._where @where.setter def where(self, value): from sql.operators import And, Or if value is not None: if not isinstance(value, (Expression, And, Or)): raise ValueError("invalid where: %r" % value) self._where = value def __str__(self): indexed_columns = '' if self.indexed_columns: assert all(c.table == self.table for c in self.indexed_columns) # Get columns without alias indexed_columns = ', '.join( c.column_name for c in self.indexed_columns) indexed_columns = ' (' + indexed_columns + ')' if self.index_where: indexed_columns += ' WHERE ' + str(self.index_where) else: assert not self.index_where do = '' if not self.columns: assert not self.values assert not self.where do = 'NOTHING' else: assert all(c.table == self.table for c in self.columns) # Get columns without alias do = ', '.join(c.column_name for c in self.columns) # TODO manage DEFAULT values = str(self.values) if values.startswith('VALUES'): values = values[len('VALUES'):] else: values = ' (' + values + ')' if len(self.columns) == 1: # PostgreSQL would require ROW expression # with single column with parenthesis do = 'UPDATE SET ' + do + ' =' + values else: do = 'UPDATE SET (' + do + ') =' + values if self.where: do += ' WHERE %s' % self.where return 'ON CONFLICT' + indexed_columns + ' DO ' + do @property def params(self): p = [] if self.index_where: p.extend(self.index_where.params) if self.values: p.extend(self.values.params) if self.where: p.extend(self.where.params) return p class Update(Insert): __slots__ = ('_where', '_values', 'from_') def __init__(self, table, columns, values, from_=None, where=None, returning=None, **kwargs): super(Update, self).__init__(table, columns=columns, values=values, returning=returning, **kwargs) self._where = None self.from_ = From(from_) if from_ else None self.where = where @property def values(self): return self._values @values.setter def values(self, value): if isinstance(value, Select): value = [value] if not isinstance(value, list): raise ValueError("invalid values: %r" % value) self._values = value @property def where(self): return self._where @where.setter def where(self, value): from sql.operators import And, Or if value is not None: if not isinstance(value, (Expression, And, Or)): raise ValueError("invalid where: %r" % value) self._where = value @staticmethod def _format_column(value): return Select._format_column(value) def __str__(self): assert all(col.table == self.table for col in self.columns) # Get columns without alias columns = [c.column_name for c in self.columns] with AliasManager(): from_ = '' if self.from_: from_ = ' FROM %s' % str(self.from_) values = ', '.join('%s = %s' % (c, self._format(v)) for c, v in zip(columns, self.values)) where = '' if self.where: where = ' WHERE ' + str(self.where) returning = '' if self.returning: returning = ' RETURNING ' + ', '.join( map(self._format_column, self.returning)) return (self._with_str() + 'UPDATE %s AS "%s" SET ' % (self.table, self.table.alias) + values + from_ + where + returning) @property def params(self): p = [] p.extend(self._with_params()) for value in self.values: if isinstance(value, (Expression, Select)): p.extend(value.params) else: p.append(value) if self.from_: p.extend(self.from_.params) if self.where: p.extend(self.where.params) if self.returning: for exp in self.returning: p.extend(exp.params) return tuple(p) class Delete(WithQuery): __slots__ = ('_table', '_where', '_returning', 'only') def __init__(self, table, only=False, using=None, where=None, returning=None, **kwargs): self._table = None self._where = None self._returning = None self.table = table self.only = only # TODO using (not standard) self.where = where self.returning = returning super(Delete, self).__init__(**kwargs) @property def table(self): return self._table @table.setter def table(self, value): if not isinstance(value, Table): raise ValueError("invalid table: %r" % value) self._table = value @property def where(self): return self._where @where.setter def where(self, value): from sql.operators import And, Or if value is not None: if not isinstance(value, (Expression, And, Or)): raise ValueError("invalid where: %r" % value) self._where = value @property def returning(self): return self._returning @returning.setter def returning(self, value): if value is not None: if any( not isinstance(col, (Expression, SelectQuery)) for col in value): raise ValueError("invalid returning: %r" % value) self._returning = value @staticmethod def _format(value): return Select._format_column(value) def __str__(self): with AliasManager(exclude=[self.table]): only = ' ONLY' if self.only else '' where = '' if self.where: where = ' WHERE ' + str(self.where) returning = '' if self.returning: returning = ' RETURNING ' + ', '.join( map(self._format, self.returning)) return (self._with_str() + 'DELETE FROM%s %s' % (only, self.table) + where + returning) @property def params(self): p = [] p.extend(self._with_params()) if self.where: p.extend(self.where.params) if self.returning: for exp in self.returning: p.extend(exp.params) return tuple(p) class Merge(WithQuery): __slots__ = ('_target', '_source', '_condition', '_whens') def __init__(self, target, source, condition, *whens, **kwargs): self._target = None self._source = None self._condition = None self._whens = None self.target = target self.source = source self.condition = condition self.whens = whens super().__init__(**kwargs) @property def target(self): return self._target @target.setter def target(self, value): if not isinstance(value, Table): raise ValueError("invalid target: %r" % value) self._target = value @property def source(self): return self._source @source.setter def source(self, value): if not isinstance(value, (Table, SelectQuery, Values)): raise ValueError("invalid source: %r" % value) self._source = value @property def condition(self): return self._condition @condition.setter def condition(self, value): if not isinstance(value, Expression): raise ValueError("invalid condition: %r" % value) self._condition = value @property def whens(self): return self._whens @whens.setter def whens(self, value): if any(not isinstance(w, Matched) for w in value): raise ValueError("invalid whens: %r" % value) self._whens = tuple(value) def __str__(self): with AliasManager(): if isinstance(self.source, (Select, Values)): source = '(%s)' % self.source else: source = self.source condition = 'ON %s' % self.condition return (self._with_str() + 'MERGE INTO %s AS "%s" ' % (self.target, self.target.alias) + 'USING %s AS "%s" ' % (source, self.source.alias) + condition + ' ' + ' '.join(map(str, self.whens))) @property def params(self): p = [] p.extend(self._with_params()) if isinstance(self.source, (SelectQuery, Values)): p.extend(self.source.params) if self.condition: p.extend(self.condition.params) for match in self.whens: p.extend(match.params) return tuple(p) class Matched(object): __slots__ = ('_condition',) _when = 'MATCHED' def __init__(self, condition=None): self._condition = None self.condition = condition @property def condition(self): return self._condition @condition.setter def condition(self, value): if value is not None: if not isinstance(value, Expression): raise ValueError("invalid condition: %r" % value) self._condition = value def _then_str(self): return 'DO NOTHING' def __str__(self): if self.condition is not None: condition = ' AND ' + str(self.condition) else: condition = '' return 'WHEN ' + self._when + condition + ' THEN ' + self._then_str() @property def params(self): p = [] if self.condition: p.extend(self.condition.params) return tuple(p) class _MatchedValues(Matched): __slots__ = ('_columns', '_values') def __init__(self, columns, values, **kwargs): self._columns = columns self._values = values self.columns = columns self.values = values super().__init__(**kwargs) @property def columns(self): return self._columns @columns.setter def columns(self, value): if any(not isinstance(col, Column) for col in value): raise ValueError("invalid columns: %r" % value) self._columns = value class MatchedUpdate(_MatchedValues, Matched): __slots__ = () @property def values(self): return self._values @values.setter def values(self, value): self._values = value def _then_str(self): columns = [c.column_name for c in self.columns] return 'UPDATE SET ' + ', '.join( '%s = %s' % (c, Update._format(v)) for c, v in zip(columns, self.values)) @property def params(self): p = list(super().params) for value in self.values: if isinstance(value, (Expression, Select)): p.extend(value.params) else: p.append(value) return tuple(p) class MatchedDelete(Matched): __slots__ = () def _then_str(self): return 'DELETE' class NotMatched(Matched): __slots__ = () _when = 'NOT MATCHED' class NotMatchedInsert(_MatchedValues, NotMatched): __slots__ = () @property def values(self): return self._values @values.setter def values(self, value): if value is not None: value = Values([value]) self._values = value def _then_str(self): columns = ', '.join(c.column_name for c in self.columns) columns = '(' + columns + ')' if self.values is None: values = ' DEFAULT VALUES' else: values = ' ' + str(self.values) return 'INSERT ' + columns + values @property def params(self): p = list(super().params) if self.values: p.extend(self.values.params) return tuple(p) class CombiningQuery(FromItem, SelectQuery): __slots__ = ('queries', 'all_') _operator = '' def __init__(self, *queries, **kwargs): if any(not isinstance(q, Query) for q in queries): raise ValueError("invalid queries: %r" % (queries,)) self.queries = queries self.all_ = kwargs.pop('all_', False) super(CombiningQuery, self).__init__(**kwargs) def __str__(self): with AliasManager(): operator = ' %s %s' % (self._operator, 'ALL ' if self.all_ else '') return ( self._with_str() + operator.join(map(str, self.queries)) + self._order_by_str + self._limit_offset_str) @property def params(self): p = [] with AliasManager(): p.extend(self._with_params()) for q in self.queries: p.extend(q.params) if self.order_by: for expression in self.order_by: p.extend(expression.params) return tuple(p) class Union(CombiningQuery): __slots__ = () _operator = 'UNION' class Intersect(CombiningQuery): __slots__ = () _operator = 'INTERSECT' class Interesect(Intersect): def __init__(self, *args, **kwargs): warnings.warn('Interesect query is deprecated, use Intersect', DeprecationWarning, stacklevel=2) super(Interesect, self).__init__(*args, **kwargs) class Except(CombiningQuery): __slots__ = () _operator = 'EXCEPT' class Table(FromItem): __slots__ = ('_name', '_schema', '_database') def __init__(self, name, schema=None, database=None): super(Table, self).__init__() self._name = name self._schema = schema self._database = database def __str__(self): return '.'.join(map(_escape_identifier, filter(None, (self._database, self._schema, self._name)))) @property def params(self): return () def insert( self, columns=None, values=None, returning=None, with_=None, on_conflict=None): return Insert(self, columns=columns, values=values, on_conflict=on_conflict, returning=returning, with_=with_) def update(self, columns, values, from_=None, where=None, returning=None, with_=None): return Update(self, columns=columns, values=values, from_=from_, where=where, returning=returning, with_=with_) def delete(self, only=False, using=None, where=None, returning=None, with_=None): return Delete(self, only=only, using=using, where=where, returning=returning, with_=with_) def merge(self, source, condition, *whens, with_=None): return Merge(self, source, condition, *whens, with_=with_) class _Excluded(Table): def __init__(self): super().__init__('EXCLUDED') @property def alias(self): return 'EXCLUDED' @property def has_alias(self): return False Excluded = _Excluded() class Join(FromItem): __slots__ = ('_left', '_right', '_condition', '_type_') def __init__(self, left, right, type_='INNER', condition=None): super(Join, self).__init__() self._left, self._right = None, None self._condition = None self._type_ = None self.left = left self.right = right self.condition = condition self.type_ = type_ @property def left(self): return self._left @left.setter def left(self, value): if not isinstance(value, FromItem): raise ValueError("invalid left: %r" % value) self._left = value @property def right(self): return self._right @right.setter def right(self, value): if not isinstance(value, FromItem): raise ValueError("invalid right: %r" % value) self._right = value @property def condition(self): return self._condition @condition.setter def condition(self, value): from sql.operators import And, Or if value is not None: if not isinstance(value, (Expression, And, Or)): raise ValueError("invalid condition: %r" % value) self._condition = value @property def type_(self): return self._type_ @type_.setter def type_(self, value): value = value.upper() if value not in { 'INNER', 'LEFT', 'LEFT OUTER', 'RIGHT', 'RIGHT OUTER', 'FULL', 'FULL OUTER', 'CROSS'}: raise ValueError("invalid type: %r" % value) self._type_ = value def __str__(self): join = '%s %s JOIN %s' % (From([self.left]), self.type_, From([self.right])) if self.condition: condition = ' ON %s' % self.condition else: condition = '' return join + condition @property def params(self): p = [] for item in (self.left, self.right): p.extend(item.params) if self.condition: p.extend(self.condition.params) return tuple(p) @property def alias(self): raise AttributeError @property def has_alias(self): raise AttributeError def __getattr__(self, name): raise AttributeError def select(self, *args, **kwargs): return super(Join, self).select(*args, **kwargs) class From(list): __slots__ = () def select(self, *args, **kwargs): return Select(args, from_=self, **kwargs) def __str__(self): def format(from_): template = '%s' if isinstance(from_, Query): template = '(%s)' alias = getattr(from_, 'alias', None) # TODO column_alias columns_definitions = getattr(from_, 'columns_definitions', None) if Flavor.get().no_as: alias_template = ' "%s"' else: alias_template = ' AS "%s"' # XXX find a better test for __getattr__ which returns Column if (alias and columns_definitions and not isinstance(columns_definitions, Column)): return (template + alias_template + ' (%s)') % (from_, alias, columns_definitions) elif alias: return (template + alias_template) % (from_, alias) else: return template % from_ return ', '.join(map(format, self)) @property def params(self): p = [] for from_ in self: p.extend(from_.params) return tuple(p) def __add__(self, other): if not isinstance(other, FromItem): return NotImplemented elif isinstance(other, CombiningQuery): return NotImplemented return From(super(From, self).__add__([other])) class Values(list, Query, FromItem): __slots__ = () # TODO order, fetch def __str__(self): param = Flavor.get().param def format_(value): if isinstance(value, Expression): return str(value) else: return param return 'VALUES ' + ', '.join( '(%s)' % ', '.join(map(format_, v)) for v in self) @property def params(self): p = list(super().params) for values in self: for value in values: if isinstance(value, Expression): p.extend(value.params) else: p.append(value) return tuple(p) class Expression(object): __slots__ = ('__weakref__',) def __str__(self): raise NotImplementedError @property def params(self): raise NotImplementedError def __and__(self, other): from sql.operators import And return And((self, other)) def __or__(self, other): from sql.operators import Or return Or((self, other)) def __invert__(self): from sql.operators import Not return Not(self) def __add__(self, other): from sql.operators import Add return Add(self, other) def __sub__(self, other): from sql.operators import Sub return Sub(self, other) def __mul__(self, other): from sql.operators import Mul return Mul(self, other) def __div__(self, other): from sql.operators import Div return Div(self, other) __truediv__ = __div__ def __floordiv__(self, other): from sql.functions import Div return Div(self, other) def __mod__(self, other): from sql.operators import Mod return Mod(self, other) def __pow__(self, other): from sql.operators import Pow return Pow(self, other) def __neg__(self): from sql.operators import Neg return Neg(self) def __pos__(self): from sql.operators import Pos return Pos(self) def __abs__(self): from sql.operators import Abs return Abs(self) def __lshift__(self, other): from sql.operators import LShift return LShift(self, other) def __rshift__(self, other): from sql.operators import RShift return RShift(self, other) def __lt__(self, other): from sql.operators import Less return Less(self, other) def __le__(self, other): from sql.operators import LessEqual return LessEqual(self, other) def __eq__(self, other): from sql.operators import Equal return Equal(self, other) # When overriding __eq__, __hash__ is implicitly set to None __hash__ = object.__hash__ def __ne__(self, other): from sql.operators import NotEqual return NotEqual(self, other) def __gt__(self, other): from sql.operators import Greater return Greater(self, other) def __ge__(self, other): from sql.operators import GreaterEqual return GreaterEqual(self, other) def in_(self, values): from sql.operators import In return In(self, values) def like(self, test): from sql.operators import Like return Like(self, test) def ilike(self, test): from sql.operators import ILike return ILike(self, test) def as_(self, output_name): return As(self, output_name) def cast(self, typename): return Cast(self, typename) def collate(self, collation): return Collate(self, collation) @property def asc(self): return Asc(self) @property def desc(self): return Desc(self) @property def nulls_first(self): return NullsFirst(self) @property def nulls_last(self): return NullsLast(self) class Literal(Expression): __slots__ = ('_value') def __init__(self, value): super(Literal, self).__init__() self._value = value @property def value(self): return self._value def __str__(self): flavor = Flavor.get() if flavor.no_boolean: if self._value is True: return '(1 = 1)' elif self._value is False: return '(1 != 1)' return flavor.param @property def params(self): if Flavor.get().no_boolean: if self._value is True or self._value is False: return () return (self._value,) Null = None class _Rownum(Expression): def __str__(self): return 'ROWNUM' @property def params(self): return () _rownum = _Rownum() class Column(Expression): __slots__ = ('_from', '_name') def __init__(self, from_, name): super(Column, self).__init__() self._from = from_ self._name = name @property def table(self): return self._from @property def name(self): return self._name @property def column_name(self): return ( self._name if self._name == '*' else _escape_identifier(self._name)) def __str__(self): alias = self._from.alias if alias: return '%s.%s' % (_escape_identifier(alias), self.column_name) else: return self.column_name @property def params(self): return () class As(Expression): __slots__ = ('expression', 'output_name') def __init__(self, expression, output_name): super(As, self).__init__() self.expression = expression self.output_name = output_name def __str__(self): return '%s' % _escape_identifier(self.output_name) @property def params(self): return () class Cast(Expression): __slots__ = ('expression', 'typename') def __init__(self, expression, typename): super(Cast, self).__init__() self.expression = expression self.typename = typename def __str__(self): if isinstance(self.expression, Expression): value = self.expression else: value = Flavor.get().param return 'CAST(%s AS %s)' % (value, self.typename) @property def params(self): if isinstance(self.expression, Expression): return self.expression.params else: return (self.expression,) class Collate(Expression): __slots__ = ('_expression', '_collation') def __init__(self, expression, collation): super(Collate, self).__init__() self.expression = expression self.collation = collation @property def expression(self): return self._expression @expression.setter def expression(self, value): self._expression = value @property def collation(self): return self._collation @collation.setter def collation(self, value): self._collation = value def __str__(self): if isinstance(self.expression, Expression): value = self.expression else: value = Flavor.get().param return '%s COLLATE %s' % (value, _escape_identifier(self.collation)) @property def params(self): if isinstance(self.expression, Expression): return self.expression.params else: return (self.expression,) class Grouping(Expression): __slots__ = ('_sets',) def __init__(self, *sets): super().__init__() self.sets = sets @property def sets(self): return self._sets @sets.setter def sets(self, value): if any( not isinstance(col, Expression) for cols in value for col in cols): raise ValueError("invalid sets: %r" % value) self._sets = tuple(tuple(cols) for cols in value) def __str__(self): return 'GROUPING SETS (%s)' % ( ', '.join( '(%s)' % ', '.join(str(col) for col in cols) for cols in self.sets)) @property def params(self): return sum((col.params for cols in self.sets for col in cols), ()) class Rollup(Expression): __slots__ = ('_expressions',) def __init__(self, *expressions): super().__init__() self.expressions = expressions @property def expressions(self): return self._expressions @expressions.setter def expressions(self, value): if not all( isinstance(col, Expression) or all(isinstance(c, Expression) for c in col) for col in value): raise ValueError("invalid expressions: %r" % value) self._expressions = tuple(value) def __str__(self): def format(col): if isinstance(col, Expression): return str(col) else: return '(%s)' % ', '.join(str(c) for c in col) return '%s (%s)' % ( self.__class__.__name__.upper(), ', '.join(format(col) for col in self.expressions)) @property def params(self): p = [] for col in self.expressions: if isinstance(col, Expression): p.extend(col.params) else: for c in col: p.extend(c.params) return tuple(p) class Cube(Rollup): pass class Window(object): __slots__ = ( '_partition', '_order_by', '_frame', '_start', '_end', '_exclude', '__weakref__') def __init__(self, partition, order_by=None, frame=None, start=None, end=0, exclude=None): super(Window, self).__init__() self._partition = None self._order_by = None self._frame = None self._start = None self._end = None self.partition = partition self.order_by = order_by self.frame = frame self.start = start self.end = end self.exclude = exclude @property def partition(self): return self._partition @partition.setter def partition(self, value): if any(not isinstance(e, Expression) for e in value): raise ValueError("invalid partition: %r" % value) self._partition = value @property def order_by(self): return self._order_by @order_by.setter def order_by(self, value): if value is not None: if isinstance(value, Expression): value = [value] if any(not isinstance(col, Expression) for col in value): raise ValueError("invalid order by: %r" % value) self._order_by = value @property def frame(self): return self._frame @frame.setter def frame(self, value): if value: if value not in {'RANGE', 'ROWS', 'GROUPS'}: raise ValueError("invalid frame: %r" % value) self._frame = value @property def start(self): return self._start @start.setter def start(self, value): if value: if not isinstance(value, numbers.Integral): raise ValueError("invalid start: %r" % value) self._start = value @property def end(self): return self._end @end.setter def end(self, value): if value: if not isinstance(value, numbers.Integral): raise ValueError("invalid end: %r" % value) self._end = value @property def exclude(self): return self._exclude @exclude.setter def exclude(self, value): if value: if value not in {'CURRENT ROW', 'GROUP', 'TIES'}: raise ValueError("invalid exclude: %r" % value) self._exclude = value @property def alias(self): return AliasManager.get(self) @property def has_alias(self): return AliasManager.contains(self) def __str__(self): param = Flavor.get().param partition = '' if self.partition: partition = 'PARTITION BY ' + ', '.join(map(str, self.partition)) order_by = '' if self.order_by: order_by = ' ORDER BY ' + ', '.join(map(str, self.order_by)) def format(frame, direction): if frame is None: return 'UNBOUNDED %s' % direction elif not frame: return 'CURRENT ROW' elif frame < 0: return '%s PRECEDING' % param elif frame > 0: return '%s FOLLOWING' % param frame = '' if self.frame: start = format(self.start, 'PRECEDING') end = format(self.end, 'FOLLOWING') frame = ' %s BETWEEN %s AND %s' % (self.frame, start, end) exclude = '' if self.exclude: exclude = ' EXCLUDE %s' % self.exclude return partition + order_by + frame + exclude @property def params(self): p = [] if self.partition: for expression in self.partition: p.extend(expression.params) if self.order_by: for expression in self.order_by: p.extend(expression.params) if self.frame: if self.start: p.append(abs(self.start)) if self.end: p.append(abs(self.end)) return tuple(p) class Order(Expression): __slots__ = ('_expression') _sql = '' def __init__(self, expression): super(Order, self).__init__() self._expression = None self.expression = expression # TODO USING @property def expression(self): return self._expression @expression.setter def expression(self, value): if not isinstance(value, (Expression, SelectQuery)): raise ValueError("invalid expression: %r" % value) self._expression = value def __str__(self): if isinstance(self.expression, SelectQuery): return '(%s) %s' % (self.expression, self._sql) return '%s %s' % (self.expression, self._sql) @property def params(self): return self.expression.params class Asc(Order): __slots__ = () _sql = 'ASC' class Desc(Order): __slots__ = () _sql = 'DESC' class NullOrder(Expression): __slots__ = ('expression') _sql = '' def __init__(self, expression): super(NullOrder, self).__init__() self.expression = expression def __str__(self): if not Flavor.get().null_ordering: return '%s, %s' % (self._case, self.expression) return '%s NULLS %s' % (self.expression, self._sql) @property def params(self): p = [] if not Flavor.get().null_ordering: p.extend(self.expression.params) p.extend(self._case_values()) p.extend(self.expression.params) return tuple(p) @property def _case(self): from .conditionals import Case values = self._case_values() if isinstance(self.expression, Order): expression = self.expression.expression else: expression = self.expression return Asc(Case((expression == Null, values[0]), else_=values[1])) def _case_values(self): raise NotImplementedError class NullsFirst(NullOrder): __slots__ = () _sql = 'FIRST' def _case_values(self): return (0, 1) class NullsLast(NullOrder): __slots__ = () _sql = 'LAST' def _case_values(self): return (1, 0) class For(object): __slots__ = ('_tables', '_type_', 'nowait') def __init__(self, type_, *tables, **kwargs): self._tables = None self._type_ = None self.tables = list(tables) self.type_ = type_ self.nowait = kwargs.get('nowait') @property def tables(self): return self._tables @tables.setter def tables(self, value): if not isinstance(value, list): value = [value] all(isinstance(table, Table) for table in value) self._tables = value @property def type_(self): return self._type_ @type_.setter def type_(self, value): value = value.upper() if value not in {'UPDATE', 'SHARE'}: raise ValueError("invalid type: %r" % value) self._type_ = value def __str__(self): tables = '' if self.tables: tables = ' OF ' + ', '.join(map(str, self.tables)) nowait = '' if self.nowait: nowait = ' NOWAIT' return ('FOR %s' % self.type_) + tables + nowait python_sql-1.8.0/sql/aggregate.py0000644000000000000000000001357013615410400013745 0ustar00# This file is part of python-sql. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. from sql import Expression, Flavor, Literal, Window __all__ = ['Avg', 'BitAnd', 'BitOr', 'BoolAnd', 'BoolOr', 'Count', 'Every', 'Max', 'Min', 'Stddev', 'Sum', 'Variance'] class Aggregate(Expression): __slots__ = ('_expression', '_distinct', '_order_by', '_within', '_filter', '_window') _sql = '' def __init__(self, expression, distinct=False, order_by=None, within=None, filter_=None, window=None): super(Aggregate, self).__init__() self.expression = expression self.distinct = distinct self.order_by = order_by self.within = within self.filter_ = filter_ self.window = window @property def expression(self): return self._expression @expression.setter def expression(self, value): if not isinstance(value, Expression): raise ValueError("invalid expression: %r" % value) self._expression = value @property def distinct(self): return self._distinct @distinct.setter def distinct(self, value): if not isinstance(value, bool): raise ValueError("invalid distinct: %r" % value) self._distinct = value @property def order_by(self): return self._order_by @order_by.setter def order_by(self, value): if value is not None: if isinstance(value, Expression): value = [value] if any(not isinstance(col, Expression) for col in value): raise ValueError("invalid order by: %r" % value) self._order_by = value @property def within(self): return self._within @within.setter def within(self, value): if value is not None: if isinstance(value, Expression): value = [value] if any(not isinstance(col, Expression) for col in value): raise ValueError("invalid within: %r" % value) self._within = value @property def filter_(self): return self._filter @filter_.setter def filter_(self, value): from sql.operators import And, Or if value is not None: if not isinstance(value, (Expression, And, Or)): raise ValueError("invalid filter: %r" % value) self._filter = value @property def window(self): return self._window @window.setter def window(self, value): if value: if not isinstance(value, Window): raise ValueError("invalid window: %r" % value) self._window = value @property def _case_expression(self): return self.expression def __str__(self): quantifier = 'DISTINCT ' if self.distinct else '' has_filter = Flavor.get().filter_ expression = self.expression if self.filter_ and not has_filter: from sql.conditionals import Case expression = Case((self.filter_, self._case_expression)) order_by = '' if self.order_by: order_by = ' ORDER BY %s' % ', '.join(map(str, self.order_by)) aggregate = '%s(%s%s%s)' % ( self._sql, quantifier, expression, order_by) within = '' if self.within: within = (' WITHIN GROUP (ORDER BY %s)' % ', '.join(map(str, self.within))) filter_ = '' if self.filter_ and has_filter: filter_ = ' FILTER (WHERE %s)' % self.filter_ window = '' if self.window: if self.window.has_alias: window = ' OVER "%s"' % self.window.alias else: window = ' OVER (%s)' % self.window return aggregate + within + filter_ + window @property def params(self): has_filter = Flavor.get().filter_ p = [] if self.filter_ and not has_filter: p.extend(self.filter_.params) p.extend(self._case_expression.params) else: p.extend(self.expression.params) if self.order_by: for expression in self.order_by: p.extend(expression.params) if self.within: for expression in self.within: p.extend(expression.params) if self.filter_ and has_filter: p.extend(self.filter_.params) if self.window and not self.window.has_alias: p.extend(self.window.params) return tuple(p) class Avg(Aggregate): __slots__ = () _sql = 'AVG' class BitAnd(Aggregate): __slots__ = () _sql = 'BIT_AND' class BitOr(Aggregate): __slots__ = () _sql = 'BIT_OR' class BoolAnd(Aggregate): __slots__ = () _sql = 'BOOL_AND' class BoolOr(Aggregate): __slots__ = () _sql = 'BOOL_OR' class _Star(Expression): __slots__ = () def __str__(self): return '*' @property def params(self): return () class Count(Aggregate): __slots__ = () _sql = 'COUNT' def __init__(self, expression=_Star(), **kwargs): super().__init__(expression, **kwargs) @property def _case_expression(self): expression = super(Count, self)._case_expression if (isinstance(self.expression, _Star) # Keep testing Literal('*') for backward compatibility or (isinstance(self.expression, Literal) and expression.value == '*')): expression = Literal(1) return expression class Every(Aggregate): __slots__ = () _sql = 'EVERY' class Max(Aggregate): __slots__ = () _sql = 'MAX' class Min(Aggregate): __slots__ = () _sql = 'MIN' class Stddev(Aggregate): __slots__ = () _sql = 'Stddev' class Sum(Aggregate): __slots__ = () _sql = 'SUM' class Variance(Aggregate): __slots__ = () _sql = 'VARIANCE' python_sql-1.8.0/sql/conditionals.py0000644000000000000000000000475513615410400014512 0ustar00# This file is part of python-sql. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. from sql import CombiningQuery, Expression, Flavor, Select __all__ = ['Case', 'Coalesce', 'NullIf', 'Greatest', 'Least'] class Conditional(Expression): __slots__ = () _sql = '' table = '' name = '' @staticmethod def _format(value): if isinstance(value, Expression): return str(value) elif isinstance(value, (Select, CombiningQuery)): return '(%s)' % value else: return Flavor().get().param class Case(Conditional): __slots__ = ('whens', 'else_') def __init__(self, *whens, **kwargs): self.whens = whens self.else_ = kwargs.get('else_') def __str__(self): case = 'CASE ' for cond, result in self.whens: case += 'WHEN %s THEN %s ' % ( self._format(cond), self._format(result)) if self.else_ is not None: case += 'ELSE %s ' % self._format(self.else_) case += 'END' return case @property def params(self): p = [] for cond, result in self.whens: if isinstance(cond, (Expression, Select, CombiningQuery)): p.extend(cond.params) else: p.append(cond) if isinstance(result, (Expression, Select, CombiningQuery)): p.extend(result.params) else: p.append(result) if self.else_ is not None: if isinstance(self.else_, (Expression, Select, CombiningQuery)): p.extend(self.else_.params) else: p.append(self.else_) return tuple(p) class Coalesce(Conditional): __slots__ = ('values') _conditional = 'COALESCE' def __init__(self, *values): self.values = values def __str__(self): return (self._conditional + '(' + ', '.join(map(self._format, self.values)) + ')') @property def params(self): p = [] for value in self.values: if isinstance(value, (Expression, Select, CombiningQuery)): p.extend(value.params) else: p.append(value) return tuple(p) class NullIf(Coalesce): __slots__ = () _conditional = 'NULLIF' class Greatest(Coalesce): __slots__ = () _conditional = 'GREATEST' class Least(Coalesce): __slots__ = () _conditional = 'LEAST' python_sql-1.8.0/sql/functions.py0000644000000000000000000003345513615410400014033 0ustar00# This file is part of python-sql. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. from enum import Enum, auto from itertools import chain from sql import CombiningQuery, Expression, Flavor, FromItem, Select, Window __all__ = ['Abs', 'Cbrt', 'Ceil', 'Degrees', 'Div', 'Exp', 'Floor', 'Ln', 'Log', 'Mod', 'Pi', 'Power', 'Radians', 'Random', 'Round', 'SetSeed', 'Sign', 'Sqrt', 'Trunc', 'WidthBucket', 'Acos', 'Asin', 'Atan', 'Atan2', 'Cos', 'Cot', 'Sin', 'Tan', 'BitLength', 'CharLength', 'Overlay', 'Position', 'Substring', 'Trim', 'Upper', 'ToChar', 'ToDate', 'ToNumber', 'ToTimestamp', 'Age', 'ClockTimestamp', 'CurrentDate', 'CurrentTime', 'CurrentTimestamp', 'DatePart', 'DateTrunc', 'Extract', 'Isfinite', 'JustifyDays', 'JustifyHours', 'JustifyInterval', 'Localtime', 'Localtimestamp', 'Now', 'StatementTimestamp', 'Timeofday', 'TransactionTimestamp', 'AtTimeZone', 'RowNumber', 'Rank', 'DenseRank', 'PercentRank', 'CumeDist', 'Ntile', 'Lag', 'Lead', 'FirstValue', 'LastValue', 'NthValue'] # Mathematical class Function(Expression, FromItem): __slots__ = ('args', '_columns_definitions') table = '' name = '' _function = '' def __init__(self, *args, **kwargs): self.args = args self.columns_definitions = kwargs.get('columns_definitions', []) @property def columns_definitions(self): return ', '.join('"%s" %s' % (c, d) for c, d in self._columns_definitions) @columns_definitions.setter def columns_definitions(self, value): if not isinstance(value, list): raise ValueError("invalid columns definitions: %r" % value) self._columns_definitions = value @staticmethod def _format(value): if isinstance(value, Expression): return str(value) elif isinstance(value, (Select, CombiningQuery)): return '(%s)' % value else: return Flavor().get().param def __str__(self): Mapping = Flavor.get().function_mapping.get(self.__class__) if Mapping: return str(Mapping(*self.args)) return self._function + '(' + ', '.join( map(self._format, self.args)) + ')' @property def params(self): Mapping = Flavor.get().function_mapping.get(self.__class__) if Mapping: return Mapping(*self.args).params p = [] for arg in self.args: if isinstance(arg, (Expression, Select, CombiningQuery)): p.extend(arg.params) else: p.append(arg) return tuple(p) class FunctionKeyword(Function): __slots__ = () _function = '' _keywords = () def __str__(self): Mapping = Flavor.get().function_mapping.get(self.__class__) if Mapping: return str(Mapping(*self.args)) return (self._function + '(' + ' '.join(chain(*zip( self._keywords, map(self._format, self.args)))).strip() + ')') class FunctionNotCallable(Function): __slots__ = () _function = '' def __str__(self): Mapping = Flavor.get().function_mapping.get(self.__class__) if Mapping: return str(Mapping(*self.args)) return self._function class Abs(Function): __slots__ = () _function = 'ABS' class Cbrt(Function): __slots__ = () _function = 'CBRT' class Ceil(Function): __slots__ = () _function = 'CEIL' class Degrees(Function): __slots__ = () _function = 'DEGREES' class Div(Function): __slots__ = () _function = 'DIV' class Exp(Function): __slots__ = () _function = 'EXP' class Floor(Function): __slots__ = () _function = 'FLOOR' class Ln(Function): __slots__ = () _function = 'LN' class Log(Function): __slots__ = () _function = 'LOG' class Mod(Function): __slots__ = () _function = 'MOD' class Pi(Function): __slots__ = () _function = 'PI' class Power(Function): __slots__ = () _function = 'POWER' class Radians(Function): __slots__ = () _function = 'RADIANS' class Random(Function): __slots__ = () _function = 'RANDOM' class Round(Function): __slots__ = () _function = 'ROUND' class SetSeed(Function): __slots__ = () _function = 'SETSEED' class Sign(Function): __slots__ = () _function = 'SIGN' class Sqrt(Function): __slots__ = () _function = 'SQRT' class Trunc(Function): __slots__ = () _function = 'TRUNC' class WidthBucket(Function): __slots__ = () _function = 'WIDTH_BUCKET' # Trigonometric class Acos(Function): __slots__ = () _function = 'ACOS' class Asin(Function): __slots__ = () _function = 'ASIN' class Atan(Function): __slots__ = () _function = 'ATAN' class Atan2(Function): __slots__ = () _function = 'ATAN2' class Cos(Function): __slots__ = () _function = 'Cos' class Cot(Function): __slots__ = () _function = 'COT' class Sin(Function): __slots__ = () _function = 'SIN' class Tan(Function): __slots__ = () _function = 'TAN' # String class BitLength(Function): __slots__ = () _function = 'BIT_LENGTH' class CharLength(Function): __slots__ = () _function = 'CHAR_LENGTH' class Lower(Function): __slots__ = () _function = 'LOWER' class OctetLength(Function): __slots__ = () _function = 'OCTET_LENGTH' class Overlay(FunctionKeyword): __slots__ = () _function = 'OVERLAY' _keywords = ('', 'PLACING', 'FROM', 'FOR') class Position(FunctionKeyword): __slots__ = () _function = 'POSITION' _keywords = ('', 'IN') class Substring(FunctionKeyword): __slots__ = () _function = 'SUBSTRING' _keywords = ('', 'FROM', 'FOR') class Trim(Function): __slots__ = ('position', 'characters', 'string') _function = 'TRIM' def __init__(self, string, position='BOTH', characters=' '): if position.upper() not in {'LEADING', 'TRAILING', 'BOTH'}: raise ValueError("invalid position: %r" % position) self.position = position.upper() self.characters = characters self.string = string def __str__(self): flavor = Flavor.get() Mapping = flavor.function_mapping.get(self.__class__) if Mapping: return str(Mapping(self.string, self.position, self.characters)) param = flavor.param def format(arg): if isinstance(arg, str): return param else: return str(arg) return self._function + '(%s %s FROM %s)' % ( self.position, format(self.characters), format(self.string)) @property def params(self): Mapping = Flavor.get().function_mapping.get(self.__class__) if Mapping: return Mapping(self.string, self.position, self.characters).params p = [] for arg in (self.characters, self.string): if isinstance(arg, str): p.append(arg) else: p.extend(arg.params) return tuple(p) class Upper(Function): __slots__ = () _function = 'UPPER' class ToChar(Function): __slots__ = () _function = 'TO_CHAR' class ToDate(Function): __slots__ = () _function = 'TO_DATE' class ToNumber(Function): __slots__ = () _function = 'TO_NUMBER' class ToTimestamp(Function): __slots__ = () _function = 'TO_TIMESTAMP' class Age(Function): __slots__ = () _function = 'AGE' class ClockTimestamp(Function): __slots__ = () _function = 'CLOCK_TIMESTAMP' class CurrentDate(FunctionNotCallable): __slots__ = () _function = 'CURRENT_DATE' class CurrentTime(FunctionNotCallable): __slots__ = () _function = 'CURRENT_TIME' class CurrentTimestamp(FunctionNotCallable): __slots__ = () _function = 'CURRENT_TIMESTAMP' class DatePart(Function): __slots__ = () _function = 'DATE_PART' class DateTrunc(Function): __slots__ = () _function = 'DATE_TRUNC' class Extract(FunctionKeyword): __slots__ = ('_field',) _function = 'EXTRACT' class Fields(str, Enum): def _generate_next_value_(name, start, count, last_values): return name.upper() CENTURY = auto() DAY = auto() DECADE = auto() DOW = auto() DOY = auto() EPOCH = auto() HOUR = auto() ISODOW = auto() ISOYEAR = auto() JULIAN = auto() MICROSECONDS = auto() MILLENNIUM = auto() MILLISECONDS = auto() MINUTE = auto() MONTH = auto() QUARTER = auto() SECOND = auto() TIMEZONE = auto() TIMEZONE_HOUR = auto() TIMEZONE_MINUTE = auto() WEEK = auto() YEAR = auto() def __init__(self, field, *args, **kwargs): super().__init__(*args, **kwargs) self.field = field @property def field(self): return self._field @field.setter def field(self, value): value = value.upper() if not hasattr(self.Fields, value): raise ValueError("invalid field: %r" % value) self._field = value @property def _keywords(self): return ('%s FROM' % self.field,) def __str__(self): Mapping = Flavor.get().function_mapping.get(self.__class__) if Mapping: return str(Mapping(self.field, *self.args)) return super().__str__() @property def params(self): Mapping = Flavor.get().function_mapping.get(self.__class__) if Mapping: return Mapping(self.field, *self.args).params return super().params class Isfinite(Function): __slots__ = () _function = 'ISFINITE' class JustifyDays(Function): __slots__ = () _function = 'JUSTIFY_DAYS' class JustifyHours(Function): __slots__ = () _function = 'JUSTIFY_HOURS' class JustifyInterval(Function): __slots__ = () _function = 'JUSTIFY_INTERVAL' class Localtime(FunctionNotCallable): __slots__ = () _function = 'LOCALTIME' class Localtimestamp(FunctionNotCallable): __slots__ = () _function = 'LOCALTIMESTAMP' class Now(Function): __slots__ = () _function = 'NOW' class StatementTimestamp(Function): __slots__ = () _function = 'STATEMENT_TIMESTAMP' class Timeofday(Function): __slots__ = () _function = 'TIMEOFDAY' class TransactionTimestamp(Function): __slots__ = () _function = 'TRANSACTION_TIMESTAMP' class AtTimeZone(Function): __slots__ = ('field', 'zone') def __init__(self, field, zone): self.field = field self.zone = zone def __str__(self): flavor = Flavor.get() Mapping = flavor.function_mapping.get(self.__class__) if Mapping: return str(Mapping(self.field, self.zone)) if isinstance(self.zone, Expression): zone = str(self.zone) elif isinstance(self.zone, (Select, CombiningQuery)): zone = '(%s)' % self.zone else: zone = flavor.param return '%s AT TIME ZONE %s' % (str(self.field), zone) @property def params(self): Mapping = Flavor.get().function_mapping.get(self.__class__) if Mapping: return Mapping(self.field, self.zone).params if isinstance(self.zone, (Expression, Select, CombiningQuery)): return self.field.params + self.zone.params else: return self.field.params + (self.zone,) class WindowFunction(Function): __slots__ = ('_filter', '_window') def __init__(self, *args, **kwargs): self.filter_ = kwargs.pop('filter_', None) self.window = kwargs['window'] super(WindowFunction, self).__init__(*args, **kwargs) @property def filter_(self): return self._filter @filter_.setter def filter_(self, value): from sql.operators import And, Or if value is not None: if not isinstance(value, (Expression, And, Or)): raise ValueError("invalid filter: %r" % value) self._filter = value @property def window(self): return self._window @window.setter def window(self, value): if value: if not isinstance(value, Window): raise ValueError("invalid window: %r" % value) self._window = value def __str__(self): function = super(WindowFunction, self).__str__() filter_ = '' if self.filter_: filter_ = ' FILTER (WHERE %s)' % self.filter_ if self.window.has_alias: over = ' OVER "%s"' % self.window.alias else: over = ' OVER (%s)' % self.window return function + filter_ + over @property def params(self): p = list(super(WindowFunction, self).params) if self.filter_: p.extend(self.filter_.params) if not self.window.has_alias: p.extend(self.window.params) return tuple(p) class RowNumber(WindowFunction): __slots__ = () _function = 'ROW_NUMBER' class Rank(WindowFunction): __slots__ = () _function = 'RANK' class DenseRank(WindowFunction): __slots__ = () _function = 'DENSE_RANK' class PercentRank(WindowFunction): __slots__ = () _function = 'PERCENT_RANK' class CumeDist(WindowFunction): __slots__ = () _function = 'CUME_DIST' class Ntile(WindowFunction): __slots__ = () _function = 'NTILE' class Lag(WindowFunction): __slots__ = () _function = 'LAG' class Lead(WindowFunction): __slots__ = () _function = 'LEAD' class FirstValue(WindowFunction): __slots__ = () _function = 'FIRST_VALUE' class LastValue(WindowFunction): __slots__ = () _function = 'LAST_VALUE' class NthValue(WindowFunction): __slots__ = () _function = 'NTH_VALUE' python_sql-1.8.0/sql/operators.py0000644000000000000000000002633013615410400014033 0ustar00# This file is part of python-sql. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import warnings from array import array from sql import CombiningQuery, Expression, Flavor, Null, Select __all__ = ['And', 'Or', 'Not', 'Less', 'Greater', 'LessEqual', 'GreaterEqual', 'Equal', 'NotEqual', 'Between', 'NotBetween', 'IsDistinct', 'IsNotDistinct', 'Is', 'IsNot', 'Add', 'Sub', 'Mul', 'Div', 'FloorDiv', 'Mod', 'Pow', 'SquareRoot', 'CubeRoot', 'Factorial', 'Abs', 'BAnd', 'BOr', 'BXor', 'BNot', 'LShift', 'RShift', 'Concat', 'Like', 'NotLike', 'ILike', 'NotILike', 'In', 'NotIn', 'Exists', 'Any', 'Some', 'All'] class Operator(Expression): __slots__ = () @property def table(self): return '' @property def name(self): return '' @property def _operands(self): return () @property def params(self): def convert(operands): params = [] for operand in operands: if isinstance(operand, (Expression, Select, CombiningQuery)): params.extend(operand.params) elif isinstance(operand, (list, tuple)): params.extend(convert(operand)) elif isinstance(operand, array): params.extend(operand) else: params.append(operand) return params return tuple(convert(self._operands)) def _format(self, operand, param=None): if param is None: param = Flavor.get().param if (isinstance(operand, Expression) and (not isinstance(operand, Operator) or isinstance(operand, UnaryOperator))): return str(operand) elif isinstance(operand, (Expression, Select, CombiningQuery)): return '(%s)' % operand elif isinstance(operand, (list, tuple)): return '(' + ', '.join(self._format(o, param) for o in operand) + ')' elif isinstance(operand, array): return '(' + ', '.join((param,) * len(operand)) + ')' else: return param def __str__(self): raise NotImplementedError def __and__(self, other): if isinstance(other, And): return And([self] + other) else: return And((self, other)) def __or__(self, other): if isinstance(other, Or): return Or([self] + other) else: return Or((self, other)) class UnaryOperator(Operator): __slots__ = 'operand' _operator = '' def __init__(self, operand): self.operand = operand @property def _operands(self): return (self.operand,) def __str__(self): return '%s %s' % (self._operator, self._format(self.operand)) class BinaryOperator(Operator): __slots__ = ('left', 'right') _operator = '' def __init__(self, left, right): self.left = left self.right = right @property def _operands(self): return (self.left, self.right) def __str__(self): left, right = self._operands return '%s %s %s' % (self._format(left), self._operator, self._format(right)) def __invert__(self): return _INVERT[self.__class__](self.left, self.right) class NaryOperator(list, Operator): __slots__ = () _operator = '' @property def _operands(self): return self def __str__(self): return (' %s ' % self._operator).join(map(self._format, self)) class And(NaryOperator): __slots__ = () _operator = 'AND' class Or(NaryOperator): __slots__ = () _operator = 'OR' class Not(UnaryOperator): __slots__ = () _operator = 'NOT' class Neg(UnaryOperator): __slots__ = () _operator = '-' class Pos(UnaryOperator): __slots__ = () _operator = '+' class Less(BinaryOperator): __slots__ = () _operator = '<' class Greater(BinaryOperator): __slots__ = () _operator = '>' class LessEqual(BinaryOperator): __slots__ = () _operator = '<=' class GreaterEqual(BinaryOperator): __slots__ = () _operator = '>=' class Equal(BinaryOperator): __slots__ = () _operator = '=' @property def _operands(self): if self.left is Null: return (self.right,) elif self.right is Null: return (self.left,) return super(Equal, self)._operands def __str__(self): if self.left is Null: return '%s IS NULL' % self.right elif self.right is Null: return '%s IS NULL' % self.left return super(Equal, self).__str__() class NotEqual(Equal): __slots__ = () _operator = '!=' def __str__(self): if self.left is Null: return '%s IS NOT NULL' % self.right elif self.right is Null: return '%s IS NOT NULL' % self.left return super(Equal, self).__str__() class Between(Operator): __slots__ = ('operand', 'left', 'right', 'symmetric') _operator = 'BETWEEN' def __init__(self, operand, left, right, symmetric=False): self.operand = operand self.left = left self.right = right self.symmetric = symmetric @property def _operands(self): return (self.operand, self.left, self.right) def __str__(self): operator = self._operator if self.symmetric: operator += ' SYMMETRIC' return '%s %s %s AND %s' % ( self._format(self.operand), operator, self._format(self.left), self._format(self.right)) def __invert__(self): return _INVERT[self.__class__]( self.operand, self.left, self.right, self.symmetric) class NotBetween(Between): __slots__ = () _operator = 'NOT BETWEEN' class IsDistinct(BinaryOperator): __slots__ = () _operator = 'IS DISTINCT FROM' class IsNotDistinct(IsDistinct): __slots__ = () _operator = 'IS NOT DISTINCT FROM' class Is(BinaryOperator): __slots__ = () _operator = 'IS' def __init__(self, left, right): if right not in {None, True, False}: raise ValueError("invalid right: %r" % right) super(Is, self).__init__(left, right) @property def _operands(self): return (self.left,) def __str__(self): if self.right is None: return '%s %s UNKNOWN' % ( self._format(self.left), self._operator) elif self.right is True: return '%s %s TRUE' % (self._format(self.left), self._operator) elif self.right is False: return '%s %s FALSE' % (self._format(self.left), self._operator) class IsNot(Is): __slots__ = () _operator = 'IS NOT' class Add(BinaryOperator): __slots__ = () _operator = '+' class Sub(BinaryOperator): __slots__ = () _operator = '-' class Mul(BinaryOperator): __slots__ = () _operator = '*' class Div(BinaryOperator): __slots__ = () _operator = '/' # For backward compatibility class FloorDiv(BinaryOperator): __slots__ = () _operator = '/' def __init__(self, left, right): warnings.warn('FloorDiv operator is deprecated, use Div function', DeprecationWarning, stacklevel=2) super(FloorDiv, self).__init__(left, right) class Mod(BinaryOperator): __slots__ = () @property def _operator(self): # '%' must be escaped with format paramstyle if Flavor.get().paramstyle == 'format': return '%%' else: return '%' class Pow(BinaryOperator): __slots__ = () _operator = '^' class SquareRoot(UnaryOperator): __slots__ = () _operator = '|/' class CubeRoot(UnaryOperator): __slots__ = () _operator = '||/' class Factorial(UnaryOperator): __slots__ = () _operator = '!!' class Abs(UnaryOperator): __slots__ = () _operator = '@' class BAnd(BinaryOperator): __slots__ = () _operator = '&' class BOr(BinaryOperator): __slots__ = () _operator = '|' class BXor(BinaryOperator): __slots__ = () _operator = '#' class BNot(UnaryOperator): __slots__ = () _operator = '~' class LShift(BinaryOperator): __slots__ = () _operator = '<<' class RShift(BinaryOperator): __slots__ = () _operator = '>>' class Concat(BinaryOperator): __slots__ = () _operator = '||' class Like(BinaryOperator): __slots__ = 'escape' _operator = 'LIKE' def __init__(self, left, right, escape=None): super().__init__(left, right) if escape and len(escape) != 1: raise ValueError("invalid escape: %r" % escape) self.escape = escape @property def params(self): params = super().params if self.escape or Flavor().get().escape_empty: params += (self.escape or '',) return params def __str__(self): left, right = self._operands if self.escape or Flavor().get().escape_empty: return '%s %s %s ESCAPE %s' % ( self._format(left), self._operator, self._format(right), self._format(self.escape or '')) else: return '%s %s %s' % ( self._format(left), self._operator, self._format(right)) def __invert__(self): return _INVERT[self.__class__](self.left, self.right, self.escape) class NotLike(Like): __slots__ = () _operator = 'NOT LIKE' class ILike(Like): __slots__ = () @property def _operator(self): if Flavor.get().ilike: return 'ILIKE' else: return 'LIKE' @property def _operands(self): operands = super(ILike, self)._operands if not Flavor.get().ilike: from .functions import Upper operands = tuple(Upper(o) for o in operands) return operands class NotILike(ILike): __slots__ = () @property def _operator(self): if Flavor.get().ilike: return 'NOT ILIKE' else: return 'NOT LIKE' # TODO SIMILAR class In(BinaryOperator): __slots__ = () _operator = 'IN' class NotIn(BinaryOperator): __slots__ = () _operator = 'NOT IN' class Exists(UnaryOperator): __slots__ = () _operator = 'EXISTS' class _ArrayOperator(UnaryOperator): __slots__ = () @property def params(self): if isinstance(self.operand, (list, tuple, array)): return (list(self.operand),) return super().params def _format(self, operand, param=None): if param is None: param = Flavor.get().param if isinstance(operand, (list, tuple, array)): return '(%s)' % param return super()._format(operand, param=param) class Any(_ArrayOperator): __slots__ = () _operator = 'ANY' Some = Any class All(_ArrayOperator): __slots__ = () _operator = 'ALL' _INVERT = { Less: GreaterEqual, Greater: LessEqual, LessEqual: Greater, GreaterEqual: Less, Equal: NotEqual, NotEqual: Equal, Between: NotBetween, NotBetween: Between, IsDistinct: IsNotDistinct, IsNotDistinct: IsDistinct, Is: IsNot, IsNot: Is, Like: NotLike, NotLike: Like, ILike: NotILike, NotILike: ILike, In: NotIn, NotIn: In, } python_sql-1.8.0/sql/tests/__init__.py0000644000000000000000000000124713615410400014716 0ustar00# This file is part of python-sql. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import doctest import os import sql here = os.path.dirname(__file__) readme = os.path.normpath(os.path.join(here, '..', '..', 'README.rst')) def load_tests(loader, tests, pattern): tests.addTests(loader.discover(start_dir=here, pattern=pattern)) for mod in (sql,): tests.addTest(doctest.DocTestSuite(mod)) if os.path.isfile(readme): tests.addTest(doctest.DocFileSuite( readme, module_relative=False, tearDown=lambda t: sql.Flavor.set(sql.Flavor()))) return tests python_sql-1.8.0/sql/tests/test_aggregate.py0000644000000000000000000000643713615410400016152 0ustar00# This file is part of python-sql. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import unittest from sql import AliasManager, Flavor, Literal, Table, Window from sql.aggregate import Aggregate, Avg, Count class TestAggregate(unittest.TestCase): table = Table('t') def test_invalid_expression(self): with self.assertRaises(ValueError): Aggregate('foo') def test_invalid_distinct(self): with self.assertRaises(ValueError): Aggregate(self.table.c, distinct='foo') def test_invalid_order(self): with self.assertRaises(ValueError): Aggregate(self.table.c, order_by=['foo']) def test_invalid_within(self): with self.assertRaises(ValueError): Aggregate(self.table.c, within=['foo']) def test_invalid_filter(self): with self.assertRaises(ValueError): Aggregate(self.table.c, filter_='foo') def test_invalid_window(self): with self.assertRaises(ValueError): Aggregate(self.table.c, window='foo') def test_avg(self): avg = Avg(self.table.c) self.assertEqual(str(avg), 'AVG("c")') avg = Avg(self.table.a + self.table.b) self.assertEqual(str(avg), 'AVG("a" + "b")') def test_count_without_expression(self): count = Count() self.assertEqual(str(count), 'COUNT(*)') self.assertEqual(count.params, ()) def test_order_by_one_column(self): avg = Avg(self.table.a, order_by=self.table.b) self.assertEqual(str(avg), 'AVG("a" ORDER BY "b")') self.assertEqual(avg.params, ()) def test_order_by_multiple_columns(self): avg = Avg( self.table.a, order_by=[self.table.b.asc, self.table.c.desc]) self.assertEqual(str(avg), 'AVG("a" ORDER BY "b" ASC, "c" DESC)') self.assertEqual(avg.params, ()) def test_within(self): avg = Avg(self.table.a, within=self.table.b) self.assertEqual(str(avg), 'AVG("a") WITHIN GROUP (ORDER BY "b")') self.assertEqual(avg.params, ()) def test_filter(self): flavor = Flavor(filter_=True) Flavor.set(flavor) try: avg = Avg(self.table.a + 1, filter_=self.table.a > 0) self.assertEqual( str(avg), 'AVG("a" + %s) FILTER (WHERE "a" > %s)') self.assertEqual(avg.params, (1, 0)) finally: Flavor.set(Flavor()) def test_filter_case(self): avg = Avg(self.table.a + 1, filter_=self.table.a > 0) self.assertEqual( str(avg), 'AVG(CASE WHEN "a" > %s THEN "a" + %s END)') self.assertEqual(avg.params, (0, 1)) def test_filter_case_count_star(self): count = Count(Literal('*'), filter_=self.table.a > 0) self.assertEqual( str(count), 'COUNT(CASE WHEN "a" > %s THEN %s END)') self.assertEqual(count.params, (0, 1)) def test_window(self): avg = Avg(self.table.c, window=Window([])) with AliasManager(): self.assertEqual(str(avg), 'AVG("a"."c") OVER ()') self.assertEqual(avg.params, ()) def test_distinct(self): avg = Avg(self.table.c, distinct=True) self.assertEqual(str(avg), 'AVG(DISTINCT "c")') self.assertEqual(avg.params, ()) python_sql-1.8.0/sql/tests/test_alias.py0000644000000000000000000000455013615410400015307 0ustar00# This file is part of python-sql. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import threading import unittest from sql import AliasManager, Table class TestAliasManager(unittest.TestCase): def setUp(self): self.synchro = threading.Event() self.succeed1 = threading.Event() self.succeed2 = threading.Event() self.finish1 = threading.Event() self.finish2 = threading.Event() self.t1 = Table('t1') self.t2 = Table('t2') def func1(self): try: with AliasManager(exclude=[self.t2]): a1 = AliasManager.get(self.t1) a2 = AliasManager.get(self.t2) self.synchro.wait() self.assertEqual(a1, AliasManager.get(self.t1)) self.assertEqual(a2, AliasManager.get(self.t2)) self.succeed1.set() return except Exception: pass finally: self.finish1.set() def func2(self): try: with AliasManager(exclude=[self.t2]): a2 = AliasManager.get(self.t2) a1 = AliasManager.get(self.t1) self.synchro.set() self.assertEqual(a1, AliasManager.get(self.t1)) self.assertEqual(a2, AliasManager.get(self.t2)) self.succeed2.set() return except Exception: pass finally: self.synchro.set() self.finish2.set() def test_threading(self): th1 = threading.Thread(target=self.func1) th2 = threading.Thread(target=self.func2) th1.start() th2.start() self.finish1.wait() self.finish2.wait() if not self.succeed1.is_set() or not self.succeed2.is_set(): self.fail() def test_contains(self): with AliasManager(): AliasManager.get(self.t1) self.assertTrue(AliasManager.contains(self.t1)) def test_contains_exclude(self): with AliasManager(exclude=[self.t1]): self.assertEqual(AliasManager.get(self.t1), '') self.assertFalse(AliasManager.contains(self.t1)) def test_set(self): with AliasManager(): AliasManager.set(self.t1, 'foo') self.assertEqual(AliasManager.get(self.t1), 'foo') python_sql-1.8.0/sql/tests/test_as.py0000644000000000000000000000163613615410400014623 0ustar00# This file is part of python-sql. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import unittest from sql import As, Column, Flavor, Table class TestAs(unittest.TestCase): table = Table('t') column = Column(table, 'c') def test_as(self): self.assertEqual(str(As(self.column, 'foo')), '"foo"') def test_as_select(self): query = self.table.select(self.column.as_('foo')) self.assertEqual(str(query), 'SELECT "a"."c" AS "foo" FROM "t" AS "a"') self.assertEqual(tuple(query.params), ()) def test_no_as(self): query = self.table.select(self.column.as_('foo')) try: Flavor.set(Flavor(no_as=True)) self.assertEqual(str(query), 'SELECT "a"."c" "foo" FROM "t" "a"') self.assertEqual(tuple(query.params), ()) finally: Flavor.set(Flavor()) python_sql-1.8.0/sql/tests/test_cast.py0000644000000000000000000000121513615410400015143 0ustar00# This file is part of python-sql. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import unittest from sql import Cast, Column, Table class TestCast(unittest.TestCase): column = Column(Table('t'), 'c') def test_cast(self): for cast in [Cast(self.column, 'int'), self.column.cast('int')]: self.assertEqual(str(cast), 'CAST("c" AS int)') self.assertEqual(cast.params, ()) def test_cast_no_expression(self): cast = Cast(1.1, 'int') self.assertEqual(str(cast), 'CAST(%s AS int)') self.assertEqual(cast.params, (1.1,)) python_sql-1.8.0/sql/tests/test_collate.py0000644000000000000000000000126013615410400015634 0ustar00# This file is part of python-sql. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import unittest from sql import Collate, Column, Table class TestCollate(unittest.TestCase): column = Column(Table('t'), 'c') def test_collate(self): for collate in [Collate(self.column, 'C'), self.column.collate('C')]: self.assertEqual(str(collate), '"c" COLLATE "C"') self.assertEqual(collate.params, ()) def test_collate_no_expression(self): collate = Collate("foo", 'C') self.assertEqual(str(collate), '%s COLLATE "C"') self.assertEqual(collate.params, ("foo",)) python_sql-1.8.0/sql/tests/test_column.py0000644000000000000000000000154213615410400015511 0ustar00# This file is part of python-sql. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import unittest from sql import AliasManager, Column, Table class TestColumn(unittest.TestCase): def test_column(self): column = Column(Table('t'), 'c') self.assertEqual(str(column), '"c"') self.assertEqual(column.name, 'c') self.assertEqual(column.column_name, '"c"') with AliasManager(): self.assertEqual(str(column), '"a"."c"') def test_quote_in_column(self): column = Column(Table('t'), 'b "c"') self.assertEqual(str(column), '"b ""c"""') self.assertEqual(column.name, 'b "c"') self.assertEqual(column.column_name, '"b ""c"""') with AliasManager(): self.assertEqual(str(column), '"a"."b ""c"""') python_sql-1.8.0/sql/tests/test_combining_query.py0000644000000000000000000000455513615410400017415 0ustar00# This file is part of python-sql. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import unittest from sql import CombiningQuery, Table, Union, With class TestUnion(unittest.TestCase): q1 = Table('t1').select() q2 = Table('t2').select() q3 = Table('t3').select() def test_invalid_queries(self): with self.assertRaises(ValueError): CombiningQuery('foo', 'bar') def test_union2(self): query = Union(self.q1, self.q2) self.assertEqual(str(query), 'SELECT * FROM "t1" AS "a" UNION SELECT * FROM "t2" AS "b"') self.assertEqual(tuple(query.params), ()) query = self.q1 | self.q2 self.assertEqual(str(query), 'SELECT * FROM "t1" AS "a" UNION SELECT * FROM "t2" AS "b"') self.assertEqual(tuple(query.params), ()) def test_union_with(self): table = Table('t') with_ = With() with_.query = table.select(table.id, where=table.id == 1) query = Union(self.q1, self.q2, with_=with_) self.assertEqual(str(query), 'WITH "a" AS (' 'SELECT "b"."id" FROM "t" AS "b" WHERE "b"."id" = %s) ' 'SELECT * FROM "t1" AS "c" UNION SELECT * FROM "t2" AS "d"') self.assertEqual(tuple(query.params), (1,)) def test_union3(self): query = Union(self.q1, self.q2, self.q3) self.assertEqual(str(query), 'SELECT * FROM "t1" AS "a" UNION SELECT * FROM "t2" AS "b" ' 'UNION SELECT * FROM "t3" AS "c"') self.assertEqual(tuple(query.params), ()) query = Union(Union(self.q1, self.q2), self.q3) self.assertEqual(str(query), 'SELECT * FROM "t1" AS "a" UNION SELECT * FROM "t2" AS "b" ' 'UNION SELECT * FROM "t3" AS "c"') self.assertEqual(tuple(query.params), ()) query = Union(self.q1, Union(self.q2, self.q3)) self.assertEqual(str(query), 'SELECT * FROM "t1" AS "a" UNION SELECT * FROM "t2" AS "b" ' 'UNION SELECT * FROM "t3" AS "c"') self.assertEqual(tuple(query.params), ()) query = self.q1 | self.q2 | self.q3 self.assertEqual(str(query), 'SELECT * FROM "t1" AS "a" UNION SELECT * FROM "t2" AS "b" ' 'UNION SELECT * FROM "t3" AS "c"') self.assertEqual(tuple(query.params), ()) python_sql-1.8.0/sql/tests/test_conditionals.py0000644000000000000000000000521313615410400016701 0ustar00# This file is part of python-sql. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import unittest from sql import Table from sql.conditionals import Case, Coalesce, Greatest, Least, NullIf class TestConditionals(unittest.TestCase): table = Table('t') def test_case(self): case = Case((self.table.c1, 'foo'), (self.table.c2, 'bar'), else_=self.table.c3) self.assertEqual(str(case), 'CASE WHEN "c1" THEN %s ' 'WHEN "c2" THEN %s ' 'ELSE "c3" END') self.assertEqual(case.params, ('foo', 'bar')) def test_case_no_expression(self): case = Case((True, self.table.c1), (self.table.c2, False), else_=False) self.assertEqual(str(case), 'CASE WHEN %s THEN "c1" ' 'WHEN "c2" THEN %s ' 'ELSE %s END') self.assertEqual(case.params, (True, False, False)) def test_case_sql(self): case = Case( (self.table.select(self.table.bool, where=self.table.c2 == 'bar'), self.table.c1), else_=self.table.select(self.table.c1, where=self.table.c2 == 'foo')) self.assertEqual(str(case), 'CASE WHEN ' '(SELECT "a"."bool" FROM "t" AS "a" WHERE "a"."c2" = %s) ' 'THEN "c1" ' 'ELSE (SELECT "a"."c1" FROM "t" AS "a" WHERE "a"."c2" = %s) END') self.assertEqual(case.params, ('bar', 'foo')) def test_coalesce(self): coalesce = Coalesce(self.table.c1, self.table.c2, 'foo') self.assertEqual(str(coalesce), 'COALESCE("c1", "c2", %s)') self.assertEqual(coalesce.params, ('foo',)) def test_coalesce_sql(self): coalesce = Coalesce( self.table.select(self.table.c1, where=self.table.c2 == 'bar'), self.table.c2) self.assertEqual(str(coalesce), 'COALESCE(' '(SELECT "a"."c1" FROM "t" AS "a" WHERE "a"."c2" = %s), "c2")') self.assertEqual(coalesce.params, ('bar',)) def test_nullif(self): nullif = NullIf(self.table.c1, 'foo') self.assertEqual(str(nullif), 'NULLIF("c1", %s)') self.assertEqual(nullif.params, ('foo',)) def test_greatest(self): greatest = Greatest(self.table.c1, self.table.c2, 'foo') self.assertEqual(str(greatest), 'GREATEST("c1", "c2", %s)') self.assertEqual(greatest.params, ('foo',)) def test_least(self): least = Least(self.table.c1, self.table.c2, 'foo') self.assertEqual(str(least), 'LEAST("c1", "c2", %s)') self.assertEqual(least.params, ('foo',)) python_sql-1.8.0/sql/tests/test_delete.py0000644000000000000000000000427513615410400015464 0ustar00# This file is part of python-sql. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import unittest from sql import Delete, Table, With class TestDelete(unittest.TestCase): table = Table('t') def test_delete1(self): query = self.table.delete() self.assertEqual(str(query), 'DELETE FROM "t"') self.assertEqual(query.params, ()) def test_delete2(self): query = self.table.delete(where=(self.table.c == 'foo')) self.assertEqual(str(query), 'DELETE FROM "t" WHERE "c" = %s') self.assertEqual(query.params, ('foo',)) def test_delete3(self): t1 = Table('t1') t2 = Table('t2') query = t1.delete(where=(t1.c.in_(t2.select(t2.c)))) self.assertEqual(str(query), 'DELETE FROM "t1" WHERE "c" IN (' 'SELECT "a"."c" FROM "t2" AS "a")') self.assertEqual(query.params, ()) def test_delete_invalid_table(self): with self.assertRaises(ValueError): Delete('foo') def test_delete_invalid_where(self): with self.assertRaises(ValueError): self.table.delete(where='foo') def test_delete_returning(self): query = self.table.delete(returning=[self.table.c]) self.assertEqual(str(query), 'DELETE FROM "t" RETURNING "c"') self.assertEqual(query.params, ()) def test_delet_returning_select(self): query = self.table.delete(returning=[self.table.select()]) self.assertEqual( str(query), 'DELETE FROM "t" RETURNING (SELECT * FROM "t")') self.assertEqual(query.params, ()) def test_delete_invalid_returning(self): with self.assertRaises(ValueError): self.table.delete(returning='foo') def test_with(self): t1 = Table('t1') w = With(query=t1.select(t1.c1)) query = self.table.delete(with_=[w], where=self.table.c2.in_(w.select(w.c3))) self.assertEqual(str(query), 'WITH "a" AS (SELECT "b"."c1" FROM "t1" AS "b") ' 'DELETE FROM "t" WHERE ' '"c2" IN (SELECT "a"."c3" FROM "a" AS "a")') self.assertEqual(query.params, ()) python_sql-1.8.0/sql/tests/test_excluded.py0000644000000000000000000000060413615410400016007 0ustar00# This file is part of python-sql. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import unittest from sql import Excluded class TestExcluded(unittest.TestCase): def test_alias(self): self.assertEqual(Excluded.alias, 'EXCLUDED') def test_has_alias(self): self.assertFalse(Excluded.has_alias) python_sql-1.8.0/sql/tests/test_expression.py0000644000000000000000000000071113615410400016410 0ustar00# This file is part of python-sql. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import unittest from sql import Expression class TestExpression(unittest.TestCase): def test_str(self): with self.assertRaises(NotImplementedError): str(Expression()) def test_params(self): with self.assertRaises(NotImplementedError): Expression().params python_sql-1.8.0/sql/tests/test_flavor.py0000644000000000000000000000233013615410400015501 0ustar00# This file is part of python-sql. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import unittest from sql import Flavor class TestFlavor(unittest.TestCase): def test(self): Flavor() def test_limitstyle(self): flavor = Flavor(limitstyle='rownum') self.assertEqual(flavor.limitstyle, 'rownum') def test_invalid_limitstyle(self): with self.assertRaises(ValueError): Flavor(limitstyle='foo') def test_max_limit(self): flavor = Flavor(max_limit=42) self.assertEqual(flavor.max_limit, 42) def test_invalid_max_limit(self): with self.assertRaises(ValueError): Flavor(max_limit='foo') def test_paramstyle_format(self): flavor = Flavor(paramstyle='format') self.assertEqual(flavor.paramstyle, 'format') self.assertEqual(flavor.param, '%s') def test_paramstyle_qmark(self): flavor = Flavor(paramstyle='qmark') self.assertEqual(flavor.paramstyle, 'qmark') self.assertEqual(flavor.param, '?') def test_invalid_paramstyle(self): with self.assertRaises(ValueError): Flavor(paramstyle='foo') python_sql-1.8.0/sql/tests/test_for.py0000644000000000000000000000121313615410400014775 0ustar00# This file is part of python-sql. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import unittest from sql import For, Table class TestFor(unittest.TestCase): def test_for(self): for_ = For('UPDATE', Table('t1'), Table('t2'), nowait=True) self.assertEqual(str(for_), 'FOR UPDATE OF "t1", "t2" NOWAIT') def test_for_single_table(self): for_ = For('UPDATE') for_.tables = Table('t1') self.assertEqual(str(for_), 'FOR UPDATE OF "t1"') def test_invalid_type(self): with self.assertRaises(ValueError): For('foo') python_sql-1.8.0/sql/tests/test_from.py0000644000000000000000000000131113615410400015151 0ustar00# This file is part of python-sql. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import unittest from sql import CombiningQuery, From, Table class TestFrom(unittest.TestCase): def test_add(self): t1 = Table('t1') t2 = Table('t2') from_ = From([t1]) + t2 self.assertEqual(from_, [t1, t2]) def test_invalid_add(self): with self.assertRaises(TypeError): From([Table('t')]) + 'foo' def test_invalid_add_combining_query(self): with self.assertRaises(TypeError): From([Table('t')]) + CombiningQuery( Table('t1').select(), Table('t2').select()) python_sql-1.8.0/sql/tests/test_from_item.py0000644000000000000000000000223113615410400016171 0ustar00# This file is part of python-sql. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import unittest from sql import AliasManager, Column, From, FromItem class TestFromItem(unittest.TestCase): def test_from_item(self): from_item = FromItem() with AliasManager(): self.assertFalse(from_item.has_alias) from_item.alias self.assertTrue(from_item.has_alias) def test_get_column(self): from_item = FromItem() foo = from_item.foo self.assertIsInstance(foo, Column) self.assertEqual(foo.name, 'foo') def test_get_invalid_column(self): from_item = FromItem() with self.assertRaises(AttributeError): from_item.__foo__ def test_add(self): from_item1 = FromItem() from_item2 = FromItem() from_ = from_item1 + from_item2 self.assertIsInstance(from_, From) self.assertEqual(from_, [from_item1, from_item2]) def test_invalid_add(self): from_item = FromItem() with self.assertRaises(TypeError): from_item + 'foo' python_sql-1.8.0/sql/tests/test_functions.py0000644000000000000000000001564213615410400016232 0ustar00# This file is part of python-sql. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import unittest from sql import AliasManager, Flavor, Table, Window from sql.functions import ( Abs, AtTimeZone, CurrentTime, Div, Extract, Function, FunctionKeyword, FunctionNotCallable, Overlay, Rank, Trim, WindowFunction) class TestFunctions(unittest.TestCase): table = Table('t') def test_invalid_columns_definitions(self): with self.assertRaises(ValueError): Function(columns_definitions='foo') def test_abs(self): abs_ = Abs(self.table.c1) self.assertEqual(str(abs_), 'ABS("c1")') self.assertEqual(abs_.params, ()) abs_ = Abs(-12) self.assertEqual(str(abs_), 'ABS(%s)') self.assertEqual(abs_.params, (-12,)) def test_mapping(self): class MyAbs(Function): _function = 'MY_ABS' params = ('test',) class MyOverlay(FunctionKeyword): _function = 'MY_OVERLAY' _keywords = ('', 'PLACING', 'FROM', 'FOR') class MyCurrentTime(FunctionNotCallable): _function = 'MY_CURRENT_TIME' class MyTrim(Trim): _function = 'MY_TRIM' abs_ = Abs(self.table.c1) overlay = Overlay(self.table.c1, 'test', 2) current_time = CurrentTime() trim = Trim(' test ') flavor = Flavor(function_mapping={ Abs: MyAbs, Overlay: MyOverlay, CurrentTime: MyCurrentTime, Trim: MyTrim, }) Flavor.set(flavor) try: self.assertEqual(str(abs_), 'MY_ABS("c1")') self.assertEqual(abs_.params, ('test',)) self.assertEqual(str(overlay), 'MY_OVERLAY("c1" PLACING %s FROM %s)') self.assertEqual(overlay.params, ('test', 2)) self.assertEqual(str(current_time), 'MY_CURRENT_TIME') self.assertEqual(current_time.params, ()) self.assertEqual(str(trim), 'MY_TRIM(BOTH %s FROM %s)') self.assertEqual(trim.params, (' ', ' test ',)) finally: Flavor.set(Flavor()) def test_sql(self): abs_ = Abs(self.table.select(self.table.c1, where=self.table.c2 == 'foo')) self.assertEqual(str(abs_), 'ABS((SELECT "a"."c1" FROM "t" AS "a" WHERE "a"."c2" = %s))') self.assertEqual(abs_.params, ('foo',)) def test_overlay(self): overlay = Overlay(self.table.c1, 'test', 3) self.assertEqual(str(overlay), 'OVERLAY("c1" PLACING %s FROM %s)') self.assertEqual(overlay.params, ('test', 3)) overlay = Overlay(self.table.c1, 'test', 3, 7) self.assertEqual(str(overlay), 'OVERLAY("c1" PLACING %s FROM %s FOR %s)') self.assertEqual(overlay.params, ('test', 3, 7)) def test_trim(self): trim = Trim(' test ') self.assertEqual(str(trim), 'TRIM(BOTH %s FROM %s)') self.assertEqual(trim.params, (' ', ' test ',)) trim = Trim(self.table.c1) self.assertEqual(str(trim), 'TRIM(BOTH %s FROM "c1")') self.assertEqual(trim.params, (' ',)) def test_trim_invalid_position(self): with self.assertRaises(ValueError): Trim('test', 'foo') def test_at_time_zone(self): time_zone = AtTimeZone(self.table.c1, 'UTC') self.assertEqual(str(time_zone), '"c1" AT TIME ZONE %s') self.assertEqual(time_zone.params, ('UTC',)) def test_at_time_zone_expression(self): time_zone = AtTimeZone(self.table.c1, self.table.zone) self.assertEqual(str(time_zone), '"c1" AT TIME ZONE "zone"') self.assertEqual(time_zone.params, ()) def test_at_time_zone_sql(self): time_zone = AtTimeZone(self.table.c1, self.table.select(self.table.tz, where=self.table.c1 == 'foo')) self.assertEqual(str(time_zone), '"c1" AT TIME ZONE ' '(SELECT "a"."tz" FROM "t" AS "a" WHERE "a"."c1" = %s)') self.assertEqual(time_zone.params, ('foo',)) def test_at_time_zone_mapping(self): class MyAtTimeZone(Function): _function = 'MY_TIMEZONE' time_zone = AtTimeZone(self.table.c1, 'UTC') flavor = Flavor(function_mapping={ AtTimeZone: MyAtTimeZone, }) Flavor.set(flavor) try: self.assertEqual(str(time_zone), 'MY_TIMEZONE("c1", %s)') self.assertEqual(time_zone.params, ('UTC',)) finally: Flavor.set(Flavor()) def test_div(self): for div in [Div(self.table.c1, self.table.c2), self.table.c1 // self.table.c2]: self.assertEqual(str(div), 'DIV("c1", "c2")') self.assertEqual(div.params, ()) def test_current_time(self): current_time = CurrentTime() self.assertEqual(str(current_time), 'CURRENT_TIME') self.assertEqual(current_time.params, ()) def test_extract(self): extract = Extract(Extract.Fields.DAY, self.table.c) self.assertEqual(str(extract), 'EXTRACT(DAY FROM "c")') self.assertEqual(extract.params, ()) extract = Extract('day', self.table.c) self.assertEqual(str(extract), 'EXTRACT(DAY FROM "c")') self.assertEqual(extract.params, ()) extract = Extract(Extract.Fields.DAY, '2000-01-01') self.assertEqual(str(extract), 'EXTRACT(DAY FROM %s)') self.assertEqual(extract.params, ('2000-01-01',)) def test_extract_mapping(self): class MyExtract(Function): _function = 'MY_EXTRACT' extract = Extract(Extract.Fields.DAY, '2000-01-01') flavor = Flavor(function_mapping={ Extract: MyExtract, }) Flavor.set(flavor) try: self.assertEqual(str(extract), 'MY_EXTRACT(%s, %s)') self.assertEqual(extract.params, ('DAY', '2000-01-01')) finally: Flavor.set(Flavor()) def test_extract_invalid_field(self): with self.assertRaises(ValueError): Extract('foo', self.table.c) class TestWindowFunction(unittest.TestCase): def test_window(self): t = Table('t') function = Rank(t.c, window=Window([])) with AliasManager(): self.assertEqual(str(function), 'RANK("a"."c") OVER ()') self.assertEqual(function.params, ()) def test_invalid_window(self): with self.assertRaises(ValueError): WindowFunction(window='foo') def test_filter(self): t = Table('t') function = Rank(t.c, filter_=t.c > 0, window=Window([])) with AliasManager(): self.assertEqual(str(function), 'RANK("a"."c") FILTER (WHERE "a"."c" > %s) OVER ()') self.assertEqual(function.params, (0,)) def test_invalid_filter(self): with self.assertRaises(ValueError): WindowFunction(filter_='foo', window=Window([])) python_sql-1.8.0/sql/tests/test_grouping.py0000644000000000000000000000052213615410400016043 0ustar00# This file is part of python-sql. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import unittest from sql import Grouping class TestGrouping(unittest.TestCase): def test_invalid_sets(self): with self.assertRaises(ValueError): Grouping('foo') python_sql-1.8.0/sql/tests/test_insert.py0000644000000000000000000002221713615410400015522 0ustar00# This file is part of python-sql. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import unittest from sql import Conflict, Excluded, Insert, Table, With from sql.functions import Abs class TestInsert(unittest.TestCase): table = Table('t') def test_insert_invalid_table(self): with self.assertRaises(ValueError): Insert('foo') def test_insert_invalid_columns(self): with self.assertRaises(ValueError): self.table.insert(['foo'], [['foo']]) def test_insert_invalid_values(self): with self.assertRaises(ValueError): self.table.insert([self.table.c], 'foo') def test_insert_default(self): query = self.table.insert() self.assertEqual(str(query), 'INSERT INTO "t" DEFAULT VALUES') self.assertEqual(tuple(query.params), ()) def test_insert_values(self): query = self.table.insert([self.table.c1, self.table.c2], [['foo', 'bar']]) self.assertEqual(str(query), 'INSERT INTO "t" ("c1", "c2") VALUES (%s, %s)') self.assertEqual(tuple(query.params), ('foo', 'bar')) def test_insert_many_values(self): query = self.table.insert([self.table.c1, self.table.c2], [['foo', 'bar'], ['spam', 'eggs']]) self.assertEqual(str(query), 'INSERT INTO "t" ("c1", "c2") VALUES (%s, %s), (%s, %s)') self.assertEqual(tuple(query.params), ('foo', 'bar', 'spam', 'eggs')) def test_insert_subselect(self): t1 = Table('t1') t2 = Table('t2') subquery = t2.select(t2.c1, t2.c2) query = t1.insert([t1.c1, t1.c2], subquery) self.assertEqual(str(query), 'INSERT INTO "t1" ("c1", "c2") ' 'SELECT "a"."c1", "a"."c2" FROM "t2" AS "a"') self.assertEqual(tuple(query.params), ()) def test_insert_function(self): query = self.table.insert([self.table.c], [[Abs(-1)]]) self.assertEqual(str(query), 'INSERT INTO "t" ("c") VALUES (ABS(%s))') self.assertEqual(tuple(query.params), (-1,)) def test_insert_returning(self): query = self.table.insert([self.table.c1, self.table.c2], [['foo', 'bar']], returning=[self.table.c1, self.table.c2]) self.assertEqual(str(query), 'INSERT INTO "t" AS "a" ("c1", "c2") VALUES (%s, %s) ' 'RETURNING "a"."c1", "a"."c2"') self.assertEqual(tuple(query.params), ('foo', 'bar')) def test_insert_returning_select(self): t1 = Table('t1') t2 = Table('t2') query = t1.insert([t1.c], [['foo']], returning=[ t2.select(t2.c, where=(t2.c1 == t1.c) & (t2.c2 == 'bar'))]) self.assertEqual(str(query), 'INSERT INTO "t1" AS "b" ("c") VALUES (%s) ' 'RETURNING (SELECT "a"."c" FROM "t2" AS "a" ' 'WHERE ("a"."c1" = "b"."c") AND ("a"."c2" = %s))') self.assertEqual(tuple(query.params), ('foo', 'bar')) def test_insert_invalid_returning(self): with self.assertRaises(ValueError): self.table.insert(returning='foo') def test_with(self): t1 = Table('t1') w = With(query=t1.select()) query = self.table.insert( [self.table.c1], with_=[w], values=w.select()) self.assertEqual(str(query), 'WITH "a" AS (SELECT * FROM "t1" AS "b") ' 'INSERT INTO "t" ("c1") SELECT * FROM "a" AS "a"') self.assertEqual(tuple(query.params), ()) def test_insert_in_with(self): t1 = Table('t1') w = With(query=self.table.insert( [self.table.c1], values=[['foo']], returning=[self.table.id])) query = t1.update( [t1.c], [w.id], from_=[w], with_=[w]) self.assertEqual(str(query), 'WITH "a" AS (' 'INSERT INTO "t" AS "b" ("c1") VALUES (%s) ' 'RETURNING "b"."id") ' 'UPDATE "t1" AS "c" SET "c" = "a"."id" FROM "a" AS "a"') self.assertEqual(tuple(query.params), ('foo',)) def test_schema(self): t1 = Table('t1', 'default') query = t1.insert([t1.c1], [['foo']]) self.assertEqual(str(query), 'INSERT INTO "default"."t1" ("c1") VALUES (%s)') self.assertEqual(tuple(query.params), ('foo',)) def test_upsert_invalid_on_conflict(self): with self.assertRaises(ValueError): self.table.insert(on_conflict='foo') def test_upsert_invalid_table_on_conflict(self): with self.assertRaises(ValueError): self.table.insert(on_conflict=Conflict(Table('t1'))) def test_upsert_nothing(self): query = self.table.insert( [self.table.c1], [['foo']], on_conflict=Conflict(self.table)) self.assertEqual(str(query), 'INSERT INTO "t" AS "a" ("c1") VALUES (%s) ' 'ON CONFLICT DO NOTHING') self.assertEqual(tuple(query.params), ('foo',)) def test_upsert_indexed_column(self): query = self.table.insert( [self.table.c1], [['foo']], on_conflict=Conflict( self.table, indexed_columns=[self.table.c1, self.table.c2])) self.assertEqual(str(query), 'INSERT INTO "t" AS "a" ("c1") VALUES (%s) ' 'ON CONFLICT ("c1", "c2") DO NOTHING') self.assertEqual(tuple(query.params), ('foo',)) def test_upsert_indexed_column_index_where(self): query = self.table.insert( [self.table.c1], [['foo']], on_conflict=Conflict( self.table, indexed_columns=[self.table.c1], index_where=self.table.c2 == 'bar')) self.assertEqual(str(query), 'INSERT INTO "t" AS "a" ("c1") VALUES (%s) ' 'ON CONFLICT ("c1") WHERE "a"."c2" = %s DO NOTHING') self.assertEqual(tuple(query.params), ('foo', 'bar')) def test_upsert_update(self): query = self.table.insert( [self.table.c1], [['baz']], on_conflict=Conflict( self.table, columns=[self.table.c1, self.table.c2], values=['foo', 'bar'])) self.assertEqual(str(query), 'INSERT INTO "t" AS "a" ("c1") VALUES (%s) ' 'ON CONFLICT DO UPDATE SET ("c1", "c2") = (%s, %s)') self.assertEqual(tuple(query.params), ('baz', 'foo', 'bar')) def test_upsert_update_where(self): query = self.table.insert( [self.table.c1], [['baz']], on_conflict=Conflict( self.table, columns=[self.table.c1], values=['foo'], where=self.table.c2 == 'bar')) self.assertEqual(str(query), 'INSERT INTO "t" AS "a" ("c1") VALUES (%s) ' 'ON CONFLICT DO UPDATE SET "c1" = (%s) ' 'WHERE "a"."c2" = %s') self.assertEqual(tuple(query.params), ('baz', 'foo', 'bar')) def test_upsert_update_subquery(self): t1 = Table('t1') t2 = Table('t2') subquery = t2.select(t2.c1, t2.c2) query = t1.insert( [t1.c1], [['baz']], on_conflict=Conflict( t1, columns=[t1.c1, t1.c2], values=subquery)) self.assertEqual(str(query), 'INSERT INTO "t1" AS "b" ("c1") VALUES (%s) ' 'ON CONFLICT DO UPDATE SET ("c1", "c2") = ' '(SELECT "a"."c1", "a"."c2" FROM "t2" AS "a")') self.assertEqual(tuple(query.params), ('baz',)) def test_upsert_update_excluded(self): query = self.table.insert( [self.table.c1], [[1]], on_conflict=Conflict( self.table, columns=[self.table.c1], values=[Excluded.c1 + 2])) self.assertEqual(str(query), 'INSERT INTO "t" AS "a" ("c1") VALUES (%s) ' 'ON CONFLICT DO UPDATE SET "c1" = ("EXCLUDED"."c1" + %s)') self.assertEqual(tuple(query.params), (1, 2)) def test_conflict_invalid_table(self): with self.assertRaises(ValueError): Conflict('foo') def test_conflict_invalid_indexed_columns(self): with self.assertRaises(ValueError): Conflict(self.table, indexed_columns=['foo']) def test_conflict_indexed_columns_invalid_table(self): with self.assertRaises(ValueError): Conflict(self.table, indexed_columns=[Table('t').c]) def test_conflict_invalid_index_where(self): with self.assertRaises(ValueError): Conflict(self.table, index_where='foo') def test_conflict_invalid_columns(self): with self.assertRaises(ValueError): Conflict(self.table, columns=['foo']) def test_conflict_columns_invalid_table(self): with self.assertRaises(ValueError): Conflict(self.table, columns=[Table('t').c]) def test_conflict_invalid_values(self): with self.assertRaises(ValueError): Conflict(self.table, values='foo') def test_conflict_invalid_where(self): with self.assertRaises(ValueError): Conflict(self.table, where='foo') python_sql-1.8.0/sql/tests/test_join.py0000644000000000000000000000511713615410400015155 0ustar00# This file is part of python-sql. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import unittest from sql import AliasManager, Join, Table from sql.functions import Now class TestJoin(unittest.TestCase): def test_join(self): t1 = Table('t1') t2 = Table('t2') join = Join(t1, t2) with AliasManager(): self.assertEqual(str(join), '"t1" AS "a" INNER JOIN "t2" AS "b"') self.assertEqual(tuple(join.params), ()) join.condition = t1.c == t2.c with AliasManager(): self.assertEqual(str(join), '"t1" AS "a" INNER JOIN "t2" AS "b" ON "a"."c" = "b"."c"') def test_join_invalid_left(self): with self.assertRaises(ValueError): Join('foo', Table('t1')) def test_join_invalid_right(self): with self.assertRaises(ValueError): Join(Table('t1'), 'foo') def test_join_invalid_condition(self): with self.assertRaises(ValueError): Join(Table('t1'), Table('t2'), condition='foo') def test_join_invalid_type(self): with self.assertRaises(ValueError): Join(Table('t1'), Table('t2'), type_='foo') def test_join_subselect(self): t1 = Table('t1') t2 = Table('t2') select = t2.select() join = Join(t1, select) join.condition = t1.c == select.c with AliasManager(): self.assertEqual(str(join), '"t1" AS "a" INNER JOIN (SELECT * FROM "t2" AS "c") AS "b" ' 'ON "a"."c" = "b"."c"') self.assertEqual(tuple(join.params), ()) def test_join_function(self): t1 = Table('t1') join = Join(t1, Now()) with AliasManager(): self.assertEqual(str(join), '"t1" AS "a" INNER JOIN NOW() AS "b"') self.assertEqual(tuple(join.params), ()) def test_join_methods(self): t1 = Table('t1') t2 = Table('t2') for method in [ 'left_join', 'left_outer_join', 'right_join', 'right_outer_join', 'full_join', 'full_outer_join', 'cross_join']: with self.subTest(method=method): join = getattr(t1, method)(t2) type_ = method[:-len('_join')].replace('_', ' ').upper() self.assertEqual(join.type_, type_) def test_join_alias(self): join = Join(Table('t1'), Table('t2')) with self.assertRaises(AttributeError): join.alias with self.assertRaises(AttributeError): join.has_alias python_sql-1.8.0/sql/tests/test_lateral.py0000644000000000000000000000202513615410400015635 0ustar00# This file is part of python-sql. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import unittest from sql import From, Lateral, Table from sql.functions import Function class TestLateral(unittest.TestCase): def test_lateral_select(self): t1 = Table('t1') t2 = Table('t2') lateral = t2.select(where=t2.id == t1.t2).lateral() query = From([t1, lateral]).select() self.assertEqual(str(query), 'SELECT * FROM "t1" AS "a", LATERAL ' '(SELECT * FROM "t2" AS "c" WHERE "c"."id" = "a"."t2") AS "b"') self.assertEqual(tuple(query.params), ()) def test_lateral_function(self): class Func(Function): _function = 'FUNC' t = Table('t') lateral = Lateral(Func(t.a)) query = From([t, lateral]).select() self.assertEqual(str(query), 'SELECT * FROM "t" AS "a", LATERAL FUNC("a"."a") AS "b"') self.assertEqual(tuple(query.params), ()) python_sql-1.8.0/sql/tests/test_literal.py0000644000000000000000000000176213615410400015654 0ustar00# This file is part of python-sql. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import unittest from sql import Flavor, Literal class TestLiteral(unittest.TestCase): def test_literal(self): literal = Literal(1) self.assertEqual(str(literal), '%s') self.assertEqual(literal.params, (1,)) self.assertEqual(literal.value, 1) def test_no_boolean(self): true = Literal(True) false = Literal(False) self.assertEqual(str(true), '%s') self.assertEqual(true.params, (True,)) self.assertEqual(str(false), '%s') self.assertEqual(false.params, (False,)) try: Flavor.set(Flavor(no_boolean=True)) self.assertEqual(str(true), '(1 = 1)') self.assertEqual(str(false), '(1 != 1)') self.assertEqual(true.params, ()) self.assertEqual(false.params, ()) finally: Flavor.set(Flavor()) python_sql-1.8.0/sql/tests/test_merge.py0000644000000000000000000001247413615410400015321 0ustar00# This file is part of python-sql. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import unittest from sql import ( Literal, Matched, MatchedDelete, MatchedUpdate, Merge, NotMatched, NotMatchedInsert, Table, With) class TestMerge(unittest.TestCase): target = Table('t') source = Table('s') def test_merge(self): query = self.target.merge( self.source, self.target.c1 == self.source.c2, Matched()) self.assertEqual( str(query), 'MERGE INTO "t" AS "a" USING "s" AS "b" ' 'ON "a"."c1" = "b"."c2" ' 'WHEN MATCHED THEN DO NOTHING') self.assertEqual(query.params, ()) def test_merge_invalid_target(self): with self.assertRaises(ValueError): Merge('foo', self.source, Literal(True)) def test_merge_invalid_source(self): with self.assertRaises(ValueError): self.target.merge('foo', Literal(True)) def test_merge_invalid_condition(self): with self.assertRaises(ValueError): self.target.merge(self.source, 'foo') def test_merge_invalid_whens(self): with self.assertRaises(ValueError): self.target.merge(self.source, Literal(True), 'foo') def test_condition(self): query = self.target.merge( self.source, (self.target.c1 == self.source.c2) & (self.target.c3 == 42), Matched()) self.assertEqual( str(query), 'MERGE INTO "t" AS "a" USING "s" AS "b" ' 'ON ("a"."c1" = "b"."c2") AND ("a"."c3" = %s) ' 'WHEN MATCHED THEN DO NOTHING') self.assertEqual(query.params, (42,)) def test_matched(self): query = self.target.merge( self.source, self.target.c1 == self.source.c2, Matched((self.source.c3 == 42) & (self.target.c4 == self.source.c5))) self.assertEqual( str(query), 'MERGE INTO "t" AS "a" USING "s" AS "b" ' 'ON "a"."c1" = "b"."c2" ' 'WHEN MATCHED ' 'AND ("b"."c3" = %s) AND ("a"."c4" = "b"."c5") ' 'THEN DO NOTHING') self.assertEqual(query.params, (42,)) def test_matched_update(self): query = self.target.merge( self.source, self.target.c1 == self.source.c2, MatchedUpdate( [self.target.c1, self.target.c2], [self.target.c1 + self.source.c2, 42])) self.assertEqual( str(query), 'MERGE INTO "t" AS "a" USING "s" AS "b" ' 'ON "a"."c1" = "b"."c2" ' 'WHEN MATCHED THEN ' 'UPDATE SET "c1" = "a"."c1" + "b"."c2", "c2" = %s') self.assertEqual(query.params, (42,)) def test_matched_delete(self): query = self.target.merge( self.source, self.target.c1 == self.source.c2, MatchedDelete()) self.assertEqual( str(query), 'MERGE INTO "t" AS "a" USING "s" AS "b" ' 'ON "a"."c1" = "b"."c2" ' 'WHEN MATCHED THEN DELETE') self.assertEqual(query.params, ()) def test_not_matched(self): query = self.target.merge( self.source, self.target.c1 == self.source.c2, NotMatched()) self.assertEqual( str(query), 'MERGE INTO "t" AS "a" USING "s" AS "b" ' 'ON "a"."c1" = "b"."c2" ' 'WHEN NOT MATCHED THEN DO NOTHING') self.assertEqual(query.params, ()) def test_not_matched_insert(self): query = self.target.merge( self.source, self.target.c1 == self.source.c2, NotMatchedInsert( [self.target.c1, self.target.c2], [self.source.c3, self.source.c4])) self.assertEqual( str(query), 'MERGE INTO "t" AS "a" USING "s" AS "b" ' 'ON "a"."c1" = "b"."c2" ' 'WHEN NOT MATCHED THEN ' 'INSERT ("c1", "c2") VALUES ("b"."c3", "b"."c4")') self.assertEqual(query.params, ()) def test_not_matched_insert_default(self): query = self.target.merge( self.source, self.target.c1 == self.source.c2, NotMatchedInsert([self.target.c1, self.target.c2], None)) self.assertEqual( str(query), 'MERGE INTO "t" AS "a" USING "s" AS "b" ' 'ON "a"."c1" = "b"."c2" ' 'WHEN NOT MATCHED THEN ' 'INSERT ("c1", "c2") DEFAULT VALUES') self.assertEqual(query.params, ()) def test_matched_invalid_condition(self): with self.assertRaises(ValueError): Matched('foo') def test_matched_values_invalid_columns(self): with self.assertRaises(ValueError): MatchedUpdate('foo', []) def test_with(self): t1 = Table('t1') w = With(query=t1.select(where=t1.c2 == 42)) source = w.select() query = self.target.merge( source, self.target.c1 == source.c2, Matched(), with_=[w]) self.assertEqual( str(query), 'WITH "a" AS (SELECT * FROM "t1" AS "d" WHERE "d"."c2" = %s) ' 'MERGE INTO "t" AS "b" ' 'USING (SELECT * FROM "a" AS "a") AS "c" ' 'ON "b"."c1" = "c"."c2" ' 'WHEN MATCHED THEN DO NOTHING') self.assertEqual(query.params, (42,)) python_sql-1.8.0/sql/tests/test_operators.py0000644000000000000000000004056013615410400016235 0ustar00# This file is part of python-sql. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import unittest import warnings from array import array from sql import Flavor, Literal, Null, Table from sql.operators import ( Abs, And, Any, Between, Div, Equal, Exists, FloorDiv, Greater, GreaterEqual, ILike, In, Is, IsDistinct, IsNot, IsNotDistinct, Less, LessEqual, Like, LShift, Mod, Mul, Neg, Not, NotBetween, NotEqual, NotILike, NotIn, NotLike, Operator, Or, Pos, Pow, RShift, Sub) class TestOperators(unittest.TestCase): table = Table('t') def test_operator_operands(self): self.assertEqual(Operator()._operands, ()) def test_operator_str(self): with self.assertRaises(NotImplementedError): str(Operator()) def test_and(self): for and_ in [And((self.table.c1, self.table.c2)), self.table.c1 & self.table.c2]: self.assertEqual(str(and_), '"c1" AND "c2"') self.assertEqual(and_.params, ()) and_ = And((Literal(True), self.table.c2)) self.assertEqual(str(and_), '%s AND "c2"') self.assertEqual(and_.params, (True,)) and_ = And((Literal(True), 'foo')) self.assertEqual(str(and_), '%s AND %s') self.assertEqual(and_.params, (True, 'foo')) def test_operator_operators(self): and_ = And((Literal(True), self.table.c1)) and2 = and_ & And((Literal(True), self.table.c2)) self.assertEqual(str(and2), '(%s AND "c1") AND %s AND "c2"') self.assertEqual(and2.params, (True, True)) and3 = and_ & Literal(True) self.assertEqual(str(and3), '(%s AND "c1") AND %s') self.assertEqual(and3.params, (True, True)) or_ = Or((Literal(True), self.table.c1)) or2 = or_ | Or((Literal(True), self.table.c2)) self.assertEqual(str(or2), '(%s OR "c1") OR %s OR "c2"') self.assertEqual(or2.params, (True, True)) or3 = or_ | Literal(True) self.assertEqual(str(or3), '(%s OR "c1") OR %s') self.assertEqual(or3.params, (True, True)) def test_operator_compat_column(self): and_ = And((self.table.c1, self.table.c2)) self.assertEqual(and_.table, '') self.assertEqual(and_.name, '') def test_or(self): for or_ in [Or((self.table.c1, self.table.c2)), self.table.c1 | self.table.c2]: self.assertEqual(str(or_), '"c1" OR "c2"') self.assertEqual(or_.params, ()) def test_not(self): for not_ in [Not(self.table.c), ~self.table.c]: self.assertEqual(str(not_), 'NOT "c"') self.assertEqual(not_.params, ()) not_ = Not(Literal(False)) self.assertEqual(str(not_), 'NOT %s') self.assertEqual(not_.params, (False,)) def test_neg(self): for neg in [Neg(self.table.c1), -self.table.c1]: self.assertEqual(str(neg), '- "c1"') self.assertEqual(neg.params, ()) def test_pos(self): for pos in [Pos(self.table.c1), +self.table.c1]: self.assertEqual(str(pos), '+ "c1"') self.assertEqual(pos.params, ()) def test_less(self): for less in [Less(self.table.c1, self.table.c2), self.table.c1 < self.table.c2, ~GreaterEqual(self.table.c1, self.table.c2)]: self.assertEqual(str(less), '"c1" < "c2"') self.assertEqual(less.params, ()) less = Less(Literal(0), self.table.c2) self.assertEqual(str(less), '%s < "c2"') self.assertEqual(less.params, (0,)) def test_greater(self): for greater in [Greater(self.table.c1, self.table.c2), self.table.c1 > self.table.c2, ~LessEqual(self.table.c1, self.table.c2)]: self.assertEqual(str(greater), '"c1" > "c2"') self.assertEqual(greater.params, ()) def test_less_equal(self): for less in [LessEqual(self.table.c1, self.table.c2), self.table.c1 <= self.table.c2, ~Greater(self.table.c1, self.table.c2)]: self.assertEqual(str(less), '"c1" <= "c2"') self.assertEqual(less.params, ()) def test_greater_equal(self): for greater in [GreaterEqual(self.table.c1, self.table.c2), self.table.c1 >= self.table.c2, ~Less(self.table.c1, self.table.c2)]: self.assertEqual(str(greater), '"c1" >= "c2"') self.assertEqual(greater.params, ()) def test_equal(self): for equal in [Equal(self.table.c1, self.table.c2), self.table.c1 == self.table.c2, ~NotEqual(self.table.c1, self.table.c2)]: self.assertEqual(str(equal), '"c1" = "c2"') self.assertEqual(equal.params, ()) equal = Equal(Literal('foo'), Literal('bar')) self.assertEqual(str(equal), '%s = %s') self.assertEqual(equal.params, ('foo', 'bar')) equal = Equal(self.table.c1, Null) self.assertEqual(str(equal), '"c1" IS NULL') self.assertEqual(equal.params, ()) equal = Equal(Literal('test'), Null) self.assertEqual(str(equal), '%s IS NULL') self.assertEqual(equal.params, ('test',)) equal = Equal(Null, self.table.c1) self.assertEqual(str(equal), '"c1" IS NULL') self.assertEqual(equal.params, ()) equal = Equal(Null, Literal('test')) self.assertEqual(str(equal), '%s IS NULL') self.assertEqual(equal.params, ('test',)) def test_not_equal(self): for equal in [NotEqual(self.table.c1, self.table.c2), self.table.c1 != self.table.c2, ~Equal(self.table.c1, self.table.c2)]: self.assertEqual(str(equal), '"c1" != "c2"') self.assertEqual(equal.params, ()) equal = NotEqual(self.table.c1, Null) self.assertEqual(str(equal), '"c1" IS NOT NULL') self.assertEqual(equal.params, ()) equal = NotEqual(Null, self.table.c1) self.assertEqual(str(equal), '"c1" IS NOT NULL') self.assertEqual(equal.params, ()) def test_between(self): for between in [Between(self.table.c1, 1, 2), ~NotBetween(self.table.c1, 1, 2)]: self.assertEqual(str(between), '"c1" BETWEEN %s AND %s') self.assertEqual(between.params, (1, 2)) between = Between( self.table.c1, self.table.c2, self.table.c3, symmetric=True) self.assertEqual( str(between), '"c1" BETWEEN SYMMETRIC "c2" AND "c3"') self.assertEqual(between.params, ()) def test_not_between(self): for between in [NotBetween(self.table.c1, 1, 2), ~Between(self.table.c1, 1, 2)]: self.assertEqual(str(between), '"c1" NOT BETWEEN %s AND %s') self.assertEqual(between.params, (1, 2)) between = NotBetween( self.table.c1, self.table.c2, self.table.c3, symmetric=True) self.assertEqual( str(between), '"c1" NOT BETWEEN SYMMETRIC "c2" AND "c3"') self.assertEqual(between.params, ()) def test_is_distinct(self): for distinct in [IsDistinct(self.table.c1, self.table.c2), ~IsNotDistinct(self.table.c1, self.table.c2)]: self.assertEqual(str(distinct), '"c1" IS DISTINCT FROM "c2"') self.assertEqual(distinct.params, ()) def test_is_not_distinct(self): for distinct in [IsNotDistinct(self.table.c1, self.table.c2), ~IsDistinct(self.table.c1, self.table.c2)]: self.assertEqual(str(distinct), '"c1" IS NOT DISTINCT FROM "c2"') self.assertEqual(distinct.params, ()) def test_is(self): for is_ in [Is(self.table.c1, None), ~IsNot(self.table.c1, None)]: self.assertEqual(str(is_), '"c1" IS UNKNOWN') self.assertEqual(is_.params, ()) for is_ in [Is(self.table.c1, True), ~IsNot(self.table.c1, True)]: self.assertEqual(str(is_), '"c1" IS TRUE') self.assertEqual(is_.params, ()) for is_ in [Is(self.table.c1, False), ~IsNot(self.table.c1, False)]: self.assertEqual(str(is_), '"c1" IS FALSE') self.assertEqual(is_.params, ()) def test_is_invalid_right(self): with self.assertRaises(ValueError): Is(self.table.c, 'foo') def test_is_not(self): for is_ in [IsNot(self.table.c1, None), ~Is(self.table.c1, None)]: self.assertEqual(str(is_), '"c1" IS NOT UNKNOWN') self.assertEqual(is_.params, ()) for is_ in [IsNot(self.table.c1, True), ~Is(self.table.c1, True)]: self.assertEqual(str(is_), '"c1" IS NOT TRUE') self.assertEqual(is_.params, ()) for is_ in [IsNot(self.table.c1, False), ~Is(self.table.c1, False)]: self.assertEqual(str(is_), '"c1" IS NOT FALSE') self.assertEqual(is_.params, ()) def test_sub(self): for sub in [Sub(self.table.c1, self.table.c2), self.table.c1 - self.table.c2]: self.assertEqual(str(sub), '"c1" - "c2"') self.assertEqual(sub.params, ()) def test_mul(self): for mul in [Mul(self.table.c1, self.table.c2), self.table.c1 * self.table.c2]: self.assertEqual(str(mul), '"c1" * "c2"') self.assertEqual(mul.params, ()) def test_div(self): for div in [Div(self.table.c1, self.table.c2), self.table.c1 / self.table.c2]: self.assertEqual(str(div), '"c1" / "c2"') self.assertEqual(div.params, ()) def test_mod(self): for mod in [Mod(self.table.c1, self.table.c2), self.table.c1 % self.table.c2]: self.assertEqual(str(mod), '"c1" %% "c2"') self.assertEqual(mod.params, ()) def test_mod_paramstyle(self): flavor = Flavor(paramstyle='format') Flavor.set(flavor) try: mod = Mod(self.table.c1, self.table.c2) self.assertEqual(str(mod), '"c1" %% "c2"') self.assertEqual(mod.params, ()) finally: Flavor.set(Flavor()) flavor = Flavor(paramstyle='qmark') Flavor.set(flavor) try: mod = Mod(self.table.c1, self.table.c2) self.assertEqual(str(mod), '"c1" % "c2"') self.assertEqual(mod.params, ()) finally: Flavor.set(Flavor()) def test_pow(self): for pow_ in [Pow(self.table.c1, self.table.c2), self.table.c1 ** self.table.c2]: self.assertEqual(str(pow_), '"c1" ^ "c2"') self.assertEqual(pow_.params, ()) def test_abs(self): for abs_ in [Abs(self.table.c1), abs(self.table.c1)]: self.assertEqual(str(abs_), '@ "c1"') self.assertEqual(abs_.params, ()) def test_lshift(self): for lshift in [LShift(self.table.c1, 2), self.table.c1 << 2]: self.assertEqual(str(lshift), '"c1" << %s') self.assertEqual(lshift.params, (2,)) def test_rshift(self): for rshift in [RShift(self.table.c1, 2), self.table.c1 >> 2]: self.assertEqual(str(rshift), '"c1" >> %s') self.assertEqual(rshift.params, (2,)) def test_like(self): for like in [Like(self.table.c1, 'foo'), self.table.c1.like('foo'), ~NotLike(self.table.c1, 'foo'), ~~Like(self.table.c1, 'foo')]: self.assertEqual(str(like), '"c1" LIKE %s') self.assertEqual(like.params, ('foo',)) def test_like_escape(self): like = Like(self.table.c1, 'foo', escape='$') self.assertEqual(str(like), '"c1" LIKE %s ESCAPE %s') self.assertEqual(like.params, ('foo', '$')) def test_like_escape_empty_false(self): flavor = Flavor(escape_empty=False) Flavor.set(flavor) try: like = Like(self.table.c1, 'foo') self.assertEqual(str(like), '"c1" LIKE %s') self.assertEqual(like.params, ('foo',)) finally: Flavor.set(Flavor()) def test_like_escape_empty_true(self): flavor = Flavor(escape_empty=True) Flavor.set(flavor) try: like = Like(self.table.c1, 'foo') self.assertEqual(str(like), '"c1" LIKE %s ESCAPE %s') self.assertEqual(like.params, ('foo', '')) finally: Flavor.set(Flavor()) def test_like_invalid_escape(self): with self.assertRaises(ValueError): Like(self.table.c, 'test', escape='fo') def test_ilike(self): flavor = Flavor(ilike=True) Flavor.set(flavor) try: for like in [ILike(self.table.c1, 'foo'), self.table.c1.ilike('foo'), ~NotILike(self.table.c1, 'foo')]: self.assertEqual(str(like), '"c1" ILIKE %s') self.assertEqual(like.params, ('foo',)) finally: Flavor.set(Flavor()) flavor = Flavor(ilike=False) Flavor.set(flavor) try: like = ILike(self.table.c1, 'foo') self.assertEqual( str(like), 'UPPER("c1") LIKE UPPER(%s)') self.assertEqual(like.params, ('foo',)) finally: Flavor.set(Flavor()) def test_not_ilike(self): flavor = Flavor(ilike=True) Flavor.set(flavor) try: for like in [NotILike(self.table.c1, 'foo'), ~self.table.c1.ilike('foo')]: self.assertEqual(str(like), '"c1" NOT ILIKE %s') self.assertEqual(like.params, ('foo',)) finally: Flavor.set(Flavor()) flavor = Flavor(ilike=False) Flavor.set(flavor) try: like = NotILike(self.table.c1, 'foo') self.assertEqual( str(like), 'UPPER("c1") NOT LIKE UPPER(%s)') self.assertEqual(like.params, ('foo',)) finally: Flavor.set(Flavor()) def test_in(self): for in_ in [In(self.table.c1, [self.table.c2, 1, Null]), ~NotIn(self.table.c1, [self.table.c2, 1, Null]), ~~In(self.table.c1, [self.table.c2, 1, Null])]: self.assertEqual(str(in_), '"c1" IN ("c2", %s, %s)') self.assertEqual(in_.params, (1, None)) t2 = Table('t2') in_ = In(self.table.c1, t2.select(t2.c2)) self.assertEqual(str(in_), '"c1" IN (SELECT "a"."c2" FROM "t2" AS "a")') self.assertEqual(in_.params, ()) in_ = In(self.table.c1, t2.select(t2.c2) | t2.select(t2.c3)) self.assertEqual(str(in_), '"c1" IN (SELECT "a"."c2" FROM "t2" AS "a" ' 'UNION SELECT "a"."c3" FROM "t2" AS "a")') self.assertEqual(in_.params, ()) in_ = In(self.table.c1, array('l', list(range(10)))) self.assertEqual(str(in_), '"c1" IN (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)') self.assertEqual(in_.params, tuple(range(10))) def test_exists(self): exists = Exists(self.table.select(self.table.c1, where=self.table.c1 == 1)) self.assertEqual(str(exists), 'EXISTS (SELECT "a"."c1" FROM "t" AS "a" WHERE "a"."c1" = %s)') self.assertEqual(exists.params, (1,)) def test_floordiv(self): with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") FloorDiv(4, 2) self.assertEqual(len(w), 1) self.assertTrue(issubclass(w[-1].category, DeprecationWarning)) if hasattr(self, 'assertIn'): self.assertIn( 'FloorDiv operator is deprecated, use Div function', str(w[-1].message)) def test_any(self): any_ = Any(self.table.select(self.table.c1, where=self.table.c2 == 1)) self.assertEqual(str(any_), 'ANY (SELECT "a"."c1" FROM "t" AS "a" WHERE "a"."c2" = %s)') self.assertEqual(any_.params, (1,)) for value in [[1, 2, 3], (1, 2, 3), array('l', [1, 2, 3])]: with self.subTest(value=value): any_ = Any(value) self.assertEqual(str(any_), 'ANY (%s)') self.assertEqual(any_.params, ([1, 2, 3],)) def test_binary_unary(self): operator = Equal(self.table.c1, Any([1, 2, 3])) self.assertEqual(str(operator), '"c1" = ANY (%s)') self.assertEqual(operator.params, ([1, 2, 3],)) python_sql-1.8.0/sql/tests/test_order.py0000644000000000000000000000433213615410400015327 0ustar00# This file is part of python-sql. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import unittest from sql import ( Asc, Column, Desc, Flavor, Literal, NullOrder, NullsFirst, NullsLast, Order, Table) class TestOrder(unittest.TestCase): column = Column(Table('t'), 'c') def test_asc(self): self.assertEqual(str(self.column.asc), '"c" ASC') def test_desc(self): self.assertEqual(str(self.column.desc), '"c" DESC') def test_nulls_first(self): self.assertEqual(str(self.column.nulls_first), '"c" NULLS FIRST') self.assertEqual(str(Asc(self.column).nulls_first), '"c" ASC NULLS FIRST') def test_nulls_last(self): self.assertEqual(str(self.column.nulls_last), '"c" NULLS LAST') self.assertEqual(str(Asc(self.column).nulls_last), '"c" ASC NULLS LAST') def test_null_order_case_values(self): with self.assertRaises(NotImplementedError): NullOrder(self.column)._case_values() def test_no_null_ordering(self): try: Flavor.set(Flavor(null_ordering=False)) exp = NullsFirst(self.column) self.assertEqual(str(exp), 'CASE WHEN "c" IS NULL THEN %s ELSE %s END ASC, "c"') self.assertEqual(exp.params, (0, 1)) exp = NullsFirst(Desc(self.column)) self.assertEqual(str(exp), 'CASE WHEN "c" IS NULL THEN %s ELSE %s END ASC, "c" DESC') self.assertEqual(exp.params, (0, 1)) exp = NullsLast(Literal(2)) self.assertEqual(str(exp), 'CASE WHEN %s IS NULL THEN %s ELSE %s END ASC, %s') self.assertEqual(exp.params, (2, 1, 0, 2)) finally: Flavor.set(Flavor()) def test_order_query(self): table = Table('t') column = Column(table, 'c') query = table.select(column) self.assertEqual(str(Asc(query)), '(SELECT "a"."c" FROM "t" AS "a") ASC') self.assertEqual(str(Desc(query)), '(SELECT "a"."c" FROM "t" AS "a") DESC') def test_invalid_expression(self): with self.assertRaises(ValueError): Order('foo') python_sql-1.8.0/sql/tests/test_rollup.py0000644000000000000000000000052313615410400015527 0ustar00# This file is part of python-sql. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import unittest from sql import Rollup class TestRollup(unittest.TestCase): def test_invalid_expressions(self): with self.assertRaises(ValueError): Rollup('foo') python_sql-1.8.0/sql/tests/test_select.py0000644000000000000000000005546213615410400015505 0ustar00# This file is part of python-sql. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import unittest import warnings from copy import deepcopy from sql import ( Cube, Flavor, For, Grouping, Join, Literal, Rollup, Select, Table, Union, Window, With) from sql.aggregate import Max, Min from sql.functions import DatePart, Function, Now, Rank class TestSelect(unittest.TestCase): table = Table('t') def test_select1(self): query = self.table.select() self.assertEqual(str(query), 'SELECT * FROM "t" AS "a"') self.assertEqual(tuple(query.params), ()) def test_select2(self): query = self.table.select(self.table.c) self.assertEqual(str(query), 'SELECT "a"."c" FROM "t" AS "a"') self.assertEqual(tuple(query.params), ()) query.columns += (self.table.c2,) self.assertEqual(str(query), 'SELECT "a"."c", "a"."c2" FROM "t" AS "a"') def test_select3(self): query = self.table.select(where=(self.table.c == 'foo')) self.assertEqual(str(query), 'SELECT * FROM "t" AS "a" WHERE "a"."c" = %s') self.assertEqual(tuple(query.params), ('foo',)) def test_select_iter(self): query = self.table.select() self.assertEqual( tuple(query), ('SELECT * FROM "t" AS "a"', ())) def test_select_without_from(self): query = Select([Literal(1)]) self.assertEqual(str(query), 'SELECT %s') self.assertEqual(tuple(query.params), (1,)) def test_select_select(self): query = Select([Select([Literal(1)])]) self.assertEqual(str(query), 'SELECT (SELECT %s)') self.assertEqual(tuple(query.params), (1,)) def test_select_select_as(self): query = Select([Select([Literal(1)]).as_('foo')]) self.assertEqual(str(query), 'SELECT (SELECT %s) AS "foo"') self.assertEqual(tuple(query.params), (1,)) def test_select_invalid_column(self): with self.assertRaises(ValueError): Select(['foo']) def test_select_invalid_where(self): with self.assertRaises(ValueError): self.table.select(where='foo') def test_select_distinct(self): query = self.table.select(self.table.c, distinct=True) self.assertEqual( str(query), 'SELECT DISTINCT "a"."c" FROM "t" AS "a"') self.assertEqual(tuple(query.params), ()) def test_select_distinct_on(self): query = self.table.select(self.table.c, distinct_on=self.table.c) self.assertEqual( str(query), 'SELECT DISTINCT ON ("a"."c") "a"."c" FROM "t" AS "a"') self.assertEqual(tuple(query.params), ()) query = self.table.select( self.table.c, distinct_on=[self.table.a, self.table.b]) self.assertEqual( str(query), 'SELECT DISTINCT ON ("a"."a", "a"."b") "a"."c" FROM "t" AS "a"') self.assertEqual(tuple(query.params), ()) def test_select_invalid_distinct_on(self): with self.assertRaises(ValueError): self.table.select(self.table.c, distinct_on='foo') def test_select_from_list(self): t2 = Table('t2') t3 = Table('t3') query = (self.table + t2 + t3).select(self.table.c, getattr(t2, '*')) self.assertEqual(str(query), 'SELECT "a"."c", "b".* FROM "t" AS "a", "t2" AS "b", "t3" AS "c"') self.assertEqual(tuple(query.params), ()) def test_select_union(self): query1 = self.table.select() query2 = Table('t2').select() union = query1 | query2 self.assertEqual(str(union), 'SELECT * FROM "t" AS "a" UNION SELECT * FROM "t2" AS "b"') union.all_ = True self.assertEqual(str(union), 'SELECT * FROM "t" AS "a" UNION ALL ' 'SELECT * FROM "t2" AS "b"') self.assertEqual(str(union.select()), 'SELECT * FROM (' 'SELECT * FROM "t" AS "b" UNION ALL ' 'SELECT * FROM "t2" AS "c") AS "a"') query1.where = self.table.c == 'foo' self.assertEqual(str(union), 'SELECT * FROM "t" AS "a" WHERE "a"."c" = %s UNION ALL ' 'SELECT * FROM "t2" AS "b"') self.assertEqual(tuple(union.params), ('foo',)) union = Union(query1) self.assertEqual(str(union), str(query1)) self.assertEqual(tuple(union.params), tuple(query1.params)) def test_select_union_order(self): query1 = self.table.select() query2 = Table('t2').select() union = query1 | query2 union.order_by = Literal(1) self.assertEqual(str(union), 'SELECT * FROM "t" AS "a" UNION ' 'SELECT * FROM "t2" AS "b" ' 'ORDER BY %s') self.assertEqual(tuple(union.params), (1,)) def test_select_intersect(self): query1 = self.table.select() query2 = Table('t2').select() intersect = query1 & query2 self.assertEqual(str(intersect), 'SELECT * FROM "t" AS "a" INTERSECT SELECT * FROM "t2" AS "b"') from sql import Interesect, Intersect with warnings.catch_warnings(record=True) as w: warnings.simplefilter('always') interesect = Interesect(query1, query2) self.assertEqual(len(w), 1) self.assertTrue(issubclass(w[-1].category, DeprecationWarning)) if hasattr(self, 'assertIn'): self.assertIn('Interesect query is deprecated, use Intersect', str(w[-1].message)) self.assertTrue(isinstance(interesect, Intersect)) def test_select_except(self): query1 = self.table.select() query2 = Table('t2').select() except_ = query1 - query2 self.assertEqual(str(except_), 'SELECT * FROM "t" AS "a" EXCEPT SELECT * FROM "t2" AS "b"') def test_select_join(self): t1 = Table('t1') t2 = Table('t2') join = Join(t1, t2) self.assertEqual(str(join.select()), 'SELECT * FROM "t1" AS "a" INNER JOIN "t2" AS "b"') self.assertEqual(str(join.select(getattr(t1, '*'))), 'SELECT "a".* FROM "t1" AS "a" INNER JOIN "t2" AS "b"') def test_select_subselect(self): t1 = Table('t1') select = t1.select() self.assertEqual(str(select.select()), 'SELECT * FROM (SELECT * FROM "t1" AS "b") AS "a"') self.assertEqual(tuple(select.params), ()) def test_select_function(self): query = Now().select() self.assertEqual(str(query), 'SELECT * FROM NOW() AS "a"') self.assertEqual(tuple(query.params), ()) def test_select_function_columns_definitions(self): class Crosstab(Function): _function = 'CROSSTAB' query = Crosstab('query1', 'query2', columns_definitions=[ ('c1', 'INT'), ('c2', 'CHAR'), ('c3', 'BOOL')]).select() self.assertEqual(str(query), 'SELECT * FROM CROSSTAB(%s, %s) ' 'AS "a" ("c1" INT, "c2" CHAR, "c3" BOOL)') self.assertEqual(tuple(query.params), ('query1', 'query2')) def test_select_group_by(self): column = self.table.c query = self.table.select(column, group_by=column) self.assertEqual(str(query), 'SELECT "a"."c" FROM "t" AS "a" GROUP BY "a"."c"') self.assertEqual(tuple(query.params), ()) output = column.as_('c1') query = self.table.select(output, group_by=output) self.assertEqual(str(query), 'SELECT "a"."c" AS "c1" FROM "t" AS "a" GROUP BY 1') self.assertEqual(tuple(query.params), ()) query = self.table.select(Literal('foo'), group_by=Literal('foo')) self.assertEqual(str(query), 'SELECT %s FROM "t" AS "a" GROUP BY %s') self.assertEqual(tuple(query.params), ('foo', 'foo')) output1 = column.as_('c1') output2 = column.as_('c2') query = self.table.select(output1, output2, group_by=output2) self.assertEqual(str(query), 'SELECT "a"."c" AS "c1", "a"."c" AS "c2" FROM "t" AS "a" ' 'GROUP BY 2') self.assertEqual(tuple(query.params), ()) query = self.table.select(column, group_by=output) self.assertEqual(str(query), 'SELECT "a"."c" FROM "t" AS "a" GROUP BY "c1"') self.assertEqual(tuple(query.params), ()) def test_select_group_by_grouping_sets(self): query = self.table.select( Literal('*'), group_by=Grouping((self.table.a, self.table.b), (Literal('foo'),))) self.assertEqual(str(query), 'SELECT %s FROM "t" AS "a" ' 'GROUP BY GROUPING SETS (("a"."a", "a"."b"), (%s))') self.assertEqual(tuple(query.params), ('*', 'foo',)) query = self.table.select( Literal('*'), group_by=[ self.table.a, Grouping((self.table.b,), (self.table.c,))]) self.assertEqual(str(query), 'SELECT %s FROM "t" AS "a" ' 'GROUP BY "a"."a", GROUPING SETS (("a"."b"), ("a"."c"))') self.assertEqual(tuple(query.params), ('*',)) def test_select_group_by_rollup(self): query = self.table.select( Literal('*'), group_by=Rollup(self.table.a, self.table.b, Literal('foo'))) self.assertEqual(str(query), 'SELECT %s FROM "t" AS "a" ' 'GROUP BY ROLLUP ("a"."a", "a"."b", %s)') self.assertEqual(tuple(query.params), ('*', 'foo')) query = self.table.select( Literal('*'), group_by=Rollup((self.table.a, self.table.b), self.table.c)) self.assertEqual(str(query), 'SELECT %s FROM "t" AS "a" ' 'GROUP BY ROLLUP (("a"."a", "a"."b"), "a"."c")') self.assertEqual(tuple(query.params), ('*',)) def test_select_group_by_cube(self): query = self.table.select( Literal('*'), group_by=Cube(self.table.a, self.table.b)) self.assertEqual(str(query), 'SELECT %s FROM "t" AS "a" ' 'GROUP BY CUBE ("a"."a", "a"."b")') self.assertEqual(tuple(query.params), ('*',)) def test_select_invalid_group_by(self): with self.assertRaises(ValueError): self.table.select(group_by=['foo']) def test_select_invalid_group_by_alias(self): query = self.table.select( self.table.c1.as_('c'), group_by=self.table.c2.as_('c')) with self.assertRaises(ValueError): str(query) def test_select_having(self): col1 = self.table.col1 col2 = self.table.col2 query = self.table.select(col1, Min(col2), having=(Min(col2) > 3)) self.assertEqual(str(query), 'SELECT "a"."col1", MIN("a"."col2") FROM "t" AS "a" ' 'HAVING MIN("a"."col2") > %s') self.assertEqual(tuple(query.params), (3,)) def test_select_invalid_having(self): with self.assertRaises(ValueError): self.table.select(having='foo') def test_select_order(self): column = self.table.c query = self.table.select(column, order_by=column) self.assertEqual(str(query), 'SELECT "a"."c" FROM "t" AS "a" ORDER BY "a"."c"') self.assertEqual(tuple(query.params), ()) output = column.as_('c1') query = self.table.select(output, order_by=output) self.assertEqual(str(query), 'SELECT "a"."c" AS "c1" FROM "t" AS "a" ORDER BY "c1"') self.assertEqual(tuple(query.params), ()) def test_select_invalid_order(self): with self.assertRaises(ValueError): self.table.select(order_by='foo') def test_select_invalid_order_alias(self): query = self.table.select( self.table.c1.as_('c'), order_by=self.table.c2.as_('c')) with self.assertRaises(ValueError): str(query) def test_select_limit_offset(self): try: Flavor.set(Flavor(limitstyle='limit')) query = self.table.select(limit=50, offset=10) self.assertEqual(str(query), 'SELECT * FROM "t" AS "a" LIMIT %s OFFSET %s') self.assertEqual(tuple(query.params), (50, 10)) query.limit = None self.assertEqual(str(query), 'SELECT * FROM "t" AS "a" OFFSET %s') self.assertEqual(tuple(query.params), (10,)) query.offset = 0 self.assertEqual(str(query), 'SELECT * FROM "t" AS "a"') self.assertEqual(tuple(query.params), ()) Flavor.set(Flavor(limitstyle='limit', max_limit=-1)) query.offset = None self.assertEqual(str(query), 'SELECT * FROM "t" AS "a"') self.assertEqual(tuple(query.params), ()) query.offset = 0 self.assertEqual(str(query), 'SELECT * FROM "t" AS "a"') self.assertEqual(tuple(query.params), ()) query.offset = 10 self.assertEqual(str(query), 'SELECT * FROM "t" AS "a" LIMIT -1 OFFSET %s') self.assertEqual(tuple(query.params), (10,)) finally: Flavor.set(Flavor()) def test_select_invalid_limit(self): with self.assertRaises(ValueError): self.table.select(limit='foo') def test_select_invalid_offset(self): with self.assertRaises(ValueError): self.table.select(offset='foo') def test_select_offset_fetch(self): try: Flavor.set(Flavor(limitstyle='fetch')) query = self.table.select(limit=50, offset=10) self.assertEqual(str(query), 'SELECT * FROM "t" AS "a" ' 'OFFSET (%s) ROWS FETCH FIRST (%s) ROWS ONLY') self.assertEqual(tuple(query.params), (10, 50)) query.limit = None self.assertEqual(str(query), 'SELECT * FROM "t" AS "a" OFFSET (%s) ROWS') self.assertEqual(tuple(query.params), (10,)) query.offset = 0 self.assertEqual(str(query), 'SELECT * FROM "t" AS "a"') self.assertEqual(tuple(query.params), ()) finally: Flavor.set(Flavor()) def test_select_rownum(self): try: Flavor.set(Flavor(limitstyle='rownum')) query = self.table.select(limit=50, offset=10) self.assertEqual(str(query), 'SELECT "a".* FROM (' 'SELECT "b".*, ROWNUM AS "rnum" FROM (' 'SELECT * FROM "t" AS "c") AS "b" ' 'WHERE ROWNUM <= %s) AS "a" ' 'WHERE "rnum" > %s') self.assertEqual(tuple(query.params), (60, 10)) query = self.table.select( self.table.c1.as_('col1'), self.table.c2.as_('col2'), limit=50, offset=10) self.assertEqual(str(query), 'SELECT "a"."col1", "a"."col2" FROM (' 'SELECT "b"."col1", "b"."col2", ROWNUM AS "rnum" FROM (' 'SELECT "c"."c1" AS "col1", "c"."c2" AS "col2" ' 'FROM "t" AS "c") AS "b" ' 'WHERE ROWNUM <= %s) AS "a" ' 'WHERE "rnum" > %s') self.assertEqual(tuple(query.params), (60, 10)) subquery = query.select(query.col1, query.col2) self.assertEqual(str(subquery), 'SELECT "a"."col1", "a"."col2" FROM (' 'SELECT "b"."col1", "b"."col2" FROM (' 'SELECT "a"."col1", "a"."col2", ROWNUM AS "rnum" ' 'FROM (' 'SELECT "c"."c1" AS "col1", "c"."c2" AS "col2" ' 'FROM "t" AS "c") AS "a" ' 'WHERE ROWNUM <= %s) AS "b" ' 'WHERE "rnum" > %s) AS "a"') # XXX alias of query is reused but not a problem # as it is hidden in subquery self.assertEqual(tuple(query.params), (60, 10)) query = self.table.select(limit=50, offset=10, order_by=[self.table.c]) self.assertEqual(str(query), 'SELECT "a".* FROM (' 'SELECT "b".*, ROWNUM AS "rnum" FROM (' 'SELECT * FROM "t" AS "c" ORDER BY "c"."c") AS "b" ' 'WHERE ROWNUM <= %s) AS "a" ' 'WHERE "rnum" > %s') self.assertEqual(tuple(query.params), (60, 10)) query = self.table.select(limit=50) self.assertEqual(str(query), 'SELECT "a".* FROM (' 'SELECT * FROM "t" AS "b") AS "a" ' 'WHERE ROWNUM <= %s') self.assertEqual(tuple(query.params), (50,)) query = self.table.select(offset=10) self.assertEqual(str(query), 'SELECT "a".* FROM (' 'SELECT "b".*, ROWNUM AS "rnum" FROM (' 'SELECT * FROM "t" AS "c") AS "b") AS "a" ' 'WHERE "rnum" > %s') self.assertEqual(tuple(query.params), (10,)) query = self.table.select(self.table.c.as_('col'), where=self.table.c >= 20, limit=50, offset=10) self.assertEqual(str(query), 'SELECT "a"."col" FROM (' 'SELECT "b"."col", ROWNUM AS "rnum" FROM (' 'SELECT "c"."c" AS "col" FROM "t" AS "c" ' 'WHERE "c"."c" >= %s) AS "b" ' 'WHERE ROWNUM <= %s) AS "a" ' 'WHERE "rnum" > %s') self.assertEqual(tuple(query.params), (20, 60, 10)) finally: Flavor.set(Flavor()) def test_select_for(self): c = self.table.c query = self.table.select(c, for_=For('UPDATE')) self.assertEqual(str(query), 'SELECT "a"."c" FROM "t" AS "a" FOR UPDATE') self.assertEqual(tuple(query.params), ()) def test_select_invalid_for(self): with self.assertRaises(ValueError): self.table.select(for_=['foo']) def test_copy(self): query = self.table.select() copy_query = deepcopy(query) self.assertNotEqual(query, copy_query) self.assertEqual(str(copy_query), 'SELECT * FROM "t" AS "a"') self.assertEqual(tuple(copy_query.params), ()) def test_with(self): w = With(query=self.table.select(self.table.c1)) query = w.select(with_=[w]) self.assertEqual(str(query), 'WITH "a" AS (SELECT "b"."c1" FROM "t" AS "b") ' 'SELECT * FROM "a" AS "a"') self.assertEqual(tuple(query.params), ()) def test_window(self): query = self.table.select(Min(self.table.c1, window=Window([self.table.c2]))) self.assertEqual(str(query), 'SELECT MIN("a"."c1") OVER "b" FROM "t" AS "a" ' 'WINDOW "b" AS (PARTITION BY "a"."c2")') self.assertEqual(tuple(query.params), ()) query = self.table.select(Rank(window=Window([]))) self.assertEqual(str(query), 'SELECT RANK() OVER "b" FROM "t" AS "a" ' 'WINDOW "b" AS ()') self.assertEqual(tuple(query.params), ()) window = Window([self.table.c1]) query = self.table.select( Rank(filter_=self.table.c1 > 0, window=window), Min(self.table.c1, window=window)) self.assertEqual(str(query), 'SELECT RANK() FILTER (WHERE "a"."c1" > %s) OVER "b", ' 'MIN("a"."c1") OVER "b" FROM "t" AS "a" ' 'WINDOW "b" AS (PARTITION BY "a"."c1")') self.assertEqual(tuple(query.params), (0,)) window = Window([DatePart('year', self.table.date_col)]) query = self.table.select( Min(self.table.c1, window=window)) self.assertEqual(str(query), 'SELECT MIN("a"."c1") OVER "b" FROM "t" AS "a" ' 'WINDOW "b" AS (PARTITION BY DATE_PART(%s, "a"."date_col"))') self.assertEqual(tuple(query.params), ('year',)) window = Window([self.table.c2]) query = self.table.select( Max(self.table.c1, window=window) / Min(self.table.c1, window=window)) self.assertEqual(str(query), 'SELECT MAX("a"."c1") OVER (PARTITION BY "a"."c2") ' '/ MIN("a"."c1") OVER (PARTITION BY "a"."c2") ' 'FROM "t" AS "a"') self.assertEqual(tuple(query.params), ()) window = Window([Literal(1)]) query = self.table.select( Max(self.table.c1, window=window) / Min(self.table.c1, window=window)) self.assertEqual(str(query), 'SELECT MAX("a"."c1") OVER (PARTITION BY %s) ' '/ MIN("a"."c1") OVER (PARTITION BY %s) ' 'FROM "t" AS "a"') self.assertEqual(tuple(query.params), (1, 1)) window1 = Window([self.table.c2]) window2 = Window([Literal(1)]) query = self.table.select( Max(self.table.c1, window=window1) / Min(self.table.c1, window=window2), windows=[window1]) self.assertEqual(str(query), 'SELECT MAX("a"."c1") OVER "b" ' '/ MIN("a"."c1") OVER (PARTITION BY %s) ' 'FROM "t" AS "a" ' 'WINDOW "b" AS (PARTITION BY "a"."c2")') self.assertEqual(tuple(query.params), (1,)) def test_window_with_alias(self): query = self.table.select( Min(self.table.c1, window=Window([self.table.c2])).as_('min')) self.assertEqual( str(query), 'SELECT MIN("a"."c1") OVER "b" AS "min" FROM "t" AS "a" ' 'WINDOW "b" AS (PARTITION BY "a"."c2")') self.assertEqual(query.params, ()) def test_select_invalid_window(self): with self.assertRaises(ValueError): self.table.select(windows=['foo']) def test_order_params(self): with_ = With(query=self.table.select(self.table.c, where=(self.table.c > 1))) w = Window([Literal(7)]) query = Select([Literal(2), Min(self.table.c, window=w)], from_=self.table.select(where=self.table.c > 3), with_=with_, where=self.table.c > 4, group_by=[Literal(5)], having=Literal(6), order_by=[Literal(8)]) self.assertEqual( str(query), 'WITH "c" AS (SELECT "a"."c" FROM "t" AS "a" WHERE "a"."c" > %s)' ' SELECT %s, MIN("a"."c") OVER "b" ' 'FROM SELECT * FROM "t" AS "a" WHERE "a"."c" > %s ' 'WHERE "a"."c" > %s ' 'GROUP BY %s ' 'HAVING %s ' 'WINDOW "b" AS (PARTITION BY %s) ' 'ORDER BY %s') self.assertEqual(tuple(query.params), (1, 2, 3, 4, 5, 6, 7, 8)) def test_no_as(self): query = self.table.select(self.table.c) try: Flavor.set(Flavor(no_as=True)) self.assertEqual(str(query), 'SELECT "a"."c" FROM "t" "a"') self.assertEqual(tuple(query.params), ()) finally: Flavor.set(Flavor()) python_sql-1.8.0/sql/tests/test_table.py0000644000000000000000000000136613615410400015307 0ustar00# This file is part of python-sql. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import unittest from sql import Table class TestTable(unittest.TestCase): def test_name(self): t = Table('mytable') self.assertEqual(str(t), '"mytable"') def test_quoted_name(self): t = Table('my "quoted" name') self.assertEqual(str(t), '"my ""quoted"" name"') def test_schema(self): t = Table('mytable', schema='myschema') self.assertEqual(str(t), '"myschema"."mytable"') def test_database(self): t = Table('mytable', database='mydatabase', schema='myschema') self.assertEqual(str(t), '"mydatabase"."myschema"."mytable"') python_sql-1.8.0/sql/tests/test_update.py0000644000000000000000000000727413615410400015506 0ustar00# This file is part of python-sql. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import unittest from sql import Literal, Table, With class TestUpdate(unittest.TestCase): table = Table('t') def test_update1(self): query = self.table.update([self.table.c], ['foo']) self.assertEqual(str(query), 'UPDATE "t" AS "a" SET "c" = %s') self.assertEqual(query.params, ('foo',)) query.where = (self.table.b == Literal(True)) self.assertEqual(str(query), 'UPDATE "t" AS "a" SET "c" = %s WHERE "a"."b" = %s') self.assertEqual(query.params, ('foo', True)) def test_update2(self): t1 = Table('t1') t2 = Table('t2') query = t1.update([t1.c], ['foo'], from_=[t2], where=(t1.c == t2.c)) self.assertEqual(str(query), 'UPDATE "t1" AS "b" SET "c" = %s FROM "t2" AS "a" ' 'WHERE "b"."c" = "a"."c"') self.assertEqual(query.params, ('foo',)) def test_update_invalid_values(self): with self.assertRaises(ValueError): self.table.update([self.table.c], 'foo') def test_update_invalid_where(self): with self.assertRaises(ValueError): self.table.update([self.table.c], ['foo'], where='foo') def test_update_subselect(self): t1 = Table('t1') t2 = Table('t2') query_list = t1.update([t1.c], [t2.select(t2.c, where=t2.i == t1.i)]) query_nolist = t1.update([t1.c], t2.select(t2.c, where=t2.i == t1.i)) for query in [query_list, query_nolist]: self.assertEqual(str(query), 'UPDATE "t1" AS "b" SET "c" = (' 'SELECT "a"."c" FROM "t2" AS "a" WHERE "a"."i" = "b"."i")') self.assertEqual(query.params, ()) def test_update_returning(self): query = self.table.update([self.table.c], ['foo'], returning=[self.table.c]) self.assertEqual(str(query), 'UPDATE "t" AS "a" SET "c" = %s RETURNING "a"."c"') self.assertEqual(query.params, ('foo',)) def test_update_returning_select(self): t1 = Table('t1') t2 = Table('t2') query = t1.update([t1.c], ['foo'], returning=[ t2.select(t2.c, where=(t2.c1 == t1.c) & (t2.c2 == 'bar'))]) self.assertEqual(str(query), 'UPDATE "t1" AS "b" SET "c" = %s ' 'RETURNING (SELECT "a"."c" FROM "t2" AS "a" ' 'WHERE ("a"."c1" = "b"."c") AND ("a"."c2" = %s))') self.assertEqual(query.params, ('foo', 'bar')) def test_with(self): t1 = Table('t1') w = With(query=t1.select(t1.c1)) query = self.table.update( [self.table.c2], with_=[w], values=[w.select(w.c3, where=w.c4 == 2)]) self.assertEqual(str(query), 'WITH "a" AS (SELECT "b"."c1" FROM "t1" AS "b") ' 'UPDATE "t" AS "c" SET "c2" = (SELECT "a"."c3" FROM "a" AS "a" ' 'WHERE "a"."c4" = %s)') self.assertEqual(query.params, (2,)) def test_schema(self): t1 = Table('t1', 'default') query = t1.update([t1.c1], ['foo']) self.assertEqual( str(query), 'UPDATE "default"."t1" AS "a" SET "c1" = %s') self.assertEqual(query.params, ('foo',)) def test_schema_subselect(self): t1 = Table('t1', 'default') t2 = Table('t2', 'default') query = t1.update([t1.c1], t2.select(t2.c, where=t2.i == t1.i)) self.assertEqual(str(query), 'UPDATE "default"."t1" AS "b" SET "c1" = (' 'SELECT "a"."c" FROM "default"."t2" AS "a" ' 'WHERE "a"."i" = "b"."i")') self.assertEqual(query.params, ()) python_sql-1.8.0/sql/tests/test_values.py0000644000000000000000000000203513615410400015511 0ustar00# This file is part of python-sql. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import unittest from sql import Values class TestValues(unittest.TestCase): def test_single_values(self): values = Values([[1]]) self.assertEqual(str(values), 'VALUES (%s)') self.assertEqual(values.params, (1,)) def test_many_values(self): values = Values([[1, 2], [3, 4]]) self.assertEqual(str(values), 'VALUES (%s, %s), (%s, %s)') self.assertEqual(values.params, (1, 2, 3, 4)) def test_select(self): values = Values([[1], [2], [3]]) query = values.select() self.assertEqual(str(query), 'SELECT * FROM (VALUES (%s), (%s), (%s)) AS "a"') self.assertEqual(tuple(query.params), (1, 2, 3)) def test_union(self): values = Values([[1]]) values |= Values([[2]]) self.assertEqual(str(values), 'VALUES (%s) UNION VALUES (%s)') self.assertEqual(values.params, (1, 2)) python_sql-1.8.0/sql/tests/test_window.py0000644000000000000000000000615713615410400015532 0ustar00# This file is part of python-sql. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import unittest from sql import Table, Window class TestWindow(unittest.TestCase): def test_window(self): t = Table('t') window = Window([t.c1, t.c2]) self.assertEqual(str(window), 'PARTITION BY "c1", "c2"') self.assertEqual(window.params, ()) def test_window_invalid_partition(self): with self.assertRaises(ValueError): Window(['foo']) def test_window_order(self): t = Table('t') window = Window([t.c], order_by=t.c) self.assertEqual(str(window), 'PARTITION BY "c" ORDER BY "c"') self.assertEqual(window.params, ()) def test_window_invalid_order(self): with self.assertRaises(ValueError): Window([Table('t').c], order_by='foo') def test_window_range(self): t = Table('t') window = Window([t.c], frame='RANGE') self.assertEqual(str(window), 'PARTITION BY "c" RANGE ' 'BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW') self.assertEqual(window.params, ()) window.start = -1 self.assertEqual(str(window), 'PARTITION BY "c" RANGE ' 'BETWEEN %s PRECEDING AND CURRENT ROW') self.assertEqual(window.params, (1,)) window.start = 0 window.end = 1 self.assertEqual(str(window), 'PARTITION BY "c" RANGE ' 'BETWEEN CURRENT ROW AND %s FOLLOWING') self.assertEqual(window.params, (1,)) window.start = 1 window.end = None self.assertEqual(str(window), 'PARTITION BY "c" RANGE ' 'BETWEEN %s FOLLOWING AND UNBOUNDED FOLLOWING') self.assertEqual(window.params, (1,)) def test_window_invalid_frame(self): with self.assertRaises(ValueError): Window([Table('t').c], frame='foo') def test_window_invalid_start(self): with self.assertRaises(ValueError): Window([Table('t').c], start='foo') def test_window_invalid_end(self): with self.assertRaises(ValueError): Window([Table('t').c], end='foo') def test_window_exclude(self): t = Table('t') window = Window([t.c], exclude='TIES') self.assertEqual(str(window), 'PARTITION BY "c" EXCLUDE TIES') self.assertEqual(window.params, ()) def test_window_invalid_exclude(self): with self.assertRaises(ValueError): Window([Table('t').c], exclude='foo') def test_window_rows(self): t = Table('t') window = Window([t.c], frame='ROWS') self.assertEqual(str(window), 'PARTITION BY "c" ROWS ' 'BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW') self.assertEqual(window.params, ()) def test_window_groups(self): t = Table('t') window = Window([t.c], frame='GROUPS') self.assertEqual(str(window), 'PARTITION BY "c" GROUPS ' 'BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW') self.assertEqual(window.params, ()) python_sql-1.8.0/sql/tests/test_with.py0000644000000000000000000000447313615410400015175 0ustar00# This file is part of python-sql. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import unittest from sql import AliasManager, Literal, Table, Values, With, WithQuery class TestWith(unittest.TestCase): table = Table('t') def test_with(self): with AliasManager(): simple = With(query=self.table.select(self.table.id, where=self.table.id == 1)) self.assertEqual(simple.statement(), '"a" AS (' 'SELECT "b"."id" FROM "t" AS "b" WHERE "b"."id" = %s' ')') self.assertEqual(simple.statement_params(), (1,)) def test_with_columns(self): with AliasManager(): second = With('a', query=self.table.select(self.table.a)) self.assertEqual(second.statement(), '"a" ("a") AS (' 'SELECT "b"."a" FROM "t" AS "b"' ')') self.assertEqual(second.statement_params(), ()) def test_with_query(self): with AliasManager(): simple = With() simple.query = self.table.select(self.table.id, where=self.table.id == 1) second = With() second.query = simple.select() wq = WithQuery(with_=[simple, second]) self.assertEqual(wq._with_str(), 'WITH "a" AS (' 'SELECT "b"."id" FROM "t" AS "b" WHERE "b"."id" = %s' '), "c" AS (' 'SELECT * FROM "a" AS "a"' ') ') self.assertEqual(wq._with_params(), (1,)) def test_recursive(self): upto10 = With('n', recursive=True) upto10.query = Values([(1,)]) upto10.query |= upto10.select( upto10.n + Literal(1), where=upto10.n < Literal(100)) upto10.query.all_ = True q = upto10.select(with_=[upto10]) self.assertEqual(str(q), 'WITH RECURSIVE "a" ("n") AS (' 'VALUES (%s) ' 'UNION ALL ' 'SELECT "a"."n" + %s FROM "a" AS "a" WHERE "a"."n" < %s' ') SELECT * FROM "a" AS "a"') self.assertEqual(tuple(q.params), (1, 1, 100)) def test_invalid_with(self): with self.assertRaises(ValueError): WithQuery(with_=['foo']) python_sql-1.8.0/.gitignore0000755000000000000000000000000013615410400020627 2Sync/dotfiles/git/dot-gitignoreustar00python_sql-1.8.0/.hgignore0000644000000000000000000000007713615410400012447 0ustar00syntax: glob *.py[cdo] *.egg-info dist/ build/ .tox/ .coverage python_sql-1.8.0/COPYRIGHT0000644000000000000000000000313313615410400012133 0ustar00Copyright (c) 2011-2026 Cédric Krier Copyright (c) 2013-2025 Nicolas Évrard Copyright (c) 2011-2026 B2CK SRL All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. python_sql-1.8.0/README.rst0000644000000000000000000001607213615410400012335 0ustar00python-sql ========== python-sql is a library to write SQL queries in a pythonic way. Nutshell -------- Import:: >>> from sql import * >>> from sql.aggregate import * >>> from sql.conditionals import * Simple selects:: >>> user = Table('user') >>> select = user.select() >>> tuple(select) ('SELECT * FROM "user" AS "a"', ()) >>> select = user.select(user.name) >>> tuple(select) ('SELECT "a"."name" FROM "user" AS "a"', ()) >>> select = user.select(Count(Literal(1))) >>> tuple(select) ('SELECT COUNT(%s) FROM "user" AS "a"', (1,)) >>> select = user.select(user.name, distinct=True) >>> tuple(select) ('SELECT DISTINCT "a"."name" FROM "user" AS "a"', ()) >>> select = user.select(user.id, user.name) >>> tuple(select) ('SELECT "a"."id", "a"."name" FROM "user" AS "a"', ()) Select with where condition:: >>> select.where = user.name == 'foo' >>> tuple(select) ('SELECT "a"."id", "a"."name" FROM "user" AS "a" WHERE "a"."name" = %s', ('foo',)) >>> select.where = (user.name == 'foo') & (user.active == True) >>> tuple(select) ('SELECT "a"."id", "a"."name" FROM "user" AS "a" WHERE ("a"."name" = %s) AND ("a"."active" = %s)', ('foo', True)) >>> select.where = user.name == user.login >>> tuple(select) ('SELECT "a"."id", "a"."name" FROM "user" AS "a" WHERE "a"."name" = "a"."login"', ()) Select with join:: >>> join = user.join(Table('user_group')) >>> join.condition = join.right.user == user.id >>> select = join.select(user.name, join.right.group) >>> tuple(select) ('SELECT "a"."name", "b"."group" FROM "user" AS "a" INNER JOIN "user_group" AS "b" ON "b"."user" = "a"."id"', ()) Select with multiple joins:: >>> join1 = user.join(Table('user')) >>> join2 = join1.join(Table('user')) >>> select = join2.select(user.id, join1.right.id, join2.right.id) >>> tuple(select) ('SELECT "a"."id", "b"."id", "c"."id" FROM "user" AS "a" INNER JOIN "user" AS "b" INNER JOIN "user" AS "c"', ()) Select with group_by:: >>> invoice = Table('invoice') >>> select = invoice.select(Sum(invoice.amount), invoice.currency, ... group_by=invoice.currency) >>> tuple(select) ('SELECT SUM("a"."amount"), "a"."currency" FROM "invoice" AS "a" GROUP BY "a"."currency"', ()) Select with output name:: >>> tuple(user.select(user.name.as_('First Name'))) ('SELECT "a"."name" AS "First Name" FROM "user" AS "a"', ()) Select with order_by:: >>> tuple(user.select(order_by=user.date)) ('SELECT * FROM "user" AS "a" ORDER BY "a"."date"', ()) >>> tuple(user.select(order_by=Asc(user.date))) ('SELECT * FROM "user" AS "a" ORDER BY "a"."date" ASC', ()) >>> tuple(user.select(order_by=(user.date.asc, user.id.desc))) ('SELECT * FROM "user" AS "a" ORDER BY "a"."date" ASC, "a"."id" DESC', ()) Select with sub-select:: >>> user_group = Table('user_group') >>> subselect = user_group.select(user_group.user, ... where=user_group.active == True) >>> user = Table('user') >>> tuple(user.select(user.id, where=user.id.in_(subselect))) ('SELECT "a"."id" FROM "user" AS "a" WHERE "a"."id" IN (SELECT "b"."user" FROM "user_group" AS "b" WHERE "b"."active" = %s)', (True,)) >>> tuple(subselect.select(subselect.user)) ('SELECT "a"."user" FROM (SELECT "b"."user" FROM "user_group" AS "b" WHERE "b"."active" = %s) AS "a"', (True,)) Select on other schema:: >>> other_table = Table('user', 'myschema') >>> tuple(other_table.select()) ('SELECT * FROM "myschema"."user" AS "a"', ()) Insert query with default values:: >>> tuple(user.insert()) ('INSERT INTO "user" DEFAULT VALUES', ()) Insert query with values:: >>> tuple(user.insert(columns=[user.name, user.login], ... values=[['Foo', 'foo']])) ('INSERT INTO "user" ("name", "login") VALUES (%s, %s)', ('Foo', 'foo')) >>> tuple(user.insert(columns=[user.name, user.login], ... values=[['Foo', 'foo'], ['Bar', 'bar']])) ('INSERT INTO "user" ("name", "login") VALUES (%s, %s), (%s, %s)', ('Foo', 'foo', 'Bar', 'bar')) Insert query with query:: >>> passwd = Table('passwd') >>> select = passwd.select(passwd.login, passwd.passwd) >>> tuple(user.insert(values=select)) ('INSERT INTO "user" SELECT "a"."login", "a"."passwd" FROM "passwd" AS "a"', ()) Update query with values:: >>> tuple(user.update(columns=[user.active], values=[True])) ('UPDATE "user" AS "a" SET "active" = %s', (True,)) >>> tuple(invoice.update(columns=[invoice.total], values=[invoice.amount + invoice.tax])) ('UPDATE "invoice" AS "a" SET "total" = "a"."amount" + "a"."tax"', ()) Update query with where condition:: >>> tuple(user.update(columns=[user.active], values=[True], ... where=user.active == False)) ('UPDATE "user" AS "a" SET "active" = %s WHERE "a"."active" = %s', (True, False)) Update query with from list:: >>> group = Table('user_group') >>> tuple(user.update(columns=[user.active], values=[group.active], ... from_=[group], where=user.id == group.user)) ('UPDATE "user" AS "b" SET "active" = "a"."active" FROM "user_group" AS "a" WHERE "b"."id" = "a"."user"', ()) Delete query:: >>> tuple(user.delete()) ('DELETE FROM "user"', ()) Delete query with where condition:: >>> tuple(user.delete(where=user.name == 'foo')) ('DELETE FROM "user" WHERE "name" = %s', ('foo',)) Delete query with sub-query:: >>> tuple(user.delete( ... where=user.id.in_(user_group.select(user_group.user)))) ('DELETE FROM "user" WHERE "id" IN (SELECT "a"."user" FROM "user_group" AS "a")', ()) Flavors:: >>> select = user.select() >>> select.offset = 10 >>> Flavor.set(Flavor()) >>> tuple(select) ('SELECT * FROM "user" AS "a" OFFSET %s', (10,)) >>> Flavor.set(Flavor(max_limit=18446744073709551615)) >>> tuple(select) ('SELECT * FROM "user" AS "a" LIMIT 18446744073709551615 OFFSET %s', (10,)) >>> Flavor.set(Flavor(max_limit=-1)) >>> tuple(select) ('SELECT * FROM "user" AS "a" LIMIT -1 OFFSET %s', (10,)) Limit style:: >>> select = user.select(limit=10, offset=20) >>> Flavor.set(Flavor(limitstyle='limit')) >>> tuple(select) ('SELECT * FROM "user" AS "a" LIMIT %s OFFSET %s', (10, 20)) >>> Flavor.set(Flavor(limitstyle='fetch')) >>> tuple(select) ('SELECT * FROM "user" AS "a" OFFSET (%s) ROWS FETCH FIRST (%s) ROWS ONLY', (20, 10)) >>> Flavor.set(Flavor(limitstyle='rownum')) >>> tuple(select) ('SELECT "a".* FROM (SELECT "b".*, ROWNUM AS "rnum" FROM (SELECT * FROM "user" AS "c") AS "b" WHERE ROWNUM <= %s) AS "a" WHERE "rnum" > %s', (30, 20)) qmark style:: >>> Flavor.set(Flavor(paramstyle='qmark')) >>> select = user.select() >>> select.where = user.name == 'foo' >>> tuple(select) ('SELECT * FROM "user" AS "a" WHERE "a"."name" = ?', ('foo',)) numeric style:: >>> Flavor.set(Flavor(paramstyle='format')) >>> select = user.select() >>> select.where = user.name == 'foo' >>> format2numeric(*select) ('SELECT * FROM "user" AS "a" WHERE "a"."name" = :0', ('foo',)) python_sql-1.8.0/pyproject.toml0000644000000000000000000000204313615410400013553 0ustar00[build-system] requires = ['hatchling >= 1', 'hatch-tryton'] build-backend = 'hatchling.build' [project] name = 'python-sql' dynamic = ['version', 'authors'] requires-python = '>=3.9' maintainers = [ {name = "Tryton", email = "foundation@tryton.org"}, ] description = "Library to write SQL queries" readme = 'README.rst' license = 'BSD-3-Clause' license-files = ['COPYRIGHT'] keywords = ["SQL", "database", "query"] classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Topic :: Database", "Topic :: Software Development :: Libraries :: Python Modules", ] [project.urls] homepage = "https://www.tryton.org/" changelog = "https://code.tryton.org/python-sql/-/blob/branch/default/CHANGELOG" forum = "https://discuss.tryton.org/tags/python-sql" issues = "https://bugs.tryton.org/python-sql" repository = "https://code.tryton.org/python-sql" [tool.hatch.version] path = 'sql/__init__.py' [tool.hatch.build] packages = ['sql'] [tool.hatch.metadata.hooks.tryton] copyright = 'COPYRIGHT' python_sql-1.8.0/PKG-INFO0000644000000000000000000001772213615410400011746 0ustar00Metadata-Version: 2.4 Name: python-sql Version: 1.8.0 Summary: Library to write SQL queries Project-URL: homepage, https://www.tryton.org/ Project-URL: changelog, https://code.tryton.org/python-sql/-/blob/branch/default/CHANGELOG Project-URL: forum, https://discuss.tryton.org/tags/python-sql Project-URL: issues, https://bugs.tryton.org/python-sql Project-URL: repository, https://code.tryton.org/python-sql Author: B2CK SRL Author-email: Cédric Krier , Nicolas Évrard Maintainer-email: Tryton License-Expression: BSD-3-Clause License-File: COPYRIGHT Keywords: SQL,database,query Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Topic :: Database Classifier: Topic :: Software Development :: Libraries :: Python Modules Requires-Python: >=3.9 Description-Content-Type: text/x-rst python-sql ========== python-sql is a library to write SQL queries in a pythonic way. Nutshell -------- Import:: >>> from sql import * >>> from sql.aggregate import * >>> from sql.conditionals import * Simple selects:: >>> user = Table('user') >>> select = user.select() >>> tuple(select) ('SELECT * FROM "user" AS "a"', ()) >>> select = user.select(user.name) >>> tuple(select) ('SELECT "a"."name" FROM "user" AS "a"', ()) >>> select = user.select(Count(Literal(1))) >>> tuple(select) ('SELECT COUNT(%s) FROM "user" AS "a"', (1,)) >>> select = user.select(user.name, distinct=True) >>> tuple(select) ('SELECT DISTINCT "a"."name" FROM "user" AS "a"', ()) >>> select = user.select(user.id, user.name) >>> tuple(select) ('SELECT "a"."id", "a"."name" FROM "user" AS "a"', ()) Select with where condition:: >>> select.where = user.name == 'foo' >>> tuple(select) ('SELECT "a"."id", "a"."name" FROM "user" AS "a" WHERE "a"."name" = %s', ('foo',)) >>> select.where = (user.name == 'foo') & (user.active == True) >>> tuple(select) ('SELECT "a"."id", "a"."name" FROM "user" AS "a" WHERE ("a"."name" = %s) AND ("a"."active" = %s)', ('foo', True)) >>> select.where = user.name == user.login >>> tuple(select) ('SELECT "a"."id", "a"."name" FROM "user" AS "a" WHERE "a"."name" = "a"."login"', ()) Select with join:: >>> join = user.join(Table('user_group')) >>> join.condition = join.right.user == user.id >>> select = join.select(user.name, join.right.group) >>> tuple(select) ('SELECT "a"."name", "b"."group" FROM "user" AS "a" INNER JOIN "user_group" AS "b" ON "b"."user" = "a"."id"', ()) Select with multiple joins:: >>> join1 = user.join(Table('user')) >>> join2 = join1.join(Table('user')) >>> select = join2.select(user.id, join1.right.id, join2.right.id) >>> tuple(select) ('SELECT "a"."id", "b"."id", "c"."id" FROM "user" AS "a" INNER JOIN "user" AS "b" INNER JOIN "user" AS "c"', ()) Select with group_by:: >>> invoice = Table('invoice') >>> select = invoice.select(Sum(invoice.amount), invoice.currency, ... group_by=invoice.currency) >>> tuple(select) ('SELECT SUM("a"."amount"), "a"."currency" FROM "invoice" AS "a" GROUP BY "a"."currency"', ()) Select with output name:: >>> tuple(user.select(user.name.as_('First Name'))) ('SELECT "a"."name" AS "First Name" FROM "user" AS "a"', ()) Select with order_by:: >>> tuple(user.select(order_by=user.date)) ('SELECT * FROM "user" AS "a" ORDER BY "a"."date"', ()) >>> tuple(user.select(order_by=Asc(user.date))) ('SELECT * FROM "user" AS "a" ORDER BY "a"."date" ASC', ()) >>> tuple(user.select(order_by=(user.date.asc, user.id.desc))) ('SELECT * FROM "user" AS "a" ORDER BY "a"."date" ASC, "a"."id" DESC', ()) Select with sub-select:: >>> user_group = Table('user_group') >>> subselect = user_group.select(user_group.user, ... where=user_group.active == True) >>> user = Table('user') >>> tuple(user.select(user.id, where=user.id.in_(subselect))) ('SELECT "a"."id" FROM "user" AS "a" WHERE "a"."id" IN (SELECT "b"."user" FROM "user_group" AS "b" WHERE "b"."active" = %s)', (True,)) >>> tuple(subselect.select(subselect.user)) ('SELECT "a"."user" FROM (SELECT "b"."user" FROM "user_group" AS "b" WHERE "b"."active" = %s) AS "a"', (True,)) Select on other schema:: >>> other_table = Table('user', 'myschema') >>> tuple(other_table.select()) ('SELECT * FROM "myschema"."user" AS "a"', ()) Insert query with default values:: >>> tuple(user.insert()) ('INSERT INTO "user" DEFAULT VALUES', ()) Insert query with values:: >>> tuple(user.insert(columns=[user.name, user.login], ... values=[['Foo', 'foo']])) ('INSERT INTO "user" ("name", "login") VALUES (%s, %s)', ('Foo', 'foo')) >>> tuple(user.insert(columns=[user.name, user.login], ... values=[['Foo', 'foo'], ['Bar', 'bar']])) ('INSERT INTO "user" ("name", "login") VALUES (%s, %s), (%s, %s)', ('Foo', 'foo', 'Bar', 'bar')) Insert query with query:: >>> passwd = Table('passwd') >>> select = passwd.select(passwd.login, passwd.passwd) >>> tuple(user.insert(values=select)) ('INSERT INTO "user" SELECT "a"."login", "a"."passwd" FROM "passwd" AS "a"', ()) Update query with values:: >>> tuple(user.update(columns=[user.active], values=[True])) ('UPDATE "user" AS "a" SET "active" = %s', (True,)) >>> tuple(invoice.update(columns=[invoice.total], values=[invoice.amount + invoice.tax])) ('UPDATE "invoice" AS "a" SET "total" = "a"."amount" + "a"."tax"', ()) Update query with where condition:: >>> tuple(user.update(columns=[user.active], values=[True], ... where=user.active == False)) ('UPDATE "user" AS "a" SET "active" = %s WHERE "a"."active" = %s', (True, False)) Update query with from list:: >>> group = Table('user_group') >>> tuple(user.update(columns=[user.active], values=[group.active], ... from_=[group], where=user.id == group.user)) ('UPDATE "user" AS "b" SET "active" = "a"."active" FROM "user_group" AS "a" WHERE "b"."id" = "a"."user"', ()) Delete query:: >>> tuple(user.delete()) ('DELETE FROM "user"', ()) Delete query with where condition:: >>> tuple(user.delete(where=user.name == 'foo')) ('DELETE FROM "user" WHERE "name" = %s', ('foo',)) Delete query with sub-query:: >>> tuple(user.delete( ... where=user.id.in_(user_group.select(user_group.user)))) ('DELETE FROM "user" WHERE "id" IN (SELECT "a"."user" FROM "user_group" AS "a")', ()) Flavors:: >>> select = user.select() >>> select.offset = 10 >>> Flavor.set(Flavor()) >>> tuple(select) ('SELECT * FROM "user" AS "a" OFFSET %s', (10,)) >>> Flavor.set(Flavor(max_limit=18446744073709551615)) >>> tuple(select) ('SELECT * FROM "user" AS "a" LIMIT 18446744073709551615 OFFSET %s', (10,)) >>> Flavor.set(Flavor(max_limit=-1)) >>> tuple(select) ('SELECT * FROM "user" AS "a" LIMIT -1 OFFSET %s', (10,)) Limit style:: >>> select = user.select(limit=10, offset=20) >>> Flavor.set(Flavor(limitstyle='limit')) >>> tuple(select) ('SELECT * FROM "user" AS "a" LIMIT %s OFFSET %s', (10, 20)) >>> Flavor.set(Flavor(limitstyle='fetch')) >>> tuple(select) ('SELECT * FROM "user" AS "a" OFFSET (%s) ROWS FETCH FIRST (%s) ROWS ONLY', (20, 10)) >>> Flavor.set(Flavor(limitstyle='rownum')) >>> tuple(select) ('SELECT "a".* FROM (SELECT "b".*, ROWNUM AS "rnum" FROM (SELECT * FROM "user" AS "c") AS "b" WHERE ROWNUM <= %s) AS "a" WHERE "rnum" > %s', (30, 20)) qmark style:: >>> Flavor.set(Flavor(paramstyle='qmark')) >>> select = user.select() >>> select.where = user.name == 'foo' >>> tuple(select) ('SELECT * FROM "user" AS "a" WHERE "a"."name" = ?', ('foo',)) numeric style:: >>> Flavor.set(Flavor(paramstyle='format')) >>> select = user.select() >>> select.where = user.name == 'foo' >>> format2numeric(*select) ('SELECT * FROM "user" AS "a" WHERE "a"."name" = :0', ('foo',))