sqlobjects 1.0.16__tar.gz → 1.2.0__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.
- sqlobjects-1.2.0/CHANGELOG.md +142 -0
- {sqlobjects-1.0.16/sqlobjects.egg-info → sqlobjects-1.2.0}/PKG-INFO +25 -1
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/README.md +24 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/pyproject.toml +11 -11
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/expressions/__init__.py +30 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/expressions/aggregate.py +4 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/expressions/base.py +21 -6
- sqlobjects-1.2.0/sqlobjects/expressions/cte.py +136 -0
- sqlobjects-1.2.0/sqlobjects/expressions/explain.py +38 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/expressions/function.py +123 -4
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/expressions/mixins.py +4 -4
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/expressions/scalar.py +8 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/expressions/terminal.py +62 -0
- sqlobjects-1.2.0/sqlobjects/expressions/window.py +256 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/fields/core.py +11 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/fields/types/__init__.py +2 -1
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/fields/types/registry.py +4 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/metadata.py +33 -12
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/objects/core.py +21 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/queries/builder.py +27 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/queries/dialect.py +51 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/queries/executor.py +15 -54
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/queryset.py +58 -14
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/session.py +17 -3
- {sqlobjects-1.0.16 → sqlobjects-1.2.0/sqlobjects.egg-info}/PKG-INFO +25 -1
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects.egg-info/SOURCES.txt +4 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/LICENSE +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/docs/rules/01-database-session-guide.md +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/docs/rules/02-model-definition-guide.md +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/docs/rules/03-query-operations-guide.md +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/docs/rules/04-crud-operations-guide.md +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/docs/rules/05-relationships-guide.md +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/docs/rules/06-validation-signals-guide.md +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/docs/rules/07-performance-guide.md +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/docs/rules/README.md +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/setup.cfg +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/__init__.py +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/_install_rules.py +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/cascade.py +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/database/__init__.py +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/database/config.py +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/database/manager.py +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/exceptions.py +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/expressions/subquery.py +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/fields/__init__.py +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/fields/functions.py +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/fields/proxies.py +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/fields/relations/__init__.py +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/fields/relations/descriptors.py +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/fields/relations/managers.py +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/fields/relations/prefetch.py +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/fields/relations/strategies.py +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/fields/relations/utils.py +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/fields/shortcuts.py +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/fields/types/base.py +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/fields/types/comparators.py +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/fields/utils.py +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/internal/__init__.py +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/internal/operations.py +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/internal/results.py +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/mixins.py +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/model.py +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/objects/__init__.py +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/objects/bulk.py +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/objects/upsert.py +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/queries/__init__.py +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/signals.py +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/utils/__init__.py +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/utils/inspect.py +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/utils/naming.py +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/utils/pattern.py +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects/validators.py +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects.egg-info/dependency_links.txt +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects.egg-info/entry_points.txt +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects.egg-info/requires.txt +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/sqlobjects.egg-info/top_level.txt +0 -0
- {sqlobjects-1.0.16 → sqlobjects-1.2.0}/tests/test_config.py +0 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
## 1.2.0 (2026-02-25)
|
|
2
|
+
|
|
3
|
+
### Feat
|
|
4
|
+
|
|
5
|
+
- **metadata**: improve constraint and index naming conventions
|
|
6
|
+
|
|
7
|
+
## 1.1.0 (2026-02-14)
|
|
8
|
+
|
|
9
|
+
### Feat
|
|
10
|
+
|
|
11
|
+
- update custom field type imports to use sqlobjects.fields.types
|
|
12
|
+
- implement CTE (Common Table Expressions) support
|
|
13
|
+
- add support for SQL window functions
|
|
14
|
+
- add EXPLAIN support with dialect-based architecture
|
|
15
|
+
|
|
16
|
+
### Fix
|
|
17
|
+
|
|
18
|
+
- update test imports to use DeferredObject instead of DeferredFieldProxy
|
|
19
|
+
|
|
20
|
+
## 1.0.16 (2025-11-18)
|
|
21
|
+
|
|
22
|
+
### Fix
|
|
23
|
+
|
|
24
|
+
- correct ModelMixin inheritance
|
|
25
|
+
|
|
26
|
+
## 1.0.15 (2025-11-18)
|
|
27
|
+
|
|
28
|
+
### Feat
|
|
29
|
+
|
|
30
|
+
- preserve annotate fields and add serialization options
|
|
31
|
+
|
|
32
|
+
## 1.0.14 (2025-11-18)
|
|
33
|
+
|
|
34
|
+
### Feat
|
|
35
|
+
|
|
36
|
+
- add has_session() to check explicit session availability
|
|
37
|
+
|
|
38
|
+
## 1.0.13 (2025-11-18)
|
|
39
|
+
|
|
40
|
+
## 1.0.12 (2025-11-18)
|
|
41
|
+
|
|
42
|
+
### Refactor
|
|
43
|
+
|
|
44
|
+
- move rules installer to independent scripts
|
|
45
|
+
|
|
46
|
+
## 1.0.11 (2025-11-18)
|
|
47
|
+
|
|
48
|
+
### Feat
|
|
49
|
+
|
|
50
|
+
- add AI assistant rules with auto-install support
|
|
51
|
+
|
|
52
|
+
## 1.0.10 (2025-11-17)
|
|
53
|
+
|
|
54
|
+
### Feat
|
|
55
|
+
|
|
56
|
+
- support Model class in join methods for cleaner API
|
|
57
|
+
|
|
58
|
+
## 1.0.9 (2025-11-14)
|
|
59
|
+
|
|
60
|
+
### Feat
|
|
61
|
+
|
|
62
|
+
- add optional tables parameter to create_tables/drop_tables
|
|
63
|
+
|
|
64
|
+
## 1.0.8 (2025-11-11)
|
|
65
|
+
|
|
66
|
+
### Feat
|
|
67
|
+
|
|
68
|
+
- add model-level relationship loading methods
|
|
69
|
+
- improve relation field type inference
|
|
70
|
+
- refactor relationship proxies
|
|
71
|
+
|
|
72
|
+
## 1.0.7 (2025-10-14)
|
|
73
|
+
|
|
74
|
+
### Feat
|
|
75
|
+
|
|
76
|
+
- add upsert support for PostgreSQL
|
|
77
|
+
|
|
78
|
+
### Fix
|
|
79
|
+
|
|
80
|
+
- identity support for PostgreSQL
|
|
81
|
+
|
|
82
|
+
### Refactor
|
|
83
|
+
|
|
84
|
+
- consolidate bulk and queryset logic
|
|
85
|
+
|
|
86
|
+
### Perf
|
|
87
|
+
|
|
88
|
+
- improve bulk delete performance
|
|
89
|
+
|
|
90
|
+
## 1.0.6 (2025-10-08)
|
|
91
|
+
|
|
92
|
+
### Feat
|
|
93
|
+
|
|
94
|
+
- optimize field cache and state manager
|
|
95
|
+
- implement relationship prefetch support
|
|
96
|
+
- add kwargs parameter support to filter/exclude/get methods
|
|
97
|
+
|
|
98
|
+
### Refactor
|
|
99
|
+
|
|
100
|
+
- unify cascade for model and queryset operations
|
|
101
|
+
|
|
102
|
+
## 1.0.5 (2025-09-25)
|
|
103
|
+
|
|
104
|
+
### Fix
|
|
105
|
+
|
|
106
|
+
- generate DDL using column definition order
|
|
107
|
+
|
|
108
|
+
## 1.0.4 (2025-09-25)
|
|
109
|
+
|
|
110
|
+
### Feat
|
|
111
|
+
|
|
112
|
+
- remove unnecessary exception catching
|
|
113
|
+
- implement insert or update using database upsert
|
|
114
|
+
|
|
115
|
+
### Fix
|
|
116
|
+
|
|
117
|
+
- use pk column name instead of column instance for pgsql upsert
|
|
118
|
+
|
|
119
|
+
### Refactor
|
|
120
|
+
|
|
121
|
+
- move field default value related methods to DataConversionMixin
|
|
122
|
+
|
|
123
|
+
## 1.0.3 (2025-09-25)
|
|
124
|
+
|
|
125
|
+
### Fix
|
|
126
|
+
|
|
127
|
+
- field default/default_factory not working
|
|
128
|
+
|
|
129
|
+
## 1.0.2 (2025-09-24)
|
|
130
|
+
|
|
131
|
+
### Feat
|
|
132
|
+
|
|
133
|
+
- add type support for StringColumn
|
|
134
|
+
- add support for cascade delete in relationships
|
|
135
|
+
- add cascade support to relationship fields
|
|
136
|
+
- add type checking for __registry__
|
|
137
|
+
- use base model to create database tables
|
|
138
|
+
- init public commit
|
|
139
|
+
|
|
140
|
+
### Fix
|
|
141
|
+
|
|
142
|
+
- foreign key type inference issue
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlobjects
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Django-style async ORM library based on SQLAlchemy with chainable queries, Q objects, and relationship loading
|
|
5
5
|
Author-email: XtraVisions <gitadmin@xtravisions.com>, Chen Hao <chenhao@xtravisions.com>
|
|
6
6
|
Maintainer-email: XtraVisions <gitadmin@xtravisions.com>, Chen Hao <chenhao@xtravisions.com>
|
|
@@ -246,6 +246,7 @@ sqlobjects-install-rules amazonq # or cursor, claude, kiro
|
|
|
246
246
|
- [Relationships](docs/features/05-relationships.md) - Model relationships and loading strategies
|
|
247
247
|
- [Validation & Signals](docs/features/06-validation-signals.md) - Data validation and lifecycle hooks
|
|
248
248
|
- [Performance Optimization](docs/features/07-performance-optimization.md) - Performance tuning and best practices
|
|
249
|
+
- [Custom Field Types](docs/features/08-custom-field-types.md) - Extend with database-specific types
|
|
249
250
|
|
|
250
251
|
### Design Documentation
|
|
251
252
|
|
|
@@ -309,6 +310,29 @@ users = await User.objects.raw(
|
|
|
309
310
|
)
|
|
310
311
|
```
|
|
311
312
|
|
|
313
|
+
### Custom Field Types
|
|
314
|
+
|
|
315
|
+
```python
|
|
316
|
+
# Extend with database-specific types
|
|
317
|
+
from sqlobjects.fields.types.registry import register_field_type
|
|
318
|
+
from sqlalchemy.dialects.postgresql import TSVECTOR
|
|
319
|
+
|
|
320
|
+
register_field_type(
|
|
321
|
+
TSVECTOR, "tsvector",
|
|
322
|
+
comparator=TSVectorComparator
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
class Document(ObjectModel):
|
|
326
|
+
content_vector: Column = column(type="tsvector") # PostgreSQL full-text search
|
|
327
|
+
|
|
328
|
+
# Query with custom types
|
|
329
|
+
docs = await Document.objects.filter(
|
|
330
|
+
Document.content_vector.match("python & programming")
|
|
331
|
+
).all()
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
See [Custom Field Types](docs/features/08-custom-field-types.md) for complete examples.
|
|
335
|
+
|
|
312
336
|
## 🧪 Testing
|
|
313
337
|
|
|
314
338
|
SQLObjects includes comprehensive test coverage:
|
|
@@ -217,6 +217,7 @@ sqlobjects-install-rules amazonq # or cursor, claude, kiro
|
|
|
217
217
|
- [Relationships](docs/features/05-relationships.md) - Model relationships and loading strategies
|
|
218
218
|
- [Validation & Signals](docs/features/06-validation-signals.md) - Data validation and lifecycle hooks
|
|
219
219
|
- [Performance Optimization](docs/features/07-performance-optimization.md) - Performance tuning and best practices
|
|
220
|
+
- [Custom Field Types](docs/features/08-custom-field-types.md) - Extend with database-specific types
|
|
220
221
|
|
|
221
222
|
### Design Documentation
|
|
222
223
|
|
|
@@ -280,6 +281,29 @@ users = await User.objects.raw(
|
|
|
280
281
|
)
|
|
281
282
|
```
|
|
282
283
|
|
|
284
|
+
### Custom Field Types
|
|
285
|
+
|
|
286
|
+
```python
|
|
287
|
+
# Extend with database-specific types
|
|
288
|
+
from sqlobjects.fields.types.registry import register_field_type
|
|
289
|
+
from sqlalchemy.dialects.postgresql import TSVECTOR
|
|
290
|
+
|
|
291
|
+
register_field_type(
|
|
292
|
+
TSVECTOR, "tsvector",
|
|
293
|
+
comparator=TSVectorComparator
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
class Document(ObjectModel):
|
|
297
|
+
content_vector: Column = column(type="tsvector") # PostgreSQL full-text search
|
|
298
|
+
|
|
299
|
+
# Query with custom types
|
|
300
|
+
docs = await Document.objects.filter(
|
|
301
|
+
Document.content_vector.match("python & programming")
|
|
302
|
+
).all()
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
See [Custom Field Types](docs/features/08-custom-field-types.md) for complete examples.
|
|
306
|
+
|
|
283
307
|
## 🧪 Testing
|
|
284
308
|
|
|
285
309
|
SQLObjects includes comprehensive test coverage:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sqlobjects"
|
|
3
|
-
version = "1.0
|
|
3
|
+
version = "1.2.0"
|
|
4
4
|
description = "Django-style async ORM library based on SQLAlchemy with chainable queries, Q objects, and relationship loading"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -49,22 +49,22 @@ Changelog = "https://github.com/XtraVisionsAI/sqlobjects/blob/main/CHANGELOG.md"
|
|
|
49
49
|
|
|
50
50
|
[dependency-groups]
|
|
51
51
|
dev = [
|
|
52
|
-
"pre-commit>=4.
|
|
53
|
-
"pyright>=1.1.
|
|
54
|
-
"ruff>=0.
|
|
55
|
-
"setuptools>=
|
|
52
|
+
"pre-commit>=4.5.1",
|
|
53
|
+
"pyright>=1.1.408",
|
|
54
|
+
"ruff>=0.15.2",
|
|
55
|
+
"setuptools>=82.0.0",
|
|
56
56
|
]
|
|
57
57
|
test = [
|
|
58
58
|
"aiomysql>=0.3.2",
|
|
59
|
-
"aiosqlite>=0.
|
|
60
|
-
"asyncpg>=0.
|
|
61
|
-
"pytest>=9.0.
|
|
59
|
+
"aiosqlite>=0.22.1",
|
|
60
|
+
"asyncpg>=0.31.0",
|
|
61
|
+
"pytest>=9.0.2",
|
|
62
62
|
"pytest-asyncio>=1.3.0",
|
|
63
|
-
"psutil>=7.
|
|
63
|
+
"psutil>=7.2.2", # For memory monitoring in performance tests
|
|
64
64
|
]
|
|
65
65
|
|
|
66
66
|
[build-system]
|
|
67
|
-
requires = ["setuptools>=
|
|
67
|
+
requires = ["setuptools>=82.0.0", "wheel"]
|
|
68
68
|
build-backend = "setuptools.build_meta"
|
|
69
69
|
|
|
70
70
|
[tool.setuptools]
|
|
@@ -81,7 +81,7 @@ sqlobjects = ["py.typed"]
|
|
|
81
81
|
|
|
82
82
|
[[tool.uv.index]]
|
|
83
83
|
name = "tsinghua"
|
|
84
|
-
url = "https://
|
|
84
|
+
url = "https://mirrors.aliyun.com/pypi/simple"
|
|
85
85
|
default = true
|
|
86
86
|
|
|
87
87
|
[tool.commitizen]
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from .aggregate import AggregateExpression
|
|
2
2
|
from .base import ComparisonExpression, QueryExpression
|
|
3
|
+
from .cte import CTEExpression
|
|
4
|
+
from .explain import ExplainResult
|
|
3
5
|
from .function import FunctionExpression, func
|
|
4
6
|
from .scalar import CountExpression, ExistsExpression, ScalarSubquery
|
|
5
7
|
from .subquery import SubqueryExpression
|
|
@@ -15,14 +17,30 @@ from .terminal import (
|
|
|
15
17
|
ValuesExpression,
|
|
16
18
|
ValuesListExpression,
|
|
17
19
|
)
|
|
20
|
+
from .window import (
|
|
21
|
+
DenseRankFunction,
|
|
22
|
+
FirstValueFunction,
|
|
23
|
+
LagFunction,
|
|
24
|
+
LastValueFunction,
|
|
25
|
+
LeadFunction,
|
|
26
|
+
NthValueFunction,
|
|
27
|
+
NtileFunction,
|
|
28
|
+
PercentRankFunction,
|
|
29
|
+
RankFunction,
|
|
30
|
+
RowNumberFunction,
|
|
31
|
+
WindowFunction,
|
|
32
|
+
WindowSpec,
|
|
33
|
+
)
|
|
18
34
|
|
|
19
35
|
|
|
20
36
|
__all__ = [
|
|
21
37
|
"func",
|
|
22
38
|
"FunctionExpression",
|
|
23
39
|
"SubqueryExpression",
|
|
40
|
+
"CTEExpression",
|
|
24
41
|
"QueryExpression",
|
|
25
42
|
"ComparisonExpression",
|
|
43
|
+
"ExplainResult",
|
|
26
44
|
"AggregateExpression",
|
|
27
45
|
"CountExpression",
|
|
28
46
|
"ExistsExpression",
|
|
@@ -37,4 +55,16 @@ __all__ = [
|
|
|
37
55
|
"DatesExpression",
|
|
38
56
|
"DatetimesExpression",
|
|
39
57
|
"GetItemExpression",
|
|
58
|
+
"WindowFunction",
|
|
59
|
+
"WindowSpec",
|
|
60
|
+
"RowNumberFunction",
|
|
61
|
+
"RankFunction",
|
|
62
|
+
"DenseRankFunction",
|
|
63
|
+
"PercentRankFunction",
|
|
64
|
+
"NtileFunction",
|
|
65
|
+
"LagFunction",
|
|
66
|
+
"LeadFunction",
|
|
67
|
+
"FirstValueFunction",
|
|
68
|
+
"LastValueFunction",
|
|
69
|
+
"NthValueFunction",
|
|
40
70
|
]
|
|
@@ -30,6 +30,10 @@ class AggregateExpression(QueryExpression[dict[str, Any]]):
|
|
|
30
30
|
self._builder = builder
|
|
31
31
|
self._aggregations = aggregations
|
|
32
32
|
|
|
33
|
+
def get_query(self):
|
|
34
|
+
"""Return SQLAlchemy query object."""
|
|
35
|
+
return self._builder.build(self._builder.model_class.get_table())
|
|
36
|
+
|
|
33
37
|
async def execute(self) -> dict[str, Any]:
|
|
34
38
|
"""Execute aggregation query and return results dictionary.
|
|
35
39
|
|
|
@@ -42,27 +42,42 @@ class QueryExpression(ABC, Generic[T_Result]):
|
|
|
42
42
|
pass
|
|
43
43
|
|
|
44
44
|
@abstractmethod
|
|
45
|
+
def get_query(self) -> Any:
|
|
46
|
+
"""Get SQLAlchemy query object.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
SQLAlchemy Select/Update/Delete object
|
|
50
|
+
"""
|
|
51
|
+
pass
|
|
52
|
+
|
|
45
53
|
def get_sql(self) -> str:
|
|
46
|
-
"""Generate SQL string for
|
|
54
|
+
"""Generate SQL string for debugging.
|
|
47
55
|
|
|
48
56
|
Returns:
|
|
49
57
|
SQL string representation
|
|
50
58
|
"""
|
|
51
|
-
|
|
59
|
+
query = self.get_query()
|
|
60
|
+
return str(query.compile(compile_kwargs={"literal_binds": True}))
|
|
52
61
|
|
|
53
|
-
def explain(self, analyze: bool = False, verbose: bool = False)
|
|
54
|
-
"""
|
|
62
|
+
def explain(self, analyze: bool = False, verbose: bool = False):
|
|
63
|
+
"""Return awaitable EXPLAIN result.
|
|
64
|
+
|
|
65
|
+
Usage:
|
|
66
|
+
plan = await expression.explain()
|
|
67
|
+
plan = await expression.explain(analyze=True, verbose=True)
|
|
55
68
|
|
|
56
69
|
Args:
|
|
57
70
|
analyze: Include actual execution statistics
|
|
58
71
|
verbose: Include detailed execution information
|
|
59
72
|
|
|
60
73
|
Returns:
|
|
61
|
-
|
|
74
|
+
ExplainResult that can be awaited
|
|
62
75
|
"""
|
|
76
|
+
from .explain import ExplainResult
|
|
77
|
+
|
|
63
78
|
if not self._executor:
|
|
64
79
|
raise RuntimeError("No executor available for explain operation")
|
|
65
|
-
return
|
|
80
|
+
return ExplainResult(self, analyze, verbose)
|
|
66
81
|
|
|
67
82
|
def __gt__(self, other) -> "ComparisonExpression":
|
|
68
83
|
"""Create greater-than comparison expression."""
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""CTE (Common Table Expression) support for SQLObjects.
|
|
2
|
+
|
|
3
|
+
This module provides CTE functionality for building complex queries with
|
|
4
|
+
reusable subqueries and recursive queries.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import TYPE_CHECKING, Any
|
|
10
|
+
|
|
11
|
+
from sqlalchemy import CTE as SACTE
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from sqlobjects.queryset import QuerySet
|
|
16
|
+
|
|
17
|
+
__all__ = ["CTEExpression"]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class CTEExpression:
|
|
21
|
+
"""CTE expression wrapper for query reuse.
|
|
22
|
+
|
|
23
|
+
A CTE (Common Table Expression) allows you to define a temporary named
|
|
24
|
+
result set that can be referenced within a SELECT, INSERT, UPDATE, or
|
|
25
|
+
DELETE statement.
|
|
26
|
+
|
|
27
|
+
Examples:
|
|
28
|
+
Basic CTE:
|
|
29
|
+
>>> adults = User.objects.filter(User.age >= 18).cte("adults")
|
|
30
|
+
>>> result = await User.objects.with_cte(adults).filter(adults.c.age < 30).all()
|
|
31
|
+
|
|
32
|
+
Recursive CTE:
|
|
33
|
+
>>> base = Employee.objects.filter(Employee.manager_id.is_(None)).cte("hierarchy", recursive=True)
|
|
34
|
+
>>> recursive = Employee.objects.join(base, Employee.manager_id == base.c.id)
|
|
35
|
+
>>> hierarchy = base.union_all(recursive)
|
|
36
|
+
>>> all_employees = await Employee.objects.with_cte(hierarchy).select_from(hierarchy).all()
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, queryset: QuerySet, name: str, recursive: bool = False):
|
|
40
|
+
"""Initialize CTE expression.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
queryset: The QuerySet to convert to CTE
|
|
44
|
+
name: Name of the CTE
|
|
45
|
+
recursive: Whether this is a recursive CTE
|
|
46
|
+
"""
|
|
47
|
+
self._queryset = queryset
|
|
48
|
+
self._name = name
|
|
49
|
+
self._recursive = recursive
|
|
50
|
+
self._cte_obj: SACTE | None = None
|
|
51
|
+
self._union_query: QuerySet | None = None
|
|
52
|
+
|
|
53
|
+
def _build_cte(self, session) -> SACTE:
|
|
54
|
+
"""Build SQLAlchemy CTE object.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
session: Database session for building the query
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
SQLAlchemy CTE object
|
|
61
|
+
"""
|
|
62
|
+
if self._cte_obj is not None:
|
|
63
|
+
return self._cte_obj
|
|
64
|
+
|
|
65
|
+
# Build base query
|
|
66
|
+
stmt = self._queryset._builder.build(session)
|
|
67
|
+
|
|
68
|
+
# Handle recursive CTE with UNION ALL
|
|
69
|
+
if self._recursive and self._union_query is not None:
|
|
70
|
+
# Build recursive part
|
|
71
|
+
recursive_stmt = self._union_query._builder.build(session)
|
|
72
|
+
|
|
73
|
+
# Combine with UNION ALL
|
|
74
|
+
combined = stmt.union_all(recursive_stmt)
|
|
75
|
+
cte_obj = combined.cte(self._name, recursive=True)
|
|
76
|
+
else:
|
|
77
|
+
# Non-recursive CTE
|
|
78
|
+
cte_obj = stmt.cte(self._name, recursive=self._recursive)
|
|
79
|
+
|
|
80
|
+
self._cte_obj = cte_obj
|
|
81
|
+
return cte_obj
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def c(self) -> Any:
|
|
85
|
+
"""Access CTE columns (similar to Table.c).
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Column collection for the CTE
|
|
89
|
+
|
|
90
|
+
Note:
|
|
91
|
+
The CTE must be built before accessing columns.
|
|
92
|
+
This is typically done automatically when used in a query.
|
|
93
|
+
"""
|
|
94
|
+
if self._cte_obj is None:
|
|
95
|
+
raise RuntimeError(f"CTE '{self._name}' has not been built yet. Use it in a query with .with_cte() first.")
|
|
96
|
+
return self._cte_obj.c
|
|
97
|
+
|
|
98
|
+
def union_all(self, recursive_queryset: QuerySet) -> CTEExpression:
|
|
99
|
+
"""Add UNION ALL clause for recursive CTE.
|
|
100
|
+
|
|
101
|
+
This method is used to define the recursive part of a recursive CTE.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
recursive_queryset: QuerySet for the recursive part
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Self for method chaining
|
|
108
|
+
|
|
109
|
+
Example:
|
|
110
|
+
>>> base = Employee.objects.filter(Employee.manager_id.is_(None)).cte("tree", recursive=True)
|
|
111
|
+
>>> recursive = Employee.objects.join(base, Employee.manager_id == base.c.id)
|
|
112
|
+
>>> tree = base.union_all(recursive)
|
|
113
|
+
"""
|
|
114
|
+
if not self._recursive:
|
|
115
|
+
raise ValueError(
|
|
116
|
+
f"union_all() can only be used with recursive CTEs. "
|
|
117
|
+
f"Set recursive=True when creating CTE '{self._name}'."
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
self._union_query = recursive_queryset
|
|
121
|
+
return self
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def name(self) -> str:
|
|
125
|
+
"""Get CTE name."""
|
|
126
|
+
return self._name
|
|
127
|
+
|
|
128
|
+
@property
|
|
129
|
+
def is_recursive(self) -> bool:
|
|
130
|
+
"""Check if this is a recursive CTE."""
|
|
131
|
+
return self._recursive
|
|
132
|
+
|
|
133
|
+
def __repr__(self) -> str:
|
|
134
|
+
"""String representation."""
|
|
135
|
+
recursive_str = ", recursive=True" if self._recursive else ""
|
|
136
|
+
return f"CTEExpression(name='{self._name}'{recursive_str})"
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""EXPLAIN query analysis support."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ExplainResult:
|
|
5
|
+
"""Awaitable result for EXPLAIN operations.
|
|
6
|
+
|
|
7
|
+
This class wraps EXPLAIN query execution and makes it awaitable,
|
|
8
|
+
providing a clean async interface for query plan analysis.
|
|
9
|
+
|
|
10
|
+
Examples:
|
|
11
|
+
plan = await User.objects.filter(User.age > 25).explain()
|
|
12
|
+
analysis = await User.objects.all().explain(analyze=True, verbose=True)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, expression, analyze: bool, verbose: bool):
|
|
16
|
+
"""Initialize EXPLAIN result.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
expression: QueryExpression to explain
|
|
20
|
+
analyze: Include actual execution statistics
|
|
21
|
+
verbose: Include detailed execution information
|
|
22
|
+
"""
|
|
23
|
+
self.expression = expression
|
|
24
|
+
self.analyze = analyze
|
|
25
|
+
self.verbose = verbose
|
|
26
|
+
|
|
27
|
+
def __await__(self):
|
|
28
|
+
"""Make this object awaitable."""
|
|
29
|
+
return self._execute().__await__()
|
|
30
|
+
|
|
31
|
+
async def _execute(self) -> str:
|
|
32
|
+
"""Execute EXPLAIN and return plan.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Query execution plan as string
|
|
36
|
+
"""
|
|
37
|
+
query = self.expression.get_query()
|
|
38
|
+
return await self.expression._executor.explain(query, self.analyze, self.verbose)
|