sql_fusion 1.2.0__tar.gz → 1.2.1__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.
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: sql_fusion
3
- Version: 1.2.0
3
+ Version: 1.2.1
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.14
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)
@@ -404,6 +405,94 @@ paid_orders = (
404
405
  query, params = select().from_(paid_orders).compile()
405
406
  ```
406
407
 
408
+ ## Set Operations
409
+
410
+ SQL Fusion supports compound queries through three small wrapper classes:
411
+
412
+ - `union(query1, query2, all=False, by_name=False)`
413
+ - `intersect(q1, q2, all_=False)`
414
+ - `except_(q1, q2, all_=False)`
415
+
416
+ Each builder accepts two query objects and returns a new query that compiles to
417
+ the matching SQL set operation.
418
+
419
+ ### `union(...)`
420
+
421
+ ```python
422
+ users = Table("users")
423
+ archived_users = Table("archived_users")
424
+
425
+ active_users = select(users.id, users.name).from_(users).where_by(status="active")
426
+ archived_active_users = (
427
+ select(archived_users.id, archived_users.name)
428
+ .from_(archived_users)
429
+ .where_by(status="active")
430
+ )
431
+
432
+ query, params = union(active_users, archived_active_users).compile()
433
+ ```
434
+
435
+ Use `all=True` for `UNION ALL`:
436
+
437
+ ```python
438
+ query, params = union(active_users, archived_active_users, all=True).compile()
439
+ ```
440
+
441
+ Use `by_name=True` when the two result sets expose the same logical columns in
442
+ different orders:
443
+
444
+ ```python
445
+ left = select(users.id, users.name).from_(users)
446
+ right = select(archived_users.name, archived_users.id).from_(archived_users)
447
+
448
+ query, params = union(left, right, all=True, by_name=True).compile()
449
+ ```
450
+
451
+ ### `intersect(...)`
452
+
453
+ ```python
454
+ users = Table("users")
455
+ premium_users = Table("premium_users")
456
+
457
+ active_users = select(users.id).from_(users).where_by(status="active")
458
+ premium_active_users = (
459
+ select(premium_users.id).from_(premium_users).where_by(status="active")
460
+ )
461
+
462
+ query, params = intersect(active_users, premium_active_users).compile()
463
+ ```
464
+
465
+ Use `all_=True` for `INTERSECT ALL`:
466
+
467
+ ```python
468
+ query, params = intersect(
469
+ active_users,
470
+ premium_active_users,
471
+ all_=True,
472
+ ).compile()
473
+ ```
474
+
475
+ ### `except_(...)`
476
+
477
+ ```python
478
+ users = Table("users")
479
+ banned_users = Table("banned_users")
480
+
481
+ all_users = select(users.id).from_(users)
482
+ banned_user_ids = select(banned_users.id).from_(banned_users)
483
+
484
+ query, params = except_(all_users, banned_user_ids).compile()
485
+ ```
486
+
487
+ Use `all_=True` for `EXCEPT ALL`:
488
+
489
+ ```python
490
+ query, params = except_(all_users, banned_user_ids, all_=True).compile()
491
+ ```
492
+
493
+ These builders preserve the parameter order from left to right, so the returned
494
+ `params` tuple can be passed directly to DB-API drivers.
495
+
407
496
  ### Having Example
408
497
 
409
498
  ```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)
@@ -390,6 +391,94 @@ paid_orders = (
390
391
  query, params = select().from_(paid_orders).compile()
391
392
  ```
392
393
 
394
+ ## Set Operations
395
+
396
+ SQL Fusion supports compound queries through three small wrapper classes:
397
+
398
+ - `union(query1, query2, all=False, by_name=False)`
399
+ - `intersect(q1, q2, all_=False)`
400
+ - `except_(q1, q2, all_=False)`
401
+
402
+ Each builder accepts two query objects and returns a new query that compiles to
403
+ the matching SQL set operation.
404
+
405
+ ### `union(...)`
406
+
407
+ ```python
408
+ users = Table("users")
409
+ archived_users = Table("archived_users")
410
+
411
+ active_users = select(users.id, users.name).from_(users).where_by(status="active")
412
+ archived_active_users = (
413
+ select(archived_users.id, archived_users.name)
414
+ .from_(archived_users)
415
+ .where_by(status="active")
416
+ )
417
+
418
+ query, params = union(active_users, archived_active_users).compile()
419
+ ```
420
+
421
+ Use `all=True` for `UNION ALL`:
422
+
423
+ ```python
424
+ query, params = union(active_users, archived_active_users, all=True).compile()
425
+ ```
426
+
427
+ Use `by_name=True` when the two result sets expose the same logical columns in
428
+ different orders:
429
+
430
+ ```python
431
+ left = select(users.id, users.name).from_(users)
432
+ right = select(archived_users.name, archived_users.id).from_(archived_users)
433
+
434
+ query, params = union(left, right, all=True, by_name=True).compile()
435
+ ```
436
+
437
+ ### `intersect(...)`
438
+
439
+ ```python
440
+ users = Table("users")
441
+ premium_users = Table("premium_users")
442
+
443
+ active_users = select(users.id).from_(users).where_by(status="active")
444
+ premium_active_users = (
445
+ select(premium_users.id).from_(premium_users).where_by(status="active")
446
+ )
447
+
448
+ query, params = intersect(active_users, premium_active_users).compile()
449
+ ```
450
+
451
+ Use `all_=True` for `INTERSECT ALL`:
452
+
453
+ ```python
454
+ query, params = intersect(
455
+ active_users,
456
+ premium_active_users,
457
+ all_=True,
458
+ ).compile()
459
+ ```
460
+
461
+ ### `except_(...)`
462
+
463
+ ```python
464
+ users = Table("users")
465
+ banned_users = Table("banned_users")
466
+
467
+ all_users = select(users.id).from_(users)
468
+ banned_user_ids = select(banned_users.id).from_(banned_users)
469
+
470
+ query, params = except_(all_users, banned_user_ids).compile()
471
+ ```
472
+
473
+ Use `all_=True` for `EXCEPT ALL`:
474
+
475
+ ```python
476
+ query, params = except_(all_users, banned_user_ids, all_=True).compile()
477
+ ```
478
+
479
+ These builders preserve the parameter order from left to right, so the returned
480
+ `params` tuple can be passed directly to DB-API drivers.
481
+
393
482
  ### Having Example
394
483
 
395
484
  ```python
@@ -1,10 +1,10 @@
1
1
  [project]
2
2
  name = "sql_fusion"
3
- version = "1.2.0"
3
+ version = "1.2.1"
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.14"
7
+ requires-python = ">=3.11"
8
8
  dependencies = []
9
9
 
10
10
  [project.urls]
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from collections.abc import Iterable
2
4
  from copy import copy
3
5
  from typing import Any, Callable, Self