Hello community, here is the log from the commit of package python-python-sql for openSUSE:Factory checked in at 2018-10-04 19:01:31 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-python-sql (Old) and /work/SRC/openSUSE:Factory/.python-python-sql.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-python-sql" Thu Oct 4 19:01:31 2018 rev:3 rq:639727 version:1.0.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-python-sql/python-python-sql.changes 2017-04-26 21:43:44.207555897 +0200 +++ /work/SRC/openSUSE:Factory/.python-python-sql.new/python-python-sql.changes 2018-10-04 19:01:37.535195100 +0200 @@ -1,0 +2,10 @@ +Mon Oct 1 08:59:59 UTC 2018 - Axel Braun <axel.br...@gmx.de> + +- Version 1.0.0 + * Add Flavor filter_ to fallback to case expression + * Allow to use expression in AtTimeZone + * Add comparison predicates + * Add COLLATE + * various bugfixes + +------------------------------------------------------------------- Old: ---- python-sql-0.9.tar.gz New: ---- python-sql-1.0.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-python-sql.spec ++++++ --- /var/tmp/diff_new_pack.lf78Tx/_old 2018-10-04 19:01:38.191194412 +0200 +++ /var/tmp/diff_new_pack.lf78Tx/_new 2018-10-04 19:01:38.195194408 +0200 @@ -1,7 +1,7 @@ # # spec file for package python-python-sql # -# Copyright (c) 2017 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -12,29 +12,23 @@ # license that conforms to the Open Source Definition (Version 1.9) # published by the Open Source Initiative. -# Please submit bugfixes or comments via http://bugs.opensuse.org/ +# Please submit bugfixes or comments via https://bugs.opensuse.org/ # -%{?!python_module:%define python_module() python-%{**} python3-%{**}} +%{?!python_module:%define python_module() python-%{**} python3-%{**}} %define base_name python-sql Name: python-%{base_name} -BuildRequires: %{python_module devel} -BuildRequires: %{python_module setuptools} -BuildRequires: python-rpm-macros -Version: 0.9 +Version: 1.0.0 Release: 0 -Source: https://pypi.io/packages/source/p/%{base_name}/%{base_name}-%{version}.tar.gz -Url: https://pypi.io/project/python-sql -%if 0%{?suse_version} && 0%{?suse_version} <= 1110 -%{!?python_sitelib: %global python_sitelib %(python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} -%else -BuildArch: noarch -%endif Summary: Library to write SQL queries License: BSD-3-Clause Group: Development/Languages/Python -BuildRoot: %{_tmppath}/%{name}-%{version}-build +URL: https://pypi.io/project/python-sql +Source: https://pypi.io/packages/source/p/%{base_name}/%{base_name}-%{version}.tar.gz +BuildRequires: %{python_module setuptools} +BuildRequires: python-rpm-macros +BuildArch: noarch %python_subpackages %description @@ -49,8 +43,10 @@ %install %python_install +%check +%python_exec setup.py test + %files %{python_files} -%defattr(-,root,root) %doc README %{python_sitelib}/* ++++++ python-sql-0.9.tar.gz -> python-sql-1.0.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-sql-0.9/.drone.yml new/python-sql-1.0.0/.drone.yml --- old/python-sql-0.9/.drone.yml 1970-01-01 01:00:00.000000000 +0100 +++ new/python-sql-1.0.0/.drone.yml 2018-08-18 13:25:33.000000000 +0200 @@ -0,0 +1,29 @@ +clone: + hg: + image: plugins/hg + +pipeline: + tox: + image: ${IMAGE} + commands: + - pip install tox + - tox -e "${TOXENV}" + volumes: + - cache:/root/.cache + +matrix: + include: + - IMAGE: python:2.7 + TOXENV: py27 + - IMAGE: python:3.4 + TOXENV: py34 + - IMAGE: python:3.5 + TOXENV: py35 + - IMAGE: python:3.6 + TOXENV: py36 + - IMAGE: python:3.7 + TOXENV: py37 + - IMAGE: pypy:2 + TOXENV: pypy + - IMAGE: pypy:3 + TOXENV: pypy3 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-sql-0.9/.hgtags new/python-sql-1.0.0/.hgtags --- old/python-sql-0.9/.hgtags 1970-01-01 01:00:00.000000000 +0100 +++ new/python-sql-1.0.0/.hgtags 2018-09-30 14:27:13.000000000 +0200 @@ -0,0 +1,10 @@ +2543c23b577ce86260954129b1fb002628ffa80c 0.1 +e8322fa5b1148647989c52214f07c55a8ce457bb 0.2 +00e4b65c6a964f1fb1d42c9f5e65fe0deda0de57 0.3 +45063df05605c7ca496de0c5f4adddc0b135bd4f 0.4 +aca607de1cf8b18388aac9e5aa11da9e922ab976 0.5 +43793e923bac2c7c293340dfa595736ecf6d92a4 0.6 +cc2ba29c02bc0647f1feb3ff2f49df27af3dd9d6 0.7 +5ef77ab47a7bdaaf568ae1c5b3f1b0698ee2418c 0.8 +e3bdeb99dd975024e30d8af18c324a0a7f860e63 0.9 +7459778aa23150aa6ac39356621c29d368ae1f36 1.0.0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-sql-0.9/CHANGELOG new/python-sql-1.0.0/CHANGELOG --- old/python-sql-0.9/CHANGELOG 2017-04-24 12:17:09.000000000 +0200 +++ new/python-sql-1.0.0/CHANGELOG 2018-09-30 14:26:46.000000000 +0200 @@ -1,3 +1,10 @@ +Version 1.0.0 - 2018-09-30 +* Add Flavor filter_ to fallback to case expression +* Allow to use expression in AtTimeZone +* Fix Select query in returning +* Add comparison predicates +* Add COLLATE + Version 0.9 - 2017-04-24 * Add distinct_on on Select * Allow to use Select as Column of Select query diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-sql-0.9/PKG-INFO new/python-sql-1.0.0/PKG-INFO --- old/python-sql-0.9/PKG-INFO 2017-04-24 12:19:14.000000000 +0200 +++ new/python-sql-1.0.0/PKG-INFO 2018-09-30 14:27:57.000000000 +0200 @@ -1,11 +1,12 @@ Metadata-Version: 1.1 Name: python-sql -Version: 0.9 +Version: 1.0.0 Summary: Library to write SQL queries Home-page: http://python-sql.tryton.org/ -Author: B2CK -Author-email: i...@b2ck.com +Author: Tryton +Author-email: python-...@tryton.org License: BSD +Description-Content-Type: UNKNOWN Description: python-sql ========== diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-sql-0.9/python_sql.egg-info/PKG-INFO new/python-sql-1.0.0/python_sql.egg-info/PKG-INFO --- old/python-sql-0.9/python_sql.egg-info/PKG-INFO 2017-04-24 12:19:12.000000000 +0200 +++ new/python-sql-1.0.0/python_sql.egg-info/PKG-INFO 2018-09-30 14:27:57.000000000 +0200 @@ -1,11 +1,12 @@ Metadata-Version: 1.1 Name: python-sql -Version: 0.9 +Version: 1.0.0 Summary: Library to write SQL queries Home-page: http://python-sql.tryton.org/ -Author: B2CK -Author-email: i...@b2ck.com +Author: Tryton +Author-email: python-...@tryton.org License: BSD +Description-Content-Type: UNKNOWN Description: python-sql ========== diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-sql-0.9/python_sql.egg-info/SOURCES.txt new/python-sql-1.0.0/python_sql.egg-info/SOURCES.txt --- old/python-sql-0.9/python_sql.egg-info/SOURCES.txt 2017-04-24 12:19:12.000000000 +0200 +++ new/python-sql-1.0.0/python_sql.egg-info/SOURCES.txt 2018-09-30 14:27:57.000000000 +0200 @@ -1,7 +1,10 @@ +.drone.yml +.hgtags CHANGELOG MANIFEST.in README setup.py +tox.ini python_sql.egg-info/PKG-INFO python_sql.egg-info/SOURCES.txt python_sql.egg-info/dependency_links.txt @@ -16,6 +19,7 @@ sql/tests/test_alias.py sql/tests/test_as.py sql/tests/test_cast.py +sql/tests/test_collate.py sql/tests/test_column.py sql/tests/test_combining_query.py sql/tests/test_conditionals.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-sql-0.9/setup.cfg new/python-sql-1.0.0/setup.cfg --- old/python-sql-0.9/setup.cfg 2017-04-24 12:19:14.000000000 +0200 +++ new/python-sql-1.0.0/setup.cfg 2018-09-30 14:27:57.000000000 +0200 @@ -1,5 +1,4 @@ [egg_info] tag_build = tag_date = 0 -tag_svn_revision = 0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-sql-0.9/setup.py new/python-sql-1.0.0/setup.py --- old/python-sql-0.9/setup.py 2015-06-22 17:46:50.000000000 +0200 +++ new/python-sql-1.0.0/setup.py 2018-09-30 14:17:37.000000000 +0200 @@ -42,12 +42,13 @@ init = read(os.path.join('sql', '__init__.py')) return re.search("__version__ = '([0-9.]*)'", init).group(1) + setup(name='python-sql', version=get_version(), description='Library to write SQL queries', long_description=read('README'), - author='B2CK', - author_email='i...@b2ck.com', + author='Tryton', + author_email='python-...@tryton.org', url='http://python-sql.tryton.org/', packages=find_packages(), classifiers=[ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-sql-0.9/sql/__init__.py new/python-sql-1.0.0/sql/__init__.py --- old/python-sql-0.9/sql/__init__.py 2016-09-14 11:25:03.000000000 +0200 +++ new/python-sql-1.0.0/sql/__init__.py 2018-09-30 14:25:27.000000000 +0200 @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2011-2016, Cédric Krier +# Copyright (c) 2011-2018, Cédric Krier # Copyright (c) 2013-2014, Nicolas Évrard -# Copyright (c) 2011-2016, B2CK +# Copyright (c) 2011-2018, B2CK # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -29,16 +29,16 @@ from __future__ import division -__version__ = '0.9' -__all__ = ['Flavor', 'Table', 'Values', 'Literal', 'Column', 'Join', - 'Asc', 'Desc', 'NullsFirst', 'NullsLast', 'format2numeric'] - import string import warnings from threading import local, currentThread from collections import defaultdict from itertools import chain +__version__ = '1.0.0' +__all__ = ['Flavor', 'Table', 'Values', 'Literal', 'Column', 'Join', + 'Asc', 'Desc', 'NullsFirst', 'NullsLast', 'format2numeric'] + def alias(i, letters=string.ascii_lowercase): ''' @@ -78,7 +78,7 @@ def __init__(self, limitstyle='limit', max_limit=None, paramstyle='format', ilike=False, no_as=False, no_boolean=False, null_ordering=True, - function_mapping=None): + function_mapping=None, filter_=False): assert limitstyle in ['fetch', 'limit', 'rownum'] self.limitstyle = limitstyle self.max_limit = max_limit @@ -88,6 +88,7 @@ self.no_boolean = no_boolean self.null_ordering = null_ordering self.function_mapping = function_mapping or {} + self.filter_ = filter_ @property def param(self): @@ -693,10 +694,13 @@ # TODO manage DEFAULT elif self.values is None: values = ' DEFAULT VALUES' - returning = '' - if self.returning: - returning = ' RETURNING ' + ', '.join(map(str, self.returning)) with AliasManager(): + table = self.table + AliasManager.set(table, str(table)[1:-1]) + returning = '' + if self.returning: + returning = ' RETURNING ' + ', '.join( + map(self._format, self.returning)) return (self._with_str() + 'INSERT INTO %s' % self.table + columns + values + returning) @@ -766,7 +770,8 @@ where = ' WHERE ' + str(self.where) returning = '' if self.returning: - returning = ' RETURNING ' + ', '.join(map(str, self.returning)) + returning = ' RETURNING ' + ', '.join( + map(self._format, self.returning)) return (self._with_str() + 'UPDATE %s SET ' % table + values + from_ + where + returning) @@ -1218,6 +1223,9 @@ def cast(self, typename): return Cast(self, typename) + def collate(self, collation): + return Collate(self, collation) + @property def asc(self): return Asc(self) @@ -1262,6 +1270,7 @@ return () return (self._value,) + Null = None @@ -1273,6 +1282,8 @@ @property def params(self): return () + + _rownum = _Rownum() @@ -1329,7 +1340,7 @@ __slots__ = ('expression', 'typename') def __init__(self, expression, typename): - super(Expression, self).__init__() + super(Cast, self).__init__() self.expression = expression self.typename = typename @@ -1342,6 +1353,31 @@ @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 + + def __str__(self): + if isinstance(self.expression, Expression): + value = self.expression + else: + value = Flavor.get().param + if '"' in self.collation: + raise ValueError("Wrong collation %s" % self.collation) + return '%s COLLATE "%s"' % (value, self.collation) + + @property + def params(self): if isinstance(self.expression, Expression): return self.expression.params else: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-sql-0.9/sql/aggregate.py new/python-sql-1.0.0/sql/aggregate.py --- old/python-sql-0.9/sql/aggregate.py 2015-09-08 17:51:02.000000000 +0200 +++ new/python-sql-1.0.0/sql/aggregate.py 2018-08-18 13:25:33.000000000 +0200 @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2011-2015, Cédric Krier -# Copyright (c) 2011-2015, B2CK +# Copyright (c) 2011-2018, Cédric Krier +# Copyright (c) 2011-2018, B2CK # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -27,7 +27,7 @@ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -from sql import Expression, Window +from sql import Expression, Window, Flavor, Literal __all__ = ['Avg', 'BitAnd', 'BitOr', 'BoolAnd', 'BoolOr', 'Count', 'Every', 'Max', 'Min', 'Stddev', 'Sum', 'Variance'] @@ -89,15 +89,24 @@ assert isinstance(value, Window) self._window = value + @property + def _case_expression(self): + return self.expression + def __str__(self): quantifier = 'DISTINCT ' if self.distinct else '' - aggregate = '%s(%s%s)' % (self._sql, quantifier, self.expression) + 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)) + aggregate = '%s(%s%s)' % (self._sql, quantifier, expression) within = '' if self.within: within = (' WITHIN GROUP (ORDER BY %s)' % ', '.join(map(str, self.within))) filter_ = '' - if self.filter_: + if self.filter_ and has_filter: filter_ = ' FILTER (WHERE %s)' % self.filter_ window = '' if self.window: @@ -106,11 +115,17 @@ @property def params(self): - p = list(self.expression.params) + 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.within: for expression in self.within: p.extend(expression.params) - if self.filter_: + if self.filter_ and has_filter: p.extend(self.filter_.params) return tuple(p) @@ -144,6 +159,14 @@ __slots__ = () _sql = 'COUNT' + @property + def _case_expression(self): + expression = super(Count, self)._case_expression + if (isinstance(self.expression, Literal) + and expression.value == '*'): + expression = Literal(1) + return expression + class Every(Aggregate): __slots__ = () diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-sql-0.9/sql/functions.py new/python-sql-1.0.0/sql/functions.py --- old/python-sql-0.9/sql/functions.py 2015-07-01 19:44:40.000000000 +0200 +++ new/python-sql-1.0.0/sql/functions.py 2018-08-18 13:25:33.000000000 +0200 @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2011-2015, Cédric Krier -# Copyright (c) 2011-2015, B2CK +# Copyright (c) 2011-2018, Cédric Krier +# Copyright (c) 2011-2018, B2CK # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -472,15 +472,21 @@ Mapping = flavor.function_mapping.get(self.__class__) if Mapping: return str(Mapping(self.field, self.zone)) - param = flavor.param - return '%s AT TIME ZONE %s' % (str(self.field), param) + if isinstance(self.zone, Expression): + zone = str(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 - return self.field.params + (self.zone,) + if isinstance(self.zone, Expression): + return self.field.params + self.zone.params + else: + return self.field.params + (self.zone,) class WindowFunction(Function): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-sql-0.9/sql/operators.py new/python-sql-1.0.0/sql/operators.py --- old/python-sql-0.9/sql/operators.py 2015-06-22 12:48:33.000000000 +0200 +++ new/python-sql-1.0.0/sql/operators.py 2018-09-30 14:17:53.000000000 +0200 @@ -31,9 +31,10 @@ from sql import Expression, Select, CombiningQuery, Flavor, Null __all__ = ['And', 'Or', 'Not', 'Less', 'Greater', 'LessEqual', 'GreaterEqual', - 'Equal', 'NotEqual', 'Add', 'Sub', 'Mul', 'Div', 'FloorDiv', 'Mod', 'Pow', - 'SquareRoot', 'CubeRoot', 'Factorial', 'Abs', 'BAnd', 'BOr', 'BXor', - 'BNot', 'LShift', 'RShift', 'Concat', 'Like', 'NotLike', 'ILike', + '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'] @@ -225,6 +226,75 @@ 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): + assert right in [None, True, False] + 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 = '+' @@ -389,6 +459,7 @@ __slots__ = () _operator = 'ANY' + Some = Any @@ -404,6 +475,12 @@ GreaterEqual: Less, Equal: NotEqual, NotEqual: Equal, + Between: NotBetween, + NotBetween: Between, + IsDistinct: IsNotDistinct, + IsNotDistinct: IsDistinct, + Is: IsNot, + IsNot: Is, Like: NotLike, NotLike: Like, ILike: NotILike, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-sql-0.9/sql/tests/__init__.py new/python-sql-1.0.0/sql/tests/__init__.py --- old/python-sql-0.9/sql/tests/__init__.py 2015-03-05 09:36:24.000000000 +0100 +++ new/python-sql-1.0.0/sql/tests/__init__.py 2018-09-30 14:18:53.000000000 +0200 @@ -64,6 +64,7 @@ runner = unittest.TextTestRunner() return runner.run(suite) + if __name__ == '__main__': sys.path.insert(0, os.path.dirname(os.path.dirname( os.path.dirname(os.path.abspath(__file__))))) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-sql-0.9/sql/tests/test_aggregate.py new/python-sql-1.0.0/sql/tests/test_aggregate.py --- old/python-sql-0.9/sql/tests/test_aggregate.py 2015-09-08 17:51:02.000000000 +0200 +++ new/python-sql-1.0.0/sql/tests/test_aggregate.py 2018-08-18 13:25:33.000000000 +0200 @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2011-2015, Cédric Krier -# Copyright (c) 2011-2015, B2CK +# Copyright (c) 2011-2018, Cédric Krier +# Copyright (c) 2011-2018, B2CK # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -28,8 +28,8 @@ import unittest -from sql import Table, Window, AliasManager -from sql.aggregate import Avg +from sql import Table, Window, AliasManager, Flavor, Literal +from sql.aggregate import Avg, Count class TestAggregate(unittest.TestCase): @@ -48,9 +48,27 @@ self.assertEqual(avg.params, ()) def test_filter(self): - avg = Avg(self.table.a, filter_=self.table.a > 0) - self.assertEqual(str(avg), 'AVG("a") FILTER (WHERE ("a" > %s))') - self.assertEqual(avg.params, (0,)) + 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([])) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-sql-0.9/sql/tests/test_collate.py new/python-sql-1.0.0/sql/tests/test_collate.py --- old/python-sql-0.9/sql/tests/test_collate.py 1970-01-01 01:00:00.000000000 +0100 +++ new/python-sql-1.0.0/sql/tests/test_collate.py 2018-08-18 13:25:33.000000000 +0200 @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2017, Cédric Krier +# Copyright (c) 2017, B2CK +# 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 <organization> 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 <COPYRIGHT HOLDER> 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. + +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",)) + + def test_collate_injection(self): + collate = Collate(self.column, 'C";') + with self.assertRaises(ValueError): + str(collate) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-sql-0.9/sql/tests/test_delete.py new/python-sql-1.0.0/sql/tests/test_delete.py --- old/python-sql-0.9/sql/tests/test_delete.py 2015-02-01 23:31:58.000000000 +0100 +++ new/python-sql-1.0.0/sql/tests/test_delete.py 2018-09-30 14:18:37.000000000 +0200 @@ -66,5 +66,6 @@ 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"))') + 'DELETE FROM "t" WHERE ' + '("c2" IN (SELECT "a"."c3" FROM "a" AS "a"))') self.assertEqual(query.params, ()) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-sql-0.9/sql/tests/test_functions.py new/python-sql-1.0.0/sql/tests/test_functions.py --- old/python-sql-0.9/sql/tests/test_functions.py 2015-03-18 14:35:55.000000000 +0100 +++ new/python-sql-1.0.0/sql/tests/test_functions.py 2018-08-18 13:25:33.000000000 +0200 @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2011-2015, Cédric Krier -# Copyright (c) 2011-2015, B2CK +# Copyright (c) 2011-2018, Cédric Krier +# Copyright (c) 2011-2018, B2CK # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -110,6 +110,11 @@ 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_mapping(self): class MyAtTimeZone(Function): _function = 'MY_TIMEZONE' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-sql-0.9/sql/tests/test_insert.py new/python-sql-1.0.0/sql/tests/test_insert.py --- old/python-sql-0.9/sql/tests/test_insert.py 2015-02-01 23:31:07.000000000 +0100 +++ new/python-sql-1.0.0/sql/tests/test_insert.py 2018-08-18 13:25:33.000000000 +0200 @@ -76,7 +76,19 @@ [['foo', 'bar']], returning=[self.table.c1, self.table.c2]) self.assertEqual(str(query), 'INSERT INTO "t" ("c1", "c2") VALUES (%s, %s) ' - 'RETURNING "c1", "c2"') + 'RETURNING "t"."c1", "t"."c2"') + self.assertEqual(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" ("c") VALUES (%s) ' + 'RETURNING (SELECT "b"."c" FROM "t2" AS "b" ' + 'WHERE (("b"."c1" = "t1"."c") AND ("b"."c2" = %s)))') self.assertEqual(query.params, ('foo', 'bar')) def test_with(self): @@ -88,6 +100,14 @@ with_=[w], values=w.select()) self.assertEqual(str(query), - 'WITH "a" AS (SELECT * FROM "t1" AS "b") ' + 'WITH "b" AS (SELECT * FROM "t1" AS "c") ' 'INSERT INTO "t" ("c1") SELECT * FROM "a" AS "a"') self.assertEqual(query.params, ()) + + 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(query.params, ('foo',)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-sql-0.9/sql/tests/test_operators.py new/python-sql-1.0.0/sql/tests/test_operators.py --- old/python-sql-0.9/sql/tests/test_operators.py 2015-06-22 12:49:51.000000000 +0200 +++ new/python-sql-1.0.0/sql/tests/test_operators.py 2018-08-18 13:25:33.000000000 +0200 @@ -32,8 +32,9 @@ from sql import Table, Literal, Null, Flavor from sql.operators import (And, Or, Not, Neg, Pos, Less, Greater, LessEqual, - GreaterEqual, Equal, NotEqual, Sub, Mul, Div, Mod, Pow, Abs, LShift, - RShift, Like, NotLike, ILike, NotILike, In, NotIn, FloorDiv, Exists) + GreaterEqual, Equal, NotEqual, Between, NotBetween, IsDistinct, + IsNotDistinct, Is, IsNot, Sub, Mul, Div, Mod, Pow, Abs, LShift, RShift, + Like, NotLike, ILike, NotILike, In, NotIn, FloorDiv, Exists) class TestOperators(unittest.TestCase): @@ -172,6 +173,74 @@ 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_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]: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-sql-0.9/sql/tests/test_update.py new/python-sql-1.0.0/sql/tests/test_update.py --- old/python-sql-0.9/sql/tests/test_update.py 2015-02-01 23:31:27.000000000 +0100 +++ new/python-sql-1.0.0/sql/tests/test_update.py 2018-08-18 13:25:33.000000000 +0200 @@ -71,6 +71,18 @@ 'UPDATE "t" SET "c" = %s RETURNING "t"."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" SET "c" = %s ' + 'RETURNING (SELECT "b"."c" FROM "t2" AS "b" ' + 'WHERE (("b"."c1" = "t1"."c") AND ("b"."c2" = %s)))') + self.assertEqual(query.params, ('foo', 'bar')) + def test_with(self): t1 = Table('t1') w = With(query=t1.select(t1.c1)) @@ -84,3 +96,21 @@ 'UPDATE "t" SET "c2" = (SELECT "b"."c3" FROM "b" AS "b" ' 'WHERE ("b"."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" 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" SET "c1" = (' + 'SELECT "b"."c" FROM "default"."t2" AS "b" ' + 'WHERE ("b"."i" = "default"."t1"."i"))') + self.assertEqual(query.params, ()) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-sql-0.9/tox.ini new/python-sql-1.0.0/tox.ini --- old/python-sql-0.9/tox.ini 1970-01-01 01:00:00.000000000 +0100 +++ new/python-sql-1.0.0/tox.ini 2018-08-18 13:25:33.000000000 +0200 @@ -0,0 +1,12 @@ +# Tox (http://tox.testrun.org/) is a tool for running tests +# in multiple virtualenvs. This configuration file will run the +# test suite on all supported python versions. To use it, "pip install tox" +# and then run "tox" from this directory. + +[tox] +envlist = py26, py27, py33, py34, py35, py36, py37, pypy, pypy3, jython + +[testenv] +commands = {envpython} setup.py test +deps = +