sql-blocks 0.2.2__py3-none-any.whl → 0.2.3__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 +156 -67
- {sql_blocks-0.2.2.dist-info → sql_blocks-0.2.3.dist-info}/METADATA +1 -1
- sql_blocks-0.2.3.dist-info/RECORD +7 -0
- sql_blocks-0.2.2.dist-info/RECORD +0 -7
- {sql_blocks-0.2.2.dist-info → sql_blocks-0.2.3.dist-info}/LICENSE +0 -0
- {sql_blocks-0.2.2.dist-info → sql_blocks-0.2.3.dist-info}/WHEEL +0 -0
- {sql_blocks-0.2.2.dist-info → sql_blocks-0.2.3.dist-info}/top_level.txt +0 -0
sql_blocks/sql_blocks.py
CHANGED
@@ -413,6 +413,160 @@ class Rule:
|
|
413
413
|
def apply(cls, target: 'Select'):
|
414
414
|
...
|
415
415
|
|
416
|
+
class Parser:
|
417
|
+
REGEX = {}
|
418
|
+
|
419
|
+
def prepare(self):
|
420
|
+
...
|
421
|
+
|
422
|
+
def __init__(self, txt: str, class_type):
|
423
|
+
self.queries = []
|
424
|
+
if not self.REGEX:
|
425
|
+
self.prepare()
|
426
|
+
self.class_type = class_type
|
427
|
+
self.eval(txt)
|
428
|
+
|
429
|
+
def eval(self, txt: str):
|
430
|
+
...
|
431
|
+
|
432
|
+
|
433
|
+
class SQLParser(Parser):
|
434
|
+
REGEX = {}
|
435
|
+
|
436
|
+
def prepare(self):
|
437
|
+
keywords = '|'.join(k + r'\b' for k in KEYWORD)
|
438
|
+
flags = re.IGNORECASE + re.MULTILINE
|
439
|
+
self.REGEX['keywords'] = re.compile(f'({keywords}|[*])', flags)
|
440
|
+
self.REGEX['subquery'] = re.compile(r'(\w\.)*\w+ +in +\(SELECT.*?\)', flags)
|
441
|
+
|
442
|
+
def eval(self, txt: str):
|
443
|
+
def find_last_word(pos: int) -> int:
|
444
|
+
SPACE, WORD = 1, 2
|
445
|
+
found = set()
|
446
|
+
for i in range(pos, 0, -1):
|
447
|
+
if txt[i] in [' ', '\t', '\n']:
|
448
|
+
if sum(found) == 3:
|
449
|
+
return i
|
450
|
+
found.add(SPACE)
|
451
|
+
if txt[i].isalpha():
|
452
|
+
found.add(WORD)
|
453
|
+
elif txt[i] == '.':
|
454
|
+
found.remove(WORD)
|
455
|
+
def find_parenthesis(pos: int) -> int:
|
456
|
+
for i in range(pos, len(txt)-1):
|
457
|
+
if txt[i] == ')':
|
458
|
+
return i+1
|
459
|
+
result = {}
|
460
|
+
found = self.REGEX['subquery'].search(txt)
|
461
|
+
while found:
|
462
|
+
start, end = found.span()
|
463
|
+
inner = txt[start: end]
|
464
|
+
if inner.count('(') > inner.count(')'):
|
465
|
+
end = find_parenthesis(end)
|
466
|
+
inner = txt[start: end-1]
|
467
|
+
fld, *inner = re.split(r' IN | in', inner, maxsplit=1)
|
468
|
+
if fld.upper() == 'NOT':
|
469
|
+
pos = find_last_word(start)
|
470
|
+
fld = txt[pos: start].strip() # [To-Do] Use the value of `fld`
|
471
|
+
start = pos
|
472
|
+
target_class = NotSelectIN
|
473
|
+
else:
|
474
|
+
target_class = SelectIN
|
475
|
+
obj = SQLParser(
|
476
|
+
' '.join(re.sub(r'^\(', '', s.strip()) for s in inner),
|
477
|
+
class_type=target_class
|
478
|
+
).queries[0]
|
479
|
+
result[obj.alias] = obj
|
480
|
+
txt = txt[:start-1] + txt[end+1:]
|
481
|
+
found = self.REGEX['subquery'].search(txt)
|
482
|
+
tokens = [t.strip() for t in self.REGEX['keywords'].split(txt) if t.strip()]
|
483
|
+
values = {k.upper(): v for k, v in zip(tokens[::2], tokens[1::2])}
|
484
|
+
tables = [t.strip() for t in re.split('JOIN|LEFT|RIGHT|ON', values[FROM]) if t.strip()]
|
485
|
+
for item in tables:
|
486
|
+
if '=' in item:
|
487
|
+
a1, f1, a2, f2 = [r.strip() for r in re.split('[().=]', item) if r]
|
488
|
+
obj1: SQLObject = result[a1]
|
489
|
+
obj2: SQLObject = result[a2]
|
490
|
+
PrimaryKey.add(f2, obj2)
|
491
|
+
ForeignKey(obj2.table_name).add(f1, obj1)
|
492
|
+
else:
|
493
|
+
obj = self.class_type(item)
|
494
|
+
for key in USUAL_KEYS:
|
495
|
+
if not key in values:
|
496
|
+
continue
|
497
|
+
separator = self.class_type.get_separator(key)
|
498
|
+
obj.values[key] = [
|
499
|
+
Field.format(fld, obj)
|
500
|
+
for fld in re.split(separator, values[key])
|
501
|
+
if (fld != '*' and len(tables) == 1) or obj.match(fld)
|
502
|
+
]
|
503
|
+
result[obj.alias] = obj
|
504
|
+
self.queries = list( result.values() )
|
505
|
+
|
506
|
+
|
507
|
+
class Cypher(Parser):
|
508
|
+
REGEX = {}
|
509
|
+
TOKEN_METHODS = {}
|
510
|
+
|
511
|
+
def prepare(self):
|
512
|
+
self.REGEX['separator'] = re.compile(r'([(,?)^]|->|<-)')
|
513
|
+
self.REGEX['condition'] = re.compile(r'(^\w+)|([<>=])')
|
514
|
+
self.TOKEN_METHODS = {
|
515
|
+
'(': self.add_field, '?': self.add_where,
|
516
|
+
',': self.add_field, '^': self.add_order,
|
517
|
+
')': self.new_query, '->': self.left_ftable,
|
518
|
+
'<-': self.right_ftable,
|
519
|
+
}
|
520
|
+
|
521
|
+
def new_query(self, token: str):
|
522
|
+
if token.isidentifier():
|
523
|
+
self.queries.append( self.class_type(token) )
|
524
|
+
|
525
|
+
def add_where(self, token: str):
|
526
|
+
field, *condition = [
|
527
|
+
t for t in self.REGEX['condition'].split(token) if t
|
528
|
+
]
|
529
|
+
Where(' '.join(condition)).add(field, self.queries[-1])
|
530
|
+
|
531
|
+
def add_order(self, token: str):
|
532
|
+
FieldList(token, [Field, OrderBy]).add('', self.queries[-1])
|
533
|
+
|
534
|
+
def add_field(self, token: str):
|
535
|
+
FieldList(token, [Field]).add('', self.queries[-1])
|
536
|
+
|
537
|
+
def left_ftable(self, token: str):
|
538
|
+
self.new_query(token)
|
539
|
+
self.join_type = JoinType.LEFT
|
540
|
+
|
541
|
+
def right_ftable(self, token: str):
|
542
|
+
self.new_query(token)
|
543
|
+
self.join_type = JoinType.RIGHT
|
544
|
+
|
545
|
+
def add_foreign_key(self, token: str):
|
546
|
+
curr, last = [self.queries[i] for i in (-1, -2)]
|
547
|
+
pk_field = last.values[SELECT][-1].split('.')[-1]
|
548
|
+
last.delete(pk_field, [SELECT])
|
549
|
+
if self.join_type == JoinType.RIGHT:
|
550
|
+
curr, last = last, curr
|
551
|
+
pk_field, token = token, pk_field
|
552
|
+
last.key_field = pk_field
|
553
|
+
k = ForeignKey.get_key(last, curr)
|
554
|
+
ForeignKey.references[k] = (pk_field, token)
|
555
|
+
self.join_type = JoinType.INNER
|
556
|
+
|
557
|
+
def eval(self, txt: str):
|
558
|
+
self.join_type = JoinType.INNER
|
559
|
+
self.method = self.new_query
|
560
|
+
for token in self.REGEX['separator'].split( re.sub(r'\s+', '', txt) ):
|
561
|
+
if not token:
|
562
|
+
continue
|
563
|
+
if self.method:
|
564
|
+
self.method(token)
|
565
|
+
if token == '(' and self.join_type != JoinType.INNER:
|
566
|
+
self.method = self.add_foreign_key
|
567
|
+
else:
|
568
|
+
self.method = self.TOKEN_METHODS.get(token)
|
569
|
+
|
416
570
|
|
417
571
|
class JoinType(Enum):
|
418
572
|
INNER = ''
|
@@ -504,73 +658,8 @@ class Select(SQLObject):
|
|
504
658
|
return re.findall(f'\b*{self.alias}[.]', expr) != []
|
505
659
|
|
506
660
|
@classmethod
|
507
|
-
def parse(cls, txt: str) -> list[SQLObject]:
|
508
|
-
|
509
|
-
SPACE, WORD = 1, 2
|
510
|
-
found = set()
|
511
|
-
for i in range(pos, 0, -1):
|
512
|
-
if txt[i] in [' ', '\t', '\n']:
|
513
|
-
if sum(found) == 3:
|
514
|
-
return i
|
515
|
-
found.add(SPACE)
|
516
|
-
if txt[i].isalpha():
|
517
|
-
found.add(WORD)
|
518
|
-
elif txt[i] == '.':
|
519
|
-
found.remove(WORD)
|
520
|
-
def find_parenthesis(pos: int) -> int:
|
521
|
-
for i in range(pos, len(txt)-1):
|
522
|
-
if txt[i] == ')':
|
523
|
-
return i+1
|
524
|
-
if not cls.REGEX:
|
525
|
-
keywords = '|'.join(k + r'\b' for k in KEYWORD)
|
526
|
-
flags = re.IGNORECASE + re.MULTILINE
|
527
|
-
cls.REGEX['keywords'] = re.compile(f'({keywords}|[*])', flags)
|
528
|
-
cls.REGEX['subquery'] = re.compile(r'(\w\.)*\w+ +in +\(SELECT.*?\)', flags)
|
529
|
-
result = {}
|
530
|
-
found = cls.REGEX['subquery'].search(txt)
|
531
|
-
while found:
|
532
|
-
start, end = found.span()
|
533
|
-
inner = txt[start: end]
|
534
|
-
if inner.count('(') > inner.count(')'):
|
535
|
-
end = find_parenthesis(end)
|
536
|
-
inner = txt[start: end-1]
|
537
|
-
fld, *inner = re.split(r' IN | in', inner, maxsplit=1)
|
538
|
-
if fld.upper() == 'NOT':
|
539
|
-
pos = find_last_word(start)
|
540
|
-
fld = txt[pos: start].strip() # [To-Do] Use the value of `fld`
|
541
|
-
start = pos
|
542
|
-
class_type = NotSelectIN
|
543
|
-
else:
|
544
|
-
class_type = SelectIN
|
545
|
-
obj = class_type.parse(
|
546
|
-
' '.join(re.sub(r'^\(', '', s.strip()) for s in inner)
|
547
|
-
)[0]
|
548
|
-
result[obj.alias] = obj
|
549
|
-
txt = txt[:start-1] + txt[end+1:]
|
550
|
-
found = cls.REGEX['subquery'].search(txt)
|
551
|
-
tokens = [t.strip() for t in cls.REGEX['keywords'].split(txt) if t.strip()]
|
552
|
-
values = {k.upper(): v for k, v in zip(tokens[::2], tokens[1::2])}
|
553
|
-
tables = [t.strip() for t in re.split('JOIN|LEFT|RIGHT|ON', values[FROM]) if t.strip()]
|
554
|
-
for item in tables:
|
555
|
-
if '=' in item:
|
556
|
-
a1, f1, a2, f2 = [r.strip() for r in re.split('[().=]', item) if r]
|
557
|
-
obj1: SQLObject = result[a1]
|
558
|
-
obj2: SQLObject = result[a2]
|
559
|
-
PrimaryKey.add(f2, obj2)
|
560
|
-
ForeignKey(obj2.table_name).add(f1, obj1)
|
561
|
-
else:
|
562
|
-
obj = cls(item)
|
563
|
-
for key in USUAL_KEYS:
|
564
|
-
if not key in values:
|
565
|
-
continue
|
566
|
-
separator = cls.get_separator(key)
|
567
|
-
obj.values[key] = [
|
568
|
-
Field.format(fld, obj)
|
569
|
-
for fld in re.split(separator, values[key])
|
570
|
-
if (fld != '*' and len(tables) == 1) or obj.match(fld)
|
571
|
-
]
|
572
|
-
result[obj.alias] = obj
|
573
|
-
return list( result.values() )
|
661
|
+
def parse(cls, txt: str, parser: Parser = SQLParser) -> list[SQLObject]:
|
662
|
+
return parser(txt, cls).queries
|
574
663
|
|
575
664
|
def optimize(self, rules: list[Rule]=None):
|
576
665
|
if not rules:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: sql_blocks
|
3
|
-
Version: 0.2.
|
3
|
+
Version: 0.2.3
|
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
|
@@ -0,0 +1,7 @@
|
|
1
|
+
sql_blocks/__init__.py,sha256=5ItzGCyqqa6kwY8wvF9kapyHsAiWJ7KEXCcC-OtdXKg,37
|
2
|
+
sql_blocks/sql_blocks.py,sha256=xWP7coiy5Tj8YU_WgtkN8J8tc9-0_BaE-bYljPdEp6o,24361
|
3
|
+
sql_blocks-0.2.3.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
|
4
|
+
sql_blocks-0.2.3.dist-info/METADATA,sha256=0bRpoB8KehBZ40kf-pXyOvrVcozTUZo-BsC3Aq5xn2I,8892
|
5
|
+
sql_blocks-0.2.3.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
6
|
+
sql_blocks-0.2.3.dist-info/top_level.txt,sha256=57AbUvUjYNy4m1EqDaU3WHeP-uyIAfV0n8GAUp1a1YQ,11
|
7
|
+
sql_blocks-0.2.3.dist-info/RECORD,,
|
@@ -1,7 +0,0 @@
|
|
1
|
-
sql_blocks/__init__.py,sha256=5ItzGCyqqa6kwY8wvF9kapyHsAiWJ7KEXCcC-OtdXKg,37
|
2
|
-
sql_blocks/sql_blocks.py,sha256=uzMAPjx1d7CAb5WLoel_RcC91Onx3cFVQzGaww-VYEc,21552
|
3
|
-
sql_blocks-0.2.2.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
|
4
|
-
sql_blocks-0.2.2.dist-info/METADATA,sha256=izh5P3AG_LTwdBJyqpPwntbFXVlolK2kmhVKPmr_6dk,8892
|
5
|
-
sql_blocks-0.2.2.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
6
|
-
sql_blocks-0.2.2.dist-info/top_level.txt,sha256=57AbUvUjYNy4m1EqDaU3WHeP-uyIAfV0n8GAUp1a1YQ,11
|
7
|
-
sql_blocks-0.2.2.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|