sqlalchemy-pytibero 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.
- sqlalchemy_pytibero-0.1.0/LICENSE +21 -0
- sqlalchemy_pytibero-0.1.0/PKG-INFO +88 -0
- sqlalchemy_pytibero-0.1.0/README.md +52 -0
- sqlalchemy_pytibero-0.1.0/pyproject.toml +81 -0
- sqlalchemy_pytibero-0.1.0/setup.cfg +4 -0
- sqlalchemy_pytibero-0.1.0/sqlalchemy_pytibero/__init__.py +58 -0
- sqlalchemy_pytibero-0.1.0/sqlalchemy_pytibero/base.py +155 -0
- sqlalchemy_pytibero-0.1.0/sqlalchemy_pytibero/compiler.py +236 -0
- sqlalchemy_pytibero-0.1.0/sqlalchemy_pytibero/dialect.py +534 -0
- sqlalchemy_pytibero-0.1.0/sqlalchemy_pytibero/types.py +175 -0
- sqlalchemy_pytibero-0.1.0/sqlalchemy_pytibero.egg-info/PKG-INFO +88 -0
- sqlalchemy_pytibero-0.1.0/sqlalchemy_pytibero.egg-info/SOURCES.txt +18 -0
- sqlalchemy_pytibero-0.1.0/sqlalchemy_pytibero.egg-info/dependency_links.txt +1 -0
- sqlalchemy_pytibero-0.1.0/sqlalchemy_pytibero.egg-info/entry_points.txt +3 -0
- sqlalchemy_pytibero-0.1.0/sqlalchemy_pytibero.egg-info/requires.txt +9 -0
- sqlalchemy_pytibero-0.1.0/sqlalchemy_pytibero.egg-info/top_level.txt +1 -0
- sqlalchemy_pytibero-0.1.0/test/test_base.py +68 -0
- sqlalchemy_pytibero-0.1.0/test/test_compiler.py +230 -0
- sqlalchemy_pytibero-0.1.0/test/test_dialect_offline.py +351 -0
- sqlalchemy_pytibero-0.1.0/test/test_types.py +106 -0
|
@@ -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-pytibero
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Tibero 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-pytibero
|
|
8
|
+
Project-URL: Repository, https://github.com/yeongseon/sqlalchemy-pytibero
|
|
9
|
+
Project-URL: Issues, https://github.com/yeongseon/sqlalchemy-pytibero/issues
|
|
10
|
+
Keywords: SQLAlchemy,Tibero,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: pytibero
|
|
30
|
+
Requires-Dist: pytibero; extra == "pytibero"
|
|
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-pytibero
|
|
38
|
+
|
|
39
|
+
[](https://pypi.org/project/sqlalchemy-pytibero)
|
|
40
|
+
[](https://github.com/yeongseon/sqlalchemy-pytibero/actions/workflows/ci.yml)
|
|
41
|
+
[](https://github.com/yeongseon/sqlalchemy-pytibero/blob/main/LICENSE)
|
|
42
|
+
|
|
43
|
+
SQLAlchemy 2.0 dialect for the Tibero database, backed by `pytibero`.
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install sqlalchemy-pytibero
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
With DB-API dependency:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install "sqlalchemy-pytibero[pytibero]"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Quick Start
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from sqlalchemy import create_engine, text
|
|
61
|
+
|
|
62
|
+
engine = create_engine("tibero://tibero:password@localhost:8629/TESTDB")
|
|
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["TiberoDialect"]
|
|
75
|
+
dialect --> dbapi["pytibero"]
|
|
76
|
+
dbapi --> server["Tibero 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-pytibero
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/sqlalchemy-pytibero)
|
|
4
|
+
[](https://github.com/yeongseon/sqlalchemy-pytibero/actions/workflows/ci.yml)
|
|
5
|
+
[](https://github.com/yeongseon/sqlalchemy-pytibero/blob/main/LICENSE)
|
|
6
|
+
|
|
7
|
+
SQLAlchemy 2.0 dialect for the Tibero database, backed by `pytibero`.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install sqlalchemy-pytibero
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
With DB-API dependency:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install "sqlalchemy-pytibero[pytibero]"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
from sqlalchemy import create_engine, text
|
|
25
|
+
|
|
26
|
+
engine = create_engine("tibero://tibero:password@localhost:8629/TESTDB")
|
|
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["TiberoDialect"]
|
|
39
|
+
dialect --> dbapi["pytibero"]
|
|
40
|
+
dbapi --> server["Tibero 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-pytibero"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Tibero 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", "Tibero", "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
|
+
pytibero = ["pytibero"]
|
|
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-pytibero"
|
|
46
|
+
Repository = "https://github.com/yeongseon/sqlalchemy-pytibero"
|
|
47
|
+
Issues = "https://github.com/yeongseon/sqlalchemy-pytibero/issues"
|
|
48
|
+
|
|
49
|
+
[project.entry-points."sqlalchemy.dialects"]
|
|
50
|
+
tibero = "sqlalchemy_pytibero.dialect:TiberoDialect"
|
|
51
|
+
"tibero.pytibero" = "sqlalchemy_pytibero.dialect:TiberoDialect"
|
|
52
|
+
|
|
53
|
+
[tool.setuptools]
|
|
54
|
+
packages = ["sqlalchemy_pytibero"]
|
|
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_pytibero"]
|
|
63
|
+
|
|
64
|
+
[tool.coverage.run]
|
|
65
|
+
source = ["sqlalchemy_pytibero"]
|
|
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,58 @@
|
|
|
1
|
+
from .dialect import TiberoDialect
|
|
2
|
+
from .types import (
|
|
3
|
+
BIGINT,
|
|
4
|
+
BINARY_DOUBLE,
|
|
5
|
+
BINARY_FLOAT,
|
|
6
|
+
BLOB,
|
|
7
|
+
CHAR,
|
|
8
|
+
CLOB,
|
|
9
|
+
DATE,
|
|
10
|
+
DECIMAL,
|
|
11
|
+
FLOAT,
|
|
12
|
+
INTEGER,
|
|
13
|
+
INTERVAL_DAY_TO_SECOND,
|
|
14
|
+
INTERVAL_YEAR_TO_MONTH,
|
|
15
|
+
LONG,
|
|
16
|
+
LONG_RAW,
|
|
17
|
+
NCHAR,
|
|
18
|
+
NCLOB,
|
|
19
|
+
NUMBER,
|
|
20
|
+
NUMERIC,
|
|
21
|
+
NVARCHAR2,
|
|
22
|
+
RAW,
|
|
23
|
+
ROWID,
|
|
24
|
+
SMALLINT,
|
|
25
|
+
TIMESTAMP,
|
|
26
|
+
VARCHAR2,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
__version__ = "0.1.0"
|
|
30
|
+
|
|
31
|
+
__all__ = [
|
|
32
|
+
"__version__",
|
|
33
|
+
"TiberoDialect",
|
|
34
|
+
"NUMBER",
|
|
35
|
+
"NUMERIC",
|
|
36
|
+
"DECIMAL",
|
|
37
|
+
"FLOAT",
|
|
38
|
+
"BINARY_FLOAT",
|
|
39
|
+
"BINARY_DOUBLE",
|
|
40
|
+
"SMALLINT",
|
|
41
|
+
"INTEGER",
|
|
42
|
+
"BIGINT",
|
|
43
|
+
"VARCHAR2",
|
|
44
|
+
"CHAR",
|
|
45
|
+
"NCHAR",
|
|
46
|
+
"NVARCHAR2",
|
|
47
|
+
"CLOB",
|
|
48
|
+
"BLOB",
|
|
49
|
+
"NCLOB",
|
|
50
|
+
"DATE",
|
|
51
|
+
"TIMESTAMP",
|
|
52
|
+
"INTERVAL_YEAR_TO_MONTH",
|
|
53
|
+
"INTERVAL_DAY_TO_SECOND",
|
|
54
|
+
"RAW",
|
|
55
|
+
"LONG_RAW",
|
|
56
|
+
"LONG",
|
|
57
|
+
"ROWID",
|
|
58
|
+
]
|
|
@@ -0,0 +1,155 @@
|
|
|
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
|
+
"asc",
|
|
24
|
+
"audit",
|
|
25
|
+
"between",
|
|
26
|
+
"by",
|
|
27
|
+
"char",
|
|
28
|
+
"check",
|
|
29
|
+
"cluster",
|
|
30
|
+
"column",
|
|
31
|
+
"comment",
|
|
32
|
+
"compress",
|
|
33
|
+
"connect",
|
|
34
|
+
"create",
|
|
35
|
+
"current",
|
|
36
|
+
"date",
|
|
37
|
+
"decimal",
|
|
38
|
+
"default",
|
|
39
|
+
"delete",
|
|
40
|
+
"desc",
|
|
41
|
+
"distinct",
|
|
42
|
+
"drop",
|
|
43
|
+
"else",
|
|
44
|
+
"exclusive",
|
|
45
|
+
"exists",
|
|
46
|
+
"file",
|
|
47
|
+
"float",
|
|
48
|
+
"for",
|
|
49
|
+
"from",
|
|
50
|
+
"grant",
|
|
51
|
+
"group",
|
|
52
|
+
"having",
|
|
53
|
+
"identified",
|
|
54
|
+
"if",
|
|
55
|
+
"immediate",
|
|
56
|
+
"in",
|
|
57
|
+
"increment",
|
|
58
|
+
"index",
|
|
59
|
+
"initial",
|
|
60
|
+
"insert",
|
|
61
|
+
"integer",
|
|
62
|
+
"intersect",
|
|
63
|
+
"into",
|
|
64
|
+
"is",
|
|
65
|
+
"level",
|
|
66
|
+
"like",
|
|
67
|
+
"lock",
|
|
68
|
+
"long",
|
|
69
|
+
"maxextents",
|
|
70
|
+
"minus",
|
|
71
|
+
"mlslabel",
|
|
72
|
+
"mode",
|
|
73
|
+
"modify",
|
|
74
|
+
"noaudit",
|
|
75
|
+
"nocompress",
|
|
76
|
+
"not",
|
|
77
|
+
"nowait",
|
|
78
|
+
"null",
|
|
79
|
+
"number",
|
|
80
|
+
"of",
|
|
81
|
+
"offline",
|
|
82
|
+
"on",
|
|
83
|
+
"online",
|
|
84
|
+
"option",
|
|
85
|
+
"or",
|
|
86
|
+
"order",
|
|
87
|
+
"pctfree",
|
|
88
|
+
"prior",
|
|
89
|
+
"privileges",
|
|
90
|
+
"public",
|
|
91
|
+
"raw",
|
|
92
|
+
"rename",
|
|
93
|
+
"resource",
|
|
94
|
+
"revoke",
|
|
95
|
+
"row",
|
|
96
|
+
"rowid",
|
|
97
|
+
"rownum",
|
|
98
|
+
"rows",
|
|
99
|
+
"select",
|
|
100
|
+
"session",
|
|
101
|
+
"set",
|
|
102
|
+
"share",
|
|
103
|
+
"size",
|
|
104
|
+
"smallint",
|
|
105
|
+
"start",
|
|
106
|
+
"successful",
|
|
107
|
+
"synonym",
|
|
108
|
+
"sysdate",
|
|
109
|
+
"table",
|
|
110
|
+
"then",
|
|
111
|
+
"to",
|
|
112
|
+
"trigger",
|
|
113
|
+
"uid",
|
|
114
|
+
"union",
|
|
115
|
+
"unique",
|
|
116
|
+
"update",
|
|
117
|
+
"user",
|
|
118
|
+
"validate",
|
|
119
|
+
"values",
|
|
120
|
+
"varchar",
|
|
121
|
+
"varchar2",
|
|
122
|
+
"view",
|
|
123
|
+
"whenever",
|
|
124
|
+
"where",
|
|
125
|
+
"with",
|
|
126
|
+
}
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class TiberoIdentifierPreparer(compiler.IdentifierPreparer):
|
|
131
|
+
reserved_words = RESERVED_WORDS
|
|
132
|
+
|
|
133
|
+
def __init__(
|
|
134
|
+
self,
|
|
135
|
+
dialect,
|
|
136
|
+
initial_quote='"',
|
|
137
|
+
final_quote=None,
|
|
138
|
+
escape_quote='"',
|
|
139
|
+
omit_schema=False,
|
|
140
|
+
):
|
|
141
|
+
super().__init__(dialect, initial_quote, final_quote, escape_quote, omit_schema)
|
|
142
|
+
|
|
143
|
+
def _quote_free_identifiers(self, *ids):
|
|
144
|
+
return tuple(self.quote_identifier(i) for i in ids if i is not None)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class TiberoExecutionContext(default.DefaultExecutionContext):
|
|
148
|
+
def should_autocommit_text(self, statement):
|
|
149
|
+
return AUTOCOMMIT_REGEXP.match(statement)
|
|
150
|
+
|
|
151
|
+
def get_lastrowid(self):
|
|
152
|
+
try:
|
|
153
|
+
return self.cursor.lastrowid
|
|
154
|
+
except Exception:
|
|
155
|
+
return None
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# pyright: reportIncompatibleMethodOverride=false, reportArgumentType=false
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from sqlalchemy.sql import compiler
|
|
5
|
+
from sqlalchemy.sql import sqltypes
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TiberoCompiler(compiler.SQLCompiler):
|
|
9
|
+
def visit_sysdate_func(self, fn, **kw):
|
|
10
|
+
return "SYSDATE"
|
|
11
|
+
|
|
12
|
+
def visit_systimestamp_func(self, fn, **kw):
|
|
13
|
+
return "SYSTIMESTAMP"
|
|
14
|
+
|
|
15
|
+
def visit_dual_func(self, fn, **kw):
|
|
16
|
+
return "DUAL"
|
|
17
|
+
|
|
18
|
+
def visit_nvl_func(self, fn, **kw):
|
|
19
|
+
return "NVL(%s)" % self.function_argspec(fn, **kw)
|
|
20
|
+
|
|
21
|
+
def default_from(self):
|
|
22
|
+
return " FROM DUAL"
|
|
23
|
+
|
|
24
|
+
def visit_cast(self, cast, **kw):
|
|
25
|
+
type_ = self.process(cast.typeclause)
|
|
26
|
+
if type_ is None:
|
|
27
|
+
return self.process(cast.clause.self_group())
|
|
28
|
+
return f"CAST({self.process(cast.clause)} AS {type_})"
|
|
29
|
+
|
|
30
|
+
def render_literal_value(self, value, type_):
|
|
31
|
+
value = super().render_literal_value(value, type_)
|
|
32
|
+
return value.replace("\\", "\\\\")
|
|
33
|
+
|
|
34
|
+
def get_select_precolumns(self, select, **kw):
|
|
35
|
+
if bool(select._distinct):
|
|
36
|
+
return "DISTINCT "
|
|
37
|
+
return ""
|
|
38
|
+
|
|
39
|
+
def visit_join(self, join, asfrom=False, **kwargs):
|
|
40
|
+
return "".join(
|
|
41
|
+
(
|
|
42
|
+
self.process(join.left, asfrom=True, **kwargs),
|
|
43
|
+
(join.isouter and " LEFT OUTER JOIN " or " INNER JOIN "),
|
|
44
|
+
self.process(join.right, asfrom=True, **kwargs),
|
|
45
|
+
" ON ",
|
|
46
|
+
self.process(join.onclause, **kwargs),
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
def for_update_clause(self, select, **kw):
|
|
51
|
+
if select._for_update_arg is None:
|
|
52
|
+
return ""
|
|
53
|
+
text = " FOR UPDATE"
|
|
54
|
+
if select._for_update_arg.of:
|
|
55
|
+
text += " OF " + ", ".join(self.process(col, **kw) for col in select._for_update_arg.of)
|
|
56
|
+
if select._for_update_arg.nowait:
|
|
57
|
+
text += " NOWAIT"
|
|
58
|
+
return text
|
|
59
|
+
|
|
60
|
+
def limit_clause(self, select, **kw):
|
|
61
|
+
limit_clause = select._limit_clause
|
|
62
|
+
offset_clause = select._offset_clause
|
|
63
|
+
if limit_clause is None and offset_clause is None:
|
|
64
|
+
return ""
|
|
65
|
+
if limit_clause is None and offset_clause is not None:
|
|
66
|
+
return "\n OFFSET %s ROWS" % self.process(offset_clause, **kw)
|
|
67
|
+
if offset_clause is not None:
|
|
68
|
+
return "\n OFFSET %s ROWS FETCH FIRST %s ROWS ONLY" % (
|
|
69
|
+
self.process(offset_clause, **kw),
|
|
70
|
+
self.process(limit_clause, **kw),
|
|
71
|
+
)
|
|
72
|
+
return "\n FETCH FIRST %s ROWS ONLY" % self.process(limit_clause, **kw)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class TiberoDDLCompiler(compiler.DDLCompiler):
|
|
76
|
+
def get_column_specification(self, column, **kw):
|
|
77
|
+
colspec = [
|
|
78
|
+
self.preparer.format_column(column),
|
|
79
|
+
self.dialect.type_compiler_instance.process(column.type, type_expression=column),
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
if not column.nullable:
|
|
83
|
+
colspec.append("NOT NULL")
|
|
84
|
+
|
|
85
|
+
if (
|
|
86
|
+
column.table is not None
|
|
87
|
+
and column is column.table._autoincrement_column
|
|
88
|
+
and column.server_default is None
|
|
89
|
+
):
|
|
90
|
+
colspec.append("GENERATED ALWAYS AS IDENTITY")
|
|
91
|
+
else:
|
|
92
|
+
default = self.get_column_default_string(column)
|
|
93
|
+
if default is not None:
|
|
94
|
+
colspec.append("DEFAULT " + default)
|
|
95
|
+
|
|
96
|
+
return " ".join(colspec)
|
|
97
|
+
|
|
98
|
+
def post_create_table(self, table):
|
|
99
|
+
if table.comment is None:
|
|
100
|
+
return ""
|
|
101
|
+
literal = self.sql_compiler.render_literal_value(table.comment, sqltypes.String())
|
|
102
|
+
return f"\nCOMMENT ON TABLE {self.preparer.format_table(table)} IS {literal}"
|
|
103
|
+
|
|
104
|
+
def visit_set_table_comment(self, create, **kw):
|
|
105
|
+
return "COMMENT ON TABLE %s IS %s" % (
|
|
106
|
+
self.preparer.format_table(create.element),
|
|
107
|
+
self.sql_compiler.render_literal_value(create.element.comment, sqltypes.String()),
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
def visit_drop_table_comment(self, drop, **kw):
|
|
111
|
+
return "COMMENT ON TABLE %s IS ''" % (self.preparer.format_table(drop.element),)
|
|
112
|
+
|
|
113
|
+
def visit_set_column_comment(self, create, **kw):
|
|
114
|
+
return "COMMENT ON COLUMN %s.%s IS %s" % (
|
|
115
|
+
self.preparer.format_table(create.element.table),
|
|
116
|
+
self.preparer.format_column(create.element),
|
|
117
|
+
self.sql_compiler.render_literal_value(create.element.comment, sqltypes.String()),
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class TiberoTypeCompiler(compiler.GenericTypeCompiler):
|
|
122
|
+
def visit_BOOLEAN(self, type_, **kw):
|
|
123
|
+
return "NUMBER(1)"
|
|
124
|
+
|
|
125
|
+
def visit_NUMERIC(self, type_, **kw):
|
|
126
|
+
if type_.precision is None:
|
|
127
|
+
return "NUMERIC"
|
|
128
|
+
if type_.scale is None:
|
|
129
|
+
return f"NUMERIC({type_.precision})"
|
|
130
|
+
return f"NUMERIC({type_.precision}, {type_.scale})"
|
|
131
|
+
|
|
132
|
+
def visit_NUMBER(self, type_, **kw):
|
|
133
|
+
if type_.precision is None:
|
|
134
|
+
return "NUMBER"
|
|
135
|
+
if type_.scale is None:
|
|
136
|
+
return f"NUMBER({type_.precision})"
|
|
137
|
+
return f"NUMBER({type_.precision}, {type_.scale})"
|
|
138
|
+
|
|
139
|
+
def visit_DECIMAL(self, type_, **kw):
|
|
140
|
+
if type_.precision is None:
|
|
141
|
+
return "DECIMAL"
|
|
142
|
+
if type_.scale is None:
|
|
143
|
+
return f"DECIMAL({type_.precision})"
|
|
144
|
+
return f"DECIMAL({type_.precision}, {type_.scale})"
|
|
145
|
+
|
|
146
|
+
def visit_FLOAT(self, type_, **kw):
|
|
147
|
+
if type_.precision is None:
|
|
148
|
+
return "FLOAT"
|
|
149
|
+
return f"FLOAT({type_.precision})"
|
|
150
|
+
|
|
151
|
+
def visit_BINARY_FLOAT(self, type_, **kw):
|
|
152
|
+
return "BINARY_FLOAT"
|
|
153
|
+
|
|
154
|
+
def visit_BINARY_DOUBLE(self, type_, **kw):
|
|
155
|
+
return "BINARY_DOUBLE"
|
|
156
|
+
|
|
157
|
+
def visit_SMALLINT(self, type_, **kw):
|
|
158
|
+
return "SMALLINT"
|
|
159
|
+
|
|
160
|
+
def visit_INTEGER(self, type_, **kw):
|
|
161
|
+
return "INTEGER"
|
|
162
|
+
|
|
163
|
+
def visit_BIGINT(self, type_, **kw):
|
|
164
|
+
return "NUMBER(19)"
|
|
165
|
+
|
|
166
|
+
def visit_VARCHAR(self, type_, **kw):
|
|
167
|
+
return self.visit_VARCHAR2(type_, **kw)
|
|
168
|
+
|
|
169
|
+
def visit_VARCHAR2(self, type_, **kw):
|
|
170
|
+
if type_.length:
|
|
171
|
+
return f"VARCHAR2({type_.length})"
|
|
172
|
+
return "VARCHAR2"
|
|
173
|
+
|
|
174
|
+
def visit_CHAR(self, type_, **kw):
|
|
175
|
+
if getattr(type_, "national", False):
|
|
176
|
+
return self.visit_NCHAR(type_, **kw)
|
|
177
|
+
if type_.length:
|
|
178
|
+
return f"CHAR({type_.length})"
|
|
179
|
+
return "CHAR"
|
|
180
|
+
|
|
181
|
+
def visit_NCHAR(self, type_, **kw):
|
|
182
|
+
if type_.length:
|
|
183
|
+
return f"NCHAR({type_.length})"
|
|
184
|
+
return "NCHAR"
|
|
185
|
+
|
|
186
|
+
def visit_NVARCHAR(self, type_, **kw):
|
|
187
|
+
return self.visit_NVARCHAR2(type_, **kw)
|
|
188
|
+
|
|
189
|
+
def visit_NVARCHAR2(self, type_, **kw):
|
|
190
|
+
if type_.length:
|
|
191
|
+
return f"NVARCHAR2({type_.length})"
|
|
192
|
+
return "NVARCHAR2"
|
|
193
|
+
|
|
194
|
+
def visit_CLOB(self, type_, **kw):
|
|
195
|
+
return "CLOB"
|
|
196
|
+
|
|
197
|
+
def visit_NCLOB(self, type_, **kw):
|
|
198
|
+
return "NCLOB"
|
|
199
|
+
|
|
200
|
+
def visit_BLOB(self, type_, **kw):
|
|
201
|
+
return "BLOB"
|
|
202
|
+
|
|
203
|
+
def visit_RAW(self, type_, **kw):
|
|
204
|
+
if getattr(type_, "length", None):
|
|
205
|
+
return f"RAW({type_.length})"
|
|
206
|
+
return "RAW"
|
|
207
|
+
|
|
208
|
+
def visit_LONG(self, type_, **kw):
|
|
209
|
+
return "LONG"
|
|
210
|
+
|
|
211
|
+
def visit_LONG_RAW(self, type_, **kw):
|
|
212
|
+
return "LONG RAW"
|
|
213
|
+
|
|
214
|
+
def visit_ROWID(self, type_, **kw):
|
|
215
|
+
return "ROWID"
|
|
216
|
+
|
|
217
|
+
def visit_DATE(self, type_, **kw):
|
|
218
|
+
return "DATE"
|
|
219
|
+
|
|
220
|
+
def visit_TIMESTAMP(self, type_, **kw):
|
|
221
|
+
return "TIMESTAMP"
|
|
222
|
+
|
|
223
|
+
def visit_INTERVAL_YEAR_TO_MONTH(self, type_, **kw):
|
|
224
|
+
return "INTERVAL YEAR TO MONTH"
|
|
225
|
+
|
|
226
|
+
def visit_INTERVAL_DAY_TO_SECOND(self, type_, **kw):
|
|
227
|
+
return "INTERVAL DAY TO SECOND"
|
|
228
|
+
|
|
229
|
+
def visit_large_binary(self, type_, **kw):
|
|
230
|
+
return "BLOB"
|
|
231
|
+
|
|
232
|
+
def visit_text(self, type_, **kw):
|
|
233
|
+
return "CLOB"
|
|
234
|
+
|
|
235
|
+
def visit_datetime(self, type_, **kw):
|
|
236
|
+
return "DATE"
|