sql-blocks 1.25.13__py3-none-any.whl → 1.25.111__py3-none-any.whl
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.
- sql_blocks/sql_blocks.py +124 -30
- {sql_blocks-1.25.13.dist-info → sql_blocks-1.25.111.dist-info}/METADATA +54 -2
- sql_blocks-1.25.111.dist-info/RECORD +7 -0
- sql_blocks-1.25.13.dist-info/RECORD +0 -7
- {sql_blocks-1.25.13.dist-info → sql_blocks-1.25.111.dist-info}/LICENSE +0 -0
- {sql_blocks-1.25.13.dist-info → sql_blocks-1.25.111.dist-info}/WHEEL +0 -0
- {sql_blocks-1.25.13.dist-info → sql_blocks-1.25.111.dist-info}/top_level.txt +0 -0
sql_blocks/sql_blocks.py
CHANGED
@@ -26,7 +26,7 @@ TO_LIST = lambda x: x if isinstance(x, list) else [x]
|
|
26
26
|
|
27
27
|
|
28
28
|
class SQLObject:
|
29
|
-
ALIAS_FUNC =
|
29
|
+
ALIAS_FUNC = None
|
30
30
|
""" ^^^^^^^^^^^^^^^^^^^^^^^^
|
31
31
|
You can change the behavior by assigning
|
32
32
|
a user function to SQLObject.ALIAS_FUNC
|
@@ -41,7 +41,10 @@ class SQLObject:
|
|
41
41
|
def set_table(self, table_name: str):
|
42
42
|
if not table_name:
|
43
43
|
return
|
44
|
-
|
44
|
+
cls = SQLObject
|
45
|
+
if cls.ALIAS_FUNC:
|
46
|
+
self.__alias = cls.ALIAS_FUNC(table_name)
|
47
|
+
elif ' ' in table_name.strip():
|
45
48
|
table_name, self.__alias = table_name.split()
|
46
49
|
elif '_' in table_name:
|
47
50
|
self.__alias = ''.join(
|
@@ -49,7 +52,7 @@ class SQLObject:
|
|
49
52
|
for word in table_name.split('_')
|
50
53
|
)
|
51
54
|
else:
|
52
|
-
self.__alias =
|
55
|
+
self.__alias = table_name.lower()[:3]
|
53
56
|
self.values.setdefault(FROM, []).append(f'{table_name} {self.alias}')
|
54
57
|
|
55
58
|
@property
|
@@ -111,15 +114,26 @@ class SQLObject:
|
|
111
114
|
self.values[key] = result
|
112
115
|
|
113
116
|
|
117
|
+
SQL_CONST_SYSDATE = 'SYSDATE'
|
118
|
+
SQL_CONST_CURR_DATE = 'Current_date'
|
119
|
+
SQL_CONSTS = [SQL_CONST_SYSDATE, SQL_CONST_CURR_DATE]
|
120
|
+
|
121
|
+
|
114
122
|
class Field:
|
115
123
|
prefix = ''
|
116
124
|
|
117
125
|
@classmethod
|
118
126
|
def format(cls, name: str, main: SQLObject) -> str:
|
127
|
+
def is_const() -> bool:
|
128
|
+
return any([
|
129
|
+
re.findall('[.()0-9]', name),
|
130
|
+
name in SQL_CONSTS,
|
131
|
+
re.findall(r'\w+\s*[+-]\s*\w+', name)
|
132
|
+
])
|
119
133
|
name = name.strip()
|
120
134
|
if name in ('_', '*'):
|
121
135
|
name = '*'
|
122
|
-
elif not
|
136
|
+
elif not is_const():
|
123
137
|
name = f'{main.alias}.{name}'
|
124
138
|
if Function in cls.__bases__:
|
125
139
|
name = f'{cls.__name__}({name})'
|
@@ -150,7 +164,16 @@ class NamedField:
|
|
150
164
|
)
|
151
165
|
|
152
166
|
|
167
|
+
class Dialect(Enum):
|
168
|
+
ANSI = 0
|
169
|
+
SQL_SERVER = 1
|
170
|
+
ORACLE = 2
|
171
|
+
POSTGRESQL = 3
|
172
|
+
MYSQL = 4
|
173
|
+
|
153
174
|
class Function:
|
175
|
+
dialect = Dialect.ANSI
|
176
|
+
|
154
177
|
def __init__(self, *params: list):
|
155
178
|
# --- Replace class methods by instance methods: ------
|
156
179
|
self.add = self.__add
|
@@ -158,26 +181,30 @@ class Function:
|
|
158
181
|
# -----------------------------------------------------
|
159
182
|
self.params = [str(p) for p in params]
|
160
183
|
self.field_class = Field
|
161
|
-
self.pattern =
|
184
|
+
self.pattern = self.get_pattern()
|
162
185
|
self.extra = {}
|
163
186
|
|
187
|
+
def get_pattern(self) -> str:
|
188
|
+
return '{func_name}({params})'
|
189
|
+
|
164
190
|
def As(self, field_alias: str, modifiers=None):
|
165
191
|
if modifiers:
|
166
192
|
self.extra[field_alias] = TO_LIST(modifiers)
|
167
193
|
self.field_class = NamedField(field_alias)
|
168
194
|
return self
|
169
195
|
|
196
|
+
def __str__(self) -> str:
|
197
|
+
return self.pattern.format(
|
198
|
+
func_name=self.__class__.__name__,
|
199
|
+
params=', '.join(self.params)
|
200
|
+
)
|
201
|
+
|
170
202
|
def __format(self, name: str, main: SQLObject) -> str:
|
171
|
-
if name in '*_'
|
172
|
-
params =
|
173
|
-
else:
|
174
|
-
params = [
|
203
|
+
if name not in '*_':
|
204
|
+
self.params = [
|
175
205
|
Field.format(name, main)
|
176
206
|
] + self.params
|
177
|
-
return self
|
178
|
-
self.__class__.__name__,
|
179
|
-
', '.join(params)
|
180
|
-
)
|
207
|
+
return str(self)
|
181
208
|
|
182
209
|
@classmethod
|
183
210
|
def format(cls, name: str, main: SQLObject):
|
@@ -196,7 +223,10 @@ class Function:
|
|
196
223
|
|
197
224
|
# ---- String Functions: ---------------------------------
|
198
225
|
class SubString(Function):
|
199
|
-
|
226
|
+
def get_pattern(self) -> str:
|
227
|
+
if self.dialect in (Dialect.ORACLE, Dialect.MYSQL):
|
228
|
+
return 'Substr({params})'
|
229
|
+
return super().get_pattern()
|
200
230
|
|
201
231
|
# ---- Numeric Functions: --------------------------------
|
202
232
|
class Round(Function):
|
@@ -204,13 +234,37 @@ class Round(Function):
|
|
204
234
|
|
205
235
|
# --- Date Functions: ------------------------------------
|
206
236
|
class DateDiff(Function):
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
237
|
+
def get_pattern(self) -> str:
|
238
|
+
def is_field_or_func(name: str) -> bool:
|
239
|
+
return re.sub('[()]', '', name).isidentifier()
|
240
|
+
if self.dialect != Dialect.SQL_SERVER:
|
241
|
+
return ' - '.join(
|
242
|
+
p if is_field_or_func(p) else f"'{p}'"
|
243
|
+
for p in self.params
|
244
|
+
) # <==== Date subtract
|
245
|
+
return super().get_pattern()
|
246
|
+
|
247
|
+
class Year(Function):
|
248
|
+
def get_pattern(self) -> str:
|
249
|
+
database_type = {
|
250
|
+
Dialect.ORACLE: 'Extract(YEAR FROM {params})',
|
251
|
+
Dialect.POSTGRESQL: "Date_Part('year', {params})",
|
252
|
+
}
|
253
|
+
if self.dialect in database_type:
|
254
|
+
return database_type[self.dialect]
|
255
|
+
return super().get_pattern()
|
256
|
+
|
212
257
|
class Current_Date(Function):
|
213
|
-
|
258
|
+
def get_pattern(self) -> str:
|
259
|
+
database_type = {
|
260
|
+
Dialect.ORACLE: SQL_CONST_SYSDATE,
|
261
|
+
Dialect.POSTGRESQL: SQL_CONST_CURR_DATE,
|
262
|
+
Dialect.SQL_SERVER: 'getDate()'
|
263
|
+
}
|
264
|
+
if self.dialect in database_type:
|
265
|
+
return database_type[self.dialect]
|
266
|
+
return super().get_pattern()
|
267
|
+
# --------------------------------------------------------
|
214
268
|
|
215
269
|
class Aggregate:
|
216
270
|
break_lines: bool = True
|
@@ -225,7 +279,7 @@ class Aggregate:
|
|
225
279
|
)
|
226
280
|
if keywords and self.break_lines:
|
227
281
|
keywords += '\n\t'
|
228
|
-
self.pattern =
|
282
|
+
self.pattern = self.get_pattern() + f' OVER({keywords})'
|
229
283
|
return self
|
230
284
|
|
231
285
|
|
@@ -324,6 +378,12 @@ def quoted(value) -> str:
|
|
324
378
|
return str(value)
|
325
379
|
|
326
380
|
|
381
|
+
class Position(Enum):
|
382
|
+
Middle = 0
|
383
|
+
StartsWith = 1
|
384
|
+
EndsWith = 2
|
385
|
+
|
386
|
+
|
327
387
|
class Where:
|
328
388
|
prefix = ''
|
329
389
|
|
@@ -339,8 +399,14 @@ class Where:
|
|
339
399
|
return cls.__constructor('=', value)
|
340
400
|
|
341
401
|
@classmethod
|
342
|
-
def contains(cls,
|
343
|
-
return cls(
|
402
|
+
def contains(cls, content: str, pos: Position = Position.Middle):
|
403
|
+
return cls(
|
404
|
+
"LIKE '{}{}{}'".format(
|
405
|
+
'%' if pos != Position.StartsWith else '',
|
406
|
+
content,
|
407
|
+
'%' if pos != Position.EndsWith else ''
|
408
|
+
)
|
409
|
+
)
|
344
410
|
|
345
411
|
@classmethod
|
346
412
|
def gt(cls, value):
|
@@ -420,9 +486,8 @@ class Options:
|
|
420
486
|
self.__children: dict = values
|
421
487
|
|
422
488
|
def add(self, logical_separator: str, main: SQLObject):
|
423
|
-
|
424
|
-
|
425
|
-
"""
|
489
|
+
if logical_separator not in ('AND', 'OR'):
|
490
|
+
raise ValueError('`logical_separator` must be AND or OR')
|
426
491
|
conditions: list[str] = []
|
427
492
|
child: Where
|
428
493
|
for field, child in self.__children.items():
|
@@ -1320,6 +1385,31 @@ class RuleDateFuncReplace(Rule):
|
|
1320
1385
|
target.values[WHERE][i] = ' AND '.join(temp.values[WHERE])
|
1321
1386
|
|
1322
1387
|
|
1388
|
+
class RuleReplaceJoinBySubselect(Rule):
|
1389
|
+
@classmethod
|
1390
|
+
def apply(cls, target: Select):
|
1391
|
+
main, *others = Select.parse( str(target) )
|
1392
|
+
modified = False
|
1393
|
+
for query in others:
|
1394
|
+
fk_field, primary_k = ForeignKey.find(main, query)
|
1395
|
+
more_relations = any([
|
1396
|
+
ref[0] == query.table_name for ref in ForeignKey.references
|
1397
|
+
])
|
1398
|
+
invalid = any([
|
1399
|
+
len( query.values.get(SELECT, []) ) > 0,
|
1400
|
+
len( query.values.get(WHERE, []) ) == 0,
|
1401
|
+
not fk_field, more_relations
|
1402
|
+
])
|
1403
|
+
if invalid:
|
1404
|
+
continue
|
1405
|
+
query.__class__ = SubSelect
|
1406
|
+
Field.add(primary_k, query)
|
1407
|
+
query.add(fk_field, main)
|
1408
|
+
modified = True
|
1409
|
+
if modified:
|
1410
|
+
target.values = main.values.copy()
|
1411
|
+
|
1412
|
+
|
1323
1413
|
def parser_class(text: str) -> Parser:
|
1324
1414
|
PARSER_REGEX = [
|
1325
1415
|
(r'select.*from', SQLParser),
|
@@ -1355,11 +1445,15 @@ def detect(text: str) -> Select:
|
|
1355
1445
|
result += query
|
1356
1446
|
return result
|
1357
1447
|
|
1448
|
+
|
1358
1449
|
if __name__ == "__main__":
|
1359
|
-
OrderBy.sort = SortType.DESC
|
1360
1450
|
query = Select(
|
1361
|
-
'
|
1362
|
-
|
1363
|
-
|
1451
|
+
'Installments i', due_date=Field, customer=Select(
|
1452
|
+
'Customer c', id=PrimaryKey,
|
1453
|
+
name=contains('Smith', Position.EndsWith)
|
1454
|
+
)
|
1364
1455
|
)
|
1365
1456
|
print(query)
|
1457
|
+
print('-----')
|
1458
|
+
query.optimize([RuleReplaceJoinBySubselect])
|
1459
|
+
print(query)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: sql_blocks
|
3
|
-
Version: 1.25.
|
3
|
+
Version: 1.25.111
|
4
4
|
Summary: Allows you to create objects for parts of SQL query commands. Also to combine these objects by joining them, adding or removing parts...
|
5
5
|
Home-page: https://github.com/julio-cascalles/sql_blocks
|
6
6
|
Author: Júlio Cascalles
|
@@ -97,8 +97,13 @@ query = Select('Movie m', title=Field,
|
|
97
97
|
genre=eq("Sci-Fi"),
|
98
98
|
awards=contains("Oscar")
|
99
99
|
)
|
100
|
+
AND=Options(
|
101
|
+
..., name=contains(
|
102
|
+
'Chris',
|
103
|
+
Position.StartsWith
|
104
|
+
)
|
105
|
+
)
|
100
106
|
```
|
101
|
-
> Could be AND=Options(...)
|
102
107
|
|
103
108
|
3.4 -- Negative conditions use the _Not_ class instead of _Where_
|
104
109
|
```
|
@@ -363,6 +368,35 @@ m2 = Select(
|
|
363
368
|
|
364
369
|
> The method allows you to select which rules you want to apply in the optimization...Or define your own rules!
|
365
370
|
|
371
|
+
>> NOTE: When a joined table is used only as a filter, it is possible that it can be changed to a sub-query:
|
372
|
+
|
373
|
+
query = Select(
|
374
|
+
'Installments i', due_date=Field, customer=Select(
|
375
|
+
'Customer c', id=PrimaryKey,
|
376
|
+
name=contains('Smith', Position.EndsWith)
|
377
|
+
)
|
378
|
+
)
|
379
|
+
print(query)
|
380
|
+
print('-----')
|
381
|
+
query.optimize([RuleReplaceJoinBySubselect])
|
382
|
+
print(query)
|
383
|
+
```
|
384
|
+
SELECT
|
385
|
+
i.due_date
|
386
|
+
FROM
|
387
|
+
Installments i
|
388
|
+
JOIN Customer c ON (i.customer = c.id)
|
389
|
+
WHERE
|
390
|
+
c.name LIKE '%Smith'
|
391
|
+
-----
|
392
|
+
SELECT
|
393
|
+
i.due_date
|
394
|
+
FROM
|
395
|
+
Installments i
|
396
|
+
WHERE
|
397
|
+
i.customer IN (SELECT c.id FROM Customer c WHERE c.name LIKE '%Smith')
|
398
|
+
```
|
399
|
+
|
366
400
|
---
|
367
401
|
|
368
402
|
### 12 - Adding multiple fields at once
|
@@ -567,3 +601,21 @@ GROUP BY
|
|
567
601
|
ORDER BY
|
568
602
|
customer_count
|
569
603
|
```
|
604
|
+
---
|
605
|
+
### 16 - Function classes
|
606
|
+
You may use this functions:
|
607
|
+
* SubString
|
608
|
+
* Round
|
609
|
+
* DateDiff
|
610
|
+
* Year
|
611
|
+
* Current_Date
|
612
|
+
* Avg
|
613
|
+
* Min
|
614
|
+
* Max
|
615
|
+
* Sum
|
616
|
+
* Count
|
617
|
+
> Some of these functions may vary in syntax depending on the database.
|
618
|
+
For example, if your query is going to run on Oracle, do the following:
|
619
|
+
|
620
|
+
`Function.dialect = Dialect.ORACLE`
|
621
|
+
|
@@ -0,0 +1,7 @@
|
|
1
|
+
sql_blocks/__init__.py,sha256=5ItzGCyqqa6kwY8wvF9kapyHsAiWJ7KEXCcC-OtdXKg,37
|
2
|
+
sql_blocks/sql_blocks.py,sha256=rARGbJxL3Di9mOXRBS4aEFvclO922-dzDOXPy1vSTGg,49018
|
3
|
+
sql_blocks-1.25.111.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
|
4
|
+
sql_blocks-1.25.111.dist-info/METADATA,sha256=Lm7JKPIdZSJT5dkWUc2K7fpY4r0MQ-TeTyv0pbiMy24,14581
|
5
|
+
sql_blocks-1.25.111.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
6
|
+
sql_blocks-1.25.111.dist-info/top_level.txt,sha256=57AbUvUjYNy4m1EqDaU3WHeP-uyIAfV0n8GAUp1a1YQ,11
|
7
|
+
sql_blocks-1.25.111.dist-info/RECORD,,
|
@@ -1,7 +0,0 @@
|
|
1
|
-
sql_blocks/__init__.py,sha256=5ItzGCyqqa6kwY8wvF9kapyHsAiWJ7KEXCcC-OtdXKg,37
|
2
|
-
sql_blocks/sql_blocks.py,sha256=P5yp0Ug4PD56xklmQOtpNLKm9hfhHBNb8TDkOneNAHw,45783
|
3
|
-
sql_blocks-1.25.13.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
|
4
|
-
sql_blocks-1.25.13.dist-info/METADATA,sha256=tahTyaVcsa4ROoxQSMK41W6m20bVlDs6_kh9b9mkgoc,13424
|
5
|
-
sql_blocks-1.25.13.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
6
|
-
sql_blocks-1.25.13.dist-info/top_level.txt,sha256=57AbUvUjYNy4m1EqDaU3WHeP-uyIAfV0n8GAUp1a1YQ,11
|
7
|
-
sql_blocks-1.25.13.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|