sqlalchemy-pyaltibase 0.1.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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Yeongseon Choe
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,88 @@
1
+ Metadata-Version: 2.4
2
+ Name: sqlalchemy-pyaltibase
3
+ Version: 0.1.0
4
+ Summary: Altibase dialect for SQLAlchemy
5
+ Author-email: Yeongseon Choe <yeongseon.choe@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/yeongseon/sqlalchemy-pyaltibase
8
+ Project-URL: Repository, https://github.com/yeongseon/sqlalchemy-pyaltibase
9
+ Project-URL: Issues, https://github.com/yeongseon/sqlalchemy-pyaltibase/issues
10
+ Keywords: SQLAlchemy,Altibase,dialect
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: SQL
23
+ Classifier: Topic :: Database
24
+ Classifier: Topic :: Database :: Front-Ends
25
+ Requires-Python: >=3.10
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Requires-Dist: sqlalchemy<2.2,>=2.0
29
+ Provides-Extra: pyaltibase
30
+ Requires-Dist: pyaltibase; extra == "pyaltibase"
31
+ Provides-Extra: dev
32
+ Requires-Dist: pytest>=7.0; extra == "dev"
33
+ Requires-Dist: pytest-cov; extra == "dev"
34
+ Requires-Dist: ruff==0.15.6; extra == "dev"
35
+ Dynamic: license-file
36
+
37
+ # sqlalchemy-pyaltibase
38
+
39
+ [![PyPI version](https://img.shields.io/pypi/v/sqlalchemy-pyaltibase)](https://pypi.org/project/sqlalchemy-pyaltibase)
40
+ [![CI](https://github.com/yeongseon/sqlalchemy-pyaltibase/actions/workflows/ci.yml/badge.svg)](https://github.com/yeongseon/sqlalchemy-pyaltibase/actions/workflows/ci.yml)
41
+ [![license](https://img.shields.io/github/license/yeongseon/sqlalchemy-pyaltibase)](https://github.com/yeongseon/sqlalchemy-pyaltibase/blob/main/LICENSE)
42
+
43
+ SQLAlchemy 2.0 dialect for the Altibase database, backed by `pyaltibase`.
44
+
45
+ ## Installation
46
+
47
+ ```bash
48
+ pip install sqlalchemy-pyaltibase
49
+ ```
50
+
51
+ With DB-API dependency:
52
+
53
+ ```bash
54
+ pip install "sqlalchemy-pyaltibase[pyaltibase]"
55
+ ```
56
+
57
+ ## Quick Start
58
+
59
+ ```python
60
+ from sqlalchemy import create_engine, text
61
+
62
+ engine = create_engine("altibase://user:password@localhost:20300/mydb")
63
+
64
+ with engine.connect() as conn:
65
+ value = conn.execute(text("SELECT 1 FROM DUAL")).scalar()
66
+ print(value)
67
+ ```
68
+
69
+ ## Architecture
70
+
71
+ ```mermaid
72
+ flowchart TD
73
+ app["Application"] --> sa["SQLAlchemy Core/ORM"]
74
+ sa --> dialect["AltibaseDialect"]
75
+ dialect --> dbapi["pyaltibase"]
76
+ dbapi --> server["Altibase Server"]
77
+ ```
78
+
79
+ ## Development
80
+
81
+ ```bash
82
+ make lint
83
+ make test
84
+ ```
85
+
86
+ ## License
87
+
88
+ MIT
@@ -0,0 +1,52 @@
1
+ # sqlalchemy-pyaltibase
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/sqlalchemy-pyaltibase)](https://pypi.org/project/sqlalchemy-pyaltibase)
4
+ [![CI](https://github.com/yeongseon/sqlalchemy-pyaltibase/actions/workflows/ci.yml/badge.svg)](https://github.com/yeongseon/sqlalchemy-pyaltibase/actions/workflows/ci.yml)
5
+ [![license](https://img.shields.io/github/license/yeongseon/sqlalchemy-pyaltibase)](https://github.com/yeongseon/sqlalchemy-pyaltibase/blob/main/LICENSE)
6
+
7
+ SQLAlchemy 2.0 dialect for the Altibase database, backed by `pyaltibase`.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ pip install sqlalchemy-pyaltibase
13
+ ```
14
+
15
+ With DB-API dependency:
16
+
17
+ ```bash
18
+ pip install "sqlalchemy-pyaltibase[pyaltibase]"
19
+ ```
20
+
21
+ ## Quick Start
22
+
23
+ ```python
24
+ from sqlalchemy import create_engine, text
25
+
26
+ engine = create_engine("altibase://user:password@localhost:20300/mydb")
27
+
28
+ with engine.connect() as conn:
29
+ value = conn.execute(text("SELECT 1 FROM DUAL")).scalar()
30
+ print(value)
31
+ ```
32
+
33
+ ## Architecture
34
+
35
+ ```mermaid
36
+ flowchart TD
37
+ app["Application"] --> sa["SQLAlchemy Core/ORM"]
38
+ sa --> dialect["AltibaseDialect"]
39
+ dialect --> dbapi["pyaltibase"]
40
+ dbapi --> server["Altibase Server"]
41
+ ```
42
+
43
+ ## Development
44
+
45
+ ```bash
46
+ make lint
47
+ make test
48
+ ```
49
+
50
+ ## License
51
+
52
+ MIT
@@ -0,0 +1,81 @@
1
+ [build-system]
2
+ requires = ["setuptools>=65.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "sqlalchemy-pyaltibase"
7
+ version = "0.1.0"
8
+ description = "Altibase dialect for SQLAlchemy"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.10"
12
+ authors = [
13
+ {name = "Yeongseon Choe", email = "yeongseon.choe@gmail.com"},
14
+ ]
15
+ keywords = ["SQLAlchemy", "Altibase", "dialect"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Environment :: Console",
19
+ "Intended Audience :: Developers",
20
+ "Operating System :: OS Independent",
21
+ "Programming Language :: Python",
22
+ "Programming Language :: Python :: 3",
23
+ "Programming Language :: Python :: 3 :: Only",
24
+ "Programming Language :: Python :: 3.10",
25
+ "Programming Language :: Python :: 3.11",
26
+ "Programming Language :: Python :: 3.12",
27
+ "Programming Language :: Python :: 3.13",
28
+ "Programming Language :: SQL",
29
+ "Topic :: Database",
30
+ "Topic :: Database :: Front-Ends",
31
+ ]
32
+ dependencies = [
33
+ "sqlalchemy>=2.0,<2.2",
34
+ ]
35
+
36
+ [project.optional-dependencies]
37
+ pyaltibase = ["pyaltibase"]
38
+ dev = [
39
+ "pytest>=7.0",
40
+ "pytest-cov",
41
+ "ruff==0.15.6",
42
+ ]
43
+
44
+ [project.urls]
45
+ Homepage = "https://github.com/yeongseon/sqlalchemy-pyaltibase"
46
+ Repository = "https://github.com/yeongseon/sqlalchemy-pyaltibase"
47
+ Issues = "https://github.com/yeongseon/sqlalchemy-pyaltibase/issues"
48
+
49
+ [project.entry-points."sqlalchemy.dialects"]
50
+ altibase = "sqlalchemy_altibase.dialect:AltibaseDialect"
51
+ "altibase.pyaltibase" = "sqlalchemy_altibase.dialect:AltibaseDialect"
52
+
53
+ [tool.setuptools]
54
+ packages = ["sqlalchemy_altibase"]
55
+ include-package-data = true
56
+
57
+ [tool.ruff]
58
+ line-length = 100
59
+ target-version = "py310"
60
+
61
+ [tool.ruff.lint.isort]
62
+ known-first-party = ["sqlalchemy_altibase"]
63
+
64
+ [tool.coverage.run]
65
+ source = ["sqlalchemy_altibase"]
66
+
67
+ [tool.coverage.report]
68
+ show_missing = true
69
+ fail_under = 95
70
+
71
+ [tool.pytest.ini_options]
72
+ testpaths = ["test"]
73
+ addopts = "--tb native -v -r fxX --maxfail=25 -p no:warnings"
74
+ python_files = "test/*test_*.py"
75
+
76
+ [tool.basedpyright]
77
+ typeCheckingMode = "off"
78
+ reportUnsafeMultipleInheritance = "none"
79
+ reportMissingTypeArgument = "none"
80
+ reportIncompatibleMethodOverride = "none"
81
+ reportAssignmentType = "none"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,54 @@
1
+ from .dialect import AltibaseDialect
2
+ from .types import (
3
+ BIGINT,
4
+ BIT,
5
+ BLOB,
6
+ BYTE,
7
+ CHAR,
8
+ CLOB,
9
+ DATE,
10
+ DECIMAL,
11
+ DOUBLE,
12
+ FLOAT,
13
+ GEOMETRY,
14
+ INTEGER,
15
+ NCHAR,
16
+ NIBBLE,
17
+ NUMERIC,
18
+ NVARCHAR,
19
+ REAL,
20
+ SERIAL,
21
+ SMALLINT,
22
+ VARBIT,
23
+ VARBYTE,
24
+ VARCHAR,
25
+ )
26
+
27
+ __version__ = "0.1.0"
28
+
29
+ __all__ = [
30
+ "__version__",
31
+ "AltibaseDialect",
32
+ "NUMERIC",
33
+ "DECIMAL",
34
+ "FLOAT",
35
+ "REAL",
36
+ "DOUBLE",
37
+ "SMALLINT",
38
+ "INTEGER",
39
+ "BIGINT",
40
+ "SERIAL",
41
+ "VARCHAR",
42
+ "CHAR",
43
+ "NCHAR",
44
+ "NVARCHAR",
45
+ "CLOB",
46
+ "BLOB",
47
+ "DATE",
48
+ "BIT",
49
+ "VARBIT",
50
+ "BYTE",
51
+ "VARBYTE",
52
+ "NIBBLE",
53
+ "GEOMETRY",
54
+ ]
@@ -0,0 +1,154 @@
1
+ # pyright: reportIncompatibleMethodOverride=false
2
+ from __future__ import annotations
3
+
4
+ import re
5
+
6
+ from sqlalchemy.engine import default
7
+ from sqlalchemy.sql import compiler
8
+
9
+
10
+ AUTOCOMMIT_REGEXP = re.compile(
11
+ r"\s*(?:UPDATE|INSERT|CREATE|DELETE|DROP|ALTER|MERGE|TRUNCATE)", re.I | re.UNICODE
12
+ )
13
+
14
+ RESERVED_WORDS = frozenset(
15
+ {
16
+ "access",
17
+ "add",
18
+ "all",
19
+ "alter",
20
+ "and",
21
+ "any",
22
+ "as",
23
+ "at",
24
+ "between",
25
+ "by",
26
+ "cascade",
27
+ "case",
28
+ "check",
29
+ "column",
30
+ "connect",
31
+ "constraint",
32
+ "create",
33
+ "cross",
34
+ "current",
35
+ "cursor",
36
+ "database",
37
+ "date",
38
+ "decimal",
39
+ "default",
40
+ "delete",
41
+ "desc",
42
+ "distinct",
43
+ "drop",
44
+ "each",
45
+ "else",
46
+ "end",
47
+ "escape",
48
+ "exception",
49
+ "exec",
50
+ "exists",
51
+ "float",
52
+ "for",
53
+ "foreign",
54
+ "from",
55
+ "full",
56
+ "grant",
57
+ "group",
58
+ "having",
59
+ "if",
60
+ "in",
61
+ "index",
62
+ "inner",
63
+ "insert",
64
+ "integer",
65
+ "intersect",
66
+ "into",
67
+ "is",
68
+ "join",
69
+ "key",
70
+ "left",
71
+ "level",
72
+ "like",
73
+ "limit",
74
+ "lock",
75
+ "merge",
76
+ "minus",
77
+ "modify",
78
+ "not",
79
+ "null",
80
+ "number",
81
+ "of",
82
+ "on",
83
+ "open",
84
+ "or",
85
+ "order",
86
+ "outer",
87
+ "primary",
88
+ "prior",
89
+ "privileges",
90
+ "public",
91
+ "raw",
92
+ "references",
93
+ "rename",
94
+ "replace",
95
+ "return",
96
+ "revoke",
97
+ "right",
98
+ "row",
99
+ "rowcount",
100
+ "rownum",
101
+ "rows",
102
+ "select",
103
+ "sequence",
104
+ "session",
105
+ "set",
106
+ "some",
107
+ "start",
108
+ "step",
109
+ "table",
110
+ "then",
111
+ "to",
112
+ "trigger",
113
+ "truncate",
114
+ "union",
115
+ "unique",
116
+ "update",
117
+ "user",
118
+ "using",
119
+ "values",
120
+ "varchar",
121
+ "view",
122
+ "when",
123
+ "where",
124
+ "with",
125
+ }
126
+ )
127
+
128
+
129
+ class AltibaseIdentifierPreparer(compiler.IdentifierPreparer):
130
+ reserved_words = RESERVED_WORDS
131
+
132
+ def __init__(
133
+ self,
134
+ dialect,
135
+ initial_quote='"',
136
+ final_quote=None,
137
+ escape_quote='"',
138
+ omit_schema=False,
139
+ ):
140
+ super().__init__(dialect, initial_quote, final_quote, escape_quote, omit_schema)
141
+
142
+ def _quote_free_identifiers(self, *ids):
143
+ return tuple(self.quote_identifier(i) for i in ids if i is not None)
144
+
145
+
146
+ class AltibaseExecutionContext(default.DefaultExecutionContext):
147
+ def should_autocommit_text(self, statement):
148
+ return AUTOCOMMIT_REGEXP.match(statement)
149
+
150
+ def get_lastrowid(self):
151
+ try:
152
+ return self.cursor.lastrowid
153
+ except Exception:
154
+ return None
@@ -0,0 +1,226 @@
1
+ # pyright: reportIncompatibleMethodOverride=false, reportArgumentType=false, reportUnusedParameter=false
2
+ from __future__ import annotations
3
+
4
+ from sqlalchemy.sql import compiler
5
+ from sqlalchemy.sql import sqltypes
6
+
7
+
8
+ class AltibaseCompiler(compiler.SQLCompiler):
9
+ def visit_sysdate_func(self, fn, **kw):
10
+ return "SYSDATE"
11
+
12
+ def visit_dual_func(self, fn, **kw):
13
+ return "DUAL"
14
+
15
+ def default_from(self):
16
+ return " FROM DUAL"
17
+
18
+ def visit_cast(self, cast, **kw):
19
+ type_ = self.process(cast.typeclause)
20
+ if type_ is None:
21
+ return self.process(cast.clause.self_group())
22
+ return f"CAST({self.process(cast.clause)} AS {type_})"
23
+
24
+ def render_literal_value(self, value, type_):
25
+ value = super().render_literal_value(value, type_)
26
+ return value.replace("\\", "\\\\")
27
+
28
+ def get_select_precolumns(self, select, **kw):
29
+ if bool(select._distinct):
30
+ return "DISTINCT "
31
+ return ""
32
+
33
+ def visit_join(self, join, asfrom=False, **kwargs):
34
+ return "".join(
35
+ (
36
+ self.process(join.left, asfrom=True, **kwargs),
37
+ (join.isouter and " LEFT OUTER JOIN " or " INNER JOIN "),
38
+ self.process(join.right, asfrom=True, **kwargs),
39
+ " ON ",
40
+ self.process(join.onclause, **kwargs),
41
+ )
42
+ )
43
+
44
+ def for_update_clause(self, select, **kw):
45
+ if select._for_update_arg is None:
46
+ return ""
47
+ text = " FOR UPDATE"
48
+ if select._for_update_arg.of:
49
+ text += " OF " + ", ".join(self.process(col, **kw) for col in select._for_update_arg.of)
50
+ wait = getattr(select._for_update_arg, "wait", None)
51
+ if wait is not None:
52
+ text += f" WAIT {int(wait)}"
53
+ elif select._for_update_arg.nowait:
54
+ text += " NOWAIT"
55
+ return text
56
+
57
+ def limit_clause(self, select, **kw):
58
+ limit_clause = select._limit_clause
59
+ offset_clause = select._offset_clause
60
+ if limit_clause is None and offset_clause is None:
61
+ return ""
62
+ if limit_clause is None:
63
+ return "\n LIMIT 9223372036854775807 OFFSET %s" % self.process(offset_clause, **kw)
64
+ if offset_clause is None:
65
+ return "\n LIMIT %s" % self.process(limit_clause, **kw)
66
+ return "\n LIMIT %s OFFSET %s" % (
67
+ self.process(limit_clause, **kw),
68
+ self.process(offset_clause, **kw),
69
+ )
70
+
71
+
72
+ class AltibaseDDLCompiler(compiler.DDLCompiler):
73
+ def get_column_specification(self, column, **kw):
74
+ colspec = [self.preparer.format_column(column)]
75
+
76
+ if (
77
+ column.table is not None
78
+ and column is column.table._autoincrement_column
79
+ and column.server_default is None
80
+ ):
81
+ colspec.append("SERIAL")
82
+ else:
83
+ colspec.append(
84
+ self.dialect.type_compiler_instance.process(column.type, type_expression=column)
85
+ )
86
+
87
+ if not column.nullable:
88
+ colspec.append("NOT NULL")
89
+
90
+ default = self.get_column_default_string(column)
91
+ if default is not None:
92
+ colspec.append("DEFAULT " + default)
93
+
94
+ return " ".join(colspec)
95
+
96
+ def post_create_table(self, table):
97
+ if table.comment is None:
98
+ return ""
99
+ literal = self.sql_compiler.render_literal_value(table.comment, sqltypes.String())
100
+ return f"\nCOMMENT ON TABLE {self.preparer.format_table(table)} IS {literal}"
101
+
102
+ def visit_set_table_comment(self, create, **kw):
103
+ return "COMMENT ON TABLE %s IS %s" % (
104
+ self.preparer.format_table(create.element),
105
+ self.sql_compiler.render_literal_value(create.element.comment, sqltypes.String()),
106
+ )
107
+
108
+ def visit_drop_table_comment(self, drop, **kw):
109
+ return "COMMENT ON TABLE %s IS ''" % (self.preparer.format_table(drop.element),)
110
+
111
+ def visit_set_column_comment(self, create, **kw):
112
+ return "COMMENT ON COLUMN %s.%s IS %s" % (
113
+ self.preparer.format_table(create.element.table),
114
+ self.preparer.format_column(create.element),
115
+ self.sql_compiler.render_literal_value(create.element.comment, sqltypes.String()),
116
+ )
117
+
118
+
119
+ class AltibaseTypeCompiler(compiler.GenericTypeCompiler):
120
+ def visit_BOOLEAN(self, type_, **kw):
121
+ return "SMALLINT"
122
+
123
+ def visit_NUMERIC(self, type_, **kw):
124
+ if type_.precision is None:
125
+ return "NUMERIC"
126
+ if type_.scale is None:
127
+ return f"NUMERIC({type_.precision})"
128
+ return f"NUMERIC({type_.precision}, {type_.scale})"
129
+
130
+ def visit_DECIMAL(self, type_, **kw):
131
+ if type_.precision is None:
132
+ return "DECIMAL"
133
+ if type_.scale is None:
134
+ return f"DECIMAL({type_.precision})"
135
+ return f"DECIMAL({type_.precision}, {type_.scale})"
136
+
137
+ def visit_FLOAT(self, type_, **kw):
138
+ if type_.precision is None:
139
+ return "FLOAT"
140
+ return f"FLOAT({type_.precision})"
141
+
142
+ def visit_REAL(self, type_, **kw):
143
+ return "REAL"
144
+
145
+ def visit_DOUBLE(self, type_, **kw):
146
+ return "DOUBLE"
147
+
148
+ def visit_SMALLINT(self, type_, **kw):
149
+ return "SMALLINT"
150
+
151
+ def visit_INTEGER(self, type_, **kw):
152
+ return "INTEGER"
153
+
154
+ def visit_BIGINT(self, type_, **kw):
155
+ return "BIGINT"
156
+
157
+ def visit_SERIAL(self, type_, **kw):
158
+ return "SERIAL"
159
+
160
+ def visit_VARCHAR(self, type_, **kw):
161
+ if type_.length:
162
+ return f"VARCHAR({type_.length})"
163
+ return "VARCHAR"
164
+
165
+ def visit_CHAR(self, type_, **kw):
166
+ if getattr(type_, "national", False):
167
+ return self.visit_NCHAR(type_, **kw)
168
+ if type_.length:
169
+ return f"CHAR({type_.length})"
170
+ return "CHAR"
171
+
172
+ def visit_NCHAR(self, type_, **kw):
173
+ if type_.length:
174
+ return f"NCHAR({type_.length})"
175
+ return "NCHAR"
176
+
177
+ def visit_NVARCHAR(self, type_, **kw):
178
+ if type_.length:
179
+ return f"NVARCHAR({type_.length})"
180
+ return "NVARCHAR"
181
+
182
+ def visit_CLOB(self, type_, **kw):
183
+ return "CLOB"
184
+
185
+ def visit_BLOB(self, type_, **kw):
186
+ return "BLOB"
187
+
188
+ def visit_BIT(self, type_, **kw):
189
+ if getattr(type_, "length", None):
190
+ return f"BIT({type_.length})"
191
+ return "BIT"
192
+
193
+ def visit_VARBIT(self, type_, **kw):
194
+ if getattr(type_, "length", None):
195
+ return f"VARBIT({type_.length})"
196
+ return "VARBIT"
197
+
198
+ def visit_BYTE(self, type_, **kw):
199
+ if getattr(type_, "length", None):
200
+ return f"BYTE({type_.length})"
201
+ return "BYTE"
202
+
203
+ def visit_VARBYTE(self, type_, **kw):
204
+ if getattr(type_, "length", None):
205
+ return f"VARBYTE({type_.length})"
206
+ return "VARBYTE"
207
+
208
+ def visit_NIBBLE(self, type_, **kw):
209
+ if getattr(type_, "length", None):
210
+ return f"NIBBLE({type_.length})"
211
+ return "NIBBLE"
212
+
213
+ def visit_GEOMETRY(self, type_, **kw):
214
+ return "GEOMETRY"
215
+
216
+ def visit_DATE(self, type_, **kw):
217
+ return "DATE"
218
+
219
+ def visit_large_binary(self, type_, **kw):
220
+ return "BLOB"
221
+
222
+ def visit_text(self, type_, **kw):
223
+ return "CLOB"
224
+
225
+ def visit_datetime(self, type_, **kw):
226
+ return "DATE"