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 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
- StartsWith = 1
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
- Where.gte(self.start).add(name, main),
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 __add__(self, other: SQLObject):
1501
+ def copy(self) -> SQLObject:
1477
1502
  from copy import deepcopy
1478
- query = deepcopy(self)
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
- raise ValueError(f'No relationship found between {query.table_name} and {other.table_name}.')
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.420021312
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,,