sql-blocks 1.25.33022258__py3-none-any.whl → 1.25.38021329__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 +112 -45
- {sql_blocks-1.25.33022258.dist-info → sql_blocks-1.25.38021329.dist-info}/METADATA +42 -1
- sql_blocks-1.25.38021329.dist-info/RECORD +7 -0
- sql_blocks-1.25.33022258.dist-info/RECORD +0 -7
- {sql_blocks-1.25.33022258.dist-info → sql_blocks-1.25.38021329.dist-info}/LICENSE +0 -0
- {sql_blocks-1.25.33022258.dist-info → sql_blocks-1.25.38021329.dist-info}/WHEEL +0 -0
- {sql_blocks-1.25.33022258.dist-info → sql_blocks-1.25.38021329.dist-info}/top_level.txt +0 -0
sql_blocks/sql_blocks.py
CHANGED
@@ -82,8 +82,14 @@ class SQLObject:
|
|
82
82
|
return KEYWORD[key][0].format(appendix.get(key, ''))
|
83
83
|
|
84
84
|
@staticmethod
|
85
|
-
def is_named_field(fld: str,
|
86
|
-
return
|
85
|
+
def is_named_field(fld: str, name: str='') -> bool:
|
86
|
+
return re.search(fr'(\s+as\s+|\s+AS\s+){name}', fld)
|
87
|
+
|
88
|
+
def has_named_field(self, name: str) -> bool:
|
89
|
+
return any(
|
90
|
+
self.is_named_field(fld, name)
|
91
|
+
for fld in self.values.get(SELECT, [])
|
92
|
+
)
|
87
93
|
|
88
94
|
def diff(self, key: str, search_list: list, exact: bool=False) -> set:
|
89
95
|
def disassemble(source: list) -> list:
|
@@ -100,7 +106,9 @@ class SQLObject:
|
|
100
106
|
def field_set(source: list) -> set:
|
101
107
|
return set(
|
102
108
|
(
|
103
|
-
fld
|
109
|
+
fld
|
110
|
+
if key == SELECT and self.is_named_field(fld, key)
|
111
|
+
else
|
104
112
|
re.sub(pattern, '', cleanup(fld))
|
105
113
|
)
|
106
114
|
for string in disassemble(source)
|
@@ -150,7 +158,7 @@ class Field:
|
|
150
158
|
name = name.strip()
|
151
159
|
if name in ('_', '*'):
|
152
160
|
name = '*'
|
153
|
-
elif not is_const():
|
161
|
+
elif not is_const() and not main.has_named_field(name):
|
154
162
|
name = f'{main.alias}.{name}'
|
155
163
|
if Function in cls.__bases__:
|
156
164
|
name = f'{cls.__name__}({name})'
|
@@ -188,15 +196,35 @@ class Dialect(Enum):
|
|
188
196
|
POSTGRESQL = 3
|
189
197
|
MYSQL = 4
|
190
198
|
|
199
|
+
SQL_TYPES = 'CHAR INT DATE FLOAT ANY'.split()
|
200
|
+
CHAR, INT, DATE, FLOAT, ANY = SQL_TYPES
|
201
|
+
|
191
202
|
class Function:
|
192
203
|
dialect = Dialect.ANSI
|
204
|
+
inputs = None
|
205
|
+
output = None
|
206
|
+
separator = ', '
|
207
|
+
auto_convert = True
|
208
|
+
append_param = False
|
193
209
|
|
194
210
|
def __init__(self, *params: list):
|
211
|
+
def set_func_types(param):
|
212
|
+
if self.auto_convert and isinstance(param, Function):
|
213
|
+
func = param
|
214
|
+
main_param = self.inputs[0]
|
215
|
+
unfriendly = all([
|
216
|
+
func.output != main_param,
|
217
|
+
func.output != ANY,
|
218
|
+
main_param != ANY
|
219
|
+
])
|
220
|
+
if unfriendly:
|
221
|
+
return Cast(func, main_param)
|
222
|
+
return param
|
195
223
|
# --- Replace class methods by instance methods: ------
|
196
224
|
self.add = self.__add
|
197
225
|
self.format = self.__format
|
198
226
|
# -----------------------------------------------------
|
199
|
-
self.params = [
|
227
|
+
self.params = [set_func_types(p) for p in params]
|
200
228
|
self.field_class = Field
|
201
229
|
self.pattern = self.get_pattern()
|
202
230
|
self.extra = {}
|
@@ -213,14 +241,26 @@ class Function:
|
|
213
241
|
def __str__(self) -> str:
|
214
242
|
return self.pattern.format(
|
215
243
|
func_name=self.__class__.__name__,
|
216
|
-
params=
|
244
|
+
params=self.separator.join(str(p) for p in self.params)
|
217
245
|
)
|
218
246
|
|
247
|
+
def set_main_param(self, name: str, main: SQLObject) -> bool:
|
248
|
+
nested_functions = [
|
249
|
+
param for param in self.params if isinstance(param, Function)
|
250
|
+
]
|
251
|
+
for func in nested_functions:
|
252
|
+
if func.inputs:
|
253
|
+
func.set_main_param(name, main)
|
254
|
+
return
|
255
|
+
new_params = [Field.format(name, main)]
|
256
|
+
if self.append_param:
|
257
|
+
self.params += new_params
|
258
|
+
else:
|
259
|
+
self.params = new_params + self.params
|
260
|
+
|
219
261
|
def __format(self, name: str, main: SQLObject) -> str:
|
220
262
|
if name not in '*_':
|
221
|
-
self.
|
222
|
-
Field.format(name, main)
|
223
|
-
] + self.params
|
263
|
+
self.set_main_param(name, main)
|
224
264
|
return str(self)
|
225
265
|
|
226
266
|
@classmethod
|
@@ -240,6 +280,9 @@ class Function:
|
|
240
280
|
|
241
281
|
# ---- String Functions: ---------------------------------
|
242
282
|
class SubString(Function):
|
283
|
+
inputs = [CHAR, INT, INT]
|
284
|
+
output = CHAR
|
285
|
+
|
243
286
|
def get_pattern(self) -> str:
|
244
287
|
if self.dialect in (Dialect.ORACLE, Dialect.MYSQL):
|
245
288
|
return 'Substr({params})'
|
@@ -247,31 +290,55 @@ class SubString(Function):
|
|
247
290
|
|
248
291
|
# ---- Numeric Functions: --------------------------------
|
249
292
|
class Round(Function):
|
250
|
-
|
293
|
+
inputs = [FLOAT]
|
294
|
+
output = FLOAT
|
251
295
|
|
252
296
|
# --- Date Functions: ------------------------------------
|
253
297
|
class DateDiff(Function):
|
254
|
-
|
298
|
+
inputs = [DATE]
|
299
|
+
output = DATE
|
300
|
+
append_param = True
|
301
|
+
|
302
|
+
def __str__(self) -> str:
|
255
303
|
def is_field_or_func(name: str) -> bool:
|
256
|
-
|
304
|
+
candidate = re.sub(
|
305
|
+
'[()]', '', name.split('.')[-1]
|
306
|
+
)
|
307
|
+
return candidate.isidentifier()
|
257
308
|
if self.dialect != Dialect.SQL_SERVER:
|
309
|
+
params = [str(p) for p in self.params]
|
258
310
|
return ' - '.join(
|
259
311
|
p if is_field_or_func(p) else f"'{p}'"
|
260
|
-
for p in
|
312
|
+
for p in params
|
261
313
|
) # <==== Date subtract
|
262
|
-
return super().
|
314
|
+
return super().__str__()
|
315
|
+
|
316
|
+
|
317
|
+
class DatePart(Function):
|
318
|
+
inputs = [DATE]
|
319
|
+
output = INT
|
263
320
|
|
264
|
-
class Year(Function):
|
265
321
|
def get_pattern(self) -> str:
|
322
|
+
interval = self.__class__.__name__
|
266
323
|
database_type = {
|
267
|
-
Dialect.ORACLE: 'Extract(
|
268
|
-
Dialect.POSTGRESQL: "Date_Part('
|
324
|
+
Dialect.ORACLE: 'Extract('+interval+' FROM {params})',
|
325
|
+
Dialect.POSTGRESQL: "Date_Part('"+interval+"', {params})",
|
269
326
|
}
|
270
327
|
if self.dialect in database_type:
|
271
328
|
return database_type[self.dialect]
|
272
329
|
return super().get_pattern()
|
273
330
|
|
331
|
+
class Year(DatePart):
|
332
|
+
...
|
333
|
+
class Month(DatePart):
|
334
|
+
...
|
335
|
+
class Day(DatePart):
|
336
|
+
...
|
337
|
+
|
338
|
+
|
274
339
|
class Current_Date(Function):
|
340
|
+
output = DATE
|
341
|
+
|
275
342
|
def get_pattern(self) -> str:
|
276
343
|
database_type = {
|
277
344
|
Dialect.ORACLE: SQL_CONST_SYSDATE,
|
@@ -341,9 +408,13 @@ class Lead(Window, Function):
|
|
341
408
|
|
342
409
|
# ---- Conversions and other Functions: ---------------------
|
343
410
|
class Coalesce(Function):
|
344
|
-
|
411
|
+
inputs = [ANY]
|
412
|
+
output = ANY
|
413
|
+
|
345
414
|
class Cast(Function):
|
346
|
-
|
415
|
+
inputs = [ANY]
|
416
|
+
output = ANY
|
417
|
+
separator = ' As '
|
347
418
|
|
348
419
|
|
349
420
|
FUNCTION_CLASS = {f.__name__.lower(): f for f in Function.__subclasses__()}
|
@@ -516,14 +587,9 @@ class Where:
|
|
516
587
|
|
517
588
|
def add(self, name: str, main: SQLObject):
|
518
589
|
func_type = FUNCTION_CLASS.get(name.lower())
|
519
|
-
exists = any(
|
520
|
-
main.is_named_field(fld, SELECT)
|
521
|
-
for fld in main.values.get(SELECT, [])
|
522
|
-
if name in fld
|
523
|
-
)
|
524
590
|
if func_type:
|
525
591
|
name = func_type.format('*', main)
|
526
|
-
elif not
|
592
|
+
elif not main.has_named_field(name):
|
527
593
|
name = Field.format(name, main)
|
528
594
|
main.values.setdefault(WHERE, []).append('{}{} {}'.format(
|
529
595
|
self.prefix, name, self.content
|
@@ -604,6 +670,14 @@ class Between:
|
|
604
670
|
Where.gte(self.start).add(name, main),
|
605
671
|
Where.lte(self.end).add(name, main)
|
606
672
|
|
673
|
+
class SameDay(Between):
|
674
|
+
def __init__(self, date: str):
|
675
|
+
super().__init__(
|
676
|
+
f'{date} 00:00:00',
|
677
|
+
f'{date} 23:59:59',
|
678
|
+
)
|
679
|
+
|
680
|
+
|
607
681
|
|
608
682
|
class Clause:
|
609
683
|
@classmethod
|
@@ -1438,11 +1512,7 @@ class Select(SQLObject):
|
|
1438
1512
|
Recognizes if the field is from the current table
|
1439
1513
|
'''
|
1440
1514
|
if key in (ORDER_BY, GROUP_BY) and '.' not in field:
|
1441
|
-
return
|
1442
|
-
self.is_named_field(fld, SELECT)
|
1443
|
-
for fld in self.values[SELECT]
|
1444
|
-
if field in fld
|
1445
|
-
)
|
1515
|
+
return main.has_named_field(field)
|
1446
1516
|
return re.findall(f'\b*{self.alias}[.]', field) != []
|
1447
1517
|
|
1448
1518
|
@classmethod
|
@@ -1455,12 +1525,10 @@ class Select(SQLObject):
|
|
1455
1525
|
for rule in rules:
|
1456
1526
|
rule.apply(self)
|
1457
1527
|
|
1458
|
-
def add_fields(self, fields: list,
|
1459
|
-
|
1460
|
-
|
1461
|
-
|
1462
|
-
if group_by:
|
1463
|
-
class_types += [GroupBy]
|
1528
|
+
def add_fields(self, fields: list, class_types=None):
|
1529
|
+
if not class_types:
|
1530
|
+
class_types = []
|
1531
|
+
class_types += [Field]
|
1464
1532
|
FieldList(fields, class_types).add('', self)
|
1465
1533
|
|
1466
1534
|
def translate_to(self, language: QueryLanguage) -> str:
|
@@ -1491,12 +1559,17 @@ class CTE(Select):
|
|
1491
1559
|
self.break_lines = False
|
1492
1560
|
|
1493
1561
|
def __str__(self) -> str:
|
1562
|
+
size = 0
|
1563
|
+
for key in USUAL_KEYS:
|
1564
|
+
size += sum(len(v) for v in self.values.get(key, []) if '\n' not in v)
|
1565
|
+
if size > 70:
|
1566
|
+
self.break_lines = True
|
1494
1567
|
# ---------------------------------------------------------
|
1495
1568
|
def justify(query: Select) -> str:
|
1496
1569
|
result, line = [], ''
|
1497
1570
|
keywords = '|'.join(KEYWORD)
|
1498
1571
|
for word in re.split(fr'({keywords}|AND|OR|,)', str(query)):
|
1499
|
-
if len(line) >=
|
1572
|
+
if len(line) >= 60:
|
1500
1573
|
result.append(line)
|
1501
1574
|
line = ''
|
1502
1575
|
line += word
|
@@ -1510,6 +1583,7 @@ class CTE(Select):
|
|
1510
1583
|
justify(q) for q in self.query_list
|
1511
1584
|
), super().__str__()
|
1512
1585
|
)
|
1586
|
+
|
1513
1587
|
def join(self, pattern: str, fields: list | str, format: str=''):
|
1514
1588
|
if isinstance(fields, str):
|
1515
1589
|
count = len( fields.split(',') )
|
@@ -1702,11 +1776,4 @@ def detect(text: str, join_queries: bool = True, format: str='') -> Select | lis
|
|
1702
1776
|
for query in query_list[1:]:
|
1703
1777
|
result += query
|
1704
1778
|
return result
|
1705
|
-
|
1706
|
-
|
1707
|
-
if __name__ == "__main__":
|
1708
|
-
CAMPO_MEDIA = 'MEDIA_SALARIAL_DEPTO'
|
1709
|
-
employees = detect(
|
1710
|
-
f'Employees@department_id(avg$salary:{CAMPO_MEDIA})'
|
1711
|
-
)
|
1712
|
-
print(employees)
|
1779
|
+
# ===========================================================================================//
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: sql_blocks
|
3
|
-
Version: 1.25.
|
3
|
+
Version: 1.25.38021329
|
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
|
@@ -67,6 +67,16 @@ You can specify your own alias: `a = Select('Actor a')`
|
|
67
67
|
3.1 -- If you want to filter the field on a range of values:
|
68
68
|
|
69
69
|
`a = Select( 'Actor a', age=Between(45, 69) )`
|
70
|
+
...but if it is a time slot within the same day, you can do it like this:
|
71
|
+
`Select(..., event_date=SameDay("2024-10-03"))`
|
72
|
+
This results in
|
73
|
+
```
|
74
|
+
SELECT ...
|
75
|
+
WHERE
|
76
|
+
event_date >= '2024-10-03 00:00:00' AND
|
77
|
+
event_date <= '2024-10-03 23:59:59'
|
78
|
+
```
|
79
|
+
---
|
70
80
|
|
71
81
|
3.2 -- Sub-queries:
|
72
82
|
```
|
@@ -668,6 +678,37 @@ For example, if your query is going to run on Oracle, do the following:
|
|
668
678
|
|
669
679
|
`Function.dialect = Dialect.ORACLE`
|
670
680
|
|
681
|
+
|
682
|
+
> Most of this functions you can use nested inside each other.
|
683
|
+
*Example:*
|
684
|
+
```
|
685
|
+
Select(...
|
686
|
+
event_date=Substring(
|
687
|
+
Cast("CHAR"), 12, 19
|
688
|
+
).As('time')
|
689
|
+
)
|
690
|
+
```
|
691
|
+
Results...
|
692
|
+
```
|
693
|
+
SELECT ...
|
694
|
+
SubString(Cast(event_date As char), 12, 19) as time
|
695
|
+
```
|
696
|
+
|
697
|
+
>> `Function.auto_convert` option (default: True)
|
698
|
+
|
699
|
+
- Put Cast(...) when there is a difference between the types of the parameter and the return of the nested function
|
700
|
+
```
|
701
|
+
birth=Round( DateDiff(Current_Date()) ).As('age')
|
702
|
+
```
|
703
|
+
...Returns...
|
704
|
+
```
|
705
|
+
SELECT
|
706
|
+
Round(
|
707
|
+
Cast(Current_Date() - p.birth As FLOAT)
|
708
|
+
/* ^^^ */
|
709
|
+
) as age
|
710
|
+
...
|
711
|
+
```
|
671
712
|
---
|
672
713
|
|
673
714
|
### 17 - CTE and Recursive classes
|
@@ -0,0 +1,7 @@
|
|
1
|
+
sql_blocks/__init__.py,sha256=5ItzGCyqqa6kwY8wvF9kapyHsAiWJ7KEXCcC-OtdXKg,37
|
2
|
+
sql_blocks/sql_blocks.py,sha256=5LQPEOkvMylJJxoU2m1P6pAhfcMHVdsnTiYgfLLJ5bQ,59477
|
3
|
+
sql_blocks-1.25.38021329.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
|
4
|
+
sql_blocks-1.25.38021329.dist-info/METADATA,sha256=un6TFbAfNbyhIrjLzHpvG1Yy77jsyZCw2xy_i3tMw2k,20511
|
5
|
+
sql_blocks-1.25.38021329.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
6
|
+
sql_blocks-1.25.38021329.dist-info/top_level.txt,sha256=57AbUvUjYNy4m1EqDaU3WHeP-uyIAfV0n8GAUp1a1YQ,11
|
7
|
+
sql_blocks-1.25.38021329.dist-info/RECORD,,
|
@@ -1,7 +0,0 @@
|
|
1
|
-
sql_blocks/__init__.py,sha256=5ItzGCyqqa6kwY8wvF9kapyHsAiWJ7KEXCcC-OtdXKg,37
|
2
|
-
sql_blocks/sql_blocks.py,sha256=dGmMtLWiyluRp1ZwPplmBqPs1nDOL-vemRR0gi2htWA,57513
|
3
|
-
sql_blocks-1.25.33022258.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
|
4
|
-
sql_blocks-1.25.33022258.dist-info/METADATA,sha256=GsTRa0fLkerPn-D-ourudj9Pve0oP69IzQ-YGHMtRnI,19574
|
5
|
-
sql_blocks-1.25.33022258.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
6
|
-
sql_blocks-1.25.33022258.dist-info/top_level.txt,sha256=57AbUvUjYNy4m1EqDaU3WHeP-uyIAfV0n8GAUp1a1YQ,11
|
7
|
-
sql_blocks-1.25.33022258.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|