sql_fusion 1.2.0__tar.gz → 1.2.2__tar.gz
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_fusion-1.2.0 → sql_fusion-1.2.2}/PKG-INFO +107 -2
- {sql_fusion-1.2.0 → sql_fusion-1.2.2}/README.md +105 -0
- {sql_fusion-1.2.0 → sql_fusion-1.2.2}/pyproject.toml +2 -2
- {sql_fusion-1.2.0 → sql_fusion-1.2.2}/src/sql_fusion/composite_table.py +2 -0
- {sql_fusion-1.2.0 → sql_fusion-1.2.2}/src/sql_fusion/query/sets.py +2 -2
- {sql_fusion-1.2.0 → sql_fusion-1.2.2}/src/sql_fusion/__init__.py +0 -0
- {sql_fusion-1.2.0 → sql_fusion-1.2.2}/src/sql_fusion/operators.py +0 -0
- {sql_fusion-1.2.0 → sql_fusion-1.2.2}/src/sql_fusion/query/__init__.py +0 -0
- {sql_fusion-1.2.0 → sql_fusion-1.2.2}/src/sql_fusion/query/delete.py +0 -0
- {sql_fusion-1.2.0 → sql_fusion-1.2.2}/src/sql_fusion/query/insert.py +0 -0
- {sql_fusion-1.2.0 → sql_fusion-1.2.2}/src/sql_fusion/query/select.py +0 -0
- {sql_fusion-1.2.0 → sql_fusion-1.2.2}/src/sql_fusion/query/update.py +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: sql_fusion
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.2
|
|
4
4
|
Summary: Python query builder with a focus on composability and reusability.
|
|
5
5
|
Author: Mastermind-U
|
|
6
6
|
Author-email: Mastermind-U <rex49513@gmail.com>
|
|
7
|
-
Requires-Python: >=3.
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
8
|
Project-URL: Homepage, https://github.com/Mastermind-U/sql_fusion
|
|
9
9
|
Project-URL: Documentation, https://github.com/Mastermind-U/sql_fusion/blob/main/README.md
|
|
10
10
|
Project-URL: Repository, https://github.com/Mastermind-U/sql_fusion.git
|
|
@@ -40,6 +40,7 @@ That makes it easy to plug into your own connection layer.
|
|
|
40
40
|
- [Quickstart: psycopg3](#quickstart-psycopg3)
|
|
41
41
|
- [Query Basics](#query-basics)
|
|
42
42
|
- [Subquery Example](#subquery-example)
|
|
43
|
+
- [Set Operations](#set-operations)
|
|
43
44
|
- [Method Reference](#method-reference)
|
|
44
45
|
- [Functions](#functions)
|
|
45
46
|
- [CTEs](#ctes)
|
|
@@ -71,6 +72,22 @@ SQL Fusion is built for the middle ground:
|
|
|
71
72
|
|
|
72
73
|
In short, the goal is to keep the ergonomics of a lightweight builder while still covering the parts of SQL that matter in real applications.
|
|
73
74
|
|
|
75
|
+
### Why not SQLAlchemy ORM or Core?
|
|
76
|
+
|
|
77
|
+
SQLAlchemy is an excellent tool, but it is also a much heavier and more universal system:
|
|
78
|
+
|
|
79
|
+
- it brings a larger abstraction surface than this project needs
|
|
80
|
+
- it is optimized for a broad ORM and Core ecosystem, not only for a small SQL builder
|
|
81
|
+
- some connectors and databases still do not have first-class SQLAlchemy integrations, which can make adoption less straightforward in mixed environments
|
|
82
|
+
|
|
83
|
+
SQLAlchemy Core is closer to SQL Fusion than the full ORM, but it still carries more machinery than this project is meant to expose:
|
|
84
|
+
|
|
85
|
+
- it is part of a broader ecosystem with dialects, compilation layers, and extra conventions
|
|
86
|
+
- it can feel more verbose when you only want a small chainable builder
|
|
87
|
+
- some connectors and databases still do not have smooth SQLAlchemy Core support, so portability can depend on the backend
|
|
88
|
+
|
|
89
|
+
SQL Fusion is intentionally narrower so it can stay lightweight, easy to embed, and practical for DB-API style backends without extra complexity.
|
|
90
|
+
|
|
74
91
|
## What You Get
|
|
75
92
|
|
|
76
93
|
- `SELECT`, `INSERT`, `UPDATE`, and `DELETE` builders
|
|
@@ -404,6 +421,94 @@ paid_orders = (
|
|
|
404
421
|
query, params = select().from_(paid_orders).compile()
|
|
405
422
|
```
|
|
406
423
|
|
|
424
|
+
## Set Operations
|
|
425
|
+
|
|
426
|
+
SQL Fusion supports compound queries through three small wrapper classes:
|
|
427
|
+
|
|
428
|
+
- `union(query1, query2, all_=False, by_name=False)`
|
|
429
|
+
- `intersect(query1, query2, all_=False)`
|
|
430
|
+
- `except_(query1, query2, all_=False)`
|
|
431
|
+
|
|
432
|
+
Each builder accepts two query objects and returns a new query that compiles to
|
|
433
|
+
the matching SQL set operation.
|
|
434
|
+
|
|
435
|
+
### `union(...)`
|
|
436
|
+
|
|
437
|
+
```python
|
|
438
|
+
users = Table("users")
|
|
439
|
+
archived_users = Table("archived_users")
|
|
440
|
+
|
|
441
|
+
active_users = select(users.id, users.name).from_(users).where_by(status="active")
|
|
442
|
+
archived_active_users = (
|
|
443
|
+
select(archived_users.id, archived_users.name)
|
|
444
|
+
.from_(archived_users)
|
|
445
|
+
.where_by(status="active")
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
query, params = union(active_users, archived_active_users).compile()
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
Use `all=True` for `UNION ALL`:
|
|
452
|
+
|
|
453
|
+
```python
|
|
454
|
+
query, params = union(active_users, archived_active_users, all_=True).compile()
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
Use `by_name=True` when the two result sets expose the same logical columns in
|
|
458
|
+
different orders:
|
|
459
|
+
|
|
460
|
+
```python
|
|
461
|
+
left = select(users.id, users.name).from_(users)
|
|
462
|
+
right = select(archived_users.name, archived_users.id).from_(archived_users)
|
|
463
|
+
|
|
464
|
+
query, params = union(left, right, all_=True, by_name=True).compile()
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
### `intersect(...)`
|
|
468
|
+
|
|
469
|
+
```python
|
|
470
|
+
users = Table("users")
|
|
471
|
+
premium_users = Table("premium_users")
|
|
472
|
+
|
|
473
|
+
active_users = select(users.id).from_(users).where_by(status="active")
|
|
474
|
+
premium_active_users = (
|
|
475
|
+
select(premium_users.id).from_(premium_users).where_by(status="active")
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
query, params = intersect(active_users, premium_active_users).compile()
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
Use `all_=True` for `INTERSECT ALL`:
|
|
482
|
+
|
|
483
|
+
```python
|
|
484
|
+
query, params = intersect(
|
|
485
|
+
active_users,
|
|
486
|
+
premium_active_users,
|
|
487
|
+
all_=True,
|
|
488
|
+
).compile()
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
### `except_(...)`
|
|
492
|
+
|
|
493
|
+
```python
|
|
494
|
+
users = Table("users")
|
|
495
|
+
banned_users = Table("banned_users")
|
|
496
|
+
|
|
497
|
+
all_users = select(users.id).from_(users)
|
|
498
|
+
banned_user_ids = select(banned_users.id).from_(banned_users)
|
|
499
|
+
|
|
500
|
+
query, params = except_(all_users, banned_user_ids).compile()
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
Use `all_=True` for `EXCEPT ALL`:
|
|
504
|
+
|
|
505
|
+
```python
|
|
506
|
+
query, params = except_(all_users, banned_user_ids, all_=True).compile()
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
These builders preserve the parameter order from left to right, so the returned
|
|
510
|
+
`params` tuple can be passed directly to DB-API drivers.
|
|
511
|
+
|
|
407
512
|
### Having Example
|
|
408
513
|
|
|
409
514
|
```python
|
|
@@ -26,6 +26,7 @@ That makes it easy to plug into your own connection layer.
|
|
|
26
26
|
- [Quickstart: psycopg3](#quickstart-psycopg3)
|
|
27
27
|
- [Query Basics](#query-basics)
|
|
28
28
|
- [Subquery Example](#subquery-example)
|
|
29
|
+
- [Set Operations](#set-operations)
|
|
29
30
|
- [Method Reference](#method-reference)
|
|
30
31
|
- [Functions](#functions)
|
|
31
32
|
- [CTEs](#ctes)
|
|
@@ -57,6 +58,22 @@ SQL Fusion is built for the middle ground:
|
|
|
57
58
|
|
|
58
59
|
In short, the goal is to keep the ergonomics of a lightweight builder while still covering the parts of SQL that matter in real applications.
|
|
59
60
|
|
|
61
|
+
### Why not SQLAlchemy ORM or Core?
|
|
62
|
+
|
|
63
|
+
SQLAlchemy is an excellent tool, but it is also a much heavier and more universal system:
|
|
64
|
+
|
|
65
|
+
- it brings a larger abstraction surface than this project needs
|
|
66
|
+
- it is optimized for a broad ORM and Core ecosystem, not only for a small SQL builder
|
|
67
|
+
- some connectors and databases still do not have first-class SQLAlchemy integrations, which can make adoption less straightforward in mixed environments
|
|
68
|
+
|
|
69
|
+
SQLAlchemy Core is closer to SQL Fusion than the full ORM, but it still carries more machinery than this project is meant to expose:
|
|
70
|
+
|
|
71
|
+
- it is part of a broader ecosystem with dialects, compilation layers, and extra conventions
|
|
72
|
+
- it can feel more verbose when you only want a small chainable builder
|
|
73
|
+
- some connectors and databases still do not have smooth SQLAlchemy Core support, so portability can depend on the backend
|
|
74
|
+
|
|
75
|
+
SQL Fusion is intentionally narrower so it can stay lightweight, easy to embed, and practical for DB-API style backends without extra complexity.
|
|
76
|
+
|
|
60
77
|
## What You Get
|
|
61
78
|
|
|
62
79
|
- `SELECT`, `INSERT`, `UPDATE`, and `DELETE` builders
|
|
@@ -390,6 +407,94 @@ paid_orders = (
|
|
|
390
407
|
query, params = select().from_(paid_orders).compile()
|
|
391
408
|
```
|
|
392
409
|
|
|
410
|
+
## Set Operations
|
|
411
|
+
|
|
412
|
+
SQL Fusion supports compound queries through three small wrapper classes:
|
|
413
|
+
|
|
414
|
+
- `union(query1, query2, all_=False, by_name=False)`
|
|
415
|
+
- `intersect(query1, query2, all_=False)`
|
|
416
|
+
- `except_(query1, query2, all_=False)`
|
|
417
|
+
|
|
418
|
+
Each builder accepts two query objects and returns a new query that compiles to
|
|
419
|
+
the matching SQL set operation.
|
|
420
|
+
|
|
421
|
+
### `union(...)`
|
|
422
|
+
|
|
423
|
+
```python
|
|
424
|
+
users = Table("users")
|
|
425
|
+
archived_users = Table("archived_users")
|
|
426
|
+
|
|
427
|
+
active_users = select(users.id, users.name).from_(users).where_by(status="active")
|
|
428
|
+
archived_active_users = (
|
|
429
|
+
select(archived_users.id, archived_users.name)
|
|
430
|
+
.from_(archived_users)
|
|
431
|
+
.where_by(status="active")
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
query, params = union(active_users, archived_active_users).compile()
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
Use `all=True` for `UNION ALL`:
|
|
438
|
+
|
|
439
|
+
```python
|
|
440
|
+
query, params = union(active_users, archived_active_users, all_=True).compile()
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
Use `by_name=True` when the two result sets expose the same logical columns in
|
|
444
|
+
different orders:
|
|
445
|
+
|
|
446
|
+
```python
|
|
447
|
+
left = select(users.id, users.name).from_(users)
|
|
448
|
+
right = select(archived_users.name, archived_users.id).from_(archived_users)
|
|
449
|
+
|
|
450
|
+
query, params = union(left, right, all_=True, by_name=True).compile()
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
### `intersect(...)`
|
|
454
|
+
|
|
455
|
+
```python
|
|
456
|
+
users = Table("users")
|
|
457
|
+
premium_users = Table("premium_users")
|
|
458
|
+
|
|
459
|
+
active_users = select(users.id).from_(users).where_by(status="active")
|
|
460
|
+
premium_active_users = (
|
|
461
|
+
select(premium_users.id).from_(premium_users).where_by(status="active")
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
query, params = intersect(active_users, premium_active_users).compile()
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
Use `all_=True` for `INTERSECT ALL`:
|
|
468
|
+
|
|
469
|
+
```python
|
|
470
|
+
query, params = intersect(
|
|
471
|
+
active_users,
|
|
472
|
+
premium_active_users,
|
|
473
|
+
all_=True,
|
|
474
|
+
).compile()
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### `except_(...)`
|
|
478
|
+
|
|
479
|
+
```python
|
|
480
|
+
users = Table("users")
|
|
481
|
+
banned_users = Table("banned_users")
|
|
482
|
+
|
|
483
|
+
all_users = select(users.id).from_(users)
|
|
484
|
+
banned_user_ids = select(banned_users.id).from_(banned_users)
|
|
485
|
+
|
|
486
|
+
query, params = except_(all_users, banned_user_ids).compile()
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
Use `all_=True` for `EXCEPT ALL`:
|
|
490
|
+
|
|
491
|
+
```python
|
|
492
|
+
query, params = except_(all_users, banned_user_ids, all_=True).compile()
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
These builders preserve the parameter order from left to right, so the returned
|
|
496
|
+
`params` tuple can be passed directly to DB-API drivers.
|
|
497
|
+
|
|
393
498
|
### Having Example
|
|
394
499
|
|
|
395
500
|
```python
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sql_fusion"
|
|
3
|
-
version = "1.2.
|
|
3
|
+
version = "1.2.2"
|
|
4
4
|
description = "Python query builder with a focus on composability and reusability."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [{ name = "Mastermind-U", email = "rex49513@gmail.com" }]
|
|
7
|
-
requires-python = ">=3.
|
|
7
|
+
requires-python = ">=3.11"
|
|
8
8
|
dependencies = []
|
|
9
9
|
|
|
10
10
|
[project.urls]
|
|
@@ -57,11 +57,11 @@ class union(_set_operation):
|
|
|
57
57
|
self,
|
|
58
58
|
query1: AbstractQuery,
|
|
59
59
|
query2: AbstractQuery,
|
|
60
|
-
|
|
60
|
+
all_: bool = False,
|
|
61
61
|
by_name: bool = False,
|
|
62
62
|
) -> None:
|
|
63
63
|
super().__init__(query1, query2)
|
|
64
|
-
self._all =
|
|
64
|
+
self._all = all_
|
|
65
65
|
self._by_name = by_name
|
|
66
66
|
|
|
67
67
|
def _operator_sql(self) -> str:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|