sql-blocks 0.31.21__py3-none-any.whl → 1.25__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
@@ -22,6 +22,7 @@ KEYWORD = {
22
22
 
23
23
  SELECT, FROM, WHERE, GROUP_BY, ORDER_BY, LIMIT = KEYWORD.keys()
24
24
  USUAL_KEYS = [SELECT, WHERE, GROUP_BY, ORDER_BY, LIMIT]
25
+ TO_LIST = lambda x: x if isinstance(x, list) else [x]
25
26
 
26
27
 
27
28
  class SQLObject:
@@ -110,9 +111,6 @@ class SQLObject:
110
111
  self.values[key] = result
111
112
 
112
113
 
113
- class Function:
114
- ...
115
-
116
114
  class Field:
117
115
  prefix = ''
118
116
 
@@ -134,17 +132,6 @@ class Field:
134
132
  )
135
133
 
136
134
 
137
- class Avg(Function, Field):
138
- ...
139
- class Min(Function, Field):
140
- ...
141
- class Max(Function, Field):
142
- ...
143
- class Sum(Function, Field):
144
- ...
145
- class Count(Function, Field):
146
- ...
147
-
148
135
  class Distinct(Field):
149
136
  prefix = 'DISTINCT '
150
137
 
@@ -163,6 +150,107 @@ class NamedField:
163
150
  )
164
151
 
165
152
 
153
+ class Function:
154
+ instance: dict = {}
155
+
156
+ def __init__(self, *params: list):
157
+ func_name = self.__class__.__name__
158
+ Function.instance[func_name] = self
159
+ self.params = [str(p) for p in params]
160
+ self.class_type = Field
161
+ self.pattern = '{}({})'
162
+ self.extra = {}
163
+
164
+ def As(self, field_alias: str, modifiers=None):
165
+ if modifiers:
166
+ for mod in TO_LIST(modifiers):
167
+ self.extra[field_alias] = mod
168
+ self.class_type = NamedField(field_alias)
169
+ return self
170
+
171
+ @classmethod
172
+ def format(cls, name: str, main: SQLObject) -> str:
173
+ obj = cls.get_instance()
174
+ params = [
175
+ Field.format(name, main)
176
+ ] + obj.params
177
+ return obj.pattern.format(
178
+ cls.__name__,
179
+ ', '.join(params)
180
+ )
181
+
182
+ def __add(self, name: str, main: SQLObject):
183
+ name = self.format(name, main)
184
+ self.class_type.add(name, main)
185
+ if self.extra:
186
+ main.__call__(**self.extra)
187
+
188
+ @classmethod
189
+ def get_instance(cls):
190
+ obj = Function.instance.get(cls.__name__)
191
+ if not obj:
192
+ obj = cls()
193
+ return obj
194
+
195
+ @classmethod
196
+ def add(cls, name: str, main: SQLObject):
197
+ cls.get_instance().__add(name, main)
198
+
199
+
200
+ # ---- String Functions: ---------------------------------
201
+ class SubString(Function):
202
+ ...
203
+
204
+ # ---- Numeric Functions: --------------------------------
205
+ class Round(Function):
206
+ ...
207
+
208
+ # --- Date Functions: ------------------------------------
209
+ class DateDiff(Function):
210
+ ...
211
+ class Extract(Function):
212
+ ...
213
+ class DatePart(Function):
214
+ ...
215
+ class Current_Date(Function):
216
+ ...
217
+
218
+ class Aggregate:
219
+ break_lines: bool = True
220
+
221
+ def over(self, **args):
222
+ keywords = ' '.join(
223
+ '{}{} BY {}'.format(
224
+ '\n\t\t' if self.break_lines else '',
225
+ key.upper(), args[key]
226
+ ) for key in ('partition', 'order')
227
+ if key in args
228
+ )
229
+ if keywords and self.break_lines:
230
+ keywords += '\n\t'
231
+ self.pattern = '{}({})' + f' OVER({keywords})'
232
+ return self
233
+
234
+
235
+ # ---- Aggregate Functions: -------------------------------
236
+ class Avg(Aggregate, Function):
237
+ ...
238
+ class Min(Aggregate, Function):
239
+ ...
240
+ class Max(Aggregate, Function):
241
+ ...
242
+ class Sum(Aggregate, Function):
243
+ ...
244
+ class Count(Aggregate, Function):
245
+ ...
246
+
247
+ # ---- Conversions and other Functions: ---------------------
248
+ class Coalesce(Function):
249
+ ...
250
+ class Cast(Function):
251
+ ...
252
+
253
+
166
254
  class ExpressionField:
167
255
  def __init__(self, expr: str):
168
256
  self.expr = expr
@@ -361,26 +449,40 @@ class Between:
361
449
  Where.lte(self.end).add(name, main)
362
450
 
363
451
 
452
+ class Clause:
453
+ @classmethod
454
+ def format(cls, name: str, main: SQLObject) -> str:
455
+ def is_function() -> bool:
456
+ diff = main.diff(SELECT, [name.lower()], True)
457
+ FUNCTION_CLASS = {f.__name__.lower(): f for f in Function.__subclasses__()}
458
+ return diff.intersection(FUNCTION_CLASS)
459
+ found = re.findall(r'^_\d', name)
460
+ if found:
461
+ name = found[0].replace('_', '')
462
+ elif main.alias and not is_function():
463
+ name = f'{main.alias}.{name}'
464
+ return name
465
+
466
+
364
467
  class SortType(Enum):
365
468
  ASC = ''
366
469
  DESC = ' DESC'
367
470
 
368
- class OrderBy:
471
+
472
+ class OrderBy(Clause):
369
473
  sort: SortType = SortType.ASC
474
+
370
475
  @classmethod
371
476
  def add(cls, name: str, main: SQLObject):
372
- found = re.findall(r'^_\d', name)
373
- if found:
374
- name = found[0].replace('_', '')
375
- elif main.alias:
376
- name = f'{main.alias}.{name}'
477
+ name = cls.format(name, main)
377
478
  main.values.setdefault(ORDER_BY, []).append(name+cls.sort.value)
378
479
 
379
480
 
380
- class GroupBy:
381
- @staticmethod
382
- def add(name: str, main: SQLObject):
383
- main.values.setdefault(GROUP_BY, []).append(f'{main.alias}.{name}')
481
+ class GroupBy(Clause):
482
+ @classmethod
483
+ def add(cls, name: str, main: SQLObject):
484
+ name = cls.format(name, main)
485
+ main.values.setdefault(GROUP_BY, []).append(name)
384
486
 
385
487
 
386
488
  class Having:
@@ -858,7 +960,11 @@ class CypherParser(Parser):
858
960
  else:
859
961
  if not curr.values.get(SELECT):
860
962
  raise IndexError(f'Foreign Key not found for {curr.table_name}.')
861
- foreign_fld = curr.values[SELECT][0].split('.')[-1]
963
+ fields = [
964
+ fld for fld in curr.values[SELECT]
965
+ if fld not in curr.values.get(GROUP_BY, [])
966
+ ]
967
+ foreign_fld = fields[0].split('.')[-1]
862
968
  curr.delete(foreign_fld, [SELECT])
863
969
  if curr.join_type == JoinType.RIGHT:
864
970
  pk_field, foreign_fld = foreign_fld, pk_field
@@ -1087,9 +1193,8 @@ class Select(SQLObject):
1087
1193
  return self.translate_to(QueryLanguage)
1088
1194
 
1089
1195
  def __call__(self, **values):
1090
- to_list = lambda x: x if isinstance(x, list) else [x]
1091
1196
  for name, params in values.items():
1092
- for obj in to_list(params):
1197
+ for obj in TO_LIST(params):
1093
1198
  obj.add(name, self)
1094
1199
  return self
1095
1200
 
@@ -1252,15 +1357,3 @@ def detect(text: str) -> Select:
1252
1357
  for query in query_list[1:]:
1253
1358
  result += query
1254
1359
  return result
1255
-
1256
-
1257
- if __name__ == "__main__":
1258
- print('@'*100)
1259
- print( detect(
1260
- '''
1261
- Company(?jobs > 10, id)
1262
- <- Person(work_at ^name, friend_of) ->
1263
- Person(id ?skill="pANDas")
1264
- ''' # ^^^---- Test for confusion with the AND operator
1265
- ) )
1266
- print('@'*100)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sql_blocks
3
- Version: 0.31.21
3
+ Version: 1.25
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
@@ -390,7 +390,7 @@ a, c, m = Select.parse(
390
390
  <- Cast(actor_id, movie_id) ->
391
391
  Movie(id ^title)
392
392
  """,
393
- Cypher
393
+ CypherParser
394
394
  # ^^^ recognizes syntax like Neo4J queries
395
395
  )
396
396
  ```
@@ -427,7 +427,7 @@ ORDER BY
427
427
  It is useful to write a query in a few lines, without specifying the script type (cypher, mongoDB, SQL, Neo4J...)
428
428
  ### Examples:
429
429
 
430
- > **1 - Relationship**
430
+ > **13.1 - Relationship**
431
431
  ```
432
432
  query = detect(
433
433
  'MATCH(c:Customer)<-[:Order]->(p:Product)RETURN c, p'
@@ -439,7 +439,7 @@ print(query)
439
439
  Order ord
440
440
  LEFT JOIN Customer cus ON (ord.customer_id = cus.id)
441
441
  RIGHT JOIN Product pro ON (ord.product_id = pro.id)
442
- > **2 - Grouping**
442
+ > **13.2 - Grouping**
443
443
  ```
444
444
  query = detect(
445
445
  'People@gender(avg$age?region="SOUTH"^count$qtde)'
@@ -460,13 +460,13 @@ print(query)
460
460
  ORDER BY
461
461
  peo.qtde
462
462
 
463
- > **3 - Many conditions...**
463
+ > **13.3 - Many conditions...**
464
464
  ```
465
465
  print( detect('''
466
466
  db.people.find({
467
467
  {
468
468
  $or: [
469
- status:{$eq:"B"},
469
+ {status:{$eq:"B"}},
470
470
  age:{$lt:50}
471
471
  ]
472
472
  },
@@ -491,7 +491,7 @@ print(query)
491
491
  ORDER BY
492
492
  peo.user_id DESC
493
493
 
494
- > **4 - Relations with same table twice (or more)**
494
+ > **13.4 - Relations with same table twice (or more)**
495
495
 
496
496
  Automatically assigns aliases to each side of the relationship (In this example, one user invites another to add to their contact list)
497
497
  ```
@@ -516,3 +516,54 @@ It consists of the inverse process of parsing: From a Select object, it returns
516
516
  * QueryLanguage - default
517
517
  * MongoDBLanguage
518
518
  * Neo4JLanguage
519
+
520
+ ---
521
+ ### 14 - Window Function
522
+
523
+ Aggregation functions (Avg, Min, Max, Sum, Count) have the **over** method...
524
+
525
+ query=Select(
526
+ 'Enrollment e',
527
+ payment=Sum().over(
528
+ partition='student_id', order='due_date'
529
+ ).As('sum_per_student')
530
+ )
531
+
532
+ ...that generates the following query:
533
+
534
+ ```
535
+ SELECT
536
+ Sum(e.payment) OVER(
537
+ PARTITION BY student_id
538
+ ORDER BY due_date
539
+ ) as sum_per_student
540
+ FROM
541
+ Enrollment e
542
+ ```
543
+ ---
544
+ ### 15 - The `As` method:
545
+ query=Select(
546
+ 'Customers c',
547
+ phone=[
548
+ Not.is_null(),
549
+ SubString(1, 4).As('area_code', GroupBy)
550
+ ],
551
+ customer_id=[
552
+ Count().As('customer_count', OrderBy),
553
+ Having.count(gt(5))
554
+ ]
555
+ )
556
+ You can use the result of a function as a new field -- and optionally use it in ORDER BY and/or GROUP BY clause(s):
557
+ ```
558
+ SELECT
559
+ SubString(c.phone, 1, 4) as area_code,
560
+ Count(c.customer_id) as customer_count
561
+ FROM
562
+ Customers c
563
+ WHERE
564
+ NOT c.phone IS NULL
565
+ GROUP BY
566
+ area_code HAVING Count(c.customer_id) > 5
567
+ ORDER BY
568
+ customer_count
569
+ ```
@@ -0,0 +1,7 @@
1
+ sql_blocks/__init__.py,sha256=5ItzGCyqqa6kwY8wvF9kapyHsAiWJ7KEXCcC-OtdXKg,37
2
+ sql_blocks/sql_blocks.py,sha256=QxopGbzRtBgG_9oAs1ovBATFplDqrzEFSLwyKbOL-W8,45499
3
+ sql_blocks-1.25.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
4
+ sql_blocks-1.25.dist-info/METADATA,sha256=cvHewfK05stZRHfWZeyIMMtKRjdOGxZN38o94JkVS-U,13421
5
+ sql_blocks-1.25.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
6
+ sql_blocks-1.25.dist-info/top_level.txt,sha256=57AbUvUjYNy4m1EqDaU3WHeP-uyIAfV0n8GAUp1a1YQ,11
7
+ sql_blocks-1.25.dist-info/RECORD,,
@@ -1,7 +0,0 @@
1
- sql_blocks/__init__.py,sha256=5ItzGCyqqa6kwY8wvF9kapyHsAiWJ7KEXCcC-OtdXKg,37
2
- sql_blocks/sql_blocks.py,sha256=jtK4yfyLCQZYrom2Aj_En6Hvlc3mbvAI1TOnC4AK72U,42823
3
- sql_blocks-0.31.21.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
4
- sql_blocks-0.31.21.dist-info/METADATA,sha256=lDrOFqAT1hC8lEp9plVXngNJ7m_2ZnJ8qRywx_PVUCo,12195
5
- sql_blocks-0.31.21.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
6
- sql_blocks-0.31.21.dist-info/top_level.txt,sha256=57AbUvUjYNy4m1EqDaU3WHeP-uyIAfV0n8GAUp1a1YQ,11
7
- sql_blocks-0.31.21.dist-info/RECORD,,