sql-blocks 1.25.420021312__py3-none-any.whl → 1.25.51999999999__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 +56 -36
- {sql_blocks-1.25.420021312.dist-info → sql_blocks-1.25.51999999999.dist-info}/METADATA +54 -1
- sql_blocks-1.25.51999999999.dist-info/RECORD +7 -0
- sql_blocks-1.25.420021312.dist-info/RECORD +0 -7
- {sql_blocks-1.25.420021312.dist-info → sql_blocks-1.25.51999999999.dist-info}/LICENSE +0 -0
- {sql_blocks-1.25.420021312.dist-info → sql_blocks-1.25.51999999999.dist-info}/WHEEL +0 -0
- {sql_blocks-1.25.420021312.dist-info → sql_blocks-1.25.51999999999.dist-info}/top_level.txt +0 -0
sql_blocks/sql_blocks.py
CHANGED
@@ -510,14 +510,16 @@ class ForeignKey:
|
|
510
510
|
|
511
511
|
def quoted(value) -> str:
|
512
512
|
if isinstance(value, str):
|
513
|
+
if re.search(r'\bor\b', value, re.IGNORECASE):
|
514
|
+
raise PermissionError('Possible SQL injection attempt')
|
513
515
|
value = f"'{value}'"
|
514
516
|
return str(value)
|
515
517
|
|
516
518
|
|
517
519
|
class Position(Enum):
|
520
|
+
StartsWith = -1
|
518
521
|
Middle = 0
|
519
|
-
|
520
|
-
EndsWith = 2
|
522
|
+
EndsWith = 1
|
521
523
|
|
522
524
|
|
523
525
|
class Where:
|
@@ -535,7 +537,9 @@ class Where:
|
|
535
537
|
return cls.__constructor('=', value)
|
536
538
|
|
537
539
|
@classmethod
|
538
|
-
def contains(cls, text: str, pos: Position = Position.Middle):
|
540
|
+
def contains(cls, text: str, pos: int | Position = Position.Middle):
|
541
|
+
if isinstance(pos, int):
|
542
|
+
pos = Position(pos)
|
539
543
|
return cls(
|
540
544
|
"LIKE '{}{}{}'".format(
|
541
545
|
'%' if pos != Position.StartsWith else '',
|
@@ -652,7 +656,7 @@ class Case:
|
|
652
656
|
'\n'.join(
|
653
657
|
f'\t\tWHEN {field} {cond.content} THEN {res}'
|
654
658
|
for res, cond in self.__conditions.items()
|
655
|
-
) + f'\n\t\tELSE {default}' if default else '',
|
659
|
+
) + (f'\n\t\tELSE {default}' if default else ''),
|
656
660
|
name
|
657
661
|
)
|
658
662
|
main.values.setdefault(SELECT, []).append(name)
|
@@ -677,14 +681,23 @@ class Options:
|
|
677
681
|
|
678
682
|
|
679
683
|
class Between:
|
684
|
+
is_literal: bool = False
|
685
|
+
|
680
686
|
def __init__(self, start, end):
|
681
687
|
if start > end:
|
682
688
|
start, end = end, start
|
683
689
|
self.start = start
|
684
690
|
self.end = end
|
685
691
|
|
692
|
+
def literal(self) -> Where:
|
693
|
+
return Where('BETWEEN {} AND {}'.format(
|
694
|
+
self.start, self.end
|
695
|
+
))
|
696
|
+
|
686
697
|
def add(self, name: str, main:SQLObject):
|
687
|
-
|
698
|
+
if self.is_literal:
|
699
|
+
return self.literal().add(name, main)
|
700
|
+
Where.gte(self.start).add(name, main)
|
688
701
|
Where.lte(self.end).add(name, main)
|
689
702
|
|
690
703
|
class SameDay(Between):
|
@@ -695,6 +708,19 @@ class SameDay(Between):
|
|
695
708
|
)
|
696
709
|
|
697
710
|
|
711
|
+
class Range(Case):
|
712
|
+
INC_FUNCTION = lambda x: x + 1
|
713
|
+
|
714
|
+
def __init__(self, field: str, values: dict):
|
715
|
+
super().__init__(field)
|
716
|
+
start = 0
|
717
|
+
cls = self.__class__
|
718
|
+
for label, value in sorted(values.items(), key=lambda item: item[1]):
|
719
|
+
self.when(
|
720
|
+
Between(start, value).literal(), label
|
721
|
+
)
|
722
|
+
start = cls.INC_FUNCTION(value)
|
723
|
+
|
698
724
|
|
699
725
|
class Clause:
|
700
726
|
@classmethod
|
@@ -1440,7 +1466,6 @@ class MongoParser(Parser):
|
|
1440
1466
|
|
1441
1467
|
class Select(SQLObject):
|
1442
1468
|
join_type: JoinType = JoinType.INNER
|
1443
|
-
REGEX = {}
|
1444
1469
|
EQUIVALENT_NAMES = {}
|
1445
1470
|
|
1446
1471
|
def __init__(self, table_name: str='', **values):
|
@@ -1473,9 +1498,15 @@ class Select(SQLObject):
|
|
1473
1498
|
for key in USUAL_KEYS:
|
1474
1499
|
main.update_values(key, self.values.get(key, []))
|
1475
1500
|
|
1476
|
-
def
|
1501
|
+
def copy(self) -> SQLObject:
|
1477
1502
|
from copy import deepcopy
|
1478
|
-
|
1503
|
+
return deepcopy(self)
|
1504
|
+
|
1505
|
+
def no_relation_error(self, other: SQLObject):
|
1506
|
+
raise ValueError(f'No relationship found between {self.table_name} and {other.table_name}.')
|
1507
|
+
|
1508
|
+
def __add__(self, other: SQLObject):
|
1509
|
+
query = self.copy()
|
1479
1510
|
if query.table_name.lower() == other.table_name.lower():
|
1480
1511
|
for key in USUAL_KEYS:
|
1481
1512
|
query.update_values(key, other.values.get(key, []))
|
@@ -1488,7 +1519,7 @@ class Select(SQLObject):
|
|
1488
1519
|
PrimaryKey.add(primary_key, query)
|
1489
1520
|
query.add(foreign_field, other)
|
1490
1521
|
return other
|
1491
|
-
|
1522
|
+
self.no_relation_error(other) # === raise ERROR ... ===
|
1492
1523
|
elif primary_key:
|
1493
1524
|
PrimaryKey.add(primary_key, other)
|
1494
1525
|
other.add(foreign_field, query)
|
@@ -1508,6 +1539,22 @@ class Select(SQLObject):
|
|
1508
1539
|
if self.diff(key, other.values.get(key, []), True):
|
1509
1540
|
return False
|
1510
1541
|
return True
|
1542
|
+
|
1543
|
+
def __sub__(self, other: SQLObject) -> SQLObject:
|
1544
|
+
fk_field, primary_k = ForeignKey.find(self, other)
|
1545
|
+
if fk_field:
|
1546
|
+
query = self.copy()
|
1547
|
+
other = other.copy()
|
1548
|
+
else:
|
1549
|
+
fk_field, primary_k = ForeignKey.find(other, self)
|
1550
|
+
if not fk_field:
|
1551
|
+
self.no_relation_error(other) # === raise ERROR ... ===
|
1552
|
+
query = other.copy()
|
1553
|
+
other = self.copy()
|
1554
|
+
query.__class__ = NotSelectIN
|
1555
|
+
Field.add(fk_field, query)
|
1556
|
+
query.add(primary_k, other)
|
1557
|
+
return other
|
1511
1558
|
|
1512
1559
|
def limit(self, row_count: int=100, offset: int=0):
|
1513
1560
|
if Function.dialect == Dialect.SQL_SERVER:
|
@@ -1799,30 +1846,3 @@ def detect(text: str, join_queries: bool = True, format: str='') -> Select | lis
|
|
1799
1846
|
result += query
|
1800
1847
|
return result
|
1801
1848
|
# ===========================================================================================//
|
1802
|
-
|
1803
|
-
|
1804
|
-
if __name__ == "__main__":
|
1805
|
-
query = Select(
|
1806
|
-
'Matricula M',
|
1807
|
-
aluno_id=Select(
|
1808
|
-
'Aluno A',
|
1809
|
-
nome=NamedField('nome_aluno'),
|
1810
|
-
id=PrimaryKey
|
1811
|
-
),
|
1812
|
-
curso=Select(
|
1813
|
-
'Curso C',
|
1814
|
-
nome=NamedField('nome_curso'),
|
1815
|
-
id=PrimaryKey
|
1816
|
-
)
|
1817
|
-
)
|
1818
|
-
print('='*50)
|
1819
|
-
print(query)
|
1820
|
-
print('-'*50)
|
1821
|
-
m, c, a = Select.parse( str(query) )
|
1822
|
-
m(inicio=[Not.gt('2024-11-15'), Field])
|
1823
|
-
query = m + a
|
1824
|
-
print(query)
|
1825
|
-
print('-'*50)
|
1826
|
-
query.optimize()
|
1827
|
-
print(query)
|
1828
|
-
print('='*50)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: sql_blocks
|
3
|
-
Version: 1.25.
|
3
|
+
Version: 1.25.51999999999
|
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
|
@@ -15,6 +15,10 @@ License-File: LICENSE
|
|
15
15
|
|
16
16
|
# SQL_Blocks
|
17
17
|
|
18
|
+
## _SQL_Blocks_ is useful for building complex SQL commands through smaller query blocks:
|
19
|
+
|
20
|
+
---
|
21
|
+
|
18
22
|
### 1 - You can assemble a simple object that will then be converted into an SQL command:
|
19
23
|
|
20
24
|
> a = Select('Actor') # --> SELECT * FROM Actor act
|
@@ -315,6 +319,30 @@ m = Select...
|
|
315
319
|
>> **m + c => Ok!**
|
316
320
|
|
317
321
|
|
322
|
+
---
|
323
|
+
**8.3 Difference between queries**
|
324
|
+
```
|
325
|
+
STATUS_DELIVERED_OK = 93
|
326
|
+
orders = Select('orders',
|
327
|
+
customer_id=ForeignKey('customers'),
|
328
|
+
status=eq(STATUS_DELIVERED_OK)
|
329
|
+
)
|
330
|
+
customers = Select('customers'
|
331
|
+
id=PrimaryKey, name=Field
|
332
|
+
)
|
333
|
+
gap = orders - customers
|
334
|
+
```
|
335
|
+
return _customers without orders_:
|
336
|
+
|
337
|
+
SELECT
|
338
|
+
c.name
|
339
|
+
FROM
|
340
|
+
customers c
|
341
|
+
WHERE
|
342
|
+
NOT c.id IN (
|
343
|
+
SELECT o.customer_id FROM orders o
|
344
|
+
WHERE o.status = 93
|
345
|
+
)
|
318
346
|
---
|
319
347
|
|
320
348
|
### 9 - Comparing objects
|
@@ -387,6 +415,31 @@ m2 = Select(
|
|
387
415
|
)
|
388
416
|
)
|
389
417
|
|
418
|
+
10.1 - If the labels used in the CASE are based on ranges of values in sequence, you can use the **Range class**:
|
419
|
+
|
420
|
+
query = Select(
|
421
|
+
'People p',
|
422
|
+
age_group=Range('age',{ # <<----------
|
423
|
+
'adult': 50,
|
424
|
+
'teenager': 17,
|
425
|
+
'child': 10,
|
426
|
+
'elderly': 70,
|
427
|
+
'young': 21,
|
428
|
+
})
|
429
|
+
)
|
430
|
+
is equivalent to...
|
431
|
+
```
|
432
|
+
SELECT
|
433
|
+
CASE
|
434
|
+
WHEN p.age BETWEEN 0 AND 10 THEN 'child'
|
435
|
+
WHEN p.age BETWEEN 11 AND 17 THEN 'teenager'
|
436
|
+
WHEN p.age BETWEEN 18 AND 21 THEN 'young'
|
437
|
+
WHEN p.age BETWEEN 22 AND 50 THEN 'adult'
|
438
|
+
WHEN p.age BETWEEN 51 AND 70 THEN 'elderly'
|
439
|
+
END AS age_group
|
440
|
+
FROM
|
441
|
+
People p
|
442
|
+
```
|
390
443
|
---
|
391
444
|
|
392
445
|
### 11 - optimize method
|
@@ -0,0 +1,7 @@
|
|
1
|
+
sql_blocks/__init__.py,sha256=5ItzGCyqqa6kwY8wvF9kapyHsAiWJ7KEXCcC-OtdXKg,37
|
2
|
+
sql_blocks/sql_blocks.py,sha256=ZdCFtPShmn-nHrE2tpJCWMnJYmPsc742CIkrPc_hSs4,61854
|
3
|
+
sql_blocks-1.25.51999999999.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
|
4
|
+
sql_blocks-1.25.51999999999.dist-info/METADATA,sha256=ZK0V4KW5v8VtqFML82WFBbN_NpDN7iHbGjMo09fiRbc,22241
|
5
|
+
sql_blocks-1.25.51999999999.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
6
|
+
sql_blocks-1.25.51999999999.dist-info/top_level.txt,sha256=57AbUvUjYNy4m1EqDaU3WHeP-uyIAfV0n8GAUp1a1YQ,11
|
7
|
+
sql_blocks-1.25.51999999999.dist-info/RECORD,,
|
@@ -1,7 +0,0 @@
|
|
1
|
-
sql_blocks/__init__.py,sha256=5ItzGCyqqa6kwY8wvF9kapyHsAiWJ7KEXCcC-OtdXKg,37
|
2
|
-
sql_blocks/sql_blocks.py,sha256=WkFmxphTnqT74MgGX6JCKLMRmxwDPGXdDmZ8DU-qObs,60834
|
3
|
-
sql_blocks-1.25.420021312.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
|
4
|
-
sql_blocks-1.25.420021312.dist-info/METADATA,sha256=qCiTXUBNpwkwBGWXLROxIVo2tz5XgLr5yLJeoYQKAmQ,20708
|
5
|
-
sql_blocks-1.25.420021312.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
6
|
-
sql_blocks-1.25.420021312.dist-info/top_level.txt,sha256=57AbUvUjYNy4m1EqDaU3WHeP-uyIAfV0n8GAUp1a1YQ,11
|
7
|
-
sql_blocks-1.25.420021312.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|