sqlalchemy-cubrid 0.7.1__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.
@@ -0,0 +1,85 @@
1
+ # sqlalchemy_cubrid/__init__.py
2
+ # Copyright (C) 2021-2026 by sqlalchemy-cubrid authors and contributors
3
+ # <see AUTHORS file>
4
+ #
5
+ # This module is part of sqlalchemy-cubrid and is released under
6
+ # the MIT License: http://www.opensource.org/licenses/mit-license.php
7
+
8
+ """CUBRID dialect for SQLAlchemy.
9
+
10
+ Each dialect provides the full set of typenames supported by that backend
11
+ with its ``__all__`` collection.
12
+
13
+ See: https://docs.sqlalchemy.org/en/20/core/type_basics.html#vendor-specific-types
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from .types import (
19
+ BIGINT,
20
+ BIT,
21
+ BLOB,
22
+ CHAR,
23
+ CLOB,
24
+ DECIMAL,
25
+ DOUBLE,
26
+ DOUBLE_PRECISION,
27
+ FLOAT,
28
+ MONETARY,
29
+ MULTISET,
30
+ NCHAR,
31
+ NUMERIC,
32
+ NVARCHAR,
33
+ OBJECT,
34
+ REAL,
35
+ SEQUENCE,
36
+ SET,
37
+ SMALLINT,
38
+ STRING,
39
+ VARCHAR,
40
+ )
41
+ from .dml import insert, merge, replace
42
+ from .trace import trace_query
43
+
44
+ from sqlalchemy.sql.sqltypes import (
45
+ DATE,
46
+ DATETIME,
47
+ INTEGER,
48
+ TIME,
49
+ TIMESTAMP,
50
+ )
51
+
52
+ __version__ = "0.7.1"
53
+
54
+ __all__ = (
55
+ "insert",
56
+ "merge",
57
+ "replace",
58
+ "trace_query",
59
+ "SMALLINT",
60
+ "INTEGER",
61
+ "BIGINT",
62
+ "NUMERIC",
63
+ "DECIMAL",
64
+ "FLOAT",
65
+ "REAL",
66
+ "DOUBLE",
67
+ "DOUBLE_PRECISION",
68
+ "MONETARY",
69
+ "DATE",
70
+ "TIME",
71
+ "TIMESTAMP",
72
+ "DATETIME",
73
+ "BIT",
74
+ "CHAR",
75
+ "VARCHAR",
76
+ "NCHAR",
77
+ "NVARCHAR",
78
+ "STRING",
79
+ "BLOB",
80
+ "CLOB",
81
+ "SET",
82
+ "MULTISET",
83
+ "SEQUENCE",
84
+ "OBJECT",
85
+ )
@@ -0,0 +1,145 @@
1
+ # sqlalchemy_cubrid/alembic_impl.py
2
+ # Copyright (C) 2021-2026 by sqlalchemy-cubrid authors and contributors
3
+ # <see AUTHORS file>
4
+ #
5
+ # This module is part of sqlalchemy-cubrid and is released under
6
+ # the MIT License: http://www.opensource.org/licenses/mit-license.php
7
+
8
+ """Alembic migration support for the CUBRID dialect.
9
+
10
+ This module provides the Alembic ``DefaultImpl`` subclass that enables
11
+ Alembic migrations against a CUBRID database. It is registered as an
12
+ entry-point under ``alembic.ddl`` so that Alembic auto-discovers it
13
+ when the target database URL uses the ``cubrid://`` scheme.
14
+
15
+ Usage::
16
+
17
+ # alembic.ini
18
+ [alembic]
19
+ sqlalchemy.url = cubrid://dba:password@localhost:33000/demodb
20
+
21
+ # That's it — Alembic will pick up the CUBRID implementation
22
+ # automatically via the ``alembic.ddl`` entry point.
23
+
24
+ CUBRID-specific notes
25
+ ---------------------
26
+ * **DDL is auto-committed** — CUBRID implicitly commits every DDL
27
+ statement, so ``transactional_ddl`` is set to ``False``.
28
+ * **No ALTER COLUMN TYPE** — CUBRID does not support changing a
29
+ column's data type via ``ALTER TABLE … MODIFY``. Alembic's
30
+ ``alter_column(type_=…)`` will raise. Use ``batch_alter_table``
31
+ (table recreate) as a workaround.
32
+ * **No RENAME COLUMN** — CUBRID ≤ 11.x does not support
33
+ ``ALTER TABLE … RENAME COLUMN``. Alembic's ``alter_column(new_column_name=…)``
34
+ will raise. Use ``batch_alter_table`` (table recreate) as a
35
+ workaround.
36
+ """
37
+
38
+ from __future__ import annotations
39
+
40
+ from typing import TYPE_CHECKING, Any, Literal
41
+
42
+ import sqlalchemy as sa
43
+
44
+ try:
45
+ from alembic.ddl.impl import DefaultImpl
46
+ except ImportError: # pragma: no cover — optional dependency
47
+ raise ImportError(
48
+ "Alembic is required for migration support. Install it with: pip install sqlalchemy-cubrid[alembic]"
49
+ ) from None
50
+
51
+ if TYPE_CHECKING:
52
+ from typing import Protocol
53
+
54
+ from sqlalchemy.sql.type_api import TypeEngine
55
+
56
+ class AutogenContextLike(Protocol):
57
+ imports: set[str]
58
+
59
+
60
+ class CubridImpl(DefaultImpl):
61
+ """Alembic migration implementation for CUBRID.
62
+
63
+ Registered via the ``alembic.ddl`` entry-point so Alembic
64
+ auto-discovers it for ``cubrid://`` URLs.
65
+
66
+ Attributes
67
+ ----------
68
+ __dialect__ : str
69
+ ``"cubrid"`` — matches the SQLAlchemy dialect name.
70
+ transactional_ddl : bool
71
+ ``False`` — CUBRID implicitly commits DDL statements.
72
+ """
73
+
74
+ __dialect__: str = "cubrid"
75
+ transactional_ddl: bool = False
76
+
77
+ _collection_type_names: set[str] = {"SET", "MULTISET", "SEQUENCE"}
78
+
79
+ @staticmethod
80
+ def _normalize_collection_value(value: object) -> str:
81
+ if isinstance(value, str):
82
+ return value.strip().lower()
83
+ if isinstance(value, sa.types.TypeEngine):
84
+ return value.__class__.__name__.lower()
85
+ return repr(value).strip().lower()
86
+
87
+ def render_type(
88
+ self, type_obj: TypeEngine[Any], autogen_context: AutogenContextLike
89
+ ) -> str | Literal[False]:
90
+ if type(type_obj).__module__ != "sqlalchemy_cubrid.types":
91
+ return False
92
+
93
+ type_name = type_obj.__class__.__name__
94
+ if type_name not in self._collection_type_names:
95
+ return False
96
+
97
+ values = getattr(type_obj, "_ddl_values", ())
98
+ rendered_values: list[str] = []
99
+
100
+ for value in values:
101
+ if isinstance(value, str):
102
+ rendered_values.append(repr(value))
103
+ elif isinstance(value, sa.types.TypeEngine):
104
+ rendered_values.append(value.__class__.__name__)
105
+ else:
106
+ rendered_values.append(repr(value))
107
+
108
+ autogen_context.imports.add("from sqlalchemy_cubrid import types as cubrid_types")
109
+ args = ", ".join(rendered_values)
110
+ return f"cubrid_types.{type_name}({args})"
111
+
112
+ def compare_type(
113
+ self, inspector_column: sa.Column[Any], metadata_column: sa.Column[Any]
114
+ ) -> bool:
115
+ inspector_type = inspector_column.type
116
+ metadata_type = metadata_column.type
117
+
118
+ inspector_name = inspector_type.__class__.__name__
119
+ metadata_name = metadata_type.__class__.__name__
120
+
121
+ is_inspector_collection = inspector_name in self._collection_type_names
122
+ is_metadata_collection = metadata_name in self._collection_type_names
123
+
124
+ if not is_inspector_collection and not is_metadata_collection:
125
+ return super().compare_type(inspector_column, metadata_column)
126
+
127
+ if is_inspector_collection != is_metadata_collection:
128
+ return True
129
+
130
+ if inspector_name != metadata_name:
131
+ return True
132
+
133
+ inspector_values = [
134
+ self._normalize_collection_value(value)
135
+ for value in getattr(inspector_type, "_ddl_values", ())
136
+ ]
137
+ metadata_values = [
138
+ self._normalize_collection_value(value)
139
+ for value in getattr(metadata_type, "_ddl_values", ())
140
+ ]
141
+
142
+ if inspector_name == "SEQUENCE":
143
+ return inspector_values != metadata_values
144
+
145
+ return set(inspector_values) != set(metadata_values)
@@ -0,0 +1,428 @@
1
+ # sqlalchemy_cubrid/base.py
2
+ # Copyright (C) 2021-2026 by sqlalchemy-cubrid authors and contributors
3
+ # <see AUTHORS file>
4
+ #
5
+ # This module is part of sqlalchemy-cubrid and is released under
6
+ # the MIT License: http://www.opensource.org/licenses/mit-license.php
7
+
8
+ """CUBRID identifier preparation, execution context, and reserved words."""
9
+
10
+ from __future__ import annotations
11
+
12
+ import re
13
+
14
+ from sqlalchemy.engine import default
15
+ from sqlalchemy.sql import compiler
16
+
17
+
18
+ AUTOCOMMIT_REGEXP = re.compile(
19
+ r"\s*(?:UPDATE|INSERT|CREATE|DELETE|DROP|ALTER|MERGE|TRUNCATE)", re.I | re.UNICODE
20
+ )
21
+
22
+ # CUBRID Reserved words
23
+ # https://www.cubrid.org/manual/en/11.0/sql/keyword.html
24
+ RESERVED_WORDS = frozenset(
25
+ {
26
+ "absolute",
27
+ "action",
28
+ "add",
29
+ "add_months",
30
+ "after",
31
+ "all",
32
+ "allocate",
33
+ "alter",
34
+ "and",
35
+ "any",
36
+ "are",
37
+ "as",
38
+ "asc",
39
+ "assertion",
40
+ "at",
41
+ "attach",
42
+ "attribute",
43
+ "avg",
44
+ "before",
45
+ "between",
46
+ "bigint",
47
+ "bit",
48
+ "bit_length",
49
+ "blob",
50
+ "boolean",
51
+ "both",
52
+ "breadth",
53
+ "by",
54
+ "call",
55
+ "cascade",
56
+ "cascaded",
57
+ "case",
58
+ "cast",
59
+ "catalog",
60
+ "change",
61
+ "char",
62
+ "character",
63
+ "check",
64
+ "class",
65
+ "classes",
66
+ "clob",
67
+ "close",
68
+ "coalesce",
69
+ "collate",
70
+ "collation",
71
+ "column",
72
+ "commit",
73
+ "connect",
74
+ "connect_by_iscycle",
75
+ "connect_by_isleaf",
76
+ "connect_by_root",
77
+ "connection",
78
+ "constraint",
79
+ "constraints",
80
+ "continue",
81
+ "convert",
82
+ "corresponding",
83
+ "count",
84
+ "create",
85
+ "cross",
86
+ "current",
87
+ "current_date",
88
+ "current_datetime",
89
+ "current_time",
90
+ "current_timestamp",
91
+ "current_user",
92
+ "cursor",
93
+ "cycle",
94
+ "data",
95
+ "data_type",
96
+ "database",
97
+ "date",
98
+ "datetime",
99
+ "day",
100
+ "day_hour",
101
+ "day_millisecond",
102
+ "day_minute",
103
+ "day_second",
104
+ "deallocate",
105
+ "dec",
106
+ "decimal",
107
+ "declare",
108
+ "default",
109
+ "deferrable",
110
+ "deferred",
111
+ "delete",
112
+ "depth",
113
+ "desc",
114
+ "describe",
115
+ "descriptor",
116
+ "diagnostics",
117
+ "difference",
118
+ "disconnect",
119
+ "distinct",
120
+ "distinctrow",
121
+ "div",
122
+ "do",
123
+ "domain",
124
+ "double",
125
+ "duplicate",
126
+ "drop",
127
+ "each",
128
+ "else",
129
+ "elseif",
130
+ "end",
131
+ "equals",
132
+ "escape",
133
+ "evaluate",
134
+ "except",
135
+ "exception",
136
+ "exec",
137
+ "execute",
138
+ "exists",
139
+ "external",
140
+ "extract",
141
+ "false",
142
+ "fetch",
143
+ "file",
144
+ "first",
145
+ "float",
146
+ "for",
147
+ "foreign",
148
+ "found",
149
+ "from",
150
+ "full",
151
+ "function",
152
+ "general",
153
+ "get",
154
+ "global",
155
+ "go",
156
+ "goto",
157
+ "grant",
158
+ "group",
159
+ "having",
160
+ "hour",
161
+ "hour_millisecond",
162
+ "hour_minute",
163
+ "hour_second",
164
+ "identity",
165
+ "if",
166
+ "ignore",
167
+ "immediate",
168
+ "in",
169
+ "index",
170
+ "indicator",
171
+ "inherit",
172
+ "initially",
173
+ "inner",
174
+ "inout",
175
+ "input",
176
+ "insert",
177
+ "int",
178
+ "integer",
179
+ "intersect",
180
+ "intersection",
181
+ "interval",
182
+ "into",
183
+ "is",
184
+ "isolation",
185
+ "join",
186
+ "key",
187
+ "language",
188
+ "last",
189
+ "leading",
190
+ "leave",
191
+ "left",
192
+ "less",
193
+ "level",
194
+ "like",
195
+ "limit",
196
+ "list",
197
+ "local",
198
+ "local_transaction_id",
199
+ "localtime",
200
+ "localtimestamp",
201
+ "loop",
202
+ "lower",
203
+ "match",
204
+ "max",
205
+ "method",
206
+ "millisecond",
207
+ "min",
208
+ "minute",
209
+ "minute_millisecond",
210
+ "minute_second",
211
+ "mod",
212
+ "modify",
213
+ "module",
214
+ "month",
215
+ "multiset",
216
+ "multiset_of",
217
+ "na",
218
+ "names",
219
+ "national",
220
+ "natural",
221
+ "nchar",
222
+ "next",
223
+ "no",
224
+ "none",
225
+ "not",
226
+ "null",
227
+ "nullif",
228
+ "numeric",
229
+ "object",
230
+ "octet_length",
231
+ "of",
232
+ "off",
233
+ "on",
234
+ "only",
235
+ "open",
236
+ "optimization",
237
+ "option",
238
+ "or",
239
+ "order",
240
+ "out",
241
+ "outer",
242
+ "output",
243
+ "overlaps",
244
+ "parameters",
245
+ "partial",
246
+ "position",
247
+ "precision",
248
+ "prepare",
249
+ "preserve",
250
+ "primary",
251
+ "prior",
252
+ "privileges",
253
+ "procedure",
254
+ "query",
255
+ "read",
256
+ "real",
257
+ "recursive",
258
+ "ref",
259
+ "references",
260
+ "referencing",
261
+ "relative",
262
+ "rename",
263
+ "replace",
264
+ "resignal",
265
+ "restrict",
266
+ "return",
267
+ "returns",
268
+ "revoke",
269
+ "right",
270
+ "role",
271
+ "rollback",
272
+ "rollup",
273
+ "routine",
274
+ "row",
275
+ "rownum",
276
+ "rows",
277
+ "savepoint",
278
+ "schema",
279
+ "scope",
280
+ "scroll",
281
+ "search",
282
+ "second",
283
+ "second_millisecond",
284
+ "section",
285
+ "select",
286
+ "sensitive",
287
+ "sequence",
288
+ "sequence_of",
289
+ "serializable",
290
+ "session",
291
+ "session_user",
292
+ "set",
293
+ "set_of",
294
+ "seteq",
295
+ "shared",
296
+ "siblings",
297
+ "signal",
298
+ "similar",
299
+ "size",
300
+ "smallint",
301
+ "some",
302
+ "sql",
303
+ "sqlcode",
304
+ "sqlerror",
305
+ "sqlexception",
306
+ "sqlstate",
307
+ "sqlwarning",
308
+ "statistics",
309
+ "string",
310
+ "subclass",
311
+ "subset",
312
+ "subseteq",
313
+ "substring",
314
+ "sum",
315
+ "superclass",
316
+ "superset",
317
+ "superseteq",
318
+ "sys_connect_by_path",
319
+ "sys_date",
320
+ "sys_datetime",
321
+ "sys_time",
322
+ "sys_timestamp",
323
+ "sysdate",
324
+ "sysdatetime",
325
+ "system_user",
326
+ "systime",
327
+ "table",
328
+ "temporary",
329
+ "then",
330
+ "time",
331
+ "timestamp",
332
+ "timezone_hour",
333
+ "timezone_minute",
334
+ "to",
335
+ "trailing",
336
+ "transaction",
337
+ "translate",
338
+ "translation",
339
+ "trigger",
340
+ "trim",
341
+ "true",
342
+ "truncate",
343
+ "under",
344
+ "union",
345
+ "unique",
346
+ "unknown",
347
+ "update",
348
+ "upper",
349
+ "usage",
350
+ "use",
351
+ "user",
352
+ "using",
353
+ "utime",
354
+ "value",
355
+ "values",
356
+ "varchar",
357
+ "variable",
358
+ "varying",
359
+ "vclass",
360
+ "view",
361
+ "when",
362
+ "whenever",
363
+ "where",
364
+ "while",
365
+ "with",
366
+ "without",
367
+ "work",
368
+ "write",
369
+ "xor",
370
+ "year",
371
+ "year_month",
372
+ "zone",
373
+ }
374
+ )
375
+
376
+
377
+ class CubridIdentifierPreparer(compiler.IdentifierPreparer):
378
+ """Quoting logic for CUBRID identifiers."""
379
+
380
+ reserved_words = RESERVED_WORDS
381
+
382
+ def __init__(
383
+ self,
384
+ dialect,
385
+ initial_quote='"',
386
+ final_quote=None,
387
+ escape_quote='"',
388
+ omit_schema=False,
389
+ ):
390
+ super().__init__(dialect, initial_quote, final_quote, escape_quote, omit_schema)
391
+
392
+ def _quote_free_identifiers(self, *ids):
393
+ """Unilaterally identifier-quote any number of strings."""
394
+ return tuple(self.quote_identifier(i) for i in ids if i is not None)
395
+
396
+
397
+ class CubridExecutionContext(default.DefaultExecutionContext):
398
+ """Execution context for CUBRID connections."""
399
+
400
+ def should_autocommit_text(self, statement):
401
+ return AUTOCOMMIT_REGEXP.match(statement)
402
+
403
+ def get_lastrowid(self):
404
+ """Return the last inserted row ID.
405
+
406
+ CUBRID's Python driver does not expose ``cursor.lastrowid``.
407
+ Instead, the connection object provides ``get_last_insert_id()``.
408
+ We also fall back to ``SELECT LAST_INSERT_ID()`` if the method
409
+ is unavailable.
410
+ """
411
+ try:
412
+ # CUBRID Python driver exposes this on the raw connection
413
+ raw_conn = self.root_connection.connection.dbapi_connection
414
+ if hasattr(raw_conn, "get_last_insert_id"):
415
+ return raw_conn.get_last_insert_id()
416
+ except Exception: # nosec B110 — fallback to SQL when driver lacks method
417
+ pass
418
+
419
+ # Fallback: use SQL function
420
+ cursor = self.create_server_side_cursor()
421
+ try:
422
+ cursor.execute("SELECT LAST_INSERT_ID()")
423
+ row = cursor.fetchone()
424
+ if row:
425
+ return int(row[0])
426
+ finally:
427
+ cursor.close()
428
+ return None