sql-blocks 0.31.21__py3-none-any.whl → 1.25__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 +132 -39
- {sql_blocks-0.31.21.dist-info → sql_blocks-1.25.dist-info}/METADATA +58 -7
- sql_blocks-1.25.dist-info/RECORD +7 -0
- sql_blocks-0.31.21.dist-info/RECORD +0 -7
- {sql_blocks-0.31.21.dist-info → sql_blocks-1.25.dist-info}/LICENSE +0 -0
- {sql_blocks-0.31.21.dist-info → sql_blocks-1.25.dist-info}/WHEEL +0 -0
- {sql_blocks-0.31.21.dist-info → sql_blocks-1.25.dist-info}/top_level.txt +0 -0
sql_blocks/sql_blocks.py
CHANGED
@@ -22,6 +22,7 @@ KEYWORD = {
|
|
22
22
|
|
23
23
|
SELECT, FROM, WHERE, GROUP_BY, ORDER_BY, LIMIT = KEYWORD.keys()
|
24
24
|
USUAL_KEYS = [SELECT, WHERE, GROUP_BY, ORDER_BY, LIMIT]
|
25
|
+
TO_LIST = lambda x: x if isinstance(x, list) else [x]
|
25
26
|
|
26
27
|
|
27
28
|
class SQLObject:
|
@@ -110,9 +111,6 @@ class SQLObject:
|
|
110
111
|
self.values[key] = result
|
111
112
|
|
112
113
|
|
113
|
-
class Function:
|
114
|
-
...
|
115
|
-
|
116
114
|
class Field:
|
117
115
|
prefix = ''
|
118
116
|
|
@@ -134,17 +132,6 @@ class Field:
|
|
134
132
|
)
|
135
133
|
|
136
134
|
|
137
|
-
class Avg(Function, Field):
|
138
|
-
...
|
139
|
-
class Min(Function, Field):
|
140
|
-
...
|
141
|
-
class Max(Function, Field):
|
142
|
-
...
|
143
|
-
class Sum(Function, Field):
|
144
|
-
...
|
145
|
-
class Count(Function, Field):
|
146
|
-
...
|
147
|
-
|
148
135
|
class Distinct(Field):
|
149
136
|
prefix = 'DISTINCT '
|
150
137
|
|
@@ -163,6 +150,107 @@ class NamedField:
|
|
163
150
|
)
|
164
151
|
|
165
152
|
|
153
|
+
class Function:
|
154
|
+
instance: dict = {}
|
155
|
+
|
156
|
+
def __init__(self, *params: list):
|
157
|
+
func_name = self.__class__.__name__
|
158
|
+
Function.instance[func_name] = self
|
159
|
+
self.params = [str(p) for p in params]
|
160
|
+
self.class_type = Field
|
161
|
+
self.pattern = '{}({})'
|
162
|
+
self.extra = {}
|
163
|
+
|
164
|
+
def As(self, field_alias: str, modifiers=None):
|
165
|
+
if modifiers:
|
166
|
+
for mod in TO_LIST(modifiers):
|
167
|
+
self.extra[field_alias] = mod
|
168
|
+
self.class_type = NamedField(field_alias)
|
169
|
+
return self
|
170
|
+
|
171
|
+
@classmethod
|
172
|
+
def format(cls, name: str, main: SQLObject) -> str:
|
173
|
+
obj = cls.get_instance()
|
174
|
+
params = [
|
175
|
+
Field.format(name, main)
|
176
|
+
] + obj.params
|
177
|
+
return obj.pattern.format(
|
178
|
+
cls.__name__,
|
179
|
+
', '.join(params)
|
180
|
+
)
|
181
|
+
|
182
|
+
def __add(self, name: str, main: SQLObject):
|
183
|
+
name = self.format(name, main)
|
184
|
+
self.class_type.add(name, main)
|
185
|
+
if self.extra:
|
186
|
+
main.__call__(**self.extra)
|
187
|
+
|
188
|
+
@classmethod
|
189
|
+
def get_instance(cls):
|
190
|
+
obj = Function.instance.get(cls.__name__)
|
191
|
+
if not obj:
|
192
|
+
obj = cls()
|
193
|
+
return obj
|
194
|
+
|
195
|
+
@classmethod
|
196
|
+
def add(cls, name: str, main: SQLObject):
|
197
|
+
cls.get_instance().__add(name, main)
|
198
|
+
|
199
|
+
|
200
|
+
# ---- String Functions: ---------------------------------
|
201
|
+
class SubString(Function):
|
202
|
+
...
|
203
|
+
|
204
|
+
# ---- Numeric Functions: --------------------------------
|
205
|
+
class Round(Function):
|
206
|
+
...
|
207
|
+
|
208
|
+
# --- Date Functions: ------------------------------------
|
209
|
+
class DateDiff(Function):
|
210
|
+
...
|
211
|
+
class Extract(Function):
|
212
|
+
...
|
213
|
+
class DatePart(Function):
|
214
|
+
...
|
215
|
+
class Current_Date(Function):
|
216
|
+
...
|
217
|
+
|
218
|
+
class Aggregate:
|
219
|
+
break_lines: bool = True
|
220
|
+
|
221
|
+
def over(self, **args):
|
222
|
+
keywords = ' '.join(
|
223
|
+
'{}{} BY {}'.format(
|
224
|
+
'\n\t\t' if self.break_lines else '',
|
225
|
+
key.upper(), args[key]
|
226
|
+
) for key in ('partition', 'order')
|
227
|
+
if key in args
|
228
|
+
)
|
229
|
+
if keywords and self.break_lines:
|
230
|
+
keywords += '\n\t'
|
231
|
+
self.pattern = '{}({})' + f' OVER({keywords})'
|
232
|
+
return self
|
233
|
+
|
234
|
+
|
235
|
+
# ---- Aggregate Functions: -------------------------------
|
236
|
+
class Avg(Aggregate, Function):
|
237
|
+
...
|
238
|
+
class Min(Aggregate, Function):
|
239
|
+
...
|
240
|
+
class Max(Aggregate, Function):
|
241
|
+
...
|
242
|
+
class Sum(Aggregate, Function):
|
243
|
+
...
|
244
|
+
class Count(Aggregate, Function):
|
245
|
+
...
|
246
|
+
|
247
|
+
# ---- Conversions and other Functions: ---------------------
|
248
|
+
class Coalesce(Function):
|
249
|
+
...
|
250
|
+
class Cast(Function):
|
251
|
+
...
|
252
|
+
|
253
|
+
|
166
254
|
class ExpressionField:
|
167
255
|
def __init__(self, expr: str):
|
168
256
|
self.expr = expr
|
@@ -361,26 +449,40 @@ class Between:
|
|
361
449
|
Where.lte(self.end).add(name, main)
|
362
450
|
|
363
451
|
|
452
|
+
class Clause:
|
453
|
+
@classmethod
|
454
|
+
def format(cls, name: str, main: SQLObject) -> str:
|
455
|
+
def is_function() -> bool:
|
456
|
+
diff = main.diff(SELECT, [name.lower()], True)
|
457
|
+
FUNCTION_CLASS = {f.__name__.lower(): f for f in Function.__subclasses__()}
|
458
|
+
return diff.intersection(FUNCTION_CLASS)
|
459
|
+
found = re.findall(r'^_\d', name)
|
460
|
+
if found:
|
461
|
+
name = found[0].replace('_', '')
|
462
|
+
elif main.alias and not is_function():
|
463
|
+
name = f'{main.alias}.{name}'
|
464
|
+
return name
|
465
|
+
|
466
|
+
|
364
467
|
class SortType(Enum):
|
365
468
|
ASC = ''
|
366
469
|
DESC = ' DESC'
|
367
470
|
|
368
|
-
|
471
|
+
|
472
|
+
class OrderBy(Clause):
|
369
473
|
sort: SortType = SortType.ASC
|
474
|
+
|
370
475
|
@classmethod
|
371
476
|
def add(cls, name: str, main: SQLObject):
|
372
|
-
|
373
|
-
if found:
|
374
|
-
name = found[0].replace('_', '')
|
375
|
-
elif main.alias:
|
376
|
-
name = f'{main.alias}.{name}'
|
477
|
+
name = cls.format(name, main)
|
377
478
|
main.values.setdefault(ORDER_BY, []).append(name+cls.sort.value)
|
378
479
|
|
379
480
|
|
380
|
-
class GroupBy:
|
381
|
-
@
|
382
|
-
def add(name: str, main: SQLObject):
|
383
|
-
|
481
|
+
class GroupBy(Clause):
|
482
|
+
@classmethod
|
483
|
+
def add(cls, name: str, main: SQLObject):
|
484
|
+
name = cls.format(name, main)
|
485
|
+
main.values.setdefault(GROUP_BY, []).append(name)
|
384
486
|
|
385
487
|
|
386
488
|
class Having:
|
@@ -858,7 +960,11 @@ class CypherParser(Parser):
|
|
858
960
|
else:
|
859
961
|
if not curr.values.get(SELECT):
|
860
962
|
raise IndexError(f'Foreign Key not found for {curr.table_name}.')
|
861
|
-
|
963
|
+
fields = [
|
964
|
+
fld for fld in curr.values[SELECT]
|
965
|
+
if fld not in curr.values.get(GROUP_BY, [])
|
966
|
+
]
|
967
|
+
foreign_fld = fields[0].split('.')[-1]
|
862
968
|
curr.delete(foreign_fld, [SELECT])
|
863
969
|
if curr.join_type == JoinType.RIGHT:
|
864
970
|
pk_field, foreign_fld = foreign_fld, pk_field
|
@@ -1087,9 +1193,8 @@ class Select(SQLObject):
|
|
1087
1193
|
return self.translate_to(QueryLanguage)
|
1088
1194
|
|
1089
1195
|
def __call__(self, **values):
|
1090
|
-
to_list = lambda x: x if isinstance(x, list) else [x]
|
1091
1196
|
for name, params in values.items():
|
1092
|
-
for obj in
|
1197
|
+
for obj in TO_LIST(params):
|
1093
1198
|
obj.add(name, self)
|
1094
1199
|
return self
|
1095
1200
|
|
@@ -1252,15 +1357,3 @@ def detect(text: str) -> Select:
|
|
1252
1357
|
for query in query_list[1:]:
|
1253
1358
|
result += query
|
1254
1359
|
return result
|
1255
|
-
|
1256
|
-
|
1257
|
-
if __name__ == "__main__":
|
1258
|
-
print('@'*100)
|
1259
|
-
print( detect(
|
1260
|
-
'''
|
1261
|
-
Company(?jobs > 10, id)
|
1262
|
-
<- Person(work_at ^name, friend_of) ->
|
1263
|
-
Person(id ?skill="pANDas")
|
1264
|
-
''' # ^^^---- Test for confusion with the AND operator
|
1265
|
-
) )
|
1266
|
-
print('@'*100)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: sql_blocks
|
3
|
-
Version:
|
3
|
+
Version: 1.25
|
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
|
@@ -390,7 +390,7 @@ a, c, m = Select.parse(
|
|
390
390
|
<- Cast(actor_id, movie_id) ->
|
391
391
|
Movie(id ^title)
|
392
392
|
""",
|
393
|
-
|
393
|
+
CypherParser
|
394
394
|
# ^^^ recognizes syntax like Neo4J queries
|
395
395
|
)
|
396
396
|
```
|
@@ -427,7 +427,7 @@ ORDER BY
|
|
427
427
|
It is useful to write a query in a few lines, without specifying the script type (cypher, mongoDB, SQL, Neo4J...)
|
428
428
|
### Examples:
|
429
429
|
|
430
|
-
> **1 - Relationship**
|
430
|
+
> **13.1 - Relationship**
|
431
431
|
```
|
432
432
|
query = detect(
|
433
433
|
'MATCH(c:Customer)<-[:Order]->(p:Product)RETURN c, p'
|
@@ -439,7 +439,7 @@ print(query)
|
|
439
439
|
Order ord
|
440
440
|
LEFT JOIN Customer cus ON (ord.customer_id = cus.id)
|
441
441
|
RIGHT JOIN Product pro ON (ord.product_id = pro.id)
|
442
|
-
> **2 - Grouping**
|
442
|
+
> **13.2 - Grouping**
|
443
443
|
```
|
444
444
|
query = detect(
|
445
445
|
'People@gender(avg$age?region="SOUTH"^count$qtde)'
|
@@ -460,13 +460,13 @@ print(query)
|
|
460
460
|
ORDER BY
|
461
461
|
peo.qtde
|
462
462
|
|
463
|
-
> **3 - Many conditions...**
|
463
|
+
> **13.3 - Many conditions...**
|
464
464
|
```
|
465
465
|
print( detect('''
|
466
466
|
db.people.find({
|
467
467
|
{
|
468
468
|
$or: [
|
469
|
-
status:{$eq:"B"},
|
469
|
+
{status:{$eq:"B"}},
|
470
470
|
age:{$lt:50}
|
471
471
|
]
|
472
472
|
},
|
@@ -491,7 +491,7 @@ print(query)
|
|
491
491
|
ORDER BY
|
492
492
|
peo.user_id DESC
|
493
493
|
|
494
|
-
> **4 - Relations with same table twice (or more)**
|
494
|
+
> **13.4 - Relations with same table twice (or more)**
|
495
495
|
|
496
496
|
Automatically assigns aliases to each side of the relationship (In this example, one user invites another to add to their contact list)
|
497
497
|
```
|
@@ -516,3 +516,54 @@ It consists of the inverse process of parsing: From a Select object, it returns
|
|
516
516
|
* QueryLanguage - default
|
517
517
|
* MongoDBLanguage
|
518
518
|
* Neo4JLanguage
|
519
|
+
|
520
|
+
---
|
521
|
+
### 14 - Window Function
|
522
|
+
|
523
|
+
Aggregation functions (Avg, Min, Max, Sum, Count) have the **over** method...
|
524
|
+
|
525
|
+
query=Select(
|
526
|
+
'Enrollment e',
|
527
|
+
payment=Sum().over(
|
528
|
+
partition='student_id', order='due_date'
|
529
|
+
).As('sum_per_student')
|
530
|
+
)
|
531
|
+
|
532
|
+
...that generates the following query:
|
533
|
+
|
534
|
+
```
|
535
|
+
SELECT
|
536
|
+
Sum(e.payment) OVER(
|
537
|
+
PARTITION BY student_id
|
538
|
+
ORDER BY due_date
|
539
|
+
) as sum_per_student
|
540
|
+
FROM
|
541
|
+
Enrollment e
|
542
|
+
```
|
543
|
+
---
|
544
|
+
### 15 - The `As` method:
|
545
|
+
query=Select(
|
546
|
+
'Customers c',
|
547
|
+
phone=[
|
548
|
+
Not.is_null(),
|
549
|
+
SubString(1, 4).As('area_code', GroupBy)
|
550
|
+
],
|
551
|
+
customer_id=[
|
552
|
+
Count().As('customer_count', OrderBy),
|
553
|
+
Having.count(gt(5))
|
554
|
+
]
|
555
|
+
)
|
556
|
+
You can use the result of a function as a new field -- and optionally use it in ORDER BY and/or GROUP BY clause(s):
|
557
|
+
```
|
558
|
+
SELECT
|
559
|
+
SubString(c.phone, 1, 4) as area_code,
|
560
|
+
Count(c.customer_id) as customer_count
|
561
|
+
FROM
|
562
|
+
Customers c
|
563
|
+
WHERE
|
564
|
+
NOT c.phone IS NULL
|
565
|
+
GROUP BY
|
566
|
+
area_code HAVING Count(c.customer_id) > 5
|
567
|
+
ORDER BY
|
568
|
+
customer_count
|
569
|
+
```
|
@@ -0,0 +1,7 @@
|
|
1
|
+
sql_blocks/__init__.py,sha256=5ItzGCyqqa6kwY8wvF9kapyHsAiWJ7KEXCcC-OtdXKg,37
|
2
|
+
sql_blocks/sql_blocks.py,sha256=QxopGbzRtBgG_9oAs1ovBATFplDqrzEFSLwyKbOL-W8,45499
|
3
|
+
sql_blocks-1.25.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
|
4
|
+
sql_blocks-1.25.dist-info/METADATA,sha256=cvHewfK05stZRHfWZeyIMMtKRjdOGxZN38o94JkVS-U,13421
|
5
|
+
sql_blocks-1.25.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
6
|
+
sql_blocks-1.25.dist-info/top_level.txt,sha256=57AbUvUjYNy4m1EqDaU3WHeP-uyIAfV0n8GAUp1a1YQ,11
|
7
|
+
sql_blocks-1.25.dist-info/RECORD,,
|
@@ -1,7 +0,0 @@
|
|
1
|
-
sql_blocks/__init__.py,sha256=5ItzGCyqqa6kwY8wvF9kapyHsAiWJ7KEXCcC-OtdXKg,37
|
2
|
-
sql_blocks/sql_blocks.py,sha256=jtK4yfyLCQZYrom2Aj_En6Hvlc3mbvAI1TOnC4AK72U,42823
|
3
|
-
sql_blocks-0.31.21.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
|
4
|
-
sql_blocks-0.31.21.dist-info/METADATA,sha256=lDrOFqAT1hC8lEp9plVXngNJ7m_2ZnJ8qRywx_PVUCo,12195
|
5
|
-
sql_blocks-0.31.21.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
6
|
-
sql_blocks-0.31.21.dist-info/top_level.txt,sha256=57AbUvUjYNy4m1EqDaU3WHeP-uyIAfV0n8GAUp1a1YQ,11
|
7
|
-
sql_blocks-0.31.21.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|