sql-blocks 0.0.3__py3-none-any.whl → 0.0.4__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
@@ -1,4 +1,6 @@
1
1
  from enum import Enum
2
+ import re
3
+
2
4
 
3
5
  KEYWORD = {
4
6
  'SELECT': (',{}', 'SELECT *'),
@@ -54,9 +56,10 @@ class Field:
54
56
 
55
57
  @classmethod
56
58
  def format(cls, name: str, main: SQLObject) -> str:
59
+ name = name.strip()
57
60
  if name == '_':
58
61
  name = '*'
59
- else:
62
+ elif '.' not in name:
60
63
  name = f'{main.alias}.{name}'
61
64
  if Function in cls.__bases__:
62
65
  name = f'{cls.__name__}({name})'
@@ -319,7 +322,7 @@ class JoinType(Enum):
319
322
 
320
323
  class Select(SQLObject):
321
324
  join_type: JoinType = JoinType.INNER
322
- REGEX = None
325
+ REGEX = {}
323
326
 
324
327
  def __init__(self, table_name: str='', **values):
325
328
  super().__init__(table_name)
@@ -350,7 +353,7 @@ class Select(SQLObject):
350
353
  if not foreign_field:
351
354
  foreign_field, primary_key = ForeignKey.find(other, self)
352
355
  if foreign_field:
353
- if primary_key and not self.key_field:
356
+ if primary_key:
354
357
  PrimaryKey.add(primary_key, self)
355
358
  self.add(foreign_field, other)
356
359
  return other
@@ -396,15 +399,52 @@ class Select(SQLObject):
396
399
 
397
400
  @classmethod
398
401
  def parse(cls, txt: str) -> list[SQLObject]:
399
- import re
400
- txt = re.sub(' +', ' ', re.sub('\n|\t', ' ', txt))
402
+ def find_last_word(pos: int) -> int:
403
+ SPACE, WORD = 1, 2
404
+ found = set()
405
+ for i in range(pos, 0, -1):
406
+ if txt[i] in [' ', '\t', '\n']:
407
+ if sum(found) == 3:
408
+ return i
409
+ found.add(SPACE)
410
+ if txt[i].isalpha():
411
+ found.add(WORD)
412
+ elif txt[i] == '.':
413
+ found.remove(WORD)
414
+ def find_parenthesis(pos: int) -> int:
415
+ for i in range(pos, len(txt)-1):
416
+ if txt[i] == ')':
417
+ return i+1
401
418
  if not cls.REGEX:
402
- keywords = '|'.join(KEYWORD)
403
- cls.REGEX = re.compile(f'({keywords})', re.IGNORECASE)
404
- tokens = [t.strip() for t in cls.REGEX.split(txt) if t.strip()]
405
- values = {k: v for k, v in zip(tokens[::2], tokens[1::2])}
406
- tables = [t.strip() for t in re.split('JOIN|LEFT|RIGHT|ON', values[FROM]) if t.strip()]
419
+ keywords = '|'.join(k + r'\b' for k in KEYWORD)
420
+ flags = re.IGNORECASE + re.MULTILINE
421
+ cls.REGEX['keywords'] = re.compile(f'({keywords})', flags)
422
+ cls.REGEX['subquery'] = re.compile(r'(\w\.)*\w+ +in +\(SELECT.*?\)', flags)
407
423
  result = {}
424
+ found = cls.REGEX['subquery'].search(txt)
425
+ while found:
426
+ start, end = found.span()
427
+ inner = txt[start: end]
428
+ if inner.count('(') > inner.count(')'):
429
+ end = find_parenthesis(end)
430
+ inner = txt[start: end]
431
+ fld, *inner = re.split(r' IN | in', inner, maxsplit=1)
432
+ if fld.upper() == 'NOT':
433
+ pos = find_last_word(start)
434
+ fld = txt[pos: start].strip() # [To-Do] Use the value of `fld`
435
+ start = pos
436
+ class_type = NotSelectIN
437
+ else:
438
+ class_type = SelectIN
439
+ obj = class_type.parse(
440
+ ' '.join(re.sub(r'^\(', '', s.strip()) for s in inner)
441
+ )[0]
442
+ result[obj.alias] = obj
443
+ txt = txt[:start-1] + txt[end+1:]
444
+ found = cls.REGEX['subquery'].search(txt)
445
+ tokens = [t.strip() for t in cls.REGEX['keywords'].split(txt) if re.findall(r'\w+', t)]
446
+ values = {k.upper(): v for k, v in zip(tokens[::2], tokens[1::2])}
447
+ tables = [t.strip() for t in re.split('JOIN|LEFT|RIGHT|ON', values[FROM]) if t.strip()]
408
448
  for item in tables:
409
449
  if '=' in item:
410
450
  a1, f1, a2, f2 = [r.strip() for r in re.split('[().=]', item) if r]
@@ -417,23 +457,55 @@ class Select(SQLObject):
417
457
  for key in USUAL_KEYS:
418
458
  if not key in values:
419
459
  continue
420
- separator = KEYWORD[key][0].format(
421
- ' ' if key == WHERE else ''
422
- )
460
+ separator = KEYWORD[key][0].format('')
423
461
  fields = [
424
- fld.strip() for fld in re.split(
462
+ Field.format(fld, obj)
463
+ for fld in re.split(
425
464
  separator, values[key]
426
465
  ) if len(tables) == 1
427
- or re.findall(f'^[( ]*{obj.alias}.', fld)
428
- ]
429
- obj.values[key] = [
430
- f'{obj.alias}.{f}' if not '.' in f else f for f in fields
466
+ or re.findall(f'\b*{obj.alias}[.]', fld)
431
467
  ]
468
+ obj.values[key] = [ f for f in fields if f.strip() ]
432
469
  result[obj.alias] = obj
433
470
  return list( result.values() )
434
471
 
472
+ class SelectIN(Select):
473
+ condition_class = Where
435
474
 
436
- class SubSelect(Select):
437
475
  def add(self, name: str, main: SQLObject):
438
476
  self.break_lines = False
439
- Where.list(self).add(name, main)
477
+ self.condition_class.list(self).add(name, main)
478
+
479
+ SubSelect = SelectIN
480
+
481
+ class NotSelectIN(SelectIN):
482
+ condition_class = Not
483
+
484
+
485
+ if __name__ == "__main__":
486
+ query_list = Select.parse("""
487
+ SELECT
488
+ cas.role,
489
+ m.title,
490
+ m.release_date,
491
+ a.name as actors_name
492
+ FROM
493
+ Actor a
494
+ LEFT JOIN Cast cas ON (a.cast = cas.id)
495
+ LEFT JOIN Movie m ON (cas.movie = m.id)
496
+ WHERE
497
+ m.genre NOT in (SELECT g.id from Genres g where g.name in ('sci-fi', 'horror', 'distopia'))
498
+ AND (m.hashtag = '#cult' OR m.awards LIKE '%Oscar%')
499
+ AND m.id IN (select DISTINCT r.movie FROM Review r GROUP BY r.movie HAVING Avg(r.rate) > 4.5)
500
+ AND a.age <= 69 AND a.age >= 45
501
+ ORDER BY
502
+ m.release_date
503
+ """)
504
+ for query in query_list:
505
+ descr = ' {} ({}) '.format(
506
+ query.table_name,
507
+ query.__class__.__name__
508
+ )
509
+ print(descr.center(50, '-'))
510
+ print(query)
511
+ print('='*50)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sql_blocks
3
- Version: 0.0.3
3
+ Version: 0.0.4
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
@@ -55,7 +55,7 @@ You can specify your own alias: `a = Select('Actor a')`
55
55
  3.2 -- Sub-queries:
56
56
  ```
57
57
  query = Select('Movie m', title=Field,
58
- id=SubSelect(
58
+ id=SelectIN(
59
59
  'Review r',
60
60
  rate=Where.gt(4.5),
61
61
  movie_id=Distinct
@@ -86,12 +86,13 @@ query = Select('Movie m', title=Field,
86
86
 
87
87
  3.4 -- Negative conditions use the _Not_ class instead of _Where_
88
88
  ```
89
- franchise
90
89
  based_on_book=Not.is_null()
91
90
  ```
92
91
 
93
92
  3.5 -- List of values
93
+ ```
94
94
  hash_tag=Where.list(['space', 'monster', 'gore'])
95
+ ```
95
96
 
96
97
  ---
97
98
  ### 4 - A field can be two things at the same time:
@@ -100,7 +101,7 @@ hash_tag=Where.list(['space', 'monster', 'gore'])
100
101
  - This means that the field will appear in the results and also that the query will be ordered by that field.
101
102
  * Applying **GROUP BY** to item 3.2, it would look like this:
102
103
  ```
103
- SubSelect(
104
+ SelectIN(
104
105
  'Review r', movie=[GroupBy, Distinct],
105
106
  rate=Having.avg(Where.gt(4.5))
106
107
  )
@@ -282,7 +283,7 @@ m = Select...
282
283
 
283
284
  9.3
284
285
  ```
285
- best_movies = SubSelect(
286
+ best_movies = SelectIN(
286
287
  Review=Table('role'),
287
288
  rate=[GroupBy, Having.avg(Where.gt(4.5))]
288
289
  )
@@ -302,13 +303,13 @@ m2 = Select(
302
303
  ---
303
304
 
304
305
  ### 10 - CASE...WHEN...THEN
305
- Select(
306
- 'Product',
307
- label=Case('price').when(
308
- lt(50), 'cheap'
309
- ).when(
310
- gt(100), 'expensive'
311
- ).else_value(
312
- 'normal'
306
+ Select(
307
+ 'Product',
308
+ label=Case('price').when(
309
+ lt(50), 'cheap'
310
+ ).when(
311
+ gt(100), 'expensive'
312
+ ).else_value(
313
+ 'normal'
314
+ )
313
315
  )
314
- )
@@ -0,0 +1,7 @@
1
+ sql_blocks/__init__.py,sha256=TodC5q-UEdYEz9v1RRoogVqqRcsKnZRY1WDGinrI2zo,26
2
+ sql_blocks/sql_blocks.py,sha256=CX8rJtDvL07eGjwJbiqRKDckvrosJf4_PEBw5SK8mrQ,16051
3
+ sql_blocks-0.0.4.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
4
+ sql_blocks-0.0.4.dist-info/METADATA,sha256=RoaYK32YS_CAW2UBogLABwxIPPwjqcmZH1EQh-iA2ZQ,7028
5
+ sql_blocks-0.0.4.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
6
+ sql_blocks-0.0.4.dist-info/top_level.txt,sha256=57AbUvUjYNy4m1EqDaU3WHeP-uyIAfV0n8GAUp1a1YQ,11
7
+ sql_blocks-0.0.4.dist-info/RECORD,,
@@ -1,7 +0,0 @@
1
- sql_blocks/__init__.py,sha256=TodC5q-UEdYEz9v1RRoogVqqRcsKnZRY1WDGinrI2zo,26
2
- sql_blocks/sql_blocks.py,sha256=r_PIxS-VqaY2zuBGwBgz6Z1Z2zBrx_CrfZ35yTMb7Sw,13352
3
- sql_blocks-0.0.3.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
4
- sql_blocks-0.0.3.dist-info/METADATA,sha256=MHZb5SM_2RCZim4wESLsLaiCYReDOkn7tYJwGNsGWBE,6992
5
- sql_blocks-0.0.3.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
6
- sql_blocks-0.0.3.dist-info/top_level.txt,sha256=57AbUvUjYNy4m1EqDaU3WHeP-uyIAfV0n8GAUp1a1YQ,11
7
- sql_blocks-0.0.3.dist-info/RECORD,,