python-sql-1.1.0/0000755000175000017500000000000013613012224013131 5ustar cedced00000000000000python-sql-1.1.0/CHANGELOG0000644000175000017500000000351613613011301014343 0ustar cedced00000000000000Version 1.1.0 - 2020-01-25 * Add ORDER BY clause to aggregate functions * Add support for Python 3.8 * Add distinct on Select 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 * Support Select without from clause Version 0.8 - 2015-09-19 * Add DISTINCT qualifier to aggregate expressions * Allow to order on select queries * Add NULL ordering * Use UPPER to simulate missing ILIKE * Add CURRENT_DATE function * Fix DateTrunc function name * Add no_boolean Flavor * Add converter format2numeric * Add rownum limit style * Add no_as Flavor Version 0.7 - 2015-05-19 * Fix WINDOW and HAVING params order in Select * Add window functions * Add filter and within group to aggregate * Add limitstyle with 'offset' and 'limit' * Add Lateral Version 0.6 - 2015-02-05 * Fix Delete query under multithread * Add missing quote for with query name Version 0.5 - 2015-01-29 * Add schema * Add Common Table Expression * Escape Mod operator '%' with format paramstyle * Deprecate Interesect in favor of Intersect * Add Values Version 0.4 - 2014-08-03 * Fix Mapping in Trim function * Add __truediv__ alias of __div__ for Python3 * Fix For tables setter when tables is not a list * Allow to add Table to existing From list * Fix Select with for_ clause * Add Null alias for None * Add missing format of operand in Unary operator Version 0.3 - 2014-01-31 * Add returning to Update * Add missing params for returning * Add columns definitions to Function * Fix AtTimeZone mapping Version 0.2 - 2013-09-18 * Fix usage mixture between Div operator and function * Add array support in operators Version 0.1 - 2013-08-28 * Initial release python-sql-1.1.0/python_sql.egg-info/0000755000175000017500000000000013613012224017023 5ustar cedced00000000000000python-sql-1.1.0/python_sql.egg-info/dependency_links.txt0000644000175000017500000000000113613012223023070 0ustar cedced00000000000000 python-sql-1.1.0/python_sql.egg-info/PKG-INFO0000644000175000017500000002250513613012223020123 0ustar cedced00000000000000Metadata-Version: 1.1 Name: python-sql Version: 1.1.0 Summary: Library to write SQL queries Home-page: http://python-sql.tryton.org/ Author: Tryton Author-email: python-sql@tryton.org License: BSD Description: 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" SET "active" = %s', (True,)) >>> tuple(invoice.update(columns=[invoice.total], values=[invoice.amount + invoice.tax])) ('UPDATE "invoice" SET "total" = ("invoice"."amount" + "invoice"."tax")', ()) Update query with where condition:: >>> tuple(user.update(columns=[user.active], values=[True], ... where=user.active == False)) ('UPDATE "user" SET "active" = %s WHERE ("user"."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 10', ()) >>> Flavor.set(Flavor(max_limit=18446744073709551615)) >>> tuple(select) ('SELECT * FROM "user" AS "a" LIMIT 18446744073709551615 OFFSET 10', ()) >>> Flavor.set(Flavor(max_limit=-1)) >>> tuple(select) ('SELECT * FROM "user" AS "a" LIMIT -1 OFFSET 10', ()) Limit style:: >>> select = user.select(limit=10, offset=20) >>> Flavor.set(Flavor(limitstyle='limit')) >>> tuple(select) ('SELECT * FROM "user" AS "a" LIMIT 10 OFFSET 20', ()) >>> Flavor.set(Flavor(limitstyle='fetch')) >>> tuple(select) ('SELECT * FROM "user" AS "a" OFFSET (20) ROWS FETCH FIRST (10) ROWS ONLY', ()) >>> 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',)) Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Database Classifier: Topic :: Software Development :: Libraries :: Python Modules python-sql-1.1.0/python_sql.egg-info/top_level.txt0000644000175000017500000000000413613012223021546 0ustar cedced00000000000000sql python-sql-1.1.0/python_sql.egg-info/SOURCES.txt0000644000175000017500000000157713613012224020721 0ustar cedced00000000000000.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 python_sql.egg-info/top_level.txt sql/__init__.py sql/aggregate.py sql/conditionals.py sql/functions.py sql/operators.py sql/tests/__init__.py sql/tests/test_aggregate.py 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 sql/tests/test_delete.py sql/tests/test_for.py sql/tests/test_functions.py sql/tests/test_insert.py sql/tests/test_join.py sql/tests/test_lateral.py sql/tests/test_literal.py sql/tests/test_operators.py sql/tests/test_order.py sql/tests/test_select.py sql/tests/test_table.py sql/tests/test_update.py sql/tests/test_values.py sql/tests/test_window.py sql/tests/test_with.pypython-sql-1.1.0/setup.cfg0000644000175000017500000000004613613012224014752 0ustar cedced00000000000000[egg_info] tag_build = tag_date = 0 python-sql-1.1.0/PKG-INFO0000644000175000017500000002250513613012224014232 0ustar cedced00000000000000Metadata-Version: 1.1 Name: python-sql Version: 1.1.0 Summary: Library to write SQL queries Home-page: http://python-sql.tryton.org/ Author: Tryton Author-email: python-sql@tryton.org License: BSD Description: 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" SET "active" = %s', (True,)) >>> tuple(invoice.update(columns=[invoice.total], values=[invoice.amount + invoice.tax])) ('UPDATE "invoice" SET "total" = ("invoice"."amount" + "invoice"."tax")', ()) Update query with where condition:: >>> tuple(user.update(columns=[user.active], values=[True], ... where=user.active == False)) ('UPDATE "user" SET "active" = %s WHERE ("user"."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 10', ()) >>> Flavor.set(Flavor(max_limit=18446744073709551615)) >>> tuple(select) ('SELECT * FROM "user" AS "a" LIMIT 18446744073709551615 OFFSET 10', ()) >>> Flavor.set(Flavor(max_limit=-1)) >>> tuple(select) ('SELECT * FROM "user" AS "a" LIMIT -1 OFFSET 10', ()) Limit style:: >>> select = user.select(limit=10, offset=20) >>> Flavor.set(Flavor(limitstyle='limit')) >>> tuple(select) ('SELECT * FROM "user" AS "a" LIMIT 10 OFFSET 20', ()) >>> Flavor.set(Flavor(limitstyle='fetch')) >>> tuple(select) ('SELECT * FROM "user" AS "a" OFFSET (20) ROWS FETCH FIRST (10) ROWS ONLY', ()) >>> 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',)) Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Database Classifier: Topic :: Software Development :: Libraries :: Python Modules python-sql-1.1.0/README0000644000175000017500000001607713562317154014041 0ustar cedced00000000000000python-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" SET "active" = %s', (True,)) >>> tuple(invoice.update(columns=[invoice.total], values=[invoice.amount + invoice.tax])) ('UPDATE "invoice" SET "total" = ("invoice"."amount" + "invoice"."tax")', ()) Update query with where condition:: >>> tuple(user.update(columns=[user.active], values=[True], ... where=user.active == False)) ('UPDATE "user" SET "active" = %s WHERE ("user"."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 10', ()) >>> Flavor.set(Flavor(max_limit=18446744073709551615)) >>> tuple(select) ('SELECT * FROM "user" AS "a" LIMIT 18446744073709551615 OFFSET 10', ()) >>> Flavor.set(Flavor(max_limit=-1)) >>> tuple(select) ('SELECT * FROM "user" AS "a" LIMIT -1 OFFSET 10', ()) Limit style:: >>> select = user.select(limit=10, offset=20) >>> Flavor.set(Flavor(limitstyle='limit')) >>> tuple(select) ('SELECT * FROM "user" AS "a" LIMIT 10 OFFSET 20', ()) >>> Flavor.set(Flavor(limitstyle='fetch')) >>> tuple(select) ('SELECT * FROM "user" AS "a" OFFSET (20) ROWS FETCH FIRST (10) ROWS ONLY', ()) >>> 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.1.0/sql/0000755000175000017500000000000013613012224013730 5ustar cedced00000000000000python-sql-1.1.0/sql/operators.py0000644000175000017500000002620413354137361016340 0ustar cedced00000000000000# -*- coding: utf-8 -*- # # Copyright (c) 2011-2013, Cédric Krier # Copyright (c) 2011-2013, 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 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. import warnings from array import array from sql import Expression, Select, CombiningQuery, Flavor, Null __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): return str(operand) elif isinstance(operand, (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 NotImplemented 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(str, 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): 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 = '+' 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__ = () _operator = 'LIKE' class NotLike(BinaryOperator): __slots__ = () _operator = 'NOT LIKE' class ILike(BinaryOperator): __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 Any(UnaryOperator): __slots__ = () _operator = 'ANY' Some = Any class All(UnaryOperator): __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.1.0/sql/aggregate.py0000644000175000017500000001423213610440570016240 0ustar cedced00000000000000# -*- coding: utf-8 -*- # # 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 # 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. from sql import Expression, Window, Flavor, Literal __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 distinct(self): return self._distinct @distinct.setter def distinct(self, value): assert isinstance(value, bool) 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] assert all(isinstance(col, Expression) for col in 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] assert all(isinstance(col, Expression) for col in 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: assert isinstance(value, (Expression, And, Or)) self._filter = value @property def window(self): return self._window @window.setter def window(self, value): if value: assert isinstance(value, Window) 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: window = ' OVER "%s"' % self.window.alias 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) 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 Count(Aggregate): __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__ = () _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.1.0/sql/tests/0000755000175000017500000000000013613012224015072 5ustar cedced00000000000000python-sql-1.1.0/sql/tests/test_delete.py0000644000175000017500000000575513354137435017777 0ustar cedced00000000000000# -*- coding: utf-8 -*- # # Copyright (c) 2011-2013, Cédric Krier # Copyright (c) 2011-2013, 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 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. import unittest from sql import 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_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_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.1.0/sql/tests/test_insert.py0000644000175000017500000001117113336001055020012 0ustar cedced00000000000000# -*- coding: utf-8 -*- # # Copyright (c) 2011-2013, Cédric Krier # Copyright (c) 2013, Nicolas Évrard # Copyright (c) 2011-2013, 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 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. import unittest from sql import Table, With from sql.functions import Abs class TestInsert(unittest.TestCase): table = Table('t') def test_insert_default(self): query = self.table.insert() self.assertEqual(str(query), 'INSERT INTO "t" DEFAULT VALUES') self.assertEqual(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(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(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(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(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" ("c1", "c2") VALUES (%s, %s) ' '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): 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 "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',)) python-sql-1.1.0/sql/tests/test_with.py0000644000175000017500000000716713336001055017473 0ustar cedced00000000000000# -*- coding: utf-8 -*- # # Copyright (c) 2014, Nicolas Évrard # Copyright (c) 2014, 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 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. import unittest from sql import AliasManager, Table, Literal, 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(q.params, (1, 1, 100)) python-sql-1.1.0/sql/tests/test_collate.py0000644000175000017500000000436613336001055020141 0ustar cedced00000000000000# -*- 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 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. 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) python-sql-1.1.0/sql/tests/test_cast.py0000644000175000017500000000410313336001055017435 0ustar cedced00000000000000# -*- coding: utf-8 -*- # # Copyright (c) 2011-2013, Cédric Krier # Copyright (c) 2011-2013, 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 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. 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.1.0/sql/tests/test_window.py0000644000175000017500000000636313336001055020024 0ustar cedced00000000000000# -*- coding: utf-8 -*- # # Copyright (c) 2015, Cédric Krier # Copyright (c) 2015, 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 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. import unittest from sql import Window, Table 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_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_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 1 PRECEDING AND CURRENT ROW') self.assertEqual(window.params, ()) window.start = 0 window.end = 1 self.assertEqual(str(window), 'PARTITION BY "c" RANGE ' 'BETWEEN CURRENT ROW AND 1 FOLLOWING') self.assertEqual(window.params, ()) window.start = 1 window.end = None self.assertEqual(str(window), 'PARTITION BY "c" RANGE ' 'BETWEEN 1 FOLLOWING AND UNBOUNDED FOLLOWING') self.assertEqual(window.params, ()) 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, ()) python-sql-1.1.0/sql/tests/test_aggregate.py0000644000175000017500000000746013610440570020446 0ustar cedced00000000000000# -*- coding: utf-8 -*- # # 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 # 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. import unittest from sql import Table, Window, AliasManager, Flavor, Literal from sql.aggregate import Avg, Count class TestAggregate(unittest.TestCase): table = Table('t') 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_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 "b"') 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.1.0/sql/tests/test_operators.py0000644000175000017500000003731313336001055020532 0ustar cedced00000000000000# -*- coding: utf-8 -*- # # Copyright (c) 2011-2013, Cédric Krier # Copyright (c) 2011-2013, 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 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. import unittest import warnings from array import array from sql import Table, Literal, Null, Flavor from sql.operators import (And, Or, Not, Neg, Pos, Less, Greater, LessEqual, 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): table = Table('t') 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,)) 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_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_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', 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)) python-sql-1.1.0/sql/tests/test_table.py0000644000175000017500000000404313336001055017575 0ustar cedced00000000000000# -*- coding: utf-8 -*- # # Copyright (c) 2015, Cédric Krier # Copyright (c) 2015, 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 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. import unittest from sql import Table class TestTable(unittest.TestCase): def test_name(self): t = Table('mytable') self.assertEqual(str(t), '"mytable"') 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.1.0/sql/tests/test_conditionals.py0000644000175000017500000000626213336001055021201 0ustar cedced00000000000000# -*- coding: utf-8 -*- # # Copyright (c) 2011-2013, Cédric Krier # Copyright (c) 2011-2013, 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 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. import unittest from sql import Table from sql.conditionals import Case, Coalesce, NullIf, Greatest, Least 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_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_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.1.0/sql/tests/__init__.py0000644000175000017500000000531413354137455017226 0ustar cedced00000000000000# -*- coding: utf-8 -*- # # Copyright (c) 2011-2013, Cédric Krier # Copyright (c) 2011-2013, 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 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. import sys import os import unittest import doctest import sql here = os.path.dirname(__file__) readme = os.path.normpath(os.path.join(here, '..', '..', 'README')) def test_suite(): suite = additional_tests() loader = unittest.TestLoader() for fn in os.listdir(here): if fn.startswith('test') and fn.endswith('.py'): modname = 'sql.tests.' + fn[:-3] __import__(modname) module = sys.modules[modname] suite.addTests(loader.loadTestsFromModule(module)) return suite def additional_tests(): suite = unittest.TestSuite() for mod in (sql,): suite.addTest(doctest.DocTestSuite(mod)) if os.path.isfile(readme): suite.addTest(doctest.DocFileSuite(readme, module_relative=False, tearDown=lambda t: sql.Flavor.set(sql.Flavor()))) return suite def main(): suite = test_suite() 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__))))) sys.exit(not main().wasSuccessful()) python-sql-1.1.0/sql/tests/test_join.py0000644000175000017500000000543513336001055017453 0ustar cedced00000000000000# -*- coding: utf-8 -*- # # Copyright (c) 2011-2013, Cédric Krier # Copyright (c) 2011-2013, 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 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. import unittest from sql import Join, Table, AliasManager 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(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_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(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(join.params, ()) python-sql-1.1.0/sql/tests/test_select.py0000644000175000017500000004273213561012150017772 0ustar cedced00000000000000# -*- coding: utf-8 -*- # # Copyright (c) 2011-2016, Cédric Krier # Copyright (c) 2013-2014, Nicolas Évrard # Copyright (c) 2011-2016, 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 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. import unittest import warnings from copy import deepcopy from sql import Table, Join, Union, Literal, Flavor, For, With, Window, Select from sql.functions import Now, Function, Rank, DatePart from sql.aggregate import Min 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(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(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(query.params, ('foo',)) def test_select_without_from(self): query = Select([Literal(1)]) self.assertEqual(str(query), 'SELECT %s') self.assertEqual(query.params, (1,)) def test_select_select(self): query = Select([Select([Literal(1)])]) self.assertEqual(str(query), 'SELECT (SELECT %s)') self.assertEqual(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(query.params, (1,)) 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(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(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(query.params, ()) 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(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(union.params, ('foo',)) union = Union(query1) self.assertEqual(str(union), str(query1)) self.assertEqual(union.params, 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(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 Intersect, Interesect 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(select.params, ()) def test_select_function(self): query = Now().select() self.assertEqual(str(query), 'SELECT * FROM NOW() AS "a"') self.assertEqual(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(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(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 "c1"') self.assertEqual(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(query.params, ('foo', 'foo')) 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(query.params, (3,)) def test_select_order(self): c = self.table.c query = self.table.select(c, order_by=Literal(1)) self.assertEqual(str(query), 'SELECT "a"."c" FROM "t" AS "a" ORDER BY %s') self.assertEqual(query.params, (1,)) 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 50 OFFSET 10') self.assertEqual(query.params, ()) query.limit = None self.assertEqual(str(query), 'SELECT * FROM "t" AS "a" OFFSET 10') self.assertEqual(query.params, ()) query.offset = 0 self.assertEqual(str(query), 'SELECT * FROM "t" AS "a"') self.assertEqual(query.params, ()) Flavor.set(Flavor(limitstyle='limit', max_limit=-1)) query.offset = None self.assertEqual(str(query), 'SELECT * FROM "t" AS "a"') self.assertEqual(query.params, ()) query.offset = 0 self.assertEqual(str(query), 'SELECT * FROM "t" AS "a"') self.assertEqual(query.params, ()) query.offset = 10 self.assertEqual(str(query), 'SELECT * FROM "t" AS "a" LIMIT -1 OFFSET 10') self.assertEqual(query.params, ()) finally: Flavor.set(Flavor()) 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 (10) ROWS FETCH FIRST (50) ROWS ONLY') self.assertEqual(query.params, ()) query.limit = None self.assertEqual(str(query), 'SELECT * FROM "t" AS "a" OFFSET (10) ROWS') self.assertEqual(query.params, ()) query.offset = 0 self.assertEqual(str(query), 'SELECT * FROM "t" AS "a"') self.assertEqual(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(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(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(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(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(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(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(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(query.params, ()) 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(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(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(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(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(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(query.params, ('year',)) def test_order_params(self): with_ = With(query=self.table.select(self.table.c, where=(self.table.c > 1))) w = Window([Literal(8)]) 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)], order_by=[Literal(6)], having=Literal(7)) self.assertEqual(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(query.params, ()) finally: Flavor.set(Flavor()) python-sql-1.1.0/sql/tests/test_lateral.py0000644000175000017500000000466413336001055020143 0ustar cedced00000000000000# -*- coding: utf-8 -*- # # Copyright (c) 2015, Cédric Krier # Copyright (c) 2015, 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 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. import unittest from sql import Table, Lateral, From from sql.functions import Function class TestLateral(unittest.TestCase): def test_lateral_select(self): t1 = Table('t1') t2 = Table('t2') lateral = Lateral(t2.select(where=t2.id == t1.t2)) 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(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(query.params, ()) python-sql-1.1.0/sql/tests/test_literal.py0000644000175000017500000000465013336001055020146 0ustar cedced00000000000000# -*- coding: utf-8 -*- # # Copyright (c) 2011-2013, Cédric Krier # Copyright (c) 2011-2013, 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 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. import unittest from sql import Literal, Flavor 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.1.0/sql/tests/test_as.py0000644000175000017500000000450613336001055017115 0ustar cedced00000000000000# -*- coding: utf-8 -*- # # Copyright (c) 2011-2013, Cédric Krier # Copyright (c) 2011-2013, 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 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. import unittest from sql import As, Column, Table, Flavor 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(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(query.params, ()) finally: Flavor.set(Flavor()) python-sql-1.1.0/sql/tests/test_for.py0000644000175000017500000000373413336001055017302 0ustar cedced00000000000000# -*- coding: utf-8 -*- # # Copyright (c) 2011-2013, Cédric Krier # Copyright (c) 2011-2013, 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 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. 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"') python-sql-1.1.0/sql/tests/test_combining_query.py0000644000175000017500000000622313336001055021702 0ustar cedced00000000000000# -*- coding: utf-8 -*- # # Copyright (c) 2014, Cédric Krier # Copyright (c) 2014, 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 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. import unittest from sql import Table, Union class TestUnion(unittest.TestCase): q1 = Table('t1').select() q2 = Table('t2').select() q3 = Table('t3').select() 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(query.params, ()) query = self.q1 | self.q2 self.assertEqual(str(query), 'SELECT * FROM "t1" AS "a" UNION SELECT * FROM "t2" AS "b"') self.assertEqual(query.params, ()) 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(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(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(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(query.params, ()) python-sql-1.1.0/sql/tests/test_functions.py0000644000175000017500000001406213336001055020520 0ustar cedced00000000000000# -*- coding: utf-8 -*- # # 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 # 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. import unittest from sql import Table, Flavor, Window, AliasManager from sql.functions import (Function, FunctionKeyword, FunctionNotCallable, Abs, Overlay, Trim, AtTimeZone, Div, CurrentTime, Rank) class TestFunctions(unittest.TestCase): table = Table('t') 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_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_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_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, ()) 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 "b"') self.assertEqual(function.params, ()) 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 "b"') self.assertEqual(function.params, (0,)) python-sql-1.1.0/sql/tests/test_values.py0000644000175000017500000000470213336001055020007 0ustar cedced00000000000000# -*- coding: utf-8 -*- # # Copyright (c) 2014, Cédric Krier # Copyright (c) 2014, 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 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. 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(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.1.0/sql/tests/test_order.py0000644000175000017500000000661213336001055017625 0ustar cedced00000000000000# -*- coding: utf-8 -*- # # Copyright (c) 2011-2013, Cédric Krier # Copyright (c) 2011-2013, 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 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. import unittest from sql import Asc, Desc, NullsFirst, NullsLast, Column, Table, Literal from sql import Flavor class TestOrder(unittest.TestCase): column = Column(Table('t'), 'c') def test_asc(self): self.assertEqual(str(Asc(self.column)), '"c" ASC') def test_desc(self): self.assertEqual(str(Desc(self.column)), '"c" DESC') def test_nulls_first(self): self.assertEqual(str(NullsFirst(self.column)), '"c" NULLS FIRST') self.assertEqual(str(NullsFirst(Asc(self.column))), '"c" ASC NULLS FIRST') def test_nulls_last(self): self.assertEqual(str(NullsLast(self.column)), '"c" NULLS LAST') self.assertEqual(str(NullsLast(Asc(self.column))), '"c" ASC NULLS LAST') 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') python-sql-1.1.0/sql/tests/test_column.py0000644000175000017500000000363513336001055020011 0ustar cedced00000000000000# -*- coding: utf-8 -*- # # Copyright (c) 2011-2013, Cédric Krier # Copyright (c) 2011-2013, 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 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. import unittest from sql import Column, Table, AliasManager class TestColumn(unittest.TestCase): def test_column(self): column = Column(Table('t'), 'c') self.assertEqual(str(column), '"c"') self.assertEqual(column.name, 'c') with AliasManager(): self.assertEqual(str(column), '"a"."c"') python-sql-1.1.0/sql/tests/test_alias.py0000644000175000017500000000642213336001055017602 0ustar cedced00000000000000# -*- coding: utf-8 -*- # # Copyright (c) 2011-2013, Cédric Krier # Copyright (c) 2011-2013, 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 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. import unittest import threading 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() python-sql-1.1.0/sql/tests/test_update.py0000644000175000017500000001144013336001055017767 0ustar cedced00000000000000# -*- coding: utf-8 -*- # # Copyright (c) 2011-2013, Cédric Krier # Copyright (c) 2011-2013, 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 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. import unittest from sql import Table, Literal, 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" SET "c" = %s') self.assertEqual(query.params, ('foo',)) query.where = (self.table.b == Literal(True)) self.assertEqual(str(query), 'UPDATE "t" SET "c" = %s WHERE ("t"."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_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" SET "c" = (' 'SELECT "b"."c" FROM "t2" AS "b" WHERE ("b"."i" = "t1"."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" 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)) query = self.table.update( [self.table.c2], with_=[w], values=[w.select(w.c3, where=w.c4 == 2)]) self.assertEqual(str(query), 'WITH "b" AS (SELECT "c"."c1" FROM "t1" AS "c") ' '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, ()) python-sql-1.1.0/sql/__init__.py0000644000175000017500000013167213613011204016050 0ustar cedced00000000000000# -*- coding: utf-8 -*- # # Copyright (c) 2011-2018, Cédric Krier # Copyright (c) 2013-2014, Nicolas Évrard # Copyright (c) 2011-2018, 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 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. from __future__ import division import string import warnings from threading import local, currentThread from collections import defaultdict from itertools import chain __version__ = '1.1.0' __all__ = ['Flavor', 'Table', 'Values', 'Literal', 'Column', 'Join', 'Asc', 'Desc', 'NullsFirst', 'NullsLast', 'format2numeric'] 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 null_ordering - support NULL ordering function_mapping - dictionary with Function to replace ''' 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): assert limitstyle in ['fetch', 'limit', 'rownum'] self.limitstyle = limitstyle self.max_limit = max_limit self.paramstyle = paramstyle self.ilike = ilike self.no_as = no_as self.no_boolean = no_boolean self.null_ordering = null_ordering self.function_mapping = function_mapping or {} self.filter_ = filter_ @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.''' currentThread().__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 currentThread().__sql_flavor__ except AttributeError: flavor = Flavor() currentThread().__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 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__ = () @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] assert all(isinstance(w, With) for w in 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__ = () @property def alias(self): return AliasManager.get(self) def __getattr__(self, name): if name.startswith('__'): raise AttributeError return Column(self, name) def __add__(self, other): assert isinstance(other, FromItem) 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 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] assert all(isinstance(col, Expression) for col in 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: assert isinstance(value, (int, long)) self._limit = value @property def offset(self): return self._offset @offset.setter def offset(self, value): if value is not None: assert isinstance(value, (int, long)) self._offset = value @property def _limit_offset_str(self): if Flavor.get().limitstyle == 'limit': offset = '' if self.offset: offset = ' OFFSET %s' % self.offset limit = '' if self.limit is not None: limit = ' LIMIT %s' % self.limit 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' % self.offset fetch = '' if self.limit is not None: fetch = ' FETCH FIRST (%s) ROWS ONLY' % self.limit return offset + fetch 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') def __init__(self, columns, from_=None, where=None, group_by=None, having=None, for_=None, distinct=False, distinct_on=None, **kwargs): self._distinct = False self._distinct_on = [] self._columns = None self._where = None self._group_by = None self._having = None self._for_ = None 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_ @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] assert all(isinstance(col, Expression) for col in value) self._distinct_on = value @property def columns(self): return self._columns @columns.setter def columns(self, value): assert all(isinstance(col, (Expression, SelectQuery)) for col in 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: assert isinstance(value, (Expression, And, Or)) 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] assert all(isinstance(col, Expression) for col in 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: assert isinstance(value, (Expression, And, Or)) 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] assert all(isinstance(f, For) for f in value) self._for_ = 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 _window_functions(self): from sql.functions import WindowFunction from sql.aggregate import Aggregate windows = set() 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 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) with AliasManager(): if self.from_ is not None: from_ = ' FROM %s' % self.from_ else: from_ = '' 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, self.group_by)) having = '' if self.having: having = ' HAVING ' + str(self.having) window = '' windows = [f.window for f in self._window_functions()] if windows: window = ' WINDOW ' + ', '.join( '"%s" AS (%s)' % (w.alias, w) for w in windows) 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 = [] 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.order_by: for expression in self.order_by: p.extend(expression.params) if self.having: p.extend(self.having.params) for window_function in self._window_functions(): p.extend(window_function.window.params) return tuple(p) class Insert(WithQuery): __slots__ = ('_table', '_columns', '_values', '_returning') def __init__(self, table, columns=None, values=None, returning=None, **kwargs): self._table = None self._columns = None self._values = None self._returning = None self.table = table self.columns = columns self.values = values self.returning = returning super(Insert, self).__init__(**kwargs) @property def table(self): return self._table @table.setter def table(self, value): assert isinstance(value, Table) self._table = value @property def columns(self): return self._columns @columns.setter def columns(self, value): if value is not None: assert all(isinstance(col, Column) for col in value) assert all(col.table == self.table for col in value) self._columns = value @property def values(self): return self._values @values.setter def values(self, value): if value is not None: assert isinstance(value, (list, Select)) if isinstance(value, list): value = Values(value) self._values = value @property def returning(self): return self._returning @returning.setter def returning(self, value): if value is not None: assert isinstance(value, list) 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) columns = ' (' + ', '.join(map(str, self.columns)) + ')' if isinstance(self.values, Query): values = ' %s' % str(self.values) # TODO manage DEFAULT elif self.values is None: values = ' DEFAULT VALUES' 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) @property def params(self): p = [] p.extend(self._with_params()) if isinstance(self.values, Query): p.extend(self.values.params) if self.returning: for exp in self.returning: p.extend(exp.params) return tuple(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] assert isinstance(value, list) 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: assert isinstance(value, (Expression, And, Or)) self._where = value def __str__(self): assert all(col.table == self.table for col in self.columns) # Get columns without alias columns = map(str, self.columns) with AliasManager(): from_ = '' if self.from_: table = From([self.table]) from_ = ' FROM %s' % str(self.from_) else: table = self.table AliasManager.set(table, str(table)[1:-1]) 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, self.returning)) return (self._with_str() + 'UPDATE %s SET ' % table + 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): assert isinstance(value, Table) 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: assert isinstance(value, (Expression, And, Or)) self._where = value @property def returning(self): return self._returning @returning.setter def returning(self, value): if value is not None: assert isinstance(value, list) self._returning = 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(str, 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 CombiningQuery(FromItem, SelectQuery): __slots__ = ('queries', 'all_') _operator = '' def __init__(self, *queries, **kwargs): assert all(isinstance(q, Query) for q in 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 (operator.join(map(str, self.queries)) + self._order_by_str + self._limit_offset_str) @property def params(self): p = [] 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): if self._database: return '"%s"."%s"."%s"' % ( self._database, self._schema, self._name) elif self._schema: return '"%s"."%s"' % (self._schema, self._name) else: return '"%s"' % self._name @property def params(self): return () def insert(self, columns=None, values=None, returning=None, with_=None): return Insert(self, columns=columns, values=values, 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_) 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): assert isinstance(value, FromItem) self._left = value @property def right(self): return self._right @right.setter def right(self, value): assert isinstance(value, FromItem) 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: assert isinstance(value, (Expression, And, Or)) self._condition = value @property def type_(self): return self._type_ @type_.setter def type_(self, value): value = value.upper() assert value in ('INNER', 'LEFT', 'LEFT OUTER', 'RIGHT', 'RIGHT OUTER', 'FULL', 'FULL OUTER', 'CROSS') 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): if hasattr(item, 'params'): p.extend(item.params) if hasattr(self.condition, 'params'): p.extend(self.condition.params) return tuple(p) @property def 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): assert isinstance(other, FromItem) assert not isinstance(other, CombiningQuery) 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 = [] 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__ = () 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 def __str__(self): if self._name == '*': t = '%s' else: t = '"%s"' alias = self._from.alias if alias: t = '"%s".' + t return t % (alias, self._name) else: return t % self._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"' % 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 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: return (self.expression,) class Window(object): __slots__ = ('_partition', '_order_by', '_frame', '_start', '_end') def __init__(self, partition, order_by=None, frame=None, start=None, end=0): 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 @property def partition(self): return self._partition @partition.setter def partition(self, value): assert all(isinstance(e, Expression) for e in 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] assert all(isinstance(col, Expression) for col in value) self._order_by = value @property def frame(self): return self._frame @frame.setter def frame(self, value): if value: assert value in ['RANGE', 'ROWS'] self._frame = value @property def start(self): return self._start @start.setter def start(self, value): if value: assert isinstance(value, (int, long)) self._start = value @property def end(self): return self._end @end.setter def end(self, value): if value: assert isinstance(value, (int, long)) self._end = value @property def alias(self): return AliasManager.get(self) def __str__(self): 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' % -frame elif frame > 0: return '%s FOLLOWING' % frame frame = '' if self.frame: start = format(self.start, 'PRECEDING') end = format(self.end, 'FOLLOWING') frame = ' %s BETWEEN %s AND %s' % (self.frame, start, end) return partition + order_by + frame @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) 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): assert isinstance(value, (Expression, SelectQuery)) 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() assert value in ('UPDATE', 'SHARE') 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.1.0/sql/functions.py0000644000175000017500000003203213561011244016315 0ustar cedced00000000000000# -*- coding: utf-8 -*- # # 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 # 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. from itertools import chain from sql import Expression, Flavor, FromItem, 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): assert isinstance(value, list) self._columns_definitions = value @staticmethod def _format(value): if isinstance(value, Expression): return str(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): 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))))[1:] + ')') 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=' '): assert position.upper() in ('LEADING', 'TRAILING', 'BOTH') 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, basestring): 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, basestring): p.append(arg) elif hasattr(arg, 'params'): 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__ = () _function = 'EXTRACT' _keywords = ('', 'FROM') 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) 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): 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: assert isinstance(value, (Expression, And, Or)) self._filter = value @property def window(self): return self._window @window.setter def window(self, value): if value: assert isinstance(value, Window) self._window = value def __str__(self): function = super(WindowFunction, self).__str__() filter_ = '' if self.filter_: filter_ = ' FILTER (WHERE %s)' % self.filter_ over = ' OVER "%s"' % self.window.alias return function + filter_ + over @property def params(self): p = list(super(WindowFunction, self).params) if self.filter_: p.extend(self.filter_.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.1.0/sql/conditionals.py0000644000175000017500000000730713336001055017001 0ustar cedced00000000000000# -*- coding: utf-8 -*- # # Copyright (c) 2011-2013, Cédric Krier # Copyright (c) 2011-2013, 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 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. from sql import Expression, Flavor __all__ = ['Case', 'Coalesce', 'NullIf', 'Greatest', 'Least'] class Conditional(Expression): __slots__ = () _sql = '' table = '' name = '' @staticmethod def _format(value): if isinstance(value, Expression): return str(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): p.extend(cond.params) else: p.append(cond) if isinstance(result, Expression): p.extend(result.params) else: p.append(result) if self.else_ is not None: if isinstance(self.else_, Expression): 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): 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.1.0/tox.ini0000644000175000017500000000057713601100666014462 0ustar cedced00000000000000# 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, py38, pypy, pypy3, jython [testenv] commands = {envpython} setup.py test deps = python-sql-1.1.0/MANIFEST.in0000644000175000017500000000004113336001055014664 0ustar cedced00000000000000include README include CHANGELOG python-sql-1.1.0/.drone.yml0000644000175000017500000000136213601100666015050 0ustar cedced00000000000000clone: hg: image: plugins/hg environment: - HG_SHARE_POOL=/root/.cache/hg volumes: - cache:/root/.cache 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: python:3.8 TOXENV: py38 - IMAGE: pypy:2 TOXENV: pypy - IMAGE: pypy:3 TOXENV: pypy3 python-sql-1.1.0/setup.py0000644000175000017500000000533613567033567014677 0ustar cedced00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright (c) 2011-2013, Cédric Krier # Copyright (c) 2011-2013, 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 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. from setuptools import setup, find_packages import os import re import codecs def read(fname): return codecs.open( os.path.join(os.path.dirname(__file__), fname), 'r', 'utf-8').read() def get_version(): 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='Tryton', author_email='python-sql@tryton.org', url='http://python-sql.tryton.org/', packages=find_packages(), classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Topic :: Database', 'Topic :: Software Development :: Libraries :: Python Modules', ], license='BSD', test_suite='sql.tests', use_2to3=True, ) python-sql-1.1.0/.hgtags0000644000175000017500000000076313613012150014413 0ustar cedced000000000000002543c23b577ce86260954129b1fb002628ffa80c 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 194182e5b0e2dc6486a32778860f29c80c0672f9 1.1.0