python-sql 1.6.0__tar.gz → 1.8.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- python_sql-1.8.0/.gitignore +1 -0
- python_sql-1.8.0/.hgignore +7 -0
- {python_sql-1.6.0 → python_sql-1.8.0}/COPYRIGHT +3 -3
- {python_sql-1.6.0 → python_sql-1.8.0}/PKG-INFO +28 -48
- {python_sql-1.6.0 → python_sql-1.8.0}/README.rst +14 -14
- python_sql-1.8.0/pyproject.toml +38 -0
- {python_sql-1.6.0 → python_sql-1.8.0}/sql/__init__.py +24 -2
- {python_sql-1.6.0 → python_sql-1.8.0}/sql/aggregate.py +16 -6
- {python_sql-1.6.0 → python_sql-1.8.0}/sql/functions.py +63 -3
- {python_sql-1.6.0 → python_sql-1.8.0}/sql/operators.py +36 -18
- {python_sql-1.6.0 → python_sql-1.8.0}/sql/tests/test_aggregate.py +6 -6
- {python_sql-1.6.0 → python_sql-1.8.0}/sql/tests/test_combining_query.py +1 -1
- {python_sql-1.6.0 → python_sql-1.8.0}/sql/tests/test_conditionals.py +3 -3
- {python_sql-1.6.0 → python_sql-1.8.0}/sql/tests/test_delete.py +4 -4
- {python_sql-1.6.0 → python_sql-1.8.0}/sql/tests/test_functions.py +36 -4
- {python_sql-1.6.0 → python_sql-1.8.0}/sql/tests/test_insert.py +4 -4
- {python_sql-1.6.0 → python_sql-1.8.0}/sql/tests/test_join.py +2 -2
- {python_sql-1.6.0 → python_sql-1.8.0}/sql/tests/test_lateral.py +1 -1
- {python_sql-1.6.0 → python_sql-1.8.0}/sql/tests/test_merge.py +12 -12
- {python_sql-1.6.0 → python_sql-1.8.0}/sql/tests/test_operators.py +84 -67
- {python_sql-1.6.0 → python_sql-1.8.0}/sql/tests/test_order.py +3 -3
- {python_sql-1.6.0 → python_sql-1.8.0}/sql/tests/test_select.py +62 -31
- {python_sql-1.6.0 → python_sql-1.8.0}/sql/tests/test_update.py +6 -6
- {python_sql-1.6.0 → python_sql-1.8.0}/sql/tests/test_with.py +3 -3
- python_sql-1.6.0/.flake8 +0 -2
- python_sql-1.6.0/.gitlab-ci.yml +0 -66
- python_sql-1.6.0/.hgtags +0 -23
- python_sql-1.6.0/.isort.cfg +0 -2
- python_sql-1.6.0/CHANGELOG +0 -125
- python_sql-1.6.0/MANIFEST.in +0 -3
- python_sql-1.6.0/python_sql.egg-info/PKG-INFO +0 -246
- python_sql-1.6.0/python_sql.egg-info/SOURCES.txt +0 -51
- python_sql-1.6.0/python_sql.egg-info/dependency_links.txt +0 -1
- python_sql-1.6.0/python_sql.egg-info/top_level.txt +0 -1
- python_sql-1.6.0/setup.cfg +0 -4
- python_sql-1.6.0/setup.py +0 -56
- python_sql-1.6.0/tox.ini +0 -19
- {python_sql-1.6.0 → python_sql-1.8.0}/sql/conditionals.py +0 -0
- {python_sql-1.6.0 → python_sql-1.8.0}/sql/tests/__init__.py +0 -0
- {python_sql-1.6.0 → python_sql-1.8.0}/sql/tests/test_alias.py +0 -0
- {python_sql-1.6.0 → python_sql-1.8.0}/sql/tests/test_as.py +0 -0
- {python_sql-1.6.0 → python_sql-1.8.0}/sql/tests/test_cast.py +0 -0
- {python_sql-1.6.0 → python_sql-1.8.0}/sql/tests/test_collate.py +0 -0
- {python_sql-1.6.0 → python_sql-1.8.0}/sql/tests/test_column.py +0 -0
- {python_sql-1.6.0 → python_sql-1.8.0}/sql/tests/test_excluded.py +0 -0
- {python_sql-1.6.0 → python_sql-1.8.0}/sql/tests/test_expression.py +0 -0
- {python_sql-1.6.0 → python_sql-1.8.0}/sql/tests/test_flavor.py +0 -0
- {python_sql-1.6.0 → python_sql-1.8.0}/sql/tests/test_for.py +0 -0
- {python_sql-1.6.0 → python_sql-1.8.0}/sql/tests/test_from.py +0 -0
- {python_sql-1.6.0 → python_sql-1.8.0}/sql/tests/test_from_item.py +0 -0
- {python_sql-1.6.0 → python_sql-1.8.0}/sql/tests/test_grouping.py +0 -0
- {python_sql-1.6.0 → python_sql-1.8.0}/sql/tests/test_literal.py +0 -0
- {python_sql-1.6.0 → python_sql-1.8.0}/sql/tests/test_rollup.py +0 -0
- {python_sql-1.6.0 → python_sql-1.8.0}/sql/tests/test_table.py +0 -0
- {python_sql-1.6.0 → python_sql-1.8.0}/sql/tests/test_values.py +0 -0
- {python_sql-1.6.0 → python_sql-1.8.0}/sql/tests/test_window.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Sync/dotfiles/git/dot-gitignore
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Copyright (c) 2011-
|
|
2
|
-
Copyright (c) 2013-2025
|
|
3
|
-
Copyright (c) 2011-
|
|
1
|
+
Copyright (c) 2011-2026 Cédric Krier <cedric.krier@b2ck.com>
|
|
2
|
+
Copyright (c) 2013-2025 Nicolas Évrard <nicolas.evrard@b2ck.com>
|
|
3
|
+
Copyright (c) 2011-2026 B2CK SRL
|
|
4
4
|
All rights reserved.
|
|
5
5
|
|
|
6
6
|
Redistribution and use in source and binary forms, with or without
|
|
@@ -1,44 +1,24 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-sql
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.8.0
|
|
4
4
|
Summary: Library to write SQL queries
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
5
|
+
Project-URL: homepage, https://www.tryton.org/
|
|
6
|
+
Project-URL: changelog, https://code.tryton.org/python-sql/-/blob/branch/default/CHANGELOG
|
|
7
|
+
Project-URL: forum, https://discuss.tryton.org/tags/python-sql
|
|
8
|
+
Project-URL: issues, https://bugs.tryton.org/python-sql
|
|
9
|
+
Project-URL: repository, https://code.tryton.org/python-sql
|
|
10
|
+
Author: B2CK SRL
|
|
11
|
+
Author-email: Cédric Krier <cedric.krier@b2ck.com>, Nicolas Évrard <nicolas.evrard@b2ck.com>
|
|
12
|
+
Maintainer-email: Tryton <foundation@tryton.org>
|
|
13
|
+
License-Expression: BSD-3-Clause
|
|
14
|
+
License-File: COPYRIGHT
|
|
15
|
+
Keywords: SQL,database,query
|
|
14
16
|
Classifier: Development Status :: 5 - Production/Stable
|
|
15
17
|
Classifier: Intended Audience :: Developers
|
|
16
|
-
Classifier: License :: OSI Approved :: BSD License
|
|
17
|
-
Classifier: Operating System :: OS Independent
|
|
18
|
-
Classifier: Programming Language :: Python :: 3
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.5
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.6
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
22
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
23
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
24
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
25
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
26
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
27
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
28
18
|
Classifier: Topic :: Database
|
|
29
19
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
30
|
-
Requires-Python: >=3.
|
|
31
|
-
|
|
32
|
-
Dynamic: author-email
|
|
33
|
-
Dynamic: classifier
|
|
34
|
-
Dynamic: description
|
|
35
|
-
Dynamic: download-url
|
|
36
|
-
Dynamic: home-page
|
|
37
|
-
Dynamic: keywords
|
|
38
|
-
Dynamic: license
|
|
39
|
-
Dynamic: project-url
|
|
40
|
-
Dynamic: requires-python
|
|
41
|
-
Dynamic: summary
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Description-Content-Type: text/x-rst
|
|
42
22
|
|
|
43
23
|
python-sql
|
|
44
24
|
==========
|
|
@@ -81,14 +61,14 @@ Select with where condition::
|
|
|
81
61
|
|
|
82
62
|
>>> select.where = user.name == 'foo'
|
|
83
63
|
>>> tuple(select)
|
|
84
|
-
('SELECT "a"."id", "a"."name" FROM "user" AS "a" WHERE
|
|
64
|
+
('SELECT "a"."id", "a"."name" FROM "user" AS "a" WHERE "a"."name" = %s', ('foo',))
|
|
85
65
|
|
|
86
66
|
>>> select.where = (user.name == 'foo') & (user.active == True)
|
|
87
67
|
>>> tuple(select)
|
|
88
|
-
('SELECT "a"."id", "a"."name" FROM "user" AS "a" WHERE (
|
|
68
|
+
('SELECT "a"."id", "a"."name" FROM "user" AS "a" WHERE ("a"."name" = %s) AND ("a"."active" = %s)', ('foo', True))
|
|
89
69
|
>>> select.where = user.name == user.login
|
|
90
70
|
>>> tuple(select)
|
|
91
|
-
('SELECT "a"."id", "a"."name" FROM "user" AS "a" WHERE
|
|
71
|
+
('SELECT "a"."id", "a"."name" FROM "user" AS "a" WHERE "a"."name" = "a"."login"', ())
|
|
92
72
|
|
|
93
73
|
Select with join::
|
|
94
74
|
|
|
@@ -96,7 +76,7 @@ Select with join::
|
|
|
96
76
|
>>> join.condition = join.right.user == user.id
|
|
97
77
|
>>> select = join.select(user.name, join.right.group)
|
|
98
78
|
>>> tuple(select)
|
|
99
|
-
('SELECT "a"."name", "b"."group" FROM "user" AS "a" INNER JOIN "user_group" AS "b" ON
|
|
79
|
+
('SELECT "a"."name", "b"."group" FROM "user" AS "a" INNER JOIN "user_group" AS "b" ON "b"."user" = "a"."id"', ())
|
|
100
80
|
|
|
101
81
|
Select with multiple joins::
|
|
102
82
|
|
|
@@ -135,9 +115,9 @@ Select with sub-select::
|
|
|
135
115
|
... where=user_group.active == True)
|
|
136
116
|
>>> user = Table('user')
|
|
137
117
|
>>> tuple(user.select(user.id, where=user.id.in_(subselect)))
|
|
138
|
-
('SELECT "a"."id" FROM "user" AS "a" WHERE
|
|
118
|
+
('SELECT "a"."id" FROM "user" AS "a" WHERE "a"."id" IN (SELECT "b"."user" FROM "user_group" AS "b" WHERE "b"."active" = %s)', (True,))
|
|
139
119
|
>>> tuple(subselect.select(subselect.user))
|
|
140
|
-
('SELECT "a"."user" FROM (SELECT "b"."user" FROM "user_group" AS "b" WHERE
|
|
120
|
+
('SELECT "a"."user" FROM (SELECT "b"."user" FROM "user_group" AS "b" WHERE "b"."active" = %s) AS "a"', (True,))
|
|
141
121
|
|
|
142
122
|
Select on other schema::
|
|
143
123
|
|
|
@@ -171,20 +151,20 @@ Update query with values::
|
|
|
171
151
|
>>> tuple(user.update(columns=[user.active], values=[True]))
|
|
172
152
|
('UPDATE "user" AS "a" SET "active" = %s', (True,))
|
|
173
153
|
>>> tuple(invoice.update(columns=[invoice.total], values=[invoice.amount + invoice.tax]))
|
|
174
|
-
('UPDATE "invoice" AS "a" SET "total" =
|
|
154
|
+
('UPDATE "invoice" AS "a" SET "total" = "a"."amount" + "a"."tax"', ())
|
|
175
155
|
|
|
176
156
|
Update query with where condition::
|
|
177
157
|
|
|
178
158
|
>>> tuple(user.update(columns=[user.active], values=[True],
|
|
179
159
|
... where=user.active == False))
|
|
180
|
-
('UPDATE "user" AS "a" SET "active" = %s WHERE
|
|
160
|
+
('UPDATE "user" AS "a" SET "active" = %s WHERE "a"."active" = %s', (True, False))
|
|
181
161
|
|
|
182
162
|
Update query with from list::
|
|
183
163
|
|
|
184
164
|
>>> group = Table('user_group')
|
|
185
165
|
>>> tuple(user.update(columns=[user.active], values=[group.active],
|
|
186
166
|
... from_=[group], where=user.id == group.user))
|
|
187
|
-
('UPDATE "user" AS "b" SET "active" = "a"."active" FROM "user_group" AS "a" WHERE
|
|
167
|
+
('UPDATE "user" AS "b" SET "active" = "a"."active" FROM "user_group" AS "a" WHERE "b"."id" = "a"."user"', ())
|
|
188
168
|
|
|
189
169
|
Delete query::
|
|
190
170
|
|
|
@@ -194,13 +174,13 @@ Delete query::
|
|
|
194
174
|
Delete query with where condition::
|
|
195
175
|
|
|
196
176
|
>>> tuple(user.delete(where=user.name == 'foo'))
|
|
197
|
-
('DELETE FROM "user" WHERE
|
|
177
|
+
('DELETE FROM "user" WHERE "name" = %s', ('foo',))
|
|
198
178
|
|
|
199
179
|
Delete query with sub-query::
|
|
200
180
|
|
|
201
181
|
>>> tuple(user.delete(
|
|
202
182
|
... where=user.id.in_(user_group.select(user_group.user))))
|
|
203
|
-
('DELETE FROM "user" WHERE
|
|
183
|
+
('DELETE FROM "user" WHERE "id" IN (SELECT "a"."user" FROM "user_group" AS "a")', ())
|
|
204
184
|
|
|
205
185
|
Flavors::
|
|
206
186
|
|
|
@@ -227,7 +207,7 @@ Limit style::
|
|
|
227
207
|
('SELECT * FROM "user" AS "a" OFFSET (%s) ROWS FETCH FIRST (%s) ROWS ONLY', (20, 10))
|
|
228
208
|
>>> Flavor.set(Flavor(limitstyle='rownum'))
|
|
229
209
|
>>> tuple(select)
|
|
230
|
-
('SELECT "a".* FROM (SELECT "b".*, ROWNUM AS "rnum" FROM (SELECT * FROM "user" AS "c") AS "b" WHERE
|
|
210
|
+
('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))
|
|
231
211
|
|
|
232
212
|
qmark style::
|
|
233
213
|
|
|
@@ -235,7 +215,7 @@ qmark style::
|
|
|
235
215
|
>>> select = user.select()
|
|
236
216
|
>>> select.where = user.name == 'foo'
|
|
237
217
|
>>> tuple(select)
|
|
238
|
-
('SELECT * FROM "user" AS "a" WHERE
|
|
218
|
+
('SELECT * FROM "user" AS "a" WHERE "a"."name" = ?', ('foo',))
|
|
239
219
|
|
|
240
220
|
numeric style::
|
|
241
221
|
|
|
@@ -243,4 +223,4 @@ numeric style::
|
|
|
243
223
|
>>> select = user.select()
|
|
244
224
|
>>> select.where = user.name == 'foo'
|
|
245
225
|
>>> format2numeric(*select)
|
|
246
|
-
('SELECT * FROM "user" AS "a" WHERE
|
|
226
|
+
('SELECT * FROM "user" AS "a" WHERE "a"."name" = :0', ('foo',))
|
|
@@ -39,14 +39,14 @@ Select with where condition::
|
|
|
39
39
|
|
|
40
40
|
>>> select.where = user.name == 'foo'
|
|
41
41
|
>>> tuple(select)
|
|
42
|
-
('SELECT "a"."id", "a"."name" FROM "user" AS "a" WHERE
|
|
42
|
+
('SELECT "a"."id", "a"."name" FROM "user" AS "a" WHERE "a"."name" = %s', ('foo',))
|
|
43
43
|
|
|
44
44
|
>>> select.where = (user.name == 'foo') & (user.active == True)
|
|
45
45
|
>>> tuple(select)
|
|
46
|
-
('SELECT "a"."id", "a"."name" FROM "user" AS "a" WHERE (
|
|
46
|
+
('SELECT "a"."id", "a"."name" FROM "user" AS "a" WHERE ("a"."name" = %s) AND ("a"."active" = %s)', ('foo', True))
|
|
47
47
|
>>> select.where = user.name == user.login
|
|
48
48
|
>>> tuple(select)
|
|
49
|
-
('SELECT "a"."id", "a"."name" FROM "user" AS "a" WHERE
|
|
49
|
+
('SELECT "a"."id", "a"."name" FROM "user" AS "a" WHERE "a"."name" = "a"."login"', ())
|
|
50
50
|
|
|
51
51
|
Select with join::
|
|
52
52
|
|
|
@@ -54,7 +54,7 @@ Select with join::
|
|
|
54
54
|
>>> join.condition = join.right.user == user.id
|
|
55
55
|
>>> select = join.select(user.name, join.right.group)
|
|
56
56
|
>>> tuple(select)
|
|
57
|
-
('SELECT "a"."name", "b"."group" FROM "user" AS "a" INNER JOIN "user_group" AS "b" ON
|
|
57
|
+
('SELECT "a"."name", "b"."group" FROM "user" AS "a" INNER JOIN "user_group" AS "b" ON "b"."user" = "a"."id"', ())
|
|
58
58
|
|
|
59
59
|
Select with multiple joins::
|
|
60
60
|
|
|
@@ -93,9 +93,9 @@ Select with sub-select::
|
|
|
93
93
|
... where=user_group.active == True)
|
|
94
94
|
>>> user = Table('user')
|
|
95
95
|
>>> tuple(user.select(user.id, where=user.id.in_(subselect)))
|
|
96
|
-
('SELECT "a"."id" FROM "user" AS "a" WHERE
|
|
96
|
+
('SELECT "a"."id" FROM "user" AS "a" WHERE "a"."id" IN (SELECT "b"."user" FROM "user_group" AS "b" WHERE "b"."active" = %s)', (True,))
|
|
97
97
|
>>> tuple(subselect.select(subselect.user))
|
|
98
|
-
('SELECT "a"."user" FROM (SELECT "b"."user" FROM "user_group" AS "b" WHERE
|
|
98
|
+
('SELECT "a"."user" FROM (SELECT "b"."user" FROM "user_group" AS "b" WHERE "b"."active" = %s) AS "a"', (True,))
|
|
99
99
|
|
|
100
100
|
Select on other schema::
|
|
101
101
|
|
|
@@ -129,20 +129,20 @@ Update query with values::
|
|
|
129
129
|
>>> tuple(user.update(columns=[user.active], values=[True]))
|
|
130
130
|
('UPDATE "user" AS "a" SET "active" = %s', (True,))
|
|
131
131
|
>>> tuple(invoice.update(columns=[invoice.total], values=[invoice.amount + invoice.tax]))
|
|
132
|
-
('UPDATE "invoice" AS "a" SET "total" =
|
|
132
|
+
('UPDATE "invoice" AS "a" SET "total" = "a"."amount" + "a"."tax"', ())
|
|
133
133
|
|
|
134
134
|
Update query with where condition::
|
|
135
135
|
|
|
136
136
|
>>> tuple(user.update(columns=[user.active], values=[True],
|
|
137
137
|
... where=user.active == False))
|
|
138
|
-
('UPDATE "user" AS "a" SET "active" = %s WHERE
|
|
138
|
+
('UPDATE "user" AS "a" SET "active" = %s WHERE "a"."active" = %s', (True, False))
|
|
139
139
|
|
|
140
140
|
Update query with from list::
|
|
141
141
|
|
|
142
142
|
>>> group = Table('user_group')
|
|
143
143
|
>>> tuple(user.update(columns=[user.active], values=[group.active],
|
|
144
144
|
... from_=[group], where=user.id == group.user))
|
|
145
|
-
('UPDATE "user" AS "b" SET "active" = "a"."active" FROM "user_group" AS "a" WHERE
|
|
145
|
+
('UPDATE "user" AS "b" SET "active" = "a"."active" FROM "user_group" AS "a" WHERE "b"."id" = "a"."user"', ())
|
|
146
146
|
|
|
147
147
|
Delete query::
|
|
148
148
|
|
|
@@ -152,13 +152,13 @@ Delete query::
|
|
|
152
152
|
Delete query with where condition::
|
|
153
153
|
|
|
154
154
|
>>> tuple(user.delete(where=user.name == 'foo'))
|
|
155
|
-
('DELETE FROM "user" WHERE
|
|
155
|
+
('DELETE FROM "user" WHERE "name" = %s', ('foo',))
|
|
156
156
|
|
|
157
157
|
Delete query with sub-query::
|
|
158
158
|
|
|
159
159
|
>>> tuple(user.delete(
|
|
160
160
|
... where=user.id.in_(user_group.select(user_group.user))))
|
|
161
|
-
('DELETE FROM "user" WHERE
|
|
161
|
+
('DELETE FROM "user" WHERE "id" IN (SELECT "a"."user" FROM "user_group" AS "a")', ())
|
|
162
162
|
|
|
163
163
|
Flavors::
|
|
164
164
|
|
|
@@ -185,7 +185,7 @@ Limit style::
|
|
|
185
185
|
('SELECT * FROM "user" AS "a" OFFSET (%s) ROWS FETCH FIRST (%s) ROWS ONLY', (20, 10))
|
|
186
186
|
>>> Flavor.set(Flavor(limitstyle='rownum'))
|
|
187
187
|
>>> tuple(select)
|
|
188
|
-
('SELECT "a".* FROM (SELECT "b".*, ROWNUM AS "rnum" FROM (SELECT * FROM "user" AS "c") AS "b" WHERE
|
|
188
|
+
('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))
|
|
189
189
|
|
|
190
190
|
qmark style::
|
|
191
191
|
|
|
@@ -193,7 +193,7 @@ qmark style::
|
|
|
193
193
|
>>> select = user.select()
|
|
194
194
|
>>> select.where = user.name == 'foo'
|
|
195
195
|
>>> tuple(select)
|
|
196
|
-
('SELECT * FROM "user" AS "a" WHERE
|
|
196
|
+
('SELECT * FROM "user" AS "a" WHERE "a"."name" = ?', ('foo',))
|
|
197
197
|
|
|
198
198
|
numeric style::
|
|
199
199
|
|
|
@@ -201,4 +201,4 @@ numeric style::
|
|
|
201
201
|
>>> select = user.select()
|
|
202
202
|
>>> select.where = user.name == 'foo'
|
|
203
203
|
>>> format2numeric(*select)
|
|
204
|
-
('SELECT * FROM "user" AS "a" WHERE
|
|
204
|
+
('SELECT * FROM "user" AS "a" WHERE "a"."name" = :0', ('foo',))
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ['hatchling >= 1', 'hatch-tryton']
|
|
3
|
+
build-backend = 'hatchling.build'
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = 'python-sql'
|
|
7
|
+
dynamic = ['version', 'authors']
|
|
8
|
+
requires-python = '>=3.9'
|
|
9
|
+
maintainers = [
|
|
10
|
+
{name = "Tryton", email = "foundation@tryton.org"},
|
|
11
|
+
]
|
|
12
|
+
description = "Library to write SQL queries"
|
|
13
|
+
readme = 'README.rst'
|
|
14
|
+
license = 'BSD-3-Clause'
|
|
15
|
+
license-files = ['COPYRIGHT']
|
|
16
|
+
keywords = ["SQL", "database", "query"]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Development Status :: 5 - Production/Stable",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"Topic :: Database",
|
|
21
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
homepage = "https://www.tryton.org/"
|
|
26
|
+
changelog = "https://code.tryton.org/python-sql/-/blob/branch/default/CHANGELOG"
|
|
27
|
+
forum = "https://discuss.tryton.org/tags/python-sql"
|
|
28
|
+
issues = "https://bugs.tryton.org/python-sql"
|
|
29
|
+
repository = "https://code.tryton.org/python-sql"
|
|
30
|
+
|
|
31
|
+
[tool.hatch.version]
|
|
32
|
+
path = 'sql/__init__.py'
|
|
33
|
+
|
|
34
|
+
[tool.hatch.build]
|
|
35
|
+
packages = ['sql']
|
|
36
|
+
|
|
37
|
+
[tool.hatch.metadata.hooks.tryton]
|
|
38
|
+
copyright = 'COPYRIGHT'
|
|
@@ -7,7 +7,7 @@ from collections import defaultdict
|
|
|
7
7
|
from itertools import chain
|
|
8
8
|
from threading import current_thread, local
|
|
9
9
|
|
|
10
|
-
__version__ = '1.
|
|
10
|
+
__version__ = '1.8.0'
|
|
11
11
|
__all__ = [
|
|
12
12
|
'Flavor', 'Table', 'Values', 'Literal', 'Column', 'Grouping', 'Conflict',
|
|
13
13
|
'Matched', 'MatchedUpdate', 'MatchedDelete',
|
|
@@ -629,6 +629,27 @@ class Select(FromItem, SelectQuery):
|
|
|
629
629
|
and (self.limit is not None or self.offset is not None)):
|
|
630
630
|
return self._rownum(str)
|
|
631
631
|
|
|
632
|
+
ordinals = {}
|
|
633
|
+
for expression in chain(
|
|
634
|
+
self.group_by or [],
|
|
635
|
+
self.order_by or []):
|
|
636
|
+
if not isinstance(expression, As):
|
|
637
|
+
continue
|
|
638
|
+
for i, column in enumerate(self.columns, start=1):
|
|
639
|
+
if not isinstance(column, As):
|
|
640
|
+
continue
|
|
641
|
+
if column.output_name != expression.output_name:
|
|
642
|
+
continue
|
|
643
|
+
if (str(column.expression) != str(expression.expression)
|
|
644
|
+
or column.params != expression.params):
|
|
645
|
+
raise ValueError("%r != %r" % (expression, column))
|
|
646
|
+
ordinals[column.output_name] = i
|
|
647
|
+
|
|
648
|
+
def str_or_ordinal(expression):
|
|
649
|
+
if isinstance(expression, As):
|
|
650
|
+
expression = ordinals.get(expression.output_name, expression)
|
|
651
|
+
return str(expression)
|
|
652
|
+
|
|
632
653
|
with AliasManager():
|
|
633
654
|
if self.from_ is not None:
|
|
634
655
|
from_ = ' FROM %s' % self.from_
|
|
@@ -657,7 +678,8 @@ class Select(FromItem, SelectQuery):
|
|
|
657
678
|
where = ' WHERE ' + str(self.where)
|
|
658
679
|
group_by = ''
|
|
659
680
|
if self.group_by:
|
|
660
|
-
group_by = ' GROUP BY ' + ', '.join(
|
|
681
|
+
group_by = ' GROUP BY ' + ', '.join(
|
|
682
|
+
map(str_or_ordinal, self.group_by))
|
|
661
683
|
having = ''
|
|
662
684
|
if self.having:
|
|
663
685
|
having = ' HAVING ' + str(self.having)
|
|
@@ -4,7 +4,6 @@ from sql import Expression, Flavor, Literal, Window
|
|
|
4
4
|
|
|
5
5
|
__all__ = ['Avg', 'BitAnd', 'BitOr', 'BoolAnd', 'BoolOr', 'Count', 'Every',
|
|
6
6
|
'Max', 'Min', 'Stddev', 'Sum', 'Variance']
|
|
7
|
-
_sentinel = object()
|
|
8
7
|
|
|
9
8
|
|
|
10
9
|
class Aggregate(Expression):
|
|
@@ -169,20 +168,31 @@ class BoolOr(Aggregate):
|
|
|
169
168
|
_sql = 'BOOL_OR'
|
|
170
169
|
|
|
171
170
|
|
|
171
|
+
class _Star(Expression):
|
|
172
|
+
__slots__ = ()
|
|
173
|
+
|
|
174
|
+
def __str__(self):
|
|
175
|
+
return '*'
|
|
176
|
+
|
|
177
|
+
@property
|
|
178
|
+
def params(self):
|
|
179
|
+
return ()
|
|
180
|
+
|
|
181
|
+
|
|
172
182
|
class Count(Aggregate):
|
|
173
183
|
__slots__ = ()
|
|
174
184
|
_sql = 'COUNT'
|
|
175
185
|
|
|
176
|
-
def __init__(self, expression=
|
|
177
|
-
if expression is _sentinel:
|
|
178
|
-
expression = Literal('*')
|
|
186
|
+
def __init__(self, expression=_Star(), **kwargs):
|
|
179
187
|
super().__init__(expression, **kwargs)
|
|
180
188
|
|
|
181
189
|
@property
|
|
182
190
|
def _case_expression(self):
|
|
183
191
|
expression = super(Count, self)._case_expression
|
|
184
|
-
if (isinstance(self.expression,
|
|
185
|
-
|
|
192
|
+
if (isinstance(self.expression, _Star)
|
|
193
|
+
# Keep testing Literal('*') for backward compatibility
|
|
194
|
+
or (isinstance(self.expression, Literal)
|
|
195
|
+
and expression.value == '*')):
|
|
186
196
|
expression = Literal(1)
|
|
187
197
|
return expression
|
|
188
198
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# This file is part of python-sql. The COPYRIGHT file at the top level of
|
|
2
2
|
# this repository contains the full copyright notices and license terms.
|
|
3
|
+
|
|
4
|
+
from enum import Enum, auto
|
|
3
5
|
from itertools import chain
|
|
4
6
|
|
|
5
7
|
from sql import CombiningQuery, Expression, Flavor, FromItem, Select, Window
|
|
@@ -85,7 +87,7 @@ class FunctionKeyword(Function):
|
|
|
85
87
|
return (self._function + '('
|
|
86
88
|
+ ' '.join(chain(*zip(
|
|
87
89
|
self._keywords,
|
|
88
|
-
map(self._format, self.args))))
|
|
90
|
+
map(self._format, self.args)))).strip()
|
|
89
91
|
+ ')')
|
|
90
92
|
|
|
91
93
|
|
|
@@ -383,9 +385,67 @@ class DateTrunc(Function):
|
|
|
383
385
|
|
|
384
386
|
|
|
385
387
|
class Extract(FunctionKeyword):
|
|
386
|
-
__slots__ = ()
|
|
388
|
+
__slots__ = ('_field',)
|
|
387
389
|
_function = 'EXTRACT'
|
|
388
|
-
|
|
390
|
+
|
|
391
|
+
class Fields(str, Enum):
|
|
392
|
+
def _generate_next_value_(name, start, count, last_values):
|
|
393
|
+
return name.upper()
|
|
394
|
+
|
|
395
|
+
CENTURY = auto()
|
|
396
|
+
DAY = auto()
|
|
397
|
+
DECADE = auto()
|
|
398
|
+
DOW = auto()
|
|
399
|
+
DOY = auto()
|
|
400
|
+
EPOCH = auto()
|
|
401
|
+
HOUR = auto()
|
|
402
|
+
ISODOW = auto()
|
|
403
|
+
ISOYEAR = auto()
|
|
404
|
+
JULIAN = auto()
|
|
405
|
+
MICROSECONDS = auto()
|
|
406
|
+
MILLENNIUM = auto()
|
|
407
|
+
MILLISECONDS = auto()
|
|
408
|
+
MINUTE = auto()
|
|
409
|
+
MONTH = auto()
|
|
410
|
+
QUARTER = auto()
|
|
411
|
+
SECOND = auto()
|
|
412
|
+
TIMEZONE = auto()
|
|
413
|
+
TIMEZONE_HOUR = auto()
|
|
414
|
+
TIMEZONE_MINUTE = auto()
|
|
415
|
+
WEEK = auto()
|
|
416
|
+
YEAR = auto()
|
|
417
|
+
|
|
418
|
+
def __init__(self, field, *args, **kwargs):
|
|
419
|
+
super().__init__(*args, **kwargs)
|
|
420
|
+
self.field = field
|
|
421
|
+
|
|
422
|
+
@property
|
|
423
|
+
def field(self):
|
|
424
|
+
return self._field
|
|
425
|
+
|
|
426
|
+
@field.setter
|
|
427
|
+
def field(self, value):
|
|
428
|
+
value = value.upper()
|
|
429
|
+
if not hasattr(self.Fields, value):
|
|
430
|
+
raise ValueError("invalid field: %r" % value)
|
|
431
|
+
self._field = value
|
|
432
|
+
|
|
433
|
+
@property
|
|
434
|
+
def _keywords(self):
|
|
435
|
+
return ('%s FROM' % self.field,)
|
|
436
|
+
|
|
437
|
+
def __str__(self):
|
|
438
|
+
Mapping = Flavor.get().function_mapping.get(self.__class__)
|
|
439
|
+
if Mapping:
|
|
440
|
+
return str(Mapping(self.field, *self.args))
|
|
441
|
+
return super().__str__()
|
|
442
|
+
|
|
443
|
+
@property
|
|
444
|
+
def params(self):
|
|
445
|
+
Mapping = Flavor.get().function_mapping.get(self.__class__)
|
|
446
|
+
if Mapping:
|
|
447
|
+
return Mapping(self.field, *self.args).params
|
|
448
|
+
return super().params
|
|
389
449
|
|
|
390
450
|
|
|
391
451
|
class Isfinite(Function):
|
|
@@ -48,9 +48,11 @@ class Operator(Expression):
|
|
|
48
48
|
def _format(self, operand, param=None):
|
|
49
49
|
if param is None:
|
|
50
50
|
param = Flavor.get().param
|
|
51
|
-
if isinstance(operand, Expression)
|
|
51
|
+
if (isinstance(operand, Expression)
|
|
52
|
+
and (not isinstance(operand, Operator)
|
|
53
|
+
or isinstance(operand, UnaryOperator))):
|
|
52
54
|
return str(operand)
|
|
53
|
-
elif isinstance(operand, (Select, CombiningQuery)):
|
|
55
|
+
elif isinstance(operand, (Expression, Select, CombiningQuery)):
|
|
54
56
|
return '(%s)' % operand
|
|
55
57
|
elif isinstance(operand, (list, tuple)):
|
|
56
58
|
return '(' + ', '.join(self._format(o, param)
|
|
@@ -88,7 +90,7 @@ class UnaryOperator(Operator):
|
|
|
88
90
|
return (self.operand,)
|
|
89
91
|
|
|
90
92
|
def __str__(self):
|
|
91
|
-
return '
|
|
93
|
+
return '%s %s' % (self._operator, self._format(self.operand))
|
|
92
94
|
|
|
93
95
|
|
|
94
96
|
class BinaryOperator(Operator):
|
|
@@ -105,7 +107,7 @@ class BinaryOperator(Operator):
|
|
|
105
107
|
|
|
106
108
|
def __str__(self):
|
|
107
109
|
left, right = self._operands
|
|
108
|
-
return '
|
|
110
|
+
return '%s %s %s' % (self._format(left), self._operator,
|
|
109
111
|
self._format(right))
|
|
110
112
|
|
|
111
113
|
def __invert__(self):
|
|
@@ -121,8 +123,7 @@ class NaryOperator(list, Operator):
|
|
|
121
123
|
return self
|
|
122
124
|
|
|
123
125
|
def __str__(self):
|
|
124
|
-
return
|
|
125
|
-
map(self._format, self)) + ')'
|
|
126
|
+
return (' %s ' % self._operator).join(map(self._format, self))
|
|
126
127
|
|
|
127
128
|
|
|
128
129
|
class And(NaryOperator):
|
|
@@ -184,9 +185,9 @@ class Equal(BinaryOperator):
|
|
|
184
185
|
|
|
185
186
|
def __str__(self):
|
|
186
187
|
if self.left is Null:
|
|
187
|
-
return '
|
|
188
|
+
return '%s IS NULL' % self.right
|
|
188
189
|
elif self.right is Null:
|
|
189
|
-
return '
|
|
190
|
+
return '%s IS NULL' % self.left
|
|
190
191
|
return super(Equal, self).__str__()
|
|
191
192
|
|
|
192
193
|
|
|
@@ -196,9 +197,9 @@ class NotEqual(Equal):
|
|
|
196
197
|
|
|
197
198
|
def __str__(self):
|
|
198
199
|
if self.left is Null:
|
|
199
|
-
return '
|
|
200
|
+
return '%s IS NOT NULL' % self.right
|
|
200
201
|
elif self.right is Null:
|
|
201
|
-
return '
|
|
202
|
+
return '%s IS NOT NULL' % self.left
|
|
202
203
|
return super(Equal, self).__str__()
|
|
203
204
|
|
|
204
205
|
|
|
@@ -220,7 +221,7 @@ class Between(Operator):
|
|
|
220
221
|
operator = self._operator
|
|
221
222
|
if self.symmetric:
|
|
222
223
|
operator += ' SYMMETRIC'
|
|
223
|
-
return '
|
|
224
|
+
return '%s %s %s AND %s' % (
|
|
224
225
|
self._format(self.operand), operator,
|
|
225
226
|
self._format(self.left), self._format(self.right))
|
|
226
227
|
|
|
@@ -259,12 +260,12 @@ class Is(BinaryOperator):
|
|
|
259
260
|
|
|
260
261
|
def __str__(self):
|
|
261
262
|
if self.right is None:
|
|
262
|
-
return '
|
|
263
|
+
return '%s %s UNKNOWN' % (
|
|
263
264
|
self._format(self.left), self._operator)
|
|
264
265
|
elif self.right is True:
|
|
265
|
-
return '
|
|
266
|
+
return '%s %s TRUE' % (self._format(self.left), self._operator)
|
|
266
267
|
elif self.right is False:
|
|
267
|
-
return '
|
|
268
|
+
return '%s %s FALSE' % (self._format(self.left), self._operator)
|
|
268
269
|
|
|
269
270
|
|
|
270
271
|
class IsNot(Is):
|
|
@@ -395,11 +396,11 @@ class Like(BinaryOperator):
|
|
|
395
396
|
def __str__(self):
|
|
396
397
|
left, right = self._operands
|
|
397
398
|
if self.escape or Flavor().get().escape_empty:
|
|
398
|
-
return '
|
|
399
|
+
return '%s %s %s ESCAPE %s' % (
|
|
399
400
|
self._format(left), self._operator, self._format(right),
|
|
400
401
|
self._format(self.escape or ''))
|
|
401
402
|
else:
|
|
402
|
-
return '
|
|
403
|
+
return '%s %s %s' % (
|
|
403
404
|
self._format(left), self._operator, self._format(right))
|
|
404
405
|
|
|
405
406
|
def __invert__(self):
|
|
@@ -458,7 +459,24 @@ class Exists(UnaryOperator):
|
|
|
458
459
|
_operator = 'EXISTS'
|
|
459
460
|
|
|
460
461
|
|
|
461
|
-
class
|
|
462
|
+
class _ArrayOperator(UnaryOperator):
|
|
463
|
+
__slots__ = ()
|
|
464
|
+
|
|
465
|
+
@property
|
|
466
|
+
def params(self):
|
|
467
|
+
if isinstance(self.operand, (list, tuple, array)):
|
|
468
|
+
return (list(self.operand),)
|
|
469
|
+
return super().params
|
|
470
|
+
|
|
471
|
+
def _format(self, operand, param=None):
|
|
472
|
+
if param is None:
|
|
473
|
+
param = Flavor.get().param
|
|
474
|
+
if isinstance(operand, (list, tuple, array)):
|
|
475
|
+
return '(%s)' % param
|
|
476
|
+
return super()._format(operand, param=param)
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
class Any(_ArrayOperator):
|
|
462
480
|
__slots__ = ()
|
|
463
481
|
_operator = 'ANY'
|
|
464
482
|
|
|
@@ -466,7 +484,7 @@ class Any(UnaryOperator):
|
|
|
466
484
|
Some = Any
|
|
467
485
|
|
|
468
486
|
|
|
469
|
-
class All(
|
|
487
|
+
class All(_ArrayOperator):
|
|
470
488
|
__slots__ = ()
|
|
471
489
|
_operator = 'ALL'
|
|
472
490
|
|