@an5/adapters 0.2.5 → 0.2.9
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.
- package/README.md +283 -305
- package/dist/an5Adapter.d.ts +142 -7
- package/dist/an5Adapter.js +998 -68
- package/dist/base/metadata.d.ts +10 -0
- package/dist/base/metadata.js +107 -0
- package/dist/base/sql.d.ts +19 -1
- package/dist/base/sql.js +152 -24
- package/dist/base/types.d.ts +10 -1
- package/dist/browser.d.ts +4 -1
- package/dist/browser.js +9 -2
- package/dist/googlesheets/adapter.d.ts +1 -0
- package/dist/googlesheets/adapter.js +9 -2
- package/dist/googlesheets/config.d.ts +4 -4
- package/dist/googlesheets/helpers.js +2 -0
- package/dist/googlesheets/parseConnectionString.js +5 -2
- package/dist/googlesheets/sqlExecutor.js +29 -24
- package/dist/googlesheets/tableClient.js +14 -5
- package/dist/index.d.ts +5 -1
- package/dist/index.js +15 -1
- package/dist/mssql/engine.js +8 -4
- package/dist/mysql/engine.js +1 -1
- package/dist/nbase/client.d.ts +92 -0
- package/dist/nbase/client.js +147 -0
- package/dist/nbase/index.d.ts +47 -0
- package/dist/nbase/index.js +80 -0
- package/dist/postgres/engine.js +1 -1
- package/dist/sqlite/browserEngine.d.ts +27 -0
- package/dist/sqlite/browserEngine.js +161 -0
- package/dist/sqlite/engine.js +15 -3
- package/dist/sqlite/index.d.ts +1 -0
- package/dist/sqlite/index.js +1 -0
- package/dist/typed.d.ts +5 -0
- package/dist/typed.js +2 -0
- package/dotnet/An5Adapter.cs +1112 -275
- package/dotnet/Base/Core.cs +380 -82
- package/dotnet/Mssql/MssqlEngine.cs +145 -144
- package/dotnet/Postgres/PostgresEngine.cs +149 -121
- package/dotnet/Sqlite/SqliteEngine.cs +214 -0
- package/golang/an5_adapter.go +314 -0
- package/golang/base/base.go +100 -0
- package/golang/base/metadata.go +116 -0
- package/golang/base/where.go +311 -0
- package/golang/go.mod +3 -0
- package/golang/mssql/mssql.go +32 -0
- package/golang/postgres/postgres.go +37 -0
- package/golang/sqlite/sqlite.go +14 -0
- package/golang/sqlite_test.go +42 -0
- package/golang/table_client.go +1329 -0
- package/package.json +26 -9
- package/python/an5_adapter.py +184 -97
- package/python/base/__init__.py +3 -3
- package/python/base/dialects.py +11 -8
- package/python/base/metadata.py +59 -10
- package/python/base/sql.py +202 -121
- package/python/mssql/__init__.py +1 -1
- package/python/mssql/provider.py +62 -62
- package/python/postgres/__init__.py +1 -1
- package/python/postgres/provider.py +15 -15
- package/python/sqlite/__init__.py +3 -0
- package/python/sqlite/provider.py +83 -0
- package/python/table_client.py +636 -216
- package/rust/Cargo.toml +31 -0
- package/rust/README.md +71 -0
- package/rust/src/adapter.rs +1312 -0
- package/rust/src/base/dialect.rs +172 -0
- package/rust/src/base/metadata.rs +134 -0
- package/rust/src/base/mod.rs +17 -0
- package/rust/src/base/where_builder.rs +405 -0
- package/rust/src/lib.rs +75 -0
- package/typescript/src/an5Adapter.ts +1515 -505
- package/typescript/src/base/index.ts +3 -3
- package/typescript/src/base/metadata.ts +148 -28
- package/typescript/src/base/sql.ts +264 -98
- package/typescript/src/base/types.ts +33 -24
- package/typescript/src/browser.ts +10 -6
- package/typescript/src/config.ts +95 -95
- package/typescript/src/googlesheets/adapter.ts +269 -259
- package/typescript/src/googlesheets/an5SheetsAdapter.ts +3 -3
- package/typescript/src/googlesheets/config.ts +47 -47
- package/typescript/src/googlesheets/helpers.ts +144 -143
- package/typescript/src/googlesheets/index.ts +3 -3
- package/typescript/src/googlesheets/parseConnectionString.ts +48 -46
- package/typescript/src/googlesheets/retry.ts +126 -126
- package/typescript/src/googlesheets/sqlExecutor.ts +304 -301
- package/typescript/src/googlesheets/tableClient.ts +461 -455
- package/typescript/src/googlesheets/types.ts +6 -6
- package/typescript/src/index.ts +48 -27
- package/typescript/src/mssql/engine.ts +134 -132
- package/typescript/src/mssql/index.ts +1 -1
- package/typescript/src/mysql/engine.ts +80 -80
- package/typescript/src/mysql/index.ts +1 -1
- package/typescript/src/nbase/client.ts +214 -0
- package/typescript/src/nbase/index.ts +104 -0
- package/typescript/src/postgres/engine.ts +87 -87
- package/typescript/src/postgres/index.ts +1 -1
- package/typescript/src/sqlite/browserEngine.ts +192 -0
- package/typescript/src/sqlite/engine.ts +66 -55
- package/typescript/src/sqlite/index.ts +3 -1
- package/typescript/src/typed.ts +52 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@an5/adapters",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "Standalone language adapters for AN5 ORM - TypeScript, Python, .NET query execution.",
|
|
3
|
+
"version": "0.2.9",
|
|
4
|
+
"description": "Standalone language adapters for AN5 ORM - TypeScript, Python, .NET, Go, Rust, and Google Sheets query execution.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -20,7 +20,10 @@
|
|
|
20
20
|
"./typescript": "./dist/index.js",
|
|
21
21
|
"./typescript/*": "./dist/*",
|
|
22
22
|
"./python": "./python/an5_adapter.py",
|
|
23
|
-
"./dotnet": "./dotnet/An5Adapter.cs"
|
|
23
|
+
"./dotnet": "./dotnet/An5Adapter.cs",
|
|
24
|
+
"./golang": "./golang/an5_adapter.go",
|
|
25
|
+
"./rust": "./rust/Cargo.toml",
|
|
26
|
+
"./nbase": "./dist/nbase/index.js"
|
|
24
27
|
},
|
|
25
28
|
"author": "an5ORM",
|
|
26
29
|
"homepage": "https://github.com/an5ORM/an5",
|
|
@@ -34,20 +37,34 @@
|
|
|
34
37
|
"adapter",
|
|
35
38
|
"typescript",
|
|
36
39
|
"python",
|
|
37
|
-
"dotnet"
|
|
40
|
+
"dotnet",
|
|
41
|
+
"go",
|
|
42
|
+
"golang",
|
|
43
|
+
"rust"
|
|
38
44
|
],
|
|
39
45
|
"files": [
|
|
40
46
|
"dist/**/*.{js,d.ts}",
|
|
41
47
|
"typescript/src/**/*.ts",
|
|
42
48
|
"python/**/*.py",
|
|
43
49
|
"dotnet/**/*",
|
|
44
|
-
"
|
|
50
|
+
"golang/**/*",
|
|
51
|
+
"rust/Cargo.toml",
|
|
52
|
+
"rust/src/**/*",
|
|
53
|
+
"README.md",
|
|
54
|
+
"typescript/src/nbase/**/*.ts"
|
|
45
55
|
],
|
|
46
56
|
"scripts": {
|
|
47
57
|
"build": "tsc -p tsconfig.json",
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
58
|
+
"pretest": "npm run build",
|
|
59
|
+
"prepack": "npm run build",
|
|
60
|
+
"build:python": "(python -m build --outdir dist-py || python3 -m build --outdir dist-py)",
|
|
61
|
+
"test": "node test/unit.test.js && node test/package.test.js && node test/nbase.test.js && node --test test/version-sync.test.js",
|
|
62
|
+
"test:package:smoke": "node scripts/package-smoke.js",
|
|
63
|
+
"test:integration:live": "node test/live-db.integration.test.js",
|
|
64
|
+
"test:python": "(python -m compileall python 2>&1 || python3 -m compileall python 2>&1)",
|
|
65
|
+
"test:dotnet": "node scripts/dotnet-compile-check.js && node scripts/dotnet-sqlite-smoke.js",
|
|
66
|
+
"test:go": "cd golang && go test ./...",
|
|
67
|
+
"test:rust": "node scripts/rust-compile-check.js"
|
|
51
68
|
},
|
|
52
69
|
"dependencies": {
|
|
53
70
|
"googleapis": "^134.0.0",
|
|
@@ -58,7 +75,7 @@
|
|
|
58
75
|
"@types/mssql": "^9.1.8",
|
|
59
76
|
"@types/node": "^20.11.0",
|
|
60
77
|
"@types/pg": "^8.11.0",
|
|
61
|
-
"typescript": "^
|
|
78
|
+
"typescript": "^7.0.2"
|
|
62
79
|
},
|
|
63
80
|
"publishConfig": {
|
|
64
81
|
"access": "public"
|
package/python/an5_adapter.py
CHANGED
|
@@ -1,97 +1,184 @@
|
|
|
1
|
-
"""Standalone Python runtime adapter for AN5 ORM."""
|
|
2
|
-
|
|
3
|
-
from typing import Dict, List, Optional
|
|
4
|
-
|
|
5
|
-
try:
|
|
6
|
-
from .base import DIALECT_MSSQL, DIALECT_POSTGRES, detect_dialect, set_adapter_metadata
|
|
7
|
-
from .mssql import connect as connect_mssql
|
|
8
|
-
from .postgres import connect as connect_postgres
|
|
9
|
-
from .
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
from
|
|
13
|
-
from
|
|
14
|
-
from
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
from mssql import parse_connection_string as _parse_connection_string
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
1
|
+
"""Standalone Python runtime adapter for AN5 ORM."""
|
|
2
|
+
|
|
3
|
+
from typing import Dict, List, Optional, Any
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
from .base import DIALECT_MSSQL, DIALECT_POSTGRES, DIALECT_SQLITE, detect_dialect, set_adapter_metadata
|
|
7
|
+
from .mssql import connect as connect_mssql
|
|
8
|
+
from .postgres import connect as connect_postgres
|
|
9
|
+
from .sqlite import connect as connect_sqlite, is_memory as is_sqlite_memory
|
|
10
|
+
from .table_client import AdapterTableClient, ViewClient
|
|
11
|
+
except ImportError:
|
|
12
|
+
from base import DIALECT_MSSQL, DIALECT_POSTGRES, DIALECT_SQLITE, detect_dialect, set_adapter_metadata
|
|
13
|
+
from mssql import connect as connect_mssql
|
|
14
|
+
from postgres import connect as connect_postgres
|
|
15
|
+
from sqlite import connect as connect_sqlite, is_memory as is_sqlite_memory
|
|
16
|
+
from table_client import AdapterTableClient, ViewClient
|
|
17
|
+
|
|
18
|
+
# Backward-compatible aliases used by tests and older imports.
|
|
19
|
+
_detect_dialect = detect_dialect
|
|
20
|
+
try:
|
|
21
|
+
from .mssql import parse_connection_string as _parse_connection_string
|
|
22
|
+
except ImportError:
|
|
23
|
+
from mssql import parse_connection_string as _parse_connection_string
|
|
24
|
+
|
|
25
|
+
class An5Adapter:
|
|
26
|
+
def __init__(self, connection_string: str):
|
|
27
|
+
self._dialect = detect_dialect(connection_string)
|
|
28
|
+
self._conn_str = connection_string
|
|
29
|
+
# The open connection of `transaction()`, when there is one. Every
|
|
30
|
+
# statement in the callback has to use this very connection, otherwise a
|
|
31
|
+
# rollback returns to a different, already committed connection — that
|
|
32
|
+
# is "the error was raised but the data is still there".
|
|
33
|
+
self._txn_conn = None
|
|
34
|
+
# Long-lived connection for in-memory databases, because `:memory:` is
|
|
35
|
+
# gone as soon as every connection is closed.
|
|
36
|
+
self._conn = None
|
|
37
|
+
|
|
38
|
+
def _connect(self):
|
|
39
|
+
if self._dialect == DIALECT_POSTGRES:
|
|
40
|
+
return connect_postgres(self._conn_str)
|
|
41
|
+
if self._dialect == DIALECT_SQLITE:
|
|
42
|
+
return connect_sqlite(self._conn_str)
|
|
43
|
+
return connect_mssql(self._conn_str)
|
|
44
|
+
|
|
45
|
+
def _acquire(self):
|
|
46
|
+
"""Returns (connection, whether we opened it and must close it)."""
|
|
47
|
+
if self._txn_conn is not None:
|
|
48
|
+
return self._txn_conn, False
|
|
49
|
+
if self._dialect == DIALECT_SQLITE and is_sqlite_memory(self._conn_str):
|
|
50
|
+
if self._conn is None:
|
|
51
|
+
self._conn = self._connect()
|
|
52
|
+
return self._conn, False
|
|
53
|
+
return self._connect(), True
|
|
54
|
+
|
|
55
|
+
def close(self):
|
|
56
|
+
"""Closes the long-lived connection (in-memory databases only)."""
|
|
57
|
+
if self._conn is not None:
|
|
58
|
+
self._conn.close()
|
|
59
|
+
self._conn = None
|
|
60
|
+
|
|
61
|
+
def _to_dicts(self, cursor, query: str) -> List[Dict]:
|
|
62
|
+
if cursor.description:
|
|
63
|
+
cols = [col[0] for col in cursor.description]
|
|
64
|
+
rows = cursor.fetchall() if cursor.description else []
|
|
65
|
+
return [dict(zip(cols, row)) for row in rows]
|
|
66
|
+
return []
|
|
67
|
+
|
|
68
|
+
def exec(self, query: str, params: Optional[List] = None) -> List[Dict]:
|
|
69
|
+
conn, owned = self._acquire()
|
|
70
|
+
try:
|
|
71
|
+
cursor = conn.cursor()
|
|
72
|
+
cursor.execute(query, params or [])
|
|
73
|
+
return self._to_dicts(cursor, query)
|
|
74
|
+
finally:
|
|
75
|
+
if owned:
|
|
76
|
+
conn.close()
|
|
77
|
+
|
|
78
|
+
def execute(self, query: str, params: Optional[List] = None) -> int:
|
|
79
|
+
conn, owned = self._acquire()
|
|
80
|
+
try:
|
|
81
|
+
cursor = conn.cursor()
|
|
82
|
+
cursor.execute(query, params or [])
|
|
83
|
+
return cursor.rowcount
|
|
84
|
+
finally:
|
|
85
|
+
if owned:
|
|
86
|
+
conn.close()
|
|
87
|
+
|
|
88
|
+
def query_raw(self, query: str, *values) -> List[Dict]:
|
|
89
|
+
return self.exec(query, list(values))
|
|
90
|
+
|
|
91
|
+
def execute_raw(self, query: str, *values) -> int:
|
|
92
|
+
return self.execute(query, list(values))
|
|
93
|
+
|
|
94
|
+
def table(self, model_name: str) -> AdapterTableClient[Any]:
|
|
95
|
+
return AdapterTableClient(self, model_name)
|
|
96
|
+
|
|
97
|
+
def view(self, view_name: str) -> ViewClient:
|
|
98
|
+
return ViewClient(self, view_name)
|
|
99
|
+
|
|
100
|
+
def query_proc(self, proc_name: str, params: Optional[List] = None) -> List[Dict]:
|
|
101
|
+
if self._dialect == DIALECT_SQLITE:
|
|
102
|
+
# SQLite has no stored procedures. This used to fall through to the
|
|
103
|
+
# MSSQL branch and generate `EXEC ...`, which is not valid SQL, and
|
|
104
|
+
# the error only surfaced at the call. Saying so beats sending wrong
|
|
105
|
+
# SQL.
|
|
106
|
+
raise NotImplementedError(
|
|
107
|
+
"SQLite has no stored procedures. Use the generated table client "
|
|
108
|
+
"(db.<model>.*) or db.exec() with a parameterised query."
|
|
109
|
+
)
|
|
110
|
+
if self._dialect == DIALECT_POSTGRES:
|
|
111
|
+
placeholders = ", ".join(["%s"] * len(params or []))
|
|
112
|
+
sql = f"CALL {proc_name}({placeholders})" if placeholders else f"CALL {proc_name}()"
|
|
113
|
+
else:
|
|
114
|
+
placeholders = ", ".join(["?"] * len(params or []))
|
|
115
|
+
sql = f"EXEC {proc_name} {placeholders}" if placeholders else f"EXEC {proc_name}"
|
|
116
|
+
return self.exec(sql, params or [])
|
|
117
|
+
|
|
118
|
+
def execute_proc(self, proc_name: str, params: Optional[List] = None) -> int:
|
|
119
|
+
if self._dialect == DIALECT_SQLITE:
|
|
120
|
+
raise NotImplementedError(
|
|
121
|
+
"SQLite has no stored procedures. Use the generated table client "
|
|
122
|
+
"(db.<model>.*) or db.execute() with a parameterised query."
|
|
123
|
+
)
|
|
124
|
+
if self._dialect == DIALECT_POSTGRES:
|
|
125
|
+
placeholders = ", ".join(["%s"] * len(params or []))
|
|
126
|
+
sql = f"CALL {proc_name}({placeholders})" if placeholders else f"CALL {proc_name}()"
|
|
127
|
+
else:
|
|
128
|
+
placeholders = ", ".join(["?"] * len(params or []))
|
|
129
|
+
sql = f"EXEC {proc_name} {placeholders}" if placeholders else f"EXEC {proc_name}"
|
|
130
|
+
return self.execute(sql, params or [])
|
|
131
|
+
|
|
132
|
+
def __getattr__(self, model_name: str) -> AdapterTableClient:
|
|
133
|
+
return self.table(model_name)
|
|
134
|
+
|
|
135
|
+
def transaction(self, fn):
|
|
136
|
+
if self._txn_conn is not None:
|
|
137
|
+
raise RuntimeError("Nested transaction() is not supported")
|
|
138
|
+
# In-memory database: the connection holding the data has to be reused.
|
|
139
|
+
# Opening a new one here points at a different, empty database — the
|
|
140
|
+
# table just created disappears and every statement in the transaction
|
|
141
|
+
# fails.
|
|
142
|
+
conn = self._conn if self._conn is not None else self._connect()
|
|
143
|
+
owned = self._conn is None
|
|
144
|
+
if self._dialect == DIALECT_SQLITE:
|
|
145
|
+
# `sqlite3.Connection` has no `autocommit` attribute; transactions
|
|
146
|
+
# are driven by `isolation_level`. The provider opens at `None`
|
|
147
|
+
# (autocommit), so to get a transaction it has to be set back to `""`
|
|
148
|
+
# and sqlite3 opens an implicit BEGIN before each DML statement —
|
|
149
|
+
# only then do `commit()`/`rollback()` do anything.
|
|
150
|
+
conn.isolation_level = ""
|
|
151
|
+
else:
|
|
152
|
+
conn.autocommit = False
|
|
153
|
+
self._txn_conn = conn
|
|
154
|
+
try:
|
|
155
|
+
result = fn(self)
|
|
156
|
+
conn.commit()
|
|
157
|
+
return result
|
|
158
|
+
except Exception:
|
|
159
|
+
conn.rollback()
|
|
160
|
+
raise
|
|
161
|
+
finally:
|
|
162
|
+
self._txn_conn = None
|
|
163
|
+
if self._dialect == DIALECT_POSTGRES:
|
|
164
|
+
conn.autocommit = True
|
|
165
|
+
elif self._dialect == DIALECT_SQLITE:
|
|
166
|
+
conn.isolation_level = None
|
|
167
|
+
if owned:
|
|
168
|
+
conn.close()
|
|
169
|
+
|
|
170
|
+
def create_an5_adapter(connection_string: str) -> An5Adapter:
|
|
171
|
+
return An5Adapter(connection_string)
|
|
172
|
+
|
|
173
|
+
__all__ = [
|
|
174
|
+
"An5Adapter",
|
|
175
|
+
"AdapterTableClient",
|
|
176
|
+
"ViewClient",
|
|
177
|
+
"create_an5_adapter",
|
|
178
|
+
"DIALECT_MSSQL",
|
|
179
|
+
"DIALECT_POSTGRES",
|
|
180
|
+
"DIALECT_SQLITE",
|
|
181
|
+
"_detect_dialect",
|
|
182
|
+
"_parse_connection_string",
|
|
183
|
+
"set_adapter_metadata",
|
|
184
|
+
]
|
package/python/base/__init__.py
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
from .dialects import DIALECT_MSSQL, DIALECT_POSTGRES, detect_dialect
|
|
2
|
-
from .metadata import model_to_table, model_fields, set_adapter_metadata
|
|
3
|
-
from .sql import _quote, _parse_where, _build_order_by, _resolve_table, _quote_table
|
|
1
|
+
from .dialects import DIALECT_MSSQL, DIALECT_POSTGRES, DIALECT_SQLITE, detect_dialect
|
|
2
|
+
from .metadata import model_to_table, model_fields, relation_map, set_adapter_metadata, get_relations_for_model, get_model_to_table, get_fields_for_model
|
|
3
|
+
from .sql import _quote, _parse_where, _build_order_by, _resolve_table, _quote_table
|
package/python/base/dialects.py
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
DIALECT_MSSQL = "mssql"
|
|
2
|
-
DIALECT_POSTGRES = "postgres"
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
DIALECT_MSSQL = "mssql"
|
|
2
|
+
DIALECT_POSTGRES = "postgres"
|
|
3
|
+
DIALECT_SQLITE = "sqlite"
|
|
4
|
+
|
|
5
|
+
def detect_dialect(connection_string: str) -> str:
|
|
6
|
+
cs = connection_string.strip().lower()
|
|
7
|
+
if cs.startswith("postgres://") or cs.startswith("postgresql://"):
|
|
8
|
+
return DIALECT_POSTGRES
|
|
9
|
+
if cs.startswith("sqlite://") or cs.startswith("sqlite:") or cs.startswith("file:") or cs.endswith(".db") or cs.endswith(".sqlite") or cs.endswith(".sqlite3") or cs == ":memory:":
|
|
10
|
+
return DIALECT_SQLITE
|
|
11
|
+
return DIALECT_MSSQL
|
package/python/base/metadata.py
CHANGED
|
@@ -1,10 +1,59 @@
|
|
|
1
|
-
from typing import Any, Dict
|
|
2
|
-
|
|
3
|
-
model_to_table: Dict[str, str] = {}
|
|
4
|
-
model_fields: Dict[str, Any] = {}
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
model_to_table.
|
|
9
|
-
|
|
10
|
-
model_fields.
|
|
1
|
+
from typing import Any, Dict, List, Optional
|
|
2
|
+
|
|
3
|
+
model_to_table: Dict[str, str] = {}
|
|
4
|
+
model_fields: Dict[str, Any] = {}
|
|
5
|
+
relation_map: Dict[str, Dict[str, Dict[str, str]]] = {}
|
|
6
|
+
|
|
7
|
+
def set_adapter_metadata(metadata: Dict[str, Any]) -> None:
|
|
8
|
+
model_to_table.clear()
|
|
9
|
+
model_to_table.update(metadata.get("model_to_table") or metadata.get("modelToTable") or {})
|
|
10
|
+
model_fields.clear()
|
|
11
|
+
model_fields.update(metadata.get("model_fields") or metadata.get("modelFields") or {})
|
|
12
|
+
relation_map.clear()
|
|
13
|
+
rels = metadata.get("relation_map") or metadata.get("relationMap") or metadata.get("RELATION_MAP") or {}
|
|
14
|
+
if isinstance(rels, dict):
|
|
15
|
+
for model, relations in rels.items():
|
|
16
|
+
if not isinstance(relations, dict):
|
|
17
|
+
continue
|
|
18
|
+
relation_map[model] = {}
|
|
19
|
+
for key, defn in relations.items():
|
|
20
|
+
if isinstance(defn, dict):
|
|
21
|
+
relation_map[model][key] = {
|
|
22
|
+
"modelName": defn.get("modelName") or defn.get("model_name") or "",
|
|
23
|
+
"relationType": defn.get("relationType") or defn.get("relation_type") or "many",
|
|
24
|
+
"foreignKey": defn.get("foreignKey") or defn.get("foreign_key") or "",
|
|
25
|
+
"localKey": defn.get("localKey") or defn.get("local_key") or "",
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
def resolve_model_key(model_name: str) -> str:
|
|
29
|
+
"""The model name in the metadata, accepting a different capitalisation.
|
|
30
|
+
|
|
31
|
+
The generated client registers tables under their PascalCase name
|
|
32
|
+
(`CatalogType`) while the metadata keys them camelCase/snake_case
|
|
33
|
+
(`catalogType`, `catalog_type`). A plain lookup would find no columns at all
|
|
34
|
+
and silently skip `isId` — that is, never generate the primary key.
|
|
35
|
+
"""
|
|
36
|
+
if not model_name:
|
|
37
|
+
return model_name
|
|
38
|
+
if model_name in model_to_table or model_name in model_fields:
|
|
39
|
+
return model_name
|
|
40
|
+
camel = model_name[0].lower() + model_name[1:]
|
|
41
|
+
if camel in model_to_table or camel in model_fields:
|
|
42
|
+
return camel
|
|
43
|
+
lower = model_name.lower()
|
|
44
|
+
if lower in model_to_table or lower in model_fields:
|
|
45
|
+
return lower
|
|
46
|
+
return model_name
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def get_relations_for_model(model_name: str) -> Dict[str, Dict[str, str]]:
|
|
50
|
+
return relation_map.get(model_name) or relation_map.get(resolve_model_key(model_name)) or {}
|
|
51
|
+
|
|
52
|
+
def get_model_to_table() -> Dict[str, str]:
|
|
53
|
+
return dict(model_to_table)
|
|
54
|
+
|
|
55
|
+
def get_fields_for_model(model_name: str) -> Any:
|
|
56
|
+
key = resolve_model_key(model_name)
|
|
57
|
+
if key in model_fields:
|
|
58
|
+
return model_fields[key]
|
|
59
|
+
return model_fields.get(model_name)
|