@mostajs/orm 1.0.0
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/LICENSE +21 -0
- package/README.md +548 -0
- package/dist/core/base-repository.d.ts +26 -0
- package/dist/core/base-repository.js +82 -0
- package/dist/core/config.d.ts +62 -0
- package/dist/core/config.js +116 -0
- package/dist/core/errors.d.ts +30 -0
- package/dist/core/errors.js +49 -0
- package/dist/core/factory.d.ts +41 -0
- package/dist/core/factory.js +142 -0
- package/dist/core/normalizer.d.ts +9 -0
- package/dist/core/normalizer.js +19 -0
- package/dist/core/registry.d.ts +43 -0
- package/dist/core/registry.js +78 -0
- package/dist/core/types.d.ts +228 -0
- package/dist/core/types.js +5 -0
- package/dist/dialects/abstract-sql.dialect.d.ts +113 -0
- package/dist/dialects/abstract-sql.dialect.js +1071 -0
- package/dist/dialects/cockroachdb.dialect.d.ts +2 -0
- package/dist/dialects/cockroachdb.dialect.js +23 -0
- package/dist/dialects/db2.dialect.d.ts +2 -0
- package/dist/dialects/db2.dialect.js +190 -0
- package/dist/dialects/hana.dialect.d.ts +2 -0
- package/dist/dialects/hana.dialect.js +199 -0
- package/dist/dialects/hsqldb.dialect.d.ts +2 -0
- package/dist/dialects/hsqldb.dialect.js +114 -0
- package/dist/dialects/mariadb.dialect.d.ts +2 -0
- package/dist/dialects/mariadb.dialect.js +87 -0
- package/dist/dialects/mongo.dialect.d.ts +2 -0
- package/dist/dialects/mongo.dialect.js +480 -0
- package/dist/dialects/mssql.dialect.d.ts +27 -0
- package/dist/dialects/mssql.dialect.js +127 -0
- package/dist/dialects/mysql.dialect.d.ts +24 -0
- package/dist/dialects/mysql.dialect.js +101 -0
- package/dist/dialects/oracle.dialect.d.ts +2 -0
- package/dist/dialects/oracle.dialect.js +206 -0
- package/dist/dialects/postgres.dialect.d.ts +26 -0
- package/dist/dialects/postgres.dialect.js +105 -0
- package/dist/dialects/spanner.dialect.d.ts +2 -0
- package/dist/dialects/spanner.dialect.js +259 -0
- package/dist/dialects/sqlite.dialect.d.ts +2 -0
- package/dist/dialects/sqlite.dialect.js +1027 -0
- package/dist/dialects/sybase.dialect.d.ts +2 -0
- package/dist/dialects/sybase.dialect.js +119 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +26 -0
- package/docs/api-reference.md +1009 -0
- package/docs/dialects.md +673 -0
- package/docs/tutorial.md +846 -0
- package/package.json +91 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// CockroachDB Dialect — extends PostgresDialect
|
|
2
|
+
// Equivalent to org.hibernate.dialect.CockroachDialect (Hibernate ORM 6.4)
|
|
3
|
+
// Wire-compatible with PostgreSQL but DDL differences
|
|
4
|
+
// Driver: npm install pg (same as PostgreSQL)
|
|
5
|
+
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
6
|
+
import { PostgresDialect } from './postgres.dialect.js';
|
|
7
|
+
// ============================================================
|
|
8
|
+
// CockroachDBDialect
|
|
9
|
+
// ============================================================
|
|
10
|
+
class CockroachDBDialect extends PostgresDialect {
|
|
11
|
+
dialectType = 'cockroachdb';
|
|
12
|
+
// CockroachDB uses STRING internally (alias for TEXT)
|
|
13
|
+
// We keep TEXT for cross-compatibility — CockroachDB accepts it
|
|
14
|
+
// CockroachDB supports RETURNING like Postgres
|
|
15
|
+
// CockroachDB supports IF NOT EXISTS for tables and indexes
|
|
16
|
+
getDialectLabel() { return 'CockroachDB'; }
|
|
17
|
+
}
|
|
18
|
+
// ============================================================
|
|
19
|
+
// Factory export
|
|
20
|
+
// ============================================================
|
|
21
|
+
export function createDialect() {
|
|
22
|
+
return new CockroachDBDialect();
|
|
23
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// IBM DB2 Dialect — extends AbstractSqlDialect
|
|
2
|
+
// Equivalent to org.hibernate.dialect.DB2Dialect (Hibernate ORM 6.4)
|
|
3
|
+
// Driver: npm install ibm_db
|
|
4
|
+
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
5
|
+
import { AbstractSqlDialect } from './abstract-sql.dialect.js';
|
|
6
|
+
// ============================================================
|
|
7
|
+
// Type Mapping — DAL FieldType → DB2 column type
|
|
8
|
+
// ============================================================
|
|
9
|
+
const DB2_TYPE_MAP = {
|
|
10
|
+
string: 'VARCHAR(4000)',
|
|
11
|
+
number: 'DOUBLE',
|
|
12
|
+
boolean: 'BOOLEAN',
|
|
13
|
+
date: 'TIMESTAMP',
|
|
14
|
+
json: 'CLOB',
|
|
15
|
+
array: 'CLOB',
|
|
16
|
+
};
|
|
17
|
+
// ============================================================
|
|
18
|
+
// DB2Dialect
|
|
19
|
+
// ============================================================
|
|
20
|
+
class DB2Dialect extends AbstractSqlDialect {
|
|
21
|
+
dialectType = 'db2';
|
|
22
|
+
conn = null;
|
|
23
|
+
ibmDb = null;
|
|
24
|
+
// --- Abstract implementations ---
|
|
25
|
+
quoteIdentifier(name) {
|
|
26
|
+
return `"${name}"`;
|
|
27
|
+
}
|
|
28
|
+
getPlaceholder(_index) {
|
|
29
|
+
return '?';
|
|
30
|
+
}
|
|
31
|
+
fieldToSqlType(field) {
|
|
32
|
+
return DB2_TYPE_MAP[field.type] || 'VARCHAR(4000)';
|
|
33
|
+
}
|
|
34
|
+
getIdColumnType() {
|
|
35
|
+
return 'VARCHAR(36)';
|
|
36
|
+
}
|
|
37
|
+
getTableListQuery() {
|
|
38
|
+
return "SELECT tabname as name FROM syscat.tables WHERE tabschema = CURRENT SCHEMA AND type = 'T'";
|
|
39
|
+
}
|
|
40
|
+
// --- Hooks ---
|
|
41
|
+
// DB2 11.5+ supports IF NOT EXISTS, but we stay safe
|
|
42
|
+
supportsIfNotExists() { return false; }
|
|
43
|
+
supportsReturning() { return false; }
|
|
44
|
+
serializeBoolean(v) { return v; }
|
|
45
|
+
deserializeBoolean(v) {
|
|
46
|
+
return v === true || v === 1 || v === '1' || v === 'true';
|
|
47
|
+
}
|
|
48
|
+
/** DB2 LIKE is case-sensitive — use UPPER() for case-insensitive search */
|
|
49
|
+
buildRegexCondition(col, flags) {
|
|
50
|
+
if (flags?.includes('i')) {
|
|
51
|
+
return `UPPER(${col}) LIKE UPPER(${this.nextPlaceholder()})`;
|
|
52
|
+
}
|
|
53
|
+
return `${col} LIKE ${this.nextPlaceholder()}`;
|
|
54
|
+
}
|
|
55
|
+
// DB2 uses FETCH FIRST n ROWS ONLY
|
|
56
|
+
buildLimitOffset(options) {
|
|
57
|
+
if (!options?.limit && !options?.skip)
|
|
58
|
+
return '';
|
|
59
|
+
let sql = '';
|
|
60
|
+
if (options.skip)
|
|
61
|
+
sql += ` OFFSET ${options.skip} ROWS`;
|
|
62
|
+
if (options.limit)
|
|
63
|
+
sql += ` FETCH FIRST ${options.limit} ROWS ONLY`;
|
|
64
|
+
return sql;
|
|
65
|
+
}
|
|
66
|
+
// DB2: no IF NOT EXISTS, wrap with existence check
|
|
67
|
+
getCreateTablePrefix(tableName) {
|
|
68
|
+
return `CREATE TABLE ${this.quoteIdentifier(tableName)}`;
|
|
69
|
+
}
|
|
70
|
+
getCreateIndexPrefix(indexName, unique) {
|
|
71
|
+
const u = unique ? 'UNIQUE ' : '';
|
|
72
|
+
return `CREATE ${u}INDEX ${this.quoteIdentifier(indexName)}`;
|
|
73
|
+
}
|
|
74
|
+
// --- Connection ---
|
|
75
|
+
async doConnect(config) {
|
|
76
|
+
try {
|
|
77
|
+
this.ibmDb = await import(/* webpackIgnore: true */ 'ibm_db');
|
|
78
|
+
const open = this.ibmDb.open
|
|
79
|
+
|| this.ibmDb.default.open;
|
|
80
|
+
this.conn = await open(config.uri);
|
|
81
|
+
}
|
|
82
|
+
catch (e) {
|
|
83
|
+
throw new Error(`IBM DB2 driver not found. Install it: npm install ibm_db\n` +
|
|
84
|
+
`Original error: ${e instanceof Error ? e.message : String(e)}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async doDisconnect() {
|
|
88
|
+
if (this.conn) {
|
|
89
|
+
await this.conn.close();
|
|
90
|
+
this.conn = null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async doTestConnection() {
|
|
94
|
+
if (!this.conn)
|
|
95
|
+
return false;
|
|
96
|
+
const rows = await this.conn.query('SELECT 1 FROM SYSIBM.SYSDUMMY1');
|
|
97
|
+
return Array.isArray(rows);
|
|
98
|
+
}
|
|
99
|
+
// --- Query execution ---
|
|
100
|
+
async executeQuery(sql, params) {
|
|
101
|
+
if (!this.conn)
|
|
102
|
+
throw new Error('DB2 not connected. Call connect() first.');
|
|
103
|
+
return new Promise((resolve, reject) => {
|
|
104
|
+
this.conn.query(sql, params, (err, rows) => {
|
|
105
|
+
if (err)
|
|
106
|
+
reject(err);
|
|
107
|
+
else
|
|
108
|
+
resolve(rows ?? []);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
async executeRun(sql, params) {
|
|
113
|
+
if (!this.conn)
|
|
114
|
+
throw new Error('DB2 not connected. Call connect() first.');
|
|
115
|
+
return new Promise((resolve, reject) => {
|
|
116
|
+
this.conn.query(sql, params, (err) => {
|
|
117
|
+
if (err)
|
|
118
|
+
reject(err);
|
|
119
|
+
else
|
|
120
|
+
resolve({ changes: 0 }); // ibm_db doesn't directly return affected rows from query
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
// Override initSchema to handle DB2's lack of IF NOT EXISTS
|
|
125
|
+
async initSchema(schemas) {
|
|
126
|
+
this.schemas = schemas;
|
|
127
|
+
const strategy = this.config?.schemaStrategy ?? 'none';
|
|
128
|
+
this.log('INIT_SCHEMA', `strategy=${strategy}`, { entities: schemas.map(s => s.name) });
|
|
129
|
+
if (strategy === 'none')
|
|
130
|
+
return;
|
|
131
|
+
if (strategy === 'validate') {
|
|
132
|
+
for (const schema of schemas) {
|
|
133
|
+
const exists = await this.tableExists(schema.collection);
|
|
134
|
+
if (!exists) {
|
|
135
|
+
throw new Error(`Schema validation failed: table "${schema.collection}" does not exist ` +
|
|
136
|
+
`(entity: ${schema.name}). Set schemaStrategy to "update" or "create".`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
for (const schema of schemas) {
|
|
142
|
+
const exists = await this.tableExists(schema.collection);
|
|
143
|
+
if (!exists) {
|
|
144
|
+
const createSql = this.generateCreateTable(schema);
|
|
145
|
+
this.log('DDL', schema.collection, createSql);
|
|
146
|
+
await this.executeRun(createSql, []);
|
|
147
|
+
}
|
|
148
|
+
const indexStatements = this.generateIndexes(schema);
|
|
149
|
+
for (const stmt of indexStatements) {
|
|
150
|
+
try {
|
|
151
|
+
await this.executeRun(stmt, []);
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
// Index may already exist
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// Junction tables
|
|
159
|
+
for (const schema of schemas) {
|
|
160
|
+
for (const [, rel] of Object.entries(schema.relations)) {
|
|
161
|
+
if (rel.type === 'many-to-many' && rel.through) {
|
|
162
|
+
const exists = await this.tableExists(rel.through);
|
|
163
|
+
if (exists)
|
|
164
|
+
continue;
|
|
165
|
+
const targetSchema = schemas.find(s => s.name === rel.target);
|
|
166
|
+
if (!targetSchema)
|
|
167
|
+
continue;
|
|
168
|
+
const sourceKey = `${schema.name.toLowerCase()}Id`;
|
|
169
|
+
const targetKey = `${rel.target.toLowerCase()}Id`;
|
|
170
|
+
const q = (n) => this.quoteIdentifier(n);
|
|
171
|
+
const idType = this.getIdColumnType();
|
|
172
|
+
const ddl = `CREATE TABLE ${q(rel.through)} (
|
|
173
|
+
${q(sourceKey)} ${idType} NOT NULL,
|
|
174
|
+
${q(targetKey)} ${idType} NOT NULL,
|
|
175
|
+
PRIMARY KEY (${q(sourceKey)}, ${q(targetKey)})
|
|
176
|
+
)`;
|
|
177
|
+
this.log('DDL_JUNCTION', rel.through, ddl);
|
|
178
|
+
await this.executeRun(ddl, []);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
getDialectLabel() { return 'DB2'; }
|
|
184
|
+
}
|
|
185
|
+
// ============================================================
|
|
186
|
+
// Factory export
|
|
187
|
+
// ============================================================
|
|
188
|
+
export function createDialect() {
|
|
189
|
+
return new DB2Dialect();
|
|
190
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// SAP HANA Dialect — extends AbstractSqlDialect
|
|
2
|
+
// Equivalent to org.hibernate.dialect.HANADialect (Hibernate ORM 6.4)
|
|
3
|
+
// Driver: npm install @sap/hana-client
|
|
4
|
+
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
5
|
+
import { AbstractSqlDialect } from './abstract-sql.dialect.js';
|
|
6
|
+
// ============================================================
|
|
7
|
+
// Type Mapping — DAL FieldType → HANA column type
|
|
8
|
+
// ============================================================
|
|
9
|
+
const HANA_TYPE_MAP = {
|
|
10
|
+
string: 'NVARCHAR(5000)',
|
|
11
|
+
number: 'DOUBLE',
|
|
12
|
+
boolean: 'BOOLEAN',
|
|
13
|
+
date: 'TIMESTAMP',
|
|
14
|
+
json: 'NCLOB',
|
|
15
|
+
array: 'NCLOB',
|
|
16
|
+
};
|
|
17
|
+
// ============================================================
|
|
18
|
+
// HANADialect
|
|
19
|
+
// ============================================================
|
|
20
|
+
class HANADialect extends AbstractSqlDialect {
|
|
21
|
+
dialectType = 'hana';
|
|
22
|
+
conn = null;
|
|
23
|
+
hanaClient = null;
|
|
24
|
+
// --- Abstract implementations ---
|
|
25
|
+
quoteIdentifier(name) {
|
|
26
|
+
return `"${name}"`;
|
|
27
|
+
}
|
|
28
|
+
getPlaceholder(_index) {
|
|
29
|
+
return '?';
|
|
30
|
+
}
|
|
31
|
+
fieldToSqlType(field) {
|
|
32
|
+
return HANA_TYPE_MAP[field.type] || 'NVARCHAR(5000)';
|
|
33
|
+
}
|
|
34
|
+
getIdColumnType() {
|
|
35
|
+
return 'NVARCHAR(36)';
|
|
36
|
+
}
|
|
37
|
+
getTableListQuery() {
|
|
38
|
+
return "SELECT table_name as name FROM tables WHERE schema_name = CURRENT_SCHEMA";
|
|
39
|
+
}
|
|
40
|
+
// --- Hooks ---
|
|
41
|
+
// HANA doesn't support IF NOT EXISTS for tables
|
|
42
|
+
supportsIfNotExists() { return false; }
|
|
43
|
+
supportsReturning() { return false; }
|
|
44
|
+
serializeBoolean(v) { return v; }
|
|
45
|
+
deserializeBoolean(v) {
|
|
46
|
+
return v === true || v === 1 || v === '1' || v === 'TRUE' || v === 'true';
|
|
47
|
+
}
|
|
48
|
+
/** HANA LIKE is case-sensitive — use UPPER() for case-insensitive search */
|
|
49
|
+
buildRegexCondition(col, flags) {
|
|
50
|
+
if (flags?.includes('i')) {
|
|
51
|
+
return `UPPER(${col}) LIKE UPPER(${this.nextPlaceholder()})`;
|
|
52
|
+
}
|
|
53
|
+
return `${col} LIKE ${this.nextPlaceholder()}`;
|
|
54
|
+
}
|
|
55
|
+
// HANA supports LIMIT/OFFSET natively
|
|
56
|
+
// (default buildLimitOffset from AbstractSqlDialect works)
|
|
57
|
+
getCreateTablePrefix(tableName) {
|
|
58
|
+
return `CREATE TABLE ${this.quoteIdentifier(tableName)}`;
|
|
59
|
+
}
|
|
60
|
+
getCreateIndexPrefix(indexName, unique) {
|
|
61
|
+
const u = unique ? 'UNIQUE ' : '';
|
|
62
|
+
return `CREATE ${u}INDEX ${this.quoteIdentifier(indexName)}`;
|
|
63
|
+
}
|
|
64
|
+
// --- Connection ---
|
|
65
|
+
async doConnect(config) {
|
|
66
|
+
try {
|
|
67
|
+
this.hanaClient = await import(/* webpackIgnore: true */ '@sap/hana-client');
|
|
68
|
+
const createConnection = this.hanaClient.createConnection
|
|
69
|
+
|| this.hanaClient.default.createConnection;
|
|
70
|
+
this.conn = createConnection();
|
|
71
|
+
await new Promise((resolve, reject) => {
|
|
72
|
+
this.conn.connect(this.parseHanaUri(config.uri), (err) => err ? reject(err) : resolve());
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
catch (e) {
|
|
76
|
+
throw new Error(`SAP HANA driver not found. Install it: npm install @sap/hana-client\n` +
|
|
77
|
+
`Original error: ${e instanceof Error ? e.message : String(e)}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
async doDisconnect() {
|
|
81
|
+
if (this.conn) {
|
|
82
|
+
await new Promise((resolve) => {
|
|
83
|
+
this.conn.disconnect(() => resolve());
|
|
84
|
+
});
|
|
85
|
+
this.conn = null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async doTestConnection() {
|
|
89
|
+
if (!this.conn)
|
|
90
|
+
return false;
|
|
91
|
+
const rows = await this.executeQuery('SELECT 1 FROM DUMMY', []);
|
|
92
|
+
return Array.isArray(rows);
|
|
93
|
+
}
|
|
94
|
+
// --- Query execution ---
|
|
95
|
+
async executeQuery(sql, params) {
|
|
96
|
+
if (!this.conn)
|
|
97
|
+
throw new Error('HANA not connected. Call connect() first.');
|
|
98
|
+
return new Promise((resolve, reject) => {
|
|
99
|
+
this.conn.exec(sql, params, (err, rows) => {
|
|
100
|
+
if (err)
|
|
101
|
+
reject(err);
|
|
102
|
+
else
|
|
103
|
+
resolve(rows ?? []);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
async executeRun(sql, params) {
|
|
108
|
+
if (!this.conn)
|
|
109
|
+
throw new Error('HANA not connected. Call connect() first.');
|
|
110
|
+
return new Promise((resolve, reject) => {
|
|
111
|
+
this.conn.exec(sql, params, (err, affectedRows) => {
|
|
112
|
+
if (err)
|
|
113
|
+
reject(err);
|
|
114
|
+
else
|
|
115
|
+
resolve({ changes: affectedRows ?? 0 });
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
// Override initSchema to handle HANA's lack of IF NOT EXISTS
|
|
120
|
+
async initSchema(schemas) {
|
|
121
|
+
this.schemas = schemas;
|
|
122
|
+
const strategy = this.config?.schemaStrategy ?? 'none';
|
|
123
|
+
this.log('INIT_SCHEMA', `strategy=${strategy}`, { entities: schemas.map(s => s.name) });
|
|
124
|
+
if (strategy === 'none')
|
|
125
|
+
return;
|
|
126
|
+
if (strategy === 'validate') {
|
|
127
|
+
for (const schema of schemas) {
|
|
128
|
+
const exists = await this.tableExists(schema.collection);
|
|
129
|
+
if (!exists) {
|
|
130
|
+
throw new Error(`Schema validation failed: table "${schema.collection}" does not exist ` +
|
|
131
|
+
`(entity: ${schema.name}). Set schemaStrategy to "update" or "create".`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
for (const schema of schemas) {
|
|
137
|
+
const exists = await this.tableExists(schema.collection);
|
|
138
|
+
if (!exists) {
|
|
139
|
+
const createSql = this.generateCreateTable(schema);
|
|
140
|
+
this.log('DDL', schema.collection, createSql);
|
|
141
|
+
await this.executeRun(createSql, []);
|
|
142
|
+
}
|
|
143
|
+
const indexStatements = this.generateIndexes(schema);
|
|
144
|
+
for (const stmt of indexStatements) {
|
|
145
|
+
try {
|
|
146
|
+
await this.executeRun(stmt, []);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
// Index may already exist
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// Junction tables
|
|
154
|
+
for (const schema of schemas) {
|
|
155
|
+
for (const [, rel] of Object.entries(schema.relations)) {
|
|
156
|
+
if (rel.type === 'many-to-many' && rel.through) {
|
|
157
|
+
const exists = await this.tableExists(rel.through);
|
|
158
|
+
if (exists)
|
|
159
|
+
continue;
|
|
160
|
+
const targetSchema = schemas.find(s => s.name === rel.target);
|
|
161
|
+
if (!targetSchema)
|
|
162
|
+
continue;
|
|
163
|
+
const sourceKey = `${schema.name.toLowerCase()}Id`;
|
|
164
|
+
const targetKey = `${rel.target.toLowerCase()}Id`;
|
|
165
|
+
const q = (n) => this.quoteIdentifier(n);
|
|
166
|
+
const idType = this.getIdColumnType();
|
|
167
|
+
const ddl = `CREATE TABLE ${q(rel.through)} (
|
|
168
|
+
${q(sourceKey)} ${idType} NOT NULL,
|
|
169
|
+
${q(targetKey)} ${idType} NOT NULL,
|
|
170
|
+
PRIMARY KEY (${q(sourceKey)}, ${q(targetKey)})
|
|
171
|
+
)`;
|
|
172
|
+
this.log('DDL_JUNCTION', rel.through, ddl);
|
|
173
|
+
await this.executeRun(ddl, []);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
parseHanaUri(uri) {
|
|
179
|
+
try {
|
|
180
|
+
const url = new URL(uri.replace(/^hana:/, 'http:'));
|
|
181
|
+
return {
|
|
182
|
+
serverNode: `${url.hostname || 'localhost'}:${url.port || 30015}`,
|
|
183
|
+
uid: url.username || 'SYSTEM',
|
|
184
|
+
pwd: url.password || '',
|
|
185
|
+
databaseName: url.pathname.replace(/^\//, '') || undefined,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
return { serverNode: 'localhost:30015' };
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
getDialectLabel() { return 'HANA'; }
|
|
193
|
+
}
|
|
194
|
+
// ============================================================
|
|
195
|
+
// Factory export
|
|
196
|
+
// ============================================================
|
|
197
|
+
export function createDialect() {
|
|
198
|
+
return new HANADialect();
|
|
199
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// HyperSQL (HSQLDB) Dialect — extends AbstractSqlDialect
|
|
2
|
+
// Equivalent to org.hibernate.dialect.HSQLDialect (Hibernate ORM 6.4)
|
|
3
|
+
// HSQLDB is a Java database — accessed via HTTP/JDBC bridge or REST API
|
|
4
|
+
// Driver: HTTP fetch (no npm driver — uses Java HTTP API bridge)
|
|
5
|
+
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
6
|
+
import { AbstractSqlDialect } from './abstract-sql.dialect.js';
|
|
7
|
+
// ============================================================
|
|
8
|
+
// Type Mapping — DAL FieldType → HSQLDB column type
|
|
9
|
+
// ============================================================
|
|
10
|
+
const HSQL_TYPE_MAP = {
|
|
11
|
+
string: 'VARCHAR(4000)',
|
|
12
|
+
number: 'DOUBLE',
|
|
13
|
+
boolean: 'BOOLEAN',
|
|
14
|
+
date: 'TIMESTAMP',
|
|
15
|
+
json: 'CLOB',
|
|
16
|
+
array: 'CLOB',
|
|
17
|
+
};
|
|
18
|
+
// ============================================================
|
|
19
|
+
// HSQLDialect
|
|
20
|
+
// ============================================================
|
|
21
|
+
class HSQLDialect extends AbstractSqlDialect {
|
|
22
|
+
dialectType = 'hsqldb';
|
|
23
|
+
baseUrl = '';
|
|
24
|
+
connected = false;
|
|
25
|
+
// --- Abstract implementations ---
|
|
26
|
+
quoteIdentifier(name) {
|
|
27
|
+
return `"${name}"`;
|
|
28
|
+
}
|
|
29
|
+
getPlaceholder(_index) {
|
|
30
|
+
return '?';
|
|
31
|
+
}
|
|
32
|
+
fieldToSqlType(field) {
|
|
33
|
+
return HSQL_TYPE_MAP[field.type] || 'VARCHAR(4000)';
|
|
34
|
+
}
|
|
35
|
+
getIdColumnType() {
|
|
36
|
+
return 'VARCHAR(36)';
|
|
37
|
+
}
|
|
38
|
+
getTableListQuery() {
|
|
39
|
+
return "SELECT table_name as name FROM information_schema.tables WHERE table_schema = 'PUBLIC'";
|
|
40
|
+
}
|
|
41
|
+
// --- Hooks ---
|
|
42
|
+
supportsIfNotExists() { return true; }
|
|
43
|
+
supportsReturning() { return false; }
|
|
44
|
+
serializeBoolean(v) { return v; }
|
|
45
|
+
deserializeBoolean(v) {
|
|
46
|
+
return v === true || v === 1 || v === '1' || v === 'TRUE' || v === 'true';
|
|
47
|
+
}
|
|
48
|
+
// HSQLDB supports LIMIT/OFFSET natively
|
|
49
|
+
// (default buildLimitOffset from AbstractSqlDialect works)
|
|
50
|
+
// --- Connection (HTTP bridge to HSQLDB server) ---
|
|
51
|
+
async doConnect(config) {
|
|
52
|
+
// URI format: http://host:port/dbname or hsqldb://host:port/dbname
|
|
53
|
+
this.baseUrl = config.uri
|
|
54
|
+
.replace(/^hsqldb:\/\//, 'http://')
|
|
55
|
+
.replace(/\/$/, '');
|
|
56
|
+
// Test connectivity
|
|
57
|
+
try {
|
|
58
|
+
await this.httpPost('SELECT 1 FROM INFORMATION_SCHEMA.SYSTEM_USERS', []);
|
|
59
|
+
this.connected = true;
|
|
60
|
+
}
|
|
61
|
+
catch (e) {
|
|
62
|
+
throw new Error(`HSQLDB HTTP bridge not reachable at ${this.baseUrl}.\n` +
|
|
63
|
+
`Ensure the HSQLDB server is running with HTTP API enabled.\n` +
|
|
64
|
+
`Original error: ${e instanceof Error ? e.message : String(e)}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async doDisconnect() {
|
|
68
|
+
this.connected = false;
|
|
69
|
+
this.baseUrl = '';
|
|
70
|
+
}
|
|
71
|
+
async doTestConnection() {
|
|
72
|
+
if (!this.connected)
|
|
73
|
+
return false;
|
|
74
|
+
try {
|
|
75
|
+
await this.httpPost('SELECT 1 FROM INFORMATION_SCHEMA.SYSTEM_USERS', []);
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// --- Query execution via HTTP bridge ---
|
|
83
|
+
async executeQuery(sql, params) {
|
|
84
|
+
if (!this.connected)
|
|
85
|
+
throw new Error('HSQLDB not connected. Call connect() first.');
|
|
86
|
+
return this.httpPost(sql, params);
|
|
87
|
+
}
|
|
88
|
+
async executeRun(sql, params) {
|
|
89
|
+
if (!this.connected)
|
|
90
|
+
throw new Error('HSQLDB not connected. Call connect() first.');
|
|
91
|
+
const result = await this.httpPost(sql, params);
|
|
92
|
+
return { changes: result?.changes ?? 0 };
|
|
93
|
+
}
|
|
94
|
+
/** Send SQL to HSQLDB HTTP bridge */
|
|
95
|
+
async httpPost(sql, params) {
|
|
96
|
+
const response = await fetch(`${this.baseUrl}/query`, {
|
|
97
|
+
method: 'POST',
|
|
98
|
+
headers: { 'Content-Type': 'application/json' },
|
|
99
|
+
body: JSON.stringify({ sql, params }),
|
|
100
|
+
});
|
|
101
|
+
if (!response.ok) {
|
|
102
|
+
const text = await response.text();
|
|
103
|
+
throw new Error(`HSQLDB query failed (${response.status}): ${text}`);
|
|
104
|
+
}
|
|
105
|
+
return response.json();
|
|
106
|
+
}
|
|
107
|
+
getDialectLabel() { return 'HSQLDB'; }
|
|
108
|
+
}
|
|
109
|
+
// ============================================================
|
|
110
|
+
// Factory export
|
|
111
|
+
// ============================================================
|
|
112
|
+
export function createDialect() {
|
|
113
|
+
return new HSQLDialect();
|
|
114
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// MariaDB Dialect — extends MySQLDialect
|
|
2
|
+
// Equivalent to org.hibernate.dialect.MariaDBDialect (Hibernate ORM 6.4)
|
|
3
|
+
// MariaDB extends MySQLDialect — MySQL-compatible with some differences
|
|
4
|
+
// Driver: npm install mariadb (native MariaDB driver, better performance than mysql2)
|
|
5
|
+
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
6
|
+
import { MySQLDialect } from './mysql.dialect.js';
|
|
7
|
+
// ============================================================
|
|
8
|
+
// MariaDBDialect
|
|
9
|
+
// ============================================================
|
|
10
|
+
class MariaDBDialect extends MySQLDialect {
|
|
11
|
+
dialectType = 'mariadb';
|
|
12
|
+
// MariaDB supports RETURNING since 10.5
|
|
13
|
+
supportsReturning() { return true; }
|
|
14
|
+
// Override connection to use native mariadb driver
|
|
15
|
+
async doConnect(config) {
|
|
16
|
+
try {
|
|
17
|
+
const mariadb = await import(/* webpackIgnore: true */ 'mariadb');
|
|
18
|
+
const createPool = mariadb.default?.createPool || mariadb.createPool;
|
|
19
|
+
this.pool = createPool({
|
|
20
|
+
...this.parseUri(config.uri),
|
|
21
|
+
connectionLimit: config.poolSize ?? 10,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// Fallback to mysql2 driver (cross-compatible)
|
|
26
|
+
try {
|
|
27
|
+
await super.doConnect(config);
|
|
28
|
+
}
|
|
29
|
+
catch (e) {
|
|
30
|
+
throw new Error(`MariaDB driver not found. Install it: npm install mariadb\n` +
|
|
31
|
+
`Or use MySQL-compatible driver: npm install mysql2\n` +
|
|
32
|
+
`Original error: ${e instanceof Error ? e.message : String(e)}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// Override executeQuery to handle mariadb driver's different API
|
|
37
|
+
async executeQuery(sql, params) {
|
|
38
|
+
if (!this.pool)
|
|
39
|
+
throw new Error('MariaDB not connected. Call connect() first.');
|
|
40
|
+
try {
|
|
41
|
+
const rows = await this.pool.query(sql, params);
|
|
42
|
+
// mariadb driver returns rows directly (may include meta at the end)
|
|
43
|
+
if (Array.isArray(rows)) {
|
|
44
|
+
return rows.filter((r) => typeof r === 'object' && r !== null && !('affectedRows' in r));
|
|
45
|
+
}
|
|
46
|
+
return rows;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// Fallback: try mysql2-style execute
|
|
50
|
+
return super.executeQuery(sql, params);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async executeRun(sql, params) {
|
|
54
|
+
if (!this.pool)
|
|
55
|
+
throw new Error('MariaDB not connected. Call connect() first.');
|
|
56
|
+
try {
|
|
57
|
+
const result = await this.pool.query(sql, params);
|
|
58
|
+
return { changes: result.affectedRows ?? 0 };
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return super.executeRun(sql, params);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** Parse a MySQL/MariaDB URI into connection options */
|
|
65
|
+
parseUri(uri) {
|
|
66
|
+
try {
|
|
67
|
+
const url = new URL(uri.replace(/^mariadb:/, 'http:').replace(/^mysql:/, 'http:'));
|
|
68
|
+
return {
|
|
69
|
+
host: url.hostname || 'localhost',
|
|
70
|
+
port: url.port ? parseInt(url.port) : 3306,
|
|
71
|
+
user: url.username || undefined,
|
|
72
|
+
password: url.password || undefined,
|
|
73
|
+
database: url.pathname.replace(/^\//, '') || undefined,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return { host: 'localhost', port: 3306 };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
getDialectLabel() { return 'MariaDB'; }
|
|
81
|
+
}
|
|
82
|
+
// ============================================================
|
|
83
|
+
// Factory export
|
|
84
|
+
// ============================================================
|
|
85
|
+
export function createDialect() {
|
|
86
|
+
return new MariaDBDialect();
|
|
87
|
+
}
|