sqlalchemy-cubrid 1.2.2__tar.gz → 1.2.3__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.
- sqlalchemy_cubrid-1.2.3/PKG-INFO +282 -0
- sqlalchemy_cubrid-1.2.3/README.md +232 -0
- {sqlalchemy_cubrid-1.2.2 → sqlalchemy_cubrid-1.2.3}/pyproject.toml +51 -8
- {sqlalchemy_cubrid-1.2.2 → sqlalchemy_cubrid-1.2.3}/sqlalchemy_cubrid/__init__.py +15 -2
- sqlalchemy_cubrid-1.2.3/sqlalchemy_cubrid/aio_pycubrid_dialect.py +130 -0
- sqlalchemy_cubrid-1.2.3/sqlalchemy_cubrid/alembic_impl.py +185 -0
- {sqlalchemy_cubrid-1.2.2 → sqlalchemy_cubrid-1.2.3}/sqlalchemy_cubrid/base.py +16 -14
- {sqlalchemy_cubrid-1.2.2 → sqlalchemy_cubrid-1.2.3}/sqlalchemy_cubrid/compiler.py +140 -58
- {sqlalchemy_cubrid-1.2.2 → sqlalchemy_cubrid-1.2.3}/sqlalchemy_cubrid/dialect.py +373 -124
- {sqlalchemy_cubrid-1.2.2 → sqlalchemy_cubrid-1.2.3}/sqlalchemy_cubrid/dml.py +57 -6
- sqlalchemy_cubrid-1.2.3/sqlalchemy_cubrid/pycubrid_dialect.py +129 -0
- sqlalchemy_cubrid-1.2.3/sqlalchemy_cubrid/requirements.py +276 -0
- sqlalchemy_cubrid-1.2.3/sqlalchemy_cubrid/trace.py +92 -0
- {sqlalchemy_cubrid-1.2.2 → sqlalchemy_cubrid-1.2.3}/sqlalchemy_cubrid/types.py +156 -30
- sqlalchemy_cubrid-1.2.3/sqlalchemy_cubrid.egg-info/PKG-INFO +282 -0
- {sqlalchemy_cubrid-1.2.2 → sqlalchemy_cubrid-1.2.3}/sqlalchemy_cubrid.egg-info/SOURCES.txt +8 -0
- {sqlalchemy_cubrid-1.2.2 → sqlalchemy_cubrid-1.2.3}/sqlalchemy_cubrid.egg-info/entry_points.txt +2 -0
- sqlalchemy_cubrid-1.2.3/sqlalchemy_cubrid.egg-info/requires.txt +21 -0
- sqlalchemy_cubrid-1.2.3/test/test_aio_integration.py +278 -0
- sqlalchemy_cubrid-1.2.3/test/test_aio_pycubrid_dialect.py +206 -0
- sqlalchemy_cubrid-1.2.3/test/test_alembic.py +301 -0
- {sqlalchemy_cubrid-1.2.2 → sqlalchemy_cubrid-1.2.3}/test/test_compiler.py +175 -1
- {sqlalchemy_cubrid-1.2.2 → sqlalchemy_cubrid-1.2.3}/test/test_dialect_offline.py +379 -32
- {sqlalchemy_cubrid-1.2.2 → sqlalchemy_cubrid-1.2.3}/test/test_dialects.py +30 -0
- sqlalchemy_cubrid-1.2.3/test/test_integration.py +613 -0
- sqlalchemy_cubrid-1.2.3/test/test_json.py +312 -0
- sqlalchemy_cubrid-1.2.3/test/test_pycubrid_dialect.py +306 -0
- sqlalchemy_cubrid-1.2.3/test/test_trace.py +197 -0
- {sqlalchemy_cubrid-1.2.2 → sqlalchemy_cubrid-1.2.3}/test/test_types.py +16 -0
- sqlalchemy_cubrid-1.2.2/PKG-INFO +0 -113
- sqlalchemy_cubrid-1.2.2/README.md +0 -79
- sqlalchemy_cubrid-1.2.2/sqlalchemy_cubrid/alembic_impl.py +0 -69
- sqlalchemy_cubrid-1.2.2/sqlalchemy_cubrid/requirements.py +0 -270
- sqlalchemy_cubrid-1.2.2/sqlalchemy_cubrid.egg-info/PKG-INFO +0 -113
- sqlalchemy_cubrid-1.2.2/sqlalchemy_cubrid.egg-info/requires.txt +0 -11
- sqlalchemy_cubrid-1.2.2/test/test_alembic.py +0 -120
- sqlalchemy_cubrid-1.2.2/test/test_integration.py +0 -326
- {sqlalchemy_cubrid-1.2.2 → sqlalchemy_cubrid-1.2.3}/AUTHORS +0 -0
- {sqlalchemy_cubrid-1.2.2 → sqlalchemy_cubrid-1.2.3}/LICENSE +0 -0
- {sqlalchemy_cubrid-1.2.2 → sqlalchemy_cubrid-1.2.3}/setup.cfg +0 -0
- {sqlalchemy_cubrid-1.2.2 → sqlalchemy_cubrid-1.2.3}/sqlalchemy_cubrid/py.typed +0 -0
- {sqlalchemy_cubrid-1.2.2 → sqlalchemy_cubrid-1.2.3}/sqlalchemy_cubrid.egg-info/dependency_links.txt +0 -0
- {sqlalchemy_cubrid-1.2.2 → sqlalchemy_cubrid-1.2.3}/sqlalchemy_cubrid.egg-info/top_level.txt +0 -0
- {sqlalchemy_cubrid-1.2.2 → sqlalchemy_cubrid-1.2.3}/test/test_base.py +0 -0
- {sqlalchemy_cubrid-1.2.2 → sqlalchemy_cubrid-1.2.3}/test/test_requirements.py +0 -0
- {sqlalchemy_cubrid-1.2.2 → sqlalchemy_cubrid-1.2.3}/test/test_suite.py +0 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sqlalchemy-cubrid
|
|
3
|
+
Version: 1.2.3
|
|
4
|
+
Summary: CUBRID dialect for SQLAlchemy
|
|
5
|
+
Author-email: Yeongseon Choe <yeongseon.choe@gmail.com>, Gyeongjun Paik <paikend@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/cubrid-labs/sqlalchemy-cubrid
|
|
8
|
+
Project-URL: Repository, https://github.com/cubrid-labs/sqlalchemy-cubrid
|
|
9
|
+
Project-URL: Documentation, https://github.com/cubrid-labs/sqlalchemy-cubrid/tree/main/docs
|
|
10
|
+
Project-URL: Changelog, https://github.com/cubrid-labs/sqlalchemy-cubrid/blob/main/CHANGELOG.md
|
|
11
|
+
Project-URL: Issues, https://github.com/cubrid-labs/sqlalchemy-cubrid/issues
|
|
12
|
+
Keywords: SQLAlchemy,CUBRID,dialect
|
|
13
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
25
|
+
Classifier: Programming Language :: SQL
|
|
26
|
+
Classifier: Topic :: Database
|
|
27
|
+
Classifier: Topic :: Database :: Front-Ends
|
|
28
|
+
Requires-Python: >=3.10
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
License-File: LICENSE
|
|
31
|
+
License-File: AUTHORS
|
|
32
|
+
Requires-Dist: sqlalchemy<2.2,>=2.0
|
|
33
|
+
Provides-Extra: cubrid
|
|
34
|
+
Requires-Dist: CUBRID-Python; extra == "cubrid"
|
|
35
|
+
Provides-Extra: dev
|
|
36
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
37
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
38
|
+
Requires-Dist: pytest-asyncio; extra == "dev"
|
|
39
|
+
Requires-Dist: ruff==0.15.8; extra == "dev"
|
|
40
|
+
Requires-Dist: mypy==1.19.1; extra == "dev"
|
|
41
|
+
Requires-Dist: bandit[toml]==1.9.4; extra == "dev"
|
|
42
|
+
Requires-Dist: pre-commit; extra == "dev"
|
|
43
|
+
Requires-Dist: tox; extra == "dev"
|
|
44
|
+
Requires-Dist: alembic>=1.7; extra == "dev"
|
|
45
|
+
Provides-Extra: alembic
|
|
46
|
+
Requires-Dist: alembic>=1.7; extra == "alembic"
|
|
47
|
+
Provides-Extra: pycubrid
|
|
48
|
+
Requires-Dist: pycubrid<2.0,>=1.0; extra == "pycubrid"
|
|
49
|
+
Dynamic: license-file
|
|
50
|
+
|
|
51
|
+
# sqlalchemy-cubrid
|
|
52
|
+
|
|
53
|
+
**SQLAlchemy 2.0–2.1 dialect for the CUBRID database** — Python ORM, schema reflection, Alembic migrations, and full type system support.
|
|
54
|
+
|
|
55
|
+
[🇰🇷 한국어](docs/README.ko.md) · [🇺🇸 English](README.md) · [🇨🇳 中文](docs/README.zh.md) · [🇮🇳 हिन्दी](docs/README.hi.md) · [🇩🇪 Deutsch](docs/README.de.md) · [🇷🇺 Русский](docs/README.ru.md)
|
|
56
|
+
|
|
57
|
+
<!-- BADGES:START -->
|
|
58
|
+
[](https://pypi.org/project/sqlalchemy-cubrid)
|
|
59
|
+
[](https://www.python.org)
|
|
60
|
+
[](https://github.com/cubrid-labs/sqlalchemy-cubrid/actions/workflows/ci.yml)
|
|
61
|
+
[](https://codecov.io/gh/cubrid-labs/sqlalchemy-cubrid)
|
|
62
|
+
[](https://github.com/cubrid-labs/sqlalchemy-cubrid/blob/main/LICENSE)
|
|
63
|
+
[](https://github.com/cubrid-labs/sqlalchemy-cubrid)
|
|
64
|
+
[](https://cubrid-labs.github.io/sqlalchemy-cubrid/)
|
|
65
|
+
<!-- BADGES:END -->
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Why sqlalchemy-cubrid?
|
|
70
|
+
|
|
71
|
+
CUBRID is a high-performance open-source relational database, widely adopted in
|
|
72
|
+
Korean public-sector and enterprise applications. Until now, there was no
|
|
73
|
+
production-ready SQLAlchemy dialect that supports the modern 2.0–2.1 API.
|
|
74
|
+
|
|
75
|
+
**sqlalchemy-cubrid** bridges that gap:
|
|
76
|
+
|
|
77
|
+
- Full SQLAlchemy 2.0–2.1 dialect with **statement caching** and **PEP 561 typing**
|
|
78
|
+
- **426 offline tests** with **99%+ code coverage** — no database required to run them
|
|
79
|
+
- Tested against **4 CUBRID versions** (10.2, 11.0, 11.2, 11.4) across **Python 3.10 -- 3.14**
|
|
80
|
+
- CUBRID-specific DML constructs: `ON DUPLICATE KEY UPDATE`, `MERGE`, `REPLACE INTO`
|
|
81
|
+
- Alembic migration support out of the box
|
|
82
|
+
- **Three driver options** — C-extension (`cubrid://`), pure Python (`cubrid+pycubrid://`), or async pure Python (`cubrid+aiopycubrid://`)
|
|
83
|
+
|
|
84
|
+
## Architecture
|
|
85
|
+
|
|
86
|
+
```mermaid
|
|
87
|
+
flowchart TD
|
|
88
|
+
app["Application"] --> sa["SQLAlchemy Core/ORM"]
|
|
89
|
+
sa --> dialect["CubridDialect"]
|
|
90
|
+
dialect --> pycubrid["pycubrid driver"]
|
|
91
|
+
dialect --> cext["CUBRIDdb driver"]
|
|
92
|
+
dialect --> aio["pycubrid.aio async driver"]
|
|
93
|
+
pycubrid --> server["CUBRID Server"]
|
|
94
|
+
cext --> server
|
|
95
|
+
aio --> server
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
```mermaid
|
|
99
|
+
flowchart TD
|
|
100
|
+
expr["SQL Expression"] --> compiler["CubridSQLCompiler"] --> sql["SQL String"]
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Requirements
|
|
104
|
+
|
|
105
|
+
- Python 3.10+
|
|
106
|
+
- SQLAlchemy 2.0 – 2.1
|
|
107
|
+
- [CUBRID-Python](https://github.com/CUBRID/cubrid-python) (C-extension) **or** [pycubrid](https://github.com/sqlalchemy-cubrid/pycubrid) (pure Python)
|
|
108
|
+
|
|
109
|
+
## Installation
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
pip install sqlalchemy-cubrid
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
With the pure Python driver (no C build needed):
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
pip install "sqlalchemy-cubrid[pycubrid]"
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
With Alembic support:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
pip install "sqlalchemy-cubrid[alembic]"
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Quick Start
|
|
128
|
+
|
|
129
|
+
### Core (Connection-Level)
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
from sqlalchemy import create_engine, text
|
|
133
|
+
|
|
134
|
+
engine = create_engine("cubrid://dba:password@localhost:33000/demodb")
|
|
135
|
+
|
|
136
|
+
with engine.connect() as conn:
|
|
137
|
+
result = conn.execute(text("SELECT 1"))
|
|
138
|
+
print(result.scalar())
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### ORM (Session-Level)
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
from sqlalchemy import create_engine, String
|
|
145
|
+
from sqlalchemy.orm import DeclarativeBase, Mapped, Session, mapped_column
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class Base(DeclarativeBase):
|
|
149
|
+
pass
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class User(Base):
|
|
153
|
+
__tablename__ = "users"
|
|
154
|
+
|
|
155
|
+
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
|
156
|
+
name: Mapped[str] = mapped_column(String(100))
|
|
157
|
+
email: Mapped[str] = mapped_column(String(200), unique=True)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
engine = create_engine("cubrid://dba:password@localhost:33000/demodb")
|
|
161
|
+
Base.metadata.create_all(engine)
|
|
162
|
+
|
|
163
|
+
with Session(engine) as session:
|
|
164
|
+
user = User(name="Alice", email="alice@example.com")
|
|
165
|
+
session.add(user)
|
|
166
|
+
session.commit()
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Async
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
|
|
173
|
+
from sqlalchemy import text
|
|
174
|
+
|
|
175
|
+
engine = create_async_engine("cubrid+aiopycubrid://dba:password@localhost:33000/demodb")
|
|
176
|
+
|
|
177
|
+
async with AsyncSession(engine) as session:
|
|
178
|
+
result = await session.execute(text("SELECT 1"))
|
|
179
|
+
print(result.scalar())
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Features
|
|
183
|
+
|
|
184
|
+
- Complete type system -- numeric, string, date/time, bit, LOB, collection, and JSON types
|
|
185
|
+
- SQL compilation -- SELECT, JOIN, CAST, LIMIT/OFFSET, subqueries, CTEs, window functions
|
|
186
|
+
- DML extensions -- `ON DUPLICATE KEY UPDATE`, `MERGE`, `REPLACE INTO`, `FOR UPDATE`, `TRUNCATE`
|
|
187
|
+
- DDL support -- `COMMENT`, `IF NOT EXISTS` / `IF EXISTS`, `AUTO_INCREMENT`
|
|
188
|
+
- Schema reflection -- tables, views, columns, PKs, FKs, indexes, unique constraints, comments
|
|
189
|
+
- Alembic migrations via `CubridImpl` (auto-discovered entry point)
|
|
190
|
+
- All 6 CUBRID isolation levels (dual-granularity: class-level + instance-level)
|
|
191
|
+
- Async support — `create_async_engine("cubrid+aiopycubrid://...")` via pycubrid.aio
|
|
192
|
+
|
|
193
|
+
## Documentation
|
|
194
|
+
|
|
195
|
+
| Guide | Description |
|
|
196
|
+
|---|---|
|
|
197
|
+
| [Connection](docs/CONNECTION.md) | Connection strings, URL format, driver setup, pool tuning |
|
|
198
|
+
| [Type Mapping](docs/TYPES.md) | Full type mapping, CUBRID-specific types, collection types |
|
|
199
|
+
| [DML Extensions](docs/DML_EXTENSIONS.md) | ON DUPLICATE KEY UPDATE, MERGE, REPLACE INTO, query trace |
|
|
200
|
+
| [Isolation Levels](docs/ISOLATION_LEVELS.md) | All 6 CUBRID isolation levels, configuration |
|
|
201
|
+
| [Alembic Migrations](docs/ALEMBIC.md) | Setup, configuration, limitations, batch workarounds |
|
|
202
|
+
| [Feature Support](docs/FEATURE_SUPPORT.md) | Comparison with MySQL, PostgreSQL, SQLite |
|
|
203
|
+
| [ORM Cookbook](docs/ORM_COOKBOOK.md) | Practical ORM examples, relationships, queries |
|
|
204
|
+
| [Development](docs/DEVELOPMENT.md) | Dev setup, testing, Docker, coverage, CI/CD |
|
|
205
|
+
| [Driver Compatibility](docs/DRIVER_COMPAT.md) | CUBRID-Python driver versions and known issues |
|
|
206
|
+
| [Troubleshooting](docs/TROUBLESHOOTING.md) | Common issues, error solutions, debugging techniques |
|
|
207
|
+
| [Async Connection](docs/CONNECTION.md#async-connection) | Async engine setup with `cubrid+aiopycubrid://` |
|
|
208
|
+
|
|
209
|
+
## Compatibility Matrix
|
|
210
|
+
|
|
211
|
+
| Component | Supported versions |
|
|
212
|
+
|---|---|
|
|
213
|
+
| Python | 3.10, 3.11, 3.12, 3.13, 3.14 |
|
|
214
|
+
| CUBRID | 10.2, 11.0, 11.2, 11.4 |
|
|
215
|
+
| SQLAlchemy | 2.0–2.1 |
|
|
216
|
+
| Alembic | >=1.7 |
|
|
217
|
+
|
|
218
|
+
## FAQ
|
|
219
|
+
|
|
220
|
+
### How do I connect to CUBRID with SQLAlchemy?
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
from sqlalchemy import create_engine
|
|
224
|
+
engine = create_engine("cubrid://dba:password@localhost:33000/demodb")
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
For the pure Python driver (no C build needed): `create_engine("cubrid+pycubrid://dba@localhost:33000/demodb")`
|
|
228
|
+
|
|
229
|
+
### Does sqlalchemy-cubrid support SQLAlchemy 2.0–2.1?
|
|
230
|
+
|
|
231
|
+
Yes. sqlalchemy-cubrid is built for SQLAlchemy 2.0–2.1 and supports the 2.0-style API including `Session.execute()`, typed `Mapped[]` columns, and statement caching.
|
|
232
|
+
|
|
233
|
+
### Does sqlalchemy-cubrid support Alembic migrations?
|
|
234
|
+
|
|
235
|
+
Yes. Install with `pip install "sqlalchemy-cubrid[alembic]"`. The dialect auto-registers via entry point. Note that CUBRID auto-commits DDL, so migrations are not transactional.
|
|
236
|
+
|
|
237
|
+
### What Python versions are supported?
|
|
238
|
+
|
|
239
|
+
Python 3.10, 3.11, 3.12, 3.13, and 3.14.
|
|
240
|
+
|
|
241
|
+
### Does CUBRID support RETURNING clauses?
|
|
242
|
+
|
|
243
|
+
No. CUBRID does not support `INSERT ... RETURNING` or `UPDATE ... RETURNING`. Use `cursor.lastrowid` or `SELECT LAST_INSERT_ID()` instead.
|
|
244
|
+
|
|
245
|
+
### How do I use ON DUPLICATE KEY UPDATE with CUBRID?
|
|
246
|
+
|
|
247
|
+
```python
|
|
248
|
+
from sqlalchemy_cubrid import insert
|
|
249
|
+
stmt = insert(users).values(name="Alice").on_duplicate_key_update(name="Alice Updated")
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### What's the difference between `cubrid://` and `cubrid+pycubrid://`?
|
|
253
|
+
|
|
254
|
+
`cubrid://` uses the C-extension driver (CUBRIDdb) which requires compilation. `cubrid+pycubrid://` uses the pure Python driver which installs with pip alone — no build tools needed. `cubrid+aiopycubrid://` uses the async variant of the pure Python driver for use with `create_async_engine` and `AsyncSession`.
|
|
255
|
+
|
|
256
|
+
### Does sqlalchemy-cubrid support async?
|
|
257
|
+
|
|
258
|
+
Yes. Use `create_async_engine("cubrid+aiopycubrid://...")` with the pycubrid async driver. Requires `pycubrid>=1.1.0`. All Core and ORM features work with `AsyncSession`.
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
## Related Projects
|
|
262
|
+
|
|
263
|
+
- [pycubrid](https://github.com/cubrid-labs/pycubrid) — Pure Python DB-API 2.0 driver for CUBRID
|
|
264
|
+
- [cubrid-cookbook-python](https://github.com/cubrid-labs/cubrid-cookbook-python) — Production-ready Python examples for CUBRID
|
|
265
|
+
|
|
266
|
+
## Roadmap
|
|
267
|
+
|
|
268
|
+
See [`ROADMAP.md`](ROADMAP.md) for this project's direction and next milestones.
|
|
269
|
+
|
|
270
|
+
For the ecosystem-wide view, see the [CUBRID Labs Ecosystem Roadmap](https://github.com/cubrid-labs/.github/blob/main/ROADMAP.md) and [Project Board](https://github.com/orgs/cubrid-labs/projects/2).
|
|
271
|
+
|
|
272
|
+
## Contributing
|
|
273
|
+
|
|
274
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines and [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for development setup.
|
|
275
|
+
|
|
276
|
+
## Security
|
|
277
|
+
|
|
278
|
+
Report vulnerabilities via email -- see [SECURITY.md](SECURITY.md). Do not open public issues for security concerns.
|
|
279
|
+
|
|
280
|
+
## License
|
|
281
|
+
|
|
282
|
+
MIT -- see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# sqlalchemy-cubrid
|
|
2
|
+
|
|
3
|
+
**SQLAlchemy 2.0–2.1 dialect for the CUBRID database** — Python ORM, schema reflection, Alembic migrations, and full type system support.
|
|
4
|
+
|
|
5
|
+
[🇰🇷 한국어](docs/README.ko.md) · [🇺🇸 English](README.md) · [🇨🇳 中文](docs/README.zh.md) · [🇮🇳 हिन्दी](docs/README.hi.md) · [🇩🇪 Deutsch](docs/README.de.md) · [🇷🇺 Русский](docs/README.ru.md)
|
|
6
|
+
|
|
7
|
+
<!-- BADGES:START -->
|
|
8
|
+
[](https://pypi.org/project/sqlalchemy-cubrid)
|
|
9
|
+
[](https://www.python.org)
|
|
10
|
+
[](https://github.com/cubrid-labs/sqlalchemy-cubrid/actions/workflows/ci.yml)
|
|
11
|
+
[](https://codecov.io/gh/cubrid-labs/sqlalchemy-cubrid)
|
|
12
|
+
[](https://github.com/cubrid-labs/sqlalchemy-cubrid/blob/main/LICENSE)
|
|
13
|
+
[](https://github.com/cubrid-labs/sqlalchemy-cubrid)
|
|
14
|
+
[](https://cubrid-labs.github.io/sqlalchemy-cubrid/)
|
|
15
|
+
<!-- BADGES:END -->
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Why sqlalchemy-cubrid?
|
|
20
|
+
|
|
21
|
+
CUBRID is a high-performance open-source relational database, widely adopted in
|
|
22
|
+
Korean public-sector and enterprise applications. Until now, there was no
|
|
23
|
+
production-ready SQLAlchemy dialect that supports the modern 2.0–2.1 API.
|
|
24
|
+
|
|
25
|
+
**sqlalchemy-cubrid** bridges that gap:
|
|
26
|
+
|
|
27
|
+
- Full SQLAlchemy 2.0–2.1 dialect with **statement caching** and **PEP 561 typing**
|
|
28
|
+
- **426 offline tests** with **99%+ code coverage** — no database required to run them
|
|
29
|
+
- Tested against **4 CUBRID versions** (10.2, 11.0, 11.2, 11.4) across **Python 3.10 -- 3.14**
|
|
30
|
+
- CUBRID-specific DML constructs: `ON DUPLICATE KEY UPDATE`, `MERGE`, `REPLACE INTO`
|
|
31
|
+
- Alembic migration support out of the box
|
|
32
|
+
- **Three driver options** — C-extension (`cubrid://`), pure Python (`cubrid+pycubrid://`), or async pure Python (`cubrid+aiopycubrid://`)
|
|
33
|
+
|
|
34
|
+
## Architecture
|
|
35
|
+
|
|
36
|
+
```mermaid
|
|
37
|
+
flowchart TD
|
|
38
|
+
app["Application"] --> sa["SQLAlchemy Core/ORM"]
|
|
39
|
+
sa --> dialect["CubridDialect"]
|
|
40
|
+
dialect --> pycubrid["pycubrid driver"]
|
|
41
|
+
dialect --> cext["CUBRIDdb driver"]
|
|
42
|
+
dialect --> aio["pycubrid.aio async driver"]
|
|
43
|
+
pycubrid --> server["CUBRID Server"]
|
|
44
|
+
cext --> server
|
|
45
|
+
aio --> server
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```mermaid
|
|
49
|
+
flowchart TD
|
|
50
|
+
expr["SQL Expression"] --> compiler["CubridSQLCompiler"] --> sql["SQL String"]
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Requirements
|
|
54
|
+
|
|
55
|
+
- Python 3.10+
|
|
56
|
+
- SQLAlchemy 2.0 – 2.1
|
|
57
|
+
- [CUBRID-Python](https://github.com/CUBRID/cubrid-python) (C-extension) **or** [pycubrid](https://github.com/sqlalchemy-cubrid/pycubrid) (pure Python)
|
|
58
|
+
|
|
59
|
+
## Installation
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install sqlalchemy-cubrid
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
With the pure Python driver (no C build needed):
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pip install "sqlalchemy-cubrid[pycubrid]"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
With Alembic support:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pip install "sqlalchemy-cubrid[alembic]"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Quick Start
|
|
78
|
+
|
|
79
|
+
### Core (Connection-Level)
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from sqlalchemy import create_engine, text
|
|
83
|
+
|
|
84
|
+
engine = create_engine("cubrid://dba:password@localhost:33000/demodb")
|
|
85
|
+
|
|
86
|
+
with engine.connect() as conn:
|
|
87
|
+
result = conn.execute(text("SELECT 1"))
|
|
88
|
+
print(result.scalar())
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### ORM (Session-Level)
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
from sqlalchemy import create_engine, String
|
|
95
|
+
from sqlalchemy.orm import DeclarativeBase, Mapped, Session, mapped_column
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class Base(DeclarativeBase):
|
|
99
|
+
pass
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class User(Base):
|
|
103
|
+
__tablename__ = "users"
|
|
104
|
+
|
|
105
|
+
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
|
106
|
+
name: Mapped[str] = mapped_column(String(100))
|
|
107
|
+
email: Mapped[str] = mapped_column(String(200), unique=True)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
engine = create_engine("cubrid://dba:password@localhost:33000/demodb")
|
|
111
|
+
Base.metadata.create_all(engine)
|
|
112
|
+
|
|
113
|
+
with Session(engine) as session:
|
|
114
|
+
user = User(name="Alice", email="alice@example.com")
|
|
115
|
+
session.add(user)
|
|
116
|
+
session.commit()
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Async
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
|
|
123
|
+
from sqlalchemy import text
|
|
124
|
+
|
|
125
|
+
engine = create_async_engine("cubrid+aiopycubrid://dba:password@localhost:33000/demodb")
|
|
126
|
+
|
|
127
|
+
async with AsyncSession(engine) as session:
|
|
128
|
+
result = await session.execute(text("SELECT 1"))
|
|
129
|
+
print(result.scalar())
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Features
|
|
133
|
+
|
|
134
|
+
- Complete type system -- numeric, string, date/time, bit, LOB, collection, and JSON types
|
|
135
|
+
- SQL compilation -- SELECT, JOIN, CAST, LIMIT/OFFSET, subqueries, CTEs, window functions
|
|
136
|
+
- DML extensions -- `ON DUPLICATE KEY UPDATE`, `MERGE`, `REPLACE INTO`, `FOR UPDATE`, `TRUNCATE`
|
|
137
|
+
- DDL support -- `COMMENT`, `IF NOT EXISTS` / `IF EXISTS`, `AUTO_INCREMENT`
|
|
138
|
+
- Schema reflection -- tables, views, columns, PKs, FKs, indexes, unique constraints, comments
|
|
139
|
+
- Alembic migrations via `CubridImpl` (auto-discovered entry point)
|
|
140
|
+
- All 6 CUBRID isolation levels (dual-granularity: class-level + instance-level)
|
|
141
|
+
- Async support — `create_async_engine("cubrid+aiopycubrid://...")` via pycubrid.aio
|
|
142
|
+
|
|
143
|
+
## Documentation
|
|
144
|
+
|
|
145
|
+
| Guide | Description |
|
|
146
|
+
|---|---|
|
|
147
|
+
| [Connection](docs/CONNECTION.md) | Connection strings, URL format, driver setup, pool tuning |
|
|
148
|
+
| [Type Mapping](docs/TYPES.md) | Full type mapping, CUBRID-specific types, collection types |
|
|
149
|
+
| [DML Extensions](docs/DML_EXTENSIONS.md) | ON DUPLICATE KEY UPDATE, MERGE, REPLACE INTO, query trace |
|
|
150
|
+
| [Isolation Levels](docs/ISOLATION_LEVELS.md) | All 6 CUBRID isolation levels, configuration |
|
|
151
|
+
| [Alembic Migrations](docs/ALEMBIC.md) | Setup, configuration, limitations, batch workarounds |
|
|
152
|
+
| [Feature Support](docs/FEATURE_SUPPORT.md) | Comparison with MySQL, PostgreSQL, SQLite |
|
|
153
|
+
| [ORM Cookbook](docs/ORM_COOKBOOK.md) | Practical ORM examples, relationships, queries |
|
|
154
|
+
| [Development](docs/DEVELOPMENT.md) | Dev setup, testing, Docker, coverage, CI/CD |
|
|
155
|
+
| [Driver Compatibility](docs/DRIVER_COMPAT.md) | CUBRID-Python driver versions and known issues |
|
|
156
|
+
| [Troubleshooting](docs/TROUBLESHOOTING.md) | Common issues, error solutions, debugging techniques |
|
|
157
|
+
| [Async Connection](docs/CONNECTION.md#async-connection) | Async engine setup with `cubrid+aiopycubrid://` |
|
|
158
|
+
|
|
159
|
+
## Compatibility Matrix
|
|
160
|
+
|
|
161
|
+
| Component | Supported versions |
|
|
162
|
+
|---|---|
|
|
163
|
+
| Python | 3.10, 3.11, 3.12, 3.13, 3.14 |
|
|
164
|
+
| CUBRID | 10.2, 11.0, 11.2, 11.4 |
|
|
165
|
+
| SQLAlchemy | 2.0–2.1 |
|
|
166
|
+
| Alembic | >=1.7 |
|
|
167
|
+
|
|
168
|
+
## FAQ
|
|
169
|
+
|
|
170
|
+
### How do I connect to CUBRID with SQLAlchemy?
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
from sqlalchemy import create_engine
|
|
174
|
+
engine = create_engine("cubrid://dba:password@localhost:33000/demodb")
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
For the pure Python driver (no C build needed): `create_engine("cubrid+pycubrid://dba@localhost:33000/demodb")`
|
|
178
|
+
|
|
179
|
+
### Does sqlalchemy-cubrid support SQLAlchemy 2.0–2.1?
|
|
180
|
+
|
|
181
|
+
Yes. sqlalchemy-cubrid is built for SQLAlchemy 2.0–2.1 and supports the 2.0-style API including `Session.execute()`, typed `Mapped[]` columns, and statement caching.
|
|
182
|
+
|
|
183
|
+
### Does sqlalchemy-cubrid support Alembic migrations?
|
|
184
|
+
|
|
185
|
+
Yes. Install with `pip install "sqlalchemy-cubrid[alembic]"`. The dialect auto-registers via entry point. Note that CUBRID auto-commits DDL, so migrations are not transactional.
|
|
186
|
+
|
|
187
|
+
### What Python versions are supported?
|
|
188
|
+
|
|
189
|
+
Python 3.10, 3.11, 3.12, 3.13, and 3.14.
|
|
190
|
+
|
|
191
|
+
### Does CUBRID support RETURNING clauses?
|
|
192
|
+
|
|
193
|
+
No. CUBRID does not support `INSERT ... RETURNING` or `UPDATE ... RETURNING`. Use `cursor.lastrowid` or `SELECT LAST_INSERT_ID()` instead.
|
|
194
|
+
|
|
195
|
+
### How do I use ON DUPLICATE KEY UPDATE with CUBRID?
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
from sqlalchemy_cubrid import insert
|
|
199
|
+
stmt = insert(users).values(name="Alice").on_duplicate_key_update(name="Alice Updated")
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### What's the difference between `cubrid://` and `cubrid+pycubrid://`?
|
|
203
|
+
|
|
204
|
+
`cubrid://` uses the C-extension driver (CUBRIDdb) which requires compilation. `cubrid+pycubrid://` uses the pure Python driver which installs with pip alone — no build tools needed. `cubrid+aiopycubrid://` uses the async variant of the pure Python driver for use with `create_async_engine` and `AsyncSession`.
|
|
205
|
+
|
|
206
|
+
### Does sqlalchemy-cubrid support async?
|
|
207
|
+
|
|
208
|
+
Yes. Use `create_async_engine("cubrid+aiopycubrid://...")` with the pycubrid async driver. Requires `pycubrid>=1.1.0`. All Core and ORM features work with `AsyncSession`.
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
## Related Projects
|
|
212
|
+
|
|
213
|
+
- [pycubrid](https://github.com/cubrid-labs/pycubrid) — Pure Python DB-API 2.0 driver for CUBRID
|
|
214
|
+
- [cubrid-cookbook-python](https://github.com/cubrid-labs/cubrid-cookbook-python) — Production-ready Python examples for CUBRID
|
|
215
|
+
|
|
216
|
+
## Roadmap
|
|
217
|
+
|
|
218
|
+
See [`ROADMAP.md`](ROADMAP.md) for this project's direction and next milestones.
|
|
219
|
+
|
|
220
|
+
For the ecosystem-wide view, see the [CUBRID Labs Ecosystem Roadmap](https://github.com/cubrid-labs/.github/blob/main/ROADMAP.md) and [Project Board](https://github.com/orgs/cubrid-labs/projects/2).
|
|
221
|
+
|
|
222
|
+
## Contributing
|
|
223
|
+
|
|
224
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines and [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for development setup.
|
|
225
|
+
|
|
226
|
+
## Security
|
|
227
|
+
|
|
228
|
+
Report vulnerabilities via email -- see [SECURITY.md](SECURITY.md). Do not open public issues for security concerns.
|
|
229
|
+
|
|
230
|
+
## License
|
|
231
|
+
|
|
232
|
+
MIT -- see [LICENSE](LICENSE).
|
|
@@ -4,10 +4,10 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sqlalchemy-cubrid"
|
|
7
|
-
version = "1.2.
|
|
7
|
+
version = "1.2.3"
|
|
8
8
|
description = "CUBRID dialect for SQLAlchemy"
|
|
9
9
|
readme = "README.md"
|
|
10
|
-
license =
|
|
10
|
+
license = "MIT"
|
|
11
11
|
requires-python = ">=3.10"
|
|
12
12
|
authors = [
|
|
13
13
|
{name = "Yeongseon Choe", email = "yeongseon.choe@gmail.com"},
|
|
@@ -15,16 +15,21 @@ authors = [
|
|
|
15
15
|
]
|
|
16
16
|
keywords = ["SQLAlchemy", "CUBRID", "dialect"]
|
|
17
17
|
classifiers = [
|
|
18
|
-
"Development Status ::
|
|
18
|
+
"Development Status :: 5 - Production/Stable",
|
|
19
19
|
"Environment :: Console",
|
|
20
20
|
"Intended Audience :: Developers",
|
|
21
|
-
"
|
|
21
|
+
"Operating System :: OS Independent",
|
|
22
22
|
"Programming Language :: Python",
|
|
23
23
|
"Programming Language :: Python :: 3",
|
|
24
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
24
25
|
"Programming Language :: Python :: 3.10",
|
|
25
26
|
"Programming Language :: Python :: 3.11",
|
|
26
27
|
"Programming Language :: Python :: 3.12",
|
|
27
28
|
"Programming Language :: Python :: 3.13",
|
|
29
|
+
"Programming Language :: Python :: 3.14",
|
|
30
|
+
"Programming Language :: SQL",
|
|
31
|
+
"Topic :: Database",
|
|
32
|
+
"Topic :: Database :: Front-Ends",
|
|
28
33
|
]
|
|
29
34
|
dependencies = [
|
|
30
35
|
"sqlalchemy>=2.0,<2.2",
|
|
@@ -36,20 +41,34 @@ cubrid = [
|
|
|
36
41
|
]
|
|
37
42
|
dev = [
|
|
38
43
|
"pytest>=7.0",
|
|
39
|
-
"
|
|
44
|
+
"pytest-cov",
|
|
45
|
+
"pytest-asyncio",
|
|
46
|
+
"ruff==0.15.8",
|
|
47
|
+
"mypy==1.19.1",
|
|
48
|
+
"bandit[toml]==1.9.4",
|
|
49
|
+
"pre-commit",
|
|
50
|
+
"tox",
|
|
51
|
+
"alembic>=1.7",
|
|
40
52
|
]
|
|
41
53
|
alembic = [
|
|
42
54
|
"alembic>=1.7",
|
|
43
55
|
]
|
|
56
|
+
pycubrid = [
|
|
57
|
+
"pycubrid>=1.0,<2.0",
|
|
58
|
+
]
|
|
44
59
|
|
|
45
60
|
[project.urls]
|
|
46
|
-
Homepage = "https://github.com/
|
|
47
|
-
Repository = "https://github.com/
|
|
48
|
-
Documentation = "https://github.com/
|
|
61
|
+
Homepage = "https://github.com/cubrid-labs/sqlalchemy-cubrid"
|
|
62
|
+
Repository = "https://github.com/cubrid-labs/sqlalchemy-cubrid"
|
|
63
|
+
Documentation = "https://github.com/cubrid-labs/sqlalchemy-cubrid/tree/main/docs"
|
|
64
|
+
Changelog = "https://github.com/cubrid-labs/sqlalchemy-cubrid/blob/main/CHANGELOG.md"
|
|
65
|
+
Issues = "https://github.com/cubrid-labs/sqlalchemy-cubrid/issues"
|
|
49
66
|
|
|
50
67
|
[project.entry-points."sqlalchemy.dialects"]
|
|
51
68
|
cubrid = "sqlalchemy_cubrid.dialect:CubridDialect"
|
|
52
69
|
"cubrid.cubrid" = "sqlalchemy_cubrid.dialect:CubridDialect"
|
|
70
|
+
"cubrid.pycubrid" = "sqlalchemy_cubrid.pycubrid_dialect:PyCubridDialect"
|
|
71
|
+
"cubrid.aiopycubrid" = "sqlalchemy_cubrid.aio_pycubrid_dialect:PyCubridAsyncDialect"
|
|
53
72
|
|
|
54
73
|
[project.entry-points."alembic.ddl"]
|
|
55
74
|
cubrid = "sqlalchemy_cubrid.alembic_impl:CubridImpl"
|
|
@@ -62,6 +81,30 @@ include-package-data = true
|
|
|
62
81
|
line-length = 100
|
|
63
82
|
target-version = "py310"
|
|
64
83
|
|
|
84
|
+
[tool.ruff.lint.isort]
|
|
85
|
+
known-first-party = ["sqlalchemy_cubrid"]
|
|
86
|
+
|
|
87
|
+
[tool.mypy]
|
|
88
|
+
python_version = "3.10"
|
|
89
|
+
strict = true
|
|
90
|
+
warn_return_any = true
|
|
91
|
+
warn_unused_configs = true
|
|
92
|
+
|
|
93
|
+
[[tool.mypy.overrides]]
|
|
94
|
+
module = "test.*"
|
|
95
|
+
ignore_errors = true
|
|
96
|
+
|
|
97
|
+
[tool.bandit]
|
|
98
|
+
exclude_dirs = ["test"]
|
|
99
|
+
skips = ["B101"]
|
|
100
|
+
|
|
101
|
+
[tool.coverage.run]
|
|
102
|
+
source = ["sqlalchemy_cubrid"]
|
|
103
|
+
|
|
104
|
+
[tool.coverage.report]
|
|
105
|
+
show_missing = true
|
|
106
|
+
fail_under = 95
|
|
107
|
+
|
|
65
108
|
[tool.pytest.ini_options]
|
|
66
109
|
testpaths = ["test"]
|
|
67
110
|
addopts = "--tb native -v -r fxX --maxfail=25 -p no:warnings"
|
|
@@ -25,10 +25,15 @@ from .types import (
|
|
|
25
25
|
DOUBLE,
|
|
26
26
|
DOUBLE_PRECISION,
|
|
27
27
|
FLOAT,
|
|
28
|
+
JSON,
|
|
29
|
+
JSONIndexType,
|
|
30
|
+
JSONPathType,
|
|
31
|
+
MONETARY,
|
|
28
32
|
MULTISET,
|
|
29
33
|
NCHAR,
|
|
30
34
|
NUMERIC,
|
|
31
35
|
NVARCHAR,
|
|
36
|
+
OBJECT,
|
|
32
37
|
REAL,
|
|
33
38
|
SEQUENCE,
|
|
34
39
|
SET,
|
|
@@ -36,7 +41,8 @@ from .types import (
|
|
|
36
41
|
STRING,
|
|
37
42
|
VARCHAR,
|
|
38
43
|
)
|
|
39
|
-
from .dml import insert, merge
|
|
44
|
+
from .dml import insert, merge, replace
|
|
45
|
+
from .trace import trace_query
|
|
40
46
|
|
|
41
47
|
from sqlalchemy.sql.sqltypes import (
|
|
42
48
|
DATE,
|
|
@@ -46,11 +52,13 @@ from sqlalchemy.sql.sqltypes import (
|
|
|
46
52
|
TIMESTAMP,
|
|
47
53
|
)
|
|
48
54
|
|
|
49
|
-
__version__ = "1.2.
|
|
55
|
+
__version__ = "1.2.3"
|
|
50
56
|
|
|
51
57
|
__all__ = (
|
|
52
58
|
"insert",
|
|
53
59
|
"merge",
|
|
60
|
+
"replace",
|
|
61
|
+
"trace_query",
|
|
54
62
|
"SMALLINT",
|
|
55
63
|
"INTEGER",
|
|
56
64
|
"BIGINT",
|
|
@@ -60,6 +68,7 @@ __all__ = (
|
|
|
60
68
|
"REAL",
|
|
61
69
|
"DOUBLE",
|
|
62
70
|
"DOUBLE_PRECISION",
|
|
71
|
+
"MONETARY",
|
|
63
72
|
"DATE",
|
|
64
73
|
"TIME",
|
|
65
74
|
"TIMESTAMP",
|
|
@@ -75,4 +84,8 @@ __all__ = (
|
|
|
75
84
|
"SET",
|
|
76
85
|
"MULTISET",
|
|
77
86
|
"SEQUENCE",
|
|
87
|
+
"OBJECT",
|
|
88
|
+
"JSON",
|
|
89
|
+
"JSONIndexType",
|
|
90
|
+
"JSONPathType",
|
|
78
91
|
)
|