sql-blocks 1.25.33022258__py3-none-any.whl → 1.25.38021329__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
@@ -82,8 +82,14 @@ class SQLObject:
82
82
  return KEYWORD[key][0].format(appendix.get(key, ''))
83
83
 
84
84
  @staticmethod
85
- def is_named_field(fld: str, key: str) -> bool:
86
- return key == SELECT and re.search(r'\s+as\s+|\s+AS\s+', fld)
85
+ def is_named_field(fld: str, name: str='') -> bool:
86
+ return re.search(fr'(\s+as\s+|\s+AS\s+){name}', fld)
87
+
88
+ def has_named_field(self, name: str) -> bool:
89
+ return any(
90
+ self.is_named_field(fld, name)
91
+ for fld in self.values.get(SELECT, [])
92
+ )
87
93
 
88
94
  def diff(self, key: str, search_list: list, exact: bool=False) -> set:
89
95
  def disassemble(source: list) -> list:
@@ -100,7 +106,9 @@ class SQLObject:
100
106
  def field_set(source: list) -> set:
101
107
  return set(
102
108
  (
103
- fld if self.is_named_field(fld, key) else
109
+ fld
110
+ if key == SELECT and self.is_named_field(fld, key)
111
+ else
104
112
  re.sub(pattern, '', cleanup(fld))
105
113
  )
106
114
  for string in disassemble(source)
@@ -150,7 +158,7 @@ class Field:
150
158
  name = name.strip()
151
159
  if name in ('_', '*'):
152
160
  name = '*'
153
- elif not is_const():
161
+ elif not is_const() and not main.has_named_field(name):
154
162
  name = f'{main.alias}.{name}'
155
163
  if Function in cls.__bases__:
156
164
  name = f'{cls.__name__}({name})'
@@ -188,15 +196,35 @@ class Dialect(Enum):
188
196
  POSTGRESQL = 3
189
197
  MYSQL = 4
190
198
 
199
+ SQL_TYPES = 'CHAR INT DATE FLOAT ANY'.split()
200
+ CHAR, INT, DATE, FLOAT, ANY = SQL_TYPES
201
+
191
202
  class Function:
192
203
  dialect = Dialect.ANSI
204
+ inputs = None
205
+ output = None
206
+ separator = ', '
207
+ auto_convert = True
208
+ append_param = False
193
209
 
194
210
  def __init__(self, *params: list):
211
+ def set_func_types(param):
212
+ if self.auto_convert and isinstance(param, Function):
213
+ func = param
214
+ main_param = self.inputs[0]
215
+ unfriendly = all([
216
+ func.output != main_param,
217
+ func.output != ANY,
218
+ main_param != ANY
219
+ ])
220
+ if unfriendly:
221
+ return Cast(func, main_param)
222
+ return param
195
223
  # --- Replace class methods by instance methods: ------
196
224
  self.add = self.__add
197
225
  self.format = self.__format
198
226
  # -----------------------------------------------------
199
- self.params = [str(p) for p in params]
227
+ self.params = [set_func_types(p) for p in params]
200
228
  self.field_class = Field
201
229
  self.pattern = self.get_pattern()
202
230
  self.extra = {}
@@ -213,14 +241,26 @@ class Function:
213
241
  def __str__(self) -> str:
214
242
  return self.pattern.format(
215
243
  func_name=self.__class__.__name__,
216
- params=', '.join(self.params)
244
+ params=self.separator.join(str(p) for p in self.params)
217
245
  )
218
246
 
247
+ def set_main_param(self, name: str, main: SQLObject) -> bool:
248
+ nested_functions = [
249
+ param for param in self.params if isinstance(param, Function)
250
+ ]
251
+ for func in nested_functions:
252
+ if func.inputs:
253
+ func.set_main_param(name, main)
254
+ return
255
+ new_params = [Field.format(name, main)]
256
+ if self.append_param:
257
+ self.params += new_params
258
+ else:
259
+ self.params = new_params + self.params
260
+
219
261
  def __format(self, name: str, main: SQLObject) -> str:
220
262
  if name not in '*_':
221
- self.params = [
222
- Field.format(name, main)
223
- ] + self.params
263
+ self.set_main_param(name, main)
224
264
  return str(self)
225
265
 
226
266
  @classmethod
@@ -240,6 +280,9 @@ class Function:
240
280
 
241
281
  # ---- String Functions: ---------------------------------
242
282
  class SubString(Function):
283
+ inputs = [CHAR, INT, INT]
284
+ output = CHAR
285
+
243
286
  def get_pattern(self) -> str:
244
287
  if self.dialect in (Dialect.ORACLE, Dialect.MYSQL):
245
288
  return 'Substr({params})'
@@ -247,31 +290,55 @@ class SubString(Function):
247
290
 
248
291
  # ---- Numeric Functions: --------------------------------
249
292
  class Round(Function):
250
- ...
293
+ inputs = [FLOAT]
294
+ output = FLOAT
251
295
 
252
296
  # --- Date Functions: ------------------------------------
253
297
  class DateDiff(Function):
254
- def get_pattern(self) -> str:
298
+ inputs = [DATE]
299
+ output = DATE
300
+ append_param = True
301
+
302
+ def __str__(self) -> str:
255
303
  def is_field_or_func(name: str) -> bool:
256
- return re.sub('[()]', '', name).isidentifier()
304
+ candidate = re.sub(
305
+ '[()]', '', name.split('.')[-1]
306
+ )
307
+ return candidate.isidentifier()
257
308
  if self.dialect != Dialect.SQL_SERVER:
309
+ params = [str(p) for p in self.params]
258
310
  return ' - '.join(
259
311
  p if is_field_or_func(p) else f"'{p}'"
260
- for p in self.params
312
+ for p in params
261
313
  ) # <==== Date subtract
262
- return super().get_pattern()
314
+ return super().__str__()
315
+
316
+
317
+ class DatePart(Function):
318
+ inputs = [DATE]
319
+ output = INT
263
320
 
264
- class Year(Function):
265
321
  def get_pattern(self) -> str:
322
+ interval = self.__class__.__name__
266
323
  database_type = {
267
- Dialect.ORACLE: 'Extract(YEAR FROM {params})',
268
- Dialect.POSTGRESQL: "Date_Part('year', {params})",
324
+ Dialect.ORACLE: 'Extract('+interval+' FROM {params})',
325
+ Dialect.POSTGRESQL: "Date_Part('"+interval+"', {params})",
269
326
  }
270
327
  if self.dialect in database_type:
271
328
  return database_type[self.dialect]
272
329
  return super().get_pattern()
273
330
 
331
+ class Year(DatePart):
332
+ ...
333
+ class Month(DatePart):
334
+ ...
335
+ class Day(DatePart):
336
+ ...
337
+
338
+
274
339
  class Current_Date(Function):
340
+ output = DATE
341
+
275
342
  def get_pattern(self) -> str:
276
343
  database_type = {
277
344
  Dialect.ORACLE: SQL_CONST_SYSDATE,
@@ -341,9 +408,13 @@ class Lead(Window, Function):
341
408
 
342
409
  # ---- Conversions and other Functions: ---------------------
343
410
  class Coalesce(Function):
344
- ...
411
+ inputs = [ANY]
412
+ output = ANY
413
+
345
414
  class Cast(Function):
346
- ...
415
+ inputs = [ANY]
416
+ output = ANY
417
+ separator = ' As '
347
418
 
348
419
 
349
420
  FUNCTION_CLASS = {f.__name__.lower(): f for f in Function.__subclasses__()}
@@ -516,14 +587,9 @@ class Where:
516
587
 
517
588
  def add(self, name: str, main: SQLObject):
518
589
  func_type = FUNCTION_CLASS.get(name.lower())
519
- exists = any(
520
- main.is_named_field(fld, SELECT)
521
- for fld in main.values.get(SELECT, [])
522
- if name in fld
523
- )
524
590
  if func_type:
525
591
  name = func_type.format('*', main)
526
- elif not exists:
592
+ elif not main.has_named_field(name):
527
593
  name = Field.format(name, main)
528
594
  main.values.setdefault(WHERE, []).append('{}{} {}'.format(
529
595
  self.prefix, name, self.content
@@ -604,6 +670,14 @@ class Between:
604
670
  Where.gte(self.start).add(name, main),
605
671
  Where.lte(self.end).add(name, main)
606
672
 
673
+ class SameDay(Between):
674
+ def __init__(self, date: str):
675
+ super().__init__(
676
+ f'{date} 00:00:00',
677
+ f'{date} 23:59:59',
678
+ )
679
+
680
+
607
681
 
608
682
  class Clause:
609
683
  @classmethod
@@ -1438,11 +1512,7 @@ class Select(SQLObject):
1438
1512
  Recognizes if the field is from the current table
1439
1513
  '''
1440
1514
  if key in (ORDER_BY, GROUP_BY) and '.' not in field:
1441
- return any(
1442
- self.is_named_field(fld, SELECT)
1443
- for fld in self.values[SELECT]
1444
- if field in fld
1445
- )
1515
+ return main.has_named_field(field)
1446
1516
  return re.findall(f'\b*{self.alias}[.]', field) != []
1447
1517
 
1448
1518
  @classmethod
@@ -1455,12 +1525,10 @@ class Select(SQLObject):
1455
1525
  for rule in rules:
1456
1526
  rule.apply(self)
1457
1527
 
1458
- def add_fields(self, fields: list, order_by: bool=False, group_by:bool=False):
1459
- class_types = [Field]
1460
- if order_by:
1461
- class_types += [OrderBy]
1462
- if group_by:
1463
- class_types += [GroupBy]
1528
+ def add_fields(self, fields: list, class_types=None):
1529
+ if not class_types:
1530
+ class_types = []
1531
+ class_types += [Field]
1464
1532
  FieldList(fields, class_types).add('', self)
1465
1533
 
1466
1534
  def translate_to(self, language: QueryLanguage) -> str:
@@ -1491,12 +1559,17 @@ class CTE(Select):
1491
1559
  self.break_lines = False
1492
1560
 
1493
1561
  def __str__(self) -> str:
1562
+ size = 0
1563
+ for key in USUAL_KEYS:
1564
+ size += sum(len(v) for v in self.values.get(key, []) if '\n' not in v)
1565
+ if size > 70:
1566
+ self.break_lines = True
1494
1567
  # ---------------------------------------------------------
1495
1568
  def justify(query: Select) -> str:
1496
1569
  result, line = [], ''
1497
1570
  keywords = '|'.join(KEYWORD)
1498
1571
  for word in re.split(fr'({keywords}|AND|OR|,)', str(query)):
1499
- if len(line) >= 65:
1572
+ if len(line) >= 60:
1500
1573
  result.append(line)
1501
1574
  line = ''
1502
1575
  line += word
@@ -1510,6 +1583,7 @@ class CTE(Select):
1510
1583
  justify(q) for q in self.query_list
1511
1584
  ), super().__str__()
1512
1585
  )
1586
+
1513
1587
  def join(self, pattern: str, fields: list | str, format: str=''):
1514
1588
  if isinstance(fields, str):
1515
1589
  count = len( fields.split(',') )
@@ -1702,11 +1776,4 @@ def detect(text: str, join_queries: bool = True, format: str='') -> Select | lis
1702
1776
  for query in query_list[1:]:
1703
1777
  result += query
1704
1778
  return result
1705
-
1706
-
1707
- if __name__ == "__main__":
1708
- CAMPO_MEDIA = 'MEDIA_SALARIAL_DEPTO'
1709
- employees = detect(
1710
- f'Employees@department_id(avg$salary:{CAMPO_MEDIA})'
1711
- )
1712
- print(employees)
1779
+ # ===========================================================================================//
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sql_blocks
3
- Version: 1.25.33022258
3
+ Version: 1.25.38021329
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
@@ -67,6 +67,16 @@ You can specify your own alias: `a = Select('Actor a')`
67
67
  3.1 -- If you want to filter the field on a range of values:
68
68
 
69
69
  `a = Select( 'Actor a', age=Between(45, 69) )`
70
+ ...but if it is a time slot within the same day, you can do it like this:
71
+ `Select(..., event_date=SameDay("2024-10-03"))`
72
+ This results in
73
+ ```
74
+ SELECT ...
75
+ WHERE
76
+ event_date >= '2024-10-03 00:00:00' AND
77
+ event_date <= '2024-10-03 23:59:59'
78
+ ```
79
+ ---
70
80
 
71
81
  3.2 -- Sub-queries:
72
82
  ```
@@ -668,6 +678,37 @@ For example, if your query is going to run on Oracle, do the following:
668
678
 
669
679
  `Function.dialect = Dialect.ORACLE`
670
680
 
681
+
682
+ > Most of this functions you can use nested inside each other.
683
+ *Example:*
684
+ ```
685
+ Select(...
686
+ event_date=Substring(
687
+ Cast("CHAR"), 12, 19
688
+ ).As('time')
689
+ )
690
+ ```
691
+ Results...
692
+ ```
693
+ SELECT ...
694
+ SubString(Cast(event_date As char), 12, 19) as time
695
+ ```
696
+
697
+ >> `Function.auto_convert` option (default: True)
698
+
699
+ - Put Cast(...) when there is a difference between the types of the parameter and the return of the nested function
700
+ ```
701
+ birth=Round( DateDiff(Current_Date()) ).As('age')
702
+ ```
703
+ ...Returns...
704
+ ```
705
+ SELECT
706
+ Round(
707
+ Cast(Current_Date() - p.birth As FLOAT)
708
+ /* ^^^ */
709
+ ) as age
710
+ ...
711
+ ```
671
712
  ---
672
713
 
673
714
  ### 17 - CTE and Recursive classes
@@ -0,0 +1,7 @@
1
+ sql_blocks/__init__.py,sha256=5ItzGCyqqa6kwY8wvF9kapyHsAiWJ7KEXCcC-OtdXKg,37
2
+ sql_blocks/sql_blocks.py,sha256=5LQPEOkvMylJJxoU2m1P6pAhfcMHVdsnTiYgfLLJ5bQ,59477
3
+ sql_blocks-1.25.38021329.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
4
+ sql_blocks-1.25.38021329.dist-info/METADATA,sha256=un6TFbAfNbyhIrjLzHpvG1Yy77jsyZCw2xy_i3tMw2k,20511
5
+ sql_blocks-1.25.38021329.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
6
+ sql_blocks-1.25.38021329.dist-info/top_level.txt,sha256=57AbUvUjYNy4m1EqDaU3WHeP-uyIAfV0n8GAUp1a1YQ,11
7
+ sql_blocks-1.25.38021329.dist-info/RECORD,,
@@ -1,7 +0,0 @@
1
- sql_blocks/__init__.py,sha256=5ItzGCyqqa6kwY8wvF9kapyHsAiWJ7KEXCcC-OtdXKg,37
2
- sql_blocks/sql_blocks.py,sha256=dGmMtLWiyluRp1ZwPplmBqPs1nDOL-vemRR0gi2htWA,57513
3
- sql_blocks-1.25.33022258.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
4
- sql_blocks-1.25.33022258.dist-info/METADATA,sha256=GsTRa0fLkerPn-D-ourudj9Pve0oP69IzQ-YGHMtRnI,19574
5
- sql_blocks-1.25.33022258.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
6
- sql_blocks-1.25.33022258.dist-info/top_level.txt,sha256=57AbUvUjYNy4m1EqDaU3WHeP-uyIAfV0n8GAUp1a1YQ,11
7
- sql_blocks-1.25.33022258.dist-info/RECORD,,