sql-blocks 0.0.2__tar.gz → 0.0.4__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.
- {sql_blocks-0.0.2/sql_blocks.egg-info → sql_blocks-0.0.4}/PKG-INFO +25 -6
- {sql_blocks-0.0.2 → sql_blocks-0.0.4}/README.md +24 -5
- {sql_blocks-0.0.2 → sql_blocks-0.0.4}/pyproject.toml +1 -1
- {sql_blocks-0.0.2 → sql_blocks-0.0.4}/setup.py +1 -1
- {sql_blocks-0.0.2 → sql_blocks-0.0.4}/sql_blocks/sql_blocks.py +105 -28
- {sql_blocks-0.0.2 → sql_blocks-0.0.4/sql_blocks.egg-info}/PKG-INFO +25 -6
- {sql_blocks-0.0.2 → sql_blocks-0.0.4}/tests/tests.py +2 -2
- {sql_blocks-0.0.2 → sql_blocks-0.0.4}/LICENSE +0 -0
- {sql_blocks-0.0.2 → sql_blocks-0.0.4}/setup.cfg +0 -0
- {sql_blocks-0.0.2 → sql_blocks-0.0.4}/sql_blocks/__init__.py +0 -0
- {sql_blocks-0.0.2 → sql_blocks-0.0.4}/sql_blocks.egg-info/SOURCES.txt +0 -0
- {sql_blocks-0.0.2 → sql_blocks-0.0.4}/sql_blocks.egg-info/dependency_links.txt +0 -0
- {sql_blocks-0.0.2 → sql_blocks-0.0.4}/sql_blocks.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: sql_blocks
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.4
|
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
|
@@ -53,10 +53,9 @@ You can specify your own alias: `a = Select('Actor a')`
|
|
53
53
|
`a = Select( 'Actor a', age=Between(45, 69) )`
|
54
54
|
|
55
55
|
3.2 -- Sub-queries:
|
56
|
-
|
57
56
|
```
|
58
57
|
query = Select('Movie m', title=Field,
|
59
|
-
id=
|
58
|
+
id=SelectIN(
|
60
59
|
'Review r',
|
61
60
|
rate=Where.gt(4.5),
|
62
61
|
movie_id=Distinct
|
@@ -85,6 +84,16 @@ query = Select('Movie m', title=Field,
|
|
85
84
|
```
|
86
85
|
> Could be AND=Options(...)
|
87
86
|
|
87
|
+
3.4 -- Negative conditions use the _Not_ class instead of _Where_
|
88
|
+
```
|
89
|
+
based_on_book=Not.is_null()
|
90
|
+
```
|
91
|
+
|
92
|
+
3.5 -- List of values
|
93
|
+
```
|
94
|
+
hash_tag=Where.list(['space', 'monster', 'gore'])
|
95
|
+
```
|
96
|
+
|
88
97
|
---
|
89
98
|
### 4 - A field can be two things at the same time:
|
90
99
|
|
@@ -92,7 +101,7 @@ query = Select('Movie m', title=Field,
|
|
92
101
|
- This means that the field will appear in the results and also that the query will be ordered by that field.
|
93
102
|
* Applying **GROUP BY** to item 3.2, it would look like this:
|
94
103
|
```
|
95
|
-
|
104
|
+
SelectIN(
|
96
105
|
'Review r', movie=[GroupBy, Distinct],
|
97
106
|
rate=Having.avg(Where.gt(4.5))
|
98
107
|
)
|
@@ -274,7 +283,7 @@ m = Select...
|
|
274
283
|
|
275
284
|
9.3
|
276
285
|
```
|
277
|
-
best_movies =
|
286
|
+
best_movies = SelectIN(
|
278
287
|
Review=Table('role'),
|
279
288
|
rate=[GroupBy, Having.avg(Where.gt(4.5))]
|
280
289
|
)
|
@@ -292,5 +301,15 @@ m2 = Select(
|
|
292
301
|
**m1 == m2 # --- True!**
|
293
302
|
|
294
303
|
---
|
295
|
-
---
|
296
304
|
|
305
|
+
### 10 - CASE...WHEN...THEN
|
306
|
+
Select(
|
307
|
+
'Product',
|
308
|
+
label=Case('price').when(
|
309
|
+
lt(50), 'cheap'
|
310
|
+
).when(
|
311
|
+
gt(100), 'expensive'
|
312
|
+
).else_value(
|
313
|
+
'normal'
|
314
|
+
)
|
315
|
+
)
|
@@ -38,10 +38,9 @@ You can specify your own alias: `a = Select('Actor a')`
|
|
38
38
|
`a = Select( 'Actor a', age=Between(45, 69) )`
|
39
39
|
|
40
40
|
3.2 -- Sub-queries:
|
41
|
-
|
42
41
|
```
|
43
42
|
query = Select('Movie m', title=Field,
|
44
|
-
id=
|
43
|
+
id=SelectIN(
|
45
44
|
'Review r',
|
46
45
|
rate=Where.gt(4.5),
|
47
46
|
movie_id=Distinct
|
@@ -70,6 +69,16 @@ query = Select('Movie m', title=Field,
|
|
70
69
|
```
|
71
70
|
> Could be AND=Options(...)
|
72
71
|
|
72
|
+
3.4 -- Negative conditions use the _Not_ class instead of _Where_
|
73
|
+
```
|
74
|
+
based_on_book=Not.is_null()
|
75
|
+
```
|
76
|
+
|
77
|
+
3.5 -- List of values
|
78
|
+
```
|
79
|
+
hash_tag=Where.list(['space', 'monster', 'gore'])
|
80
|
+
```
|
81
|
+
|
73
82
|
---
|
74
83
|
### 4 - A field can be two things at the same time:
|
75
84
|
|
@@ -77,7 +86,7 @@ query = Select('Movie m', title=Field,
|
|
77
86
|
- This means that the field will appear in the results and also that the query will be ordered by that field.
|
78
87
|
* Applying **GROUP BY** to item 3.2, it would look like this:
|
79
88
|
```
|
80
|
-
|
89
|
+
SelectIN(
|
81
90
|
'Review r', movie=[GroupBy, Distinct],
|
82
91
|
rate=Having.avg(Where.gt(4.5))
|
83
92
|
)
|
@@ -259,7 +268,7 @@ m = Select...
|
|
259
268
|
|
260
269
|
9.3
|
261
270
|
```
|
262
|
-
best_movies =
|
271
|
+
best_movies = SelectIN(
|
263
272
|
Review=Table('role'),
|
264
273
|
rate=[GroupBy, Having.avg(Where.gt(4.5))]
|
265
274
|
)
|
@@ -277,5 +286,15 @@ m2 = Select(
|
|
277
286
|
**m1 == m2 # --- True!**
|
278
287
|
|
279
288
|
---
|
280
|
-
---
|
281
289
|
|
290
|
+
### 10 - CASE...WHEN...THEN
|
291
|
+
Select(
|
292
|
+
'Product',
|
293
|
+
label=Case('price').when(
|
294
|
+
lt(50), 'cheap'
|
295
|
+
).when(
|
296
|
+
gt(100), 'expensive'
|
297
|
+
).else_value(
|
298
|
+
'normal'
|
299
|
+
)
|
300
|
+
)
|
@@ -1,4 +1,6 @@
|
|
1
1
|
from enum import Enum
|
2
|
+
import re
|
3
|
+
|
2
4
|
|
3
5
|
KEYWORD = {
|
4
6
|
'SELECT': (',{}', 'SELECT *'),
|
@@ -54,9 +56,10 @@ class Field:
|
|
54
56
|
|
55
57
|
@classmethod
|
56
58
|
def format(cls, name: str, main: SQLObject) -> str:
|
59
|
+
name = name.strip()
|
57
60
|
if name == '_':
|
58
61
|
name = '*'
|
59
|
-
|
62
|
+
elif '.' not in name:
|
60
63
|
name = f'{main.alias}.{name}'
|
61
64
|
if Function in cls.__bases__:
|
62
65
|
name = f'{cls.__name__}({name})'
|
@@ -124,14 +127,19 @@ class ForeignKey:
|
|
124
127
|
def __init__(self, table_name: str):
|
125
128
|
self.table_name = table_name
|
126
129
|
|
130
|
+
@staticmethod
|
131
|
+
def get_key(obj1: SQLObject, obj2: SQLObject) -> tuple:
|
132
|
+
return obj1.table_name, obj2.table_name
|
133
|
+
|
127
134
|
def add(self, name: str, main: SQLObject):
|
128
|
-
key =
|
129
|
-
ForeignKey.references[key] = name
|
135
|
+
key = self.get_key(main, self)
|
136
|
+
ForeignKey.references[key] = (name, '')
|
130
137
|
|
131
138
|
@classmethod
|
132
|
-
def find(cls, obj1: SQLObject, obj2: SQLObject) ->
|
133
|
-
key =
|
134
|
-
|
139
|
+
def find(cls, obj1: SQLObject, obj2: SQLObject) -> tuple:
|
140
|
+
key = cls.get_key(obj1, obj2)
|
141
|
+
a, b = cls.references.get(key, ('', ''))
|
142
|
+
return a, (b or obj2.key_field)
|
135
143
|
|
136
144
|
|
137
145
|
def quoted(value) -> str:
|
@@ -314,7 +322,7 @@ class JoinType(Enum):
|
|
314
322
|
|
315
323
|
class Select(SQLObject):
|
316
324
|
join_type: JoinType = JoinType.INNER
|
317
|
-
REGEX =
|
325
|
+
REGEX = {}
|
318
326
|
|
319
327
|
def __init__(self, table_name: str='', **values):
|
320
328
|
super().__init__(table_name)
|
@@ -341,17 +349,17 @@ class Select(SQLObject):
|
|
341
349
|
update_values(key, self.values.get(key, []))
|
342
350
|
|
343
351
|
def __add__(self, other: SQLObject):
|
344
|
-
foreign_field,
|
352
|
+
foreign_field, primary_key = ForeignKey.find(self, other)
|
345
353
|
if not foreign_field:
|
346
|
-
foreign_field,
|
354
|
+
foreign_field, primary_key = ForeignKey.find(other, self)
|
347
355
|
if foreign_field:
|
348
356
|
if primary_key:
|
349
|
-
PrimaryKey.add(primary_key
|
357
|
+
PrimaryKey.add(primary_key, self)
|
350
358
|
self.add(foreign_field, other)
|
351
359
|
return other
|
352
360
|
raise ValueError(f'No relationship found between {self.table_name} and {other.table_name}.')
|
353
361
|
elif primary_key:
|
354
|
-
PrimaryKey.add(primary_key
|
362
|
+
PrimaryKey.add(primary_key, other)
|
355
363
|
other.add(foreign_field, self)
|
356
364
|
return self
|
357
365
|
|
@@ -391,15 +399,52 @@ class Select(SQLObject):
|
|
391
399
|
|
392
400
|
@classmethod
|
393
401
|
def parse(cls, txt: str) -> list[SQLObject]:
|
394
|
-
|
395
|
-
|
402
|
+
def find_last_word(pos: int) -> int:
|
403
|
+
SPACE, WORD = 1, 2
|
404
|
+
found = set()
|
405
|
+
for i in range(pos, 0, -1):
|
406
|
+
if txt[i] in [' ', '\t', '\n']:
|
407
|
+
if sum(found) == 3:
|
408
|
+
return i
|
409
|
+
found.add(SPACE)
|
410
|
+
if txt[i].isalpha():
|
411
|
+
found.add(WORD)
|
412
|
+
elif txt[i] == '.':
|
413
|
+
found.remove(WORD)
|
414
|
+
def find_parenthesis(pos: int) -> int:
|
415
|
+
for i in range(pos, len(txt)-1):
|
416
|
+
if txt[i] == ')':
|
417
|
+
return i+1
|
396
418
|
if not cls.REGEX:
|
397
|
-
keywords = '|'.join(KEYWORD)
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
tables = [t.strip() for t in re.split('JOIN|LEFT|RIGHT|ON', values[FROM]) if t.strip()]
|
419
|
+
keywords = '|'.join(k + r'\b' for k in KEYWORD)
|
420
|
+
flags = re.IGNORECASE + re.MULTILINE
|
421
|
+
cls.REGEX['keywords'] = re.compile(f'({keywords})', flags)
|
422
|
+
cls.REGEX['subquery'] = re.compile(r'(\w\.)*\w+ +in +\(SELECT.*?\)', flags)
|
402
423
|
result = {}
|
424
|
+
found = cls.REGEX['subquery'].search(txt)
|
425
|
+
while found:
|
426
|
+
start, end = found.span()
|
427
|
+
inner = txt[start: end]
|
428
|
+
if inner.count('(') > inner.count(')'):
|
429
|
+
end = find_parenthesis(end)
|
430
|
+
inner = txt[start: end]
|
431
|
+
fld, *inner = re.split(r' IN | in', inner, maxsplit=1)
|
432
|
+
if fld.upper() == 'NOT':
|
433
|
+
pos = find_last_word(start)
|
434
|
+
fld = txt[pos: start].strip() # [To-Do] Use the value of `fld`
|
435
|
+
start = pos
|
436
|
+
class_type = NotSelectIN
|
437
|
+
else:
|
438
|
+
class_type = SelectIN
|
439
|
+
obj = class_type.parse(
|
440
|
+
' '.join(re.sub(r'^\(', '', s.strip()) for s in inner)
|
441
|
+
)[0]
|
442
|
+
result[obj.alias] = obj
|
443
|
+
txt = txt[:start-1] + txt[end+1:]
|
444
|
+
found = cls.REGEX['subquery'].search(txt)
|
445
|
+
tokens = [t.strip() for t in cls.REGEX['keywords'].split(txt) if re.findall(r'\w+', t)]
|
446
|
+
values = {k.upper(): v for k, v in zip(tokens[::2], tokens[1::2])}
|
447
|
+
tables = [t.strip() for t in re.split('JOIN|LEFT|RIGHT|ON', values[FROM]) if t.strip()]
|
403
448
|
for item in tables:
|
404
449
|
if '=' in item:
|
405
450
|
a1, f1, a2, f2 = [r.strip() for r in re.split('[().=]', item) if r]
|
@@ -412,23 +457,55 @@ class Select(SQLObject):
|
|
412
457
|
for key in USUAL_KEYS:
|
413
458
|
if not key in values:
|
414
459
|
continue
|
415
|
-
separator = KEYWORD[key][0].format(
|
416
|
-
' ' if key == WHERE else ''
|
417
|
-
)
|
460
|
+
separator = KEYWORD[key][0].format('')
|
418
461
|
fields = [
|
419
|
-
|
462
|
+
Field.format(fld, obj)
|
463
|
+
for fld in re.split(
|
420
464
|
separator, values[key]
|
421
465
|
) if len(tables) == 1
|
422
|
-
or re.findall(f'
|
423
|
-
]
|
424
|
-
obj.values[key] = [
|
425
|
-
f'{obj.alias}.{f}' if not '.' in f else f for f in fields
|
466
|
+
or re.findall(f'\b*{obj.alias}[.]', fld)
|
426
467
|
]
|
468
|
+
obj.values[key] = [ f for f in fields if f.strip() ]
|
427
469
|
result[obj.alias] = obj
|
428
470
|
return list( result.values() )
|
429
471
|
|
472
|
+
class SelectIN(Select):
|
473
|
+
condition_class = Where
|
430
474
|
|
431
|
-
class SubSelect(Select):
|
432
475
|
def add(self, name: str, main: SQLObject):
|
433
476
|
self.break_lines = False
|
434
|
-
|
477
|
+
self.condition_class.list(self).add(name, main)
|
478
|
+
|
479
|
+
SubSelect = SelectIN
|
480
|
+
|
481
|
+
class NotSelectIN(SelectIN):
|
482
|
+
condition_class = Not
|
483
|
+
|
484
|
+
|
485
|
+
if __name__ == "__main__":
|
486
|
+
query_list = Select.parse("""
|
487
|
+
SELECT
|
488
|
+
cas.role,
|
489
|
+
m.title,
|
490
|
+
m.release_date,
|
491
|
+
a.name as actors_name
|
492
|
+
FROM
|
493
|
+
Actor a
|
494
|
+
LEFT JOIN Cast cas ON (a.cast = cas.id)
|
495
|
+
LEFT JOIN Movie m ON (cas.movie = m.id)
|
496
|
+
WHERE
|
497
|
+
m.genre NOT in (SELECT g.id from Genres g where g.name in ('sci-fi', 'horror', 'distopia'))
|
498
|
+
AND (m.hashtag = '#cult' OR m.awards LIKE '%Oscar%')
|
499
|
+
AND m.id IN (select DISTINCT r.movie FROM Review r GROUP BY r.movie HAVING Avg(r.rate) > 4.5)
|
500
|
+
AND a.age <= 69 AND a.age >= 45
|
501
|
+
ORDER BY
|
502
|
+
m.release_date
|
503
|
+
""")
|
504
|
+
for query in query_list:
|
505
|
+
descr = ' {} ({}) '.format(
|
506
|
+
query.table_name,
|
507
|
+
query.__class__.__name__
|
508
|
+
)
|
509
|
+
print(descr.center(50, '-'))
|
510
|
+
print(query)
|
511
|
+
print('='*50)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: sql_blocks
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.4
|
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
|
@@ -53,10 +53,9 @@ You can specify your own alias: `a = Select('Actor a')`
|
|
53
53
|
`a = Select( 'Actor a', age=Between(45, 69) )`
|
54
54
|
|
55
55
|
3.2 -- Sub-queries:
|
56
|
-
|
57
56
|
```
|
58
57
|
query = Select('Movie m', title=Field,
|
59
|
-
id=
|
58
|
+
id=SelectIN(
|
60
59
|
'Review r',
|
61
60
|
rate=Where.gt(4.5),
|
62
61
|
movie_id=Distinct
|
@@ -85,6 +84,16 @@ query = Select('Movie m', title=Field,
|
|
85
84
|
```
|
86
85
|
> Could be AND=Options(...)
|
87
86
|
|
87
|
+
3.4 -- Negative conditions use the _Not_ class instead of _Where_
|
88
|
+
```
|
89
|
+
based_on_book=Not.is_null()
|
90
|
+
```
|
91
|
+
|
92
|
+
3.5 -- List of values
|
93
|
+
```
|
94
|
+
hash_tag=Where.list(['space', 'monster', 'gore'])
|
95
|
+
```
|
96
|
+
|
88
97
|
---
|
89
98
|
### 4 - A field can be two things at the same time:
|
90
99
|
|
@@ -92,7 +101,7 @@ query = Select('Movie m', title=Field,
|
|
92
101
|
- This means that the field will appear in the results and also that the query will be ordered by that field.
|
93
102
|
* Applying **GROUP BY** to item 3.2, it would look like this:
|
94
103
|
```
|
95
|
-
|
104
|
+
SelectIN(
|
96
105
|
'Review r', movie=[GroupBy, Distinct],
|
97
106
|
rate=Having.avg(Where.gt(4.5))
|
98
107
|
)
|
@@ -274,7 +283,7 @@ m = Select...
|
|
274
283
|
|
275
284
|
9.3
|
276
285
|
```
|
277
|
-
best_movies =
|
286
|
+
best_movies = SelectIN(
|
278
287
|
Review=Table('role'),
|
279
288
|
rate=[GroupBy, Having.avg(Where.gt(4.5))]
|
280
289
|
)
|
@@ -292,5 +301,15 @@ m2 = Select(
|
|
292
301
|
**m1 == m2 # --- True!**
|
293
302
|
|
294
303
|
---
|
295
|
-
---
|
296
304
|
|
305
|
+
### 10 - CASE...WHEN...THEN
|
306
|
+
Select(
|
307
|
+
'Product',
|
308
|
+
label=Case('price').when(
|
309
|
+
lt(50), 'cheap'
|
310
|
+
).when(
|
311
|
+
gt(100), 'expensive'
|
312
|
+
).else_value(
|
313
|
+
'normal'
|
314
|
+
)
|
315
|
+
)
|
@@ -68,8 +68,8 @@ def single_text_to_objects():
|
|
68
68
|
|
69
69
|
def many_texts_to_objects():
|
70
70
|
ForeignKey.references = {
|
71
|
-
'
|
72
|
-
'
|
71
|
+
('Actor', 'Cast'): ('cast', 'id'),
|
72
|
+
('Cast', 'Movie'): ('movie', 'id'),
|
73
73
|
}
|
74
74
|
actor = Select.parse('''
|
75
75
|
SELECT name as actors_name FROM Actor a
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|