@mlagie/sql-connector 3.1.0 → 3.3.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/index.d.ts +13 -0
- package/index.js +1 -5
- package/package.json +3 -2
- package/src/db/dialects.js +639 -1
- package/src/migration/Migration.js +1 -2
- package/src/migration/SchemaDiffer.js +34 -29
- package/src/migration/SchemaInspector.js +1 -1
- package/src/migration/index.js +0 -3
- package/src/models/Model.js +6 -6
- package/src/utils/buildQuery/buildAggregationField.js +11 -0
- package/src/utils/buildQuery/buildQuery.js +8 -7
- package/src/migration/dialects/Dialect.js +0 -112
- package/src/migration/dialects/MySQLDialect.js +0 -205
- package/src/migration/dialects/PostgreSQLDialect.js +0 -233
- package/src/migration/dialects/index.js +0 -21
- package/src/utils/buildQuery/count.js +0 -11
|
@@ -1,233 +0,0 @@
|
|
|
1
|
-
const { Dialect } = require("./Dialect");
|
|
2
|
-
const { normalizeType } = require("../utils");
|
|
3
|
-
|
|
4
|
-
class PostgreSQLDialect extends Dialect {
|
|
5
|
-
constructor() {
|
|
6
|
-
super("postgres", {
|
|
7
|
-
quote: '"',
|
|
8
|
-
capabilities: {
|
|
9
|
-
transactionalDDL: true,
|
|
10
|
-
renameColumn: true,
|
|
11
|
-
alterColumn: true
|
|
12
|
-
}
|
|
13
|
-
});
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
placeholder(index) {
|
|
17
|
-
return `$${index + 1}`;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
execute(connection, sql, values = []) {
|
|
21
|
-
if (typeof connection.query === "function") {
|
|
22
|
-
return connection.query(sql, values).then(result => result.rows);
|
|
23
|
-
}
|
|
24
|
-
throw new Error("PostgreSQL connection does not expose query().");
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
mapType(field) {
|
|
28
|
-
const rawType = normalizeType(field);
|
|
29
|
-
const type = String(rawType || "").toUpperCase();
|
|
30
|
-
const length = field.length || 255;
|
|
31
|
-
|
|
32
|
-
switch (type) {
|
|
33
|
-
case "STRING":
|
|
34
|
-
case "VARCHAR":
|
|
35
|
-
case "CHARACTER VARYING":
|
|
36
|
-
return `VARCHAR(${length})`;
|
|
37
|
-
case "CHAR":
|
|
38
|
-
case "BPCHAR":
|
|
39
|
-
case "CHARACTER":
|
|
40
|
-
return `CHAR(${length})`;
|
|
41
|
-
case "TEXT":
|
|
42
|
-
return "TEXT";
|
|
43
|
-
case "NUMBER":
|
|
44
|
-
case "INT":
|
|
45
|
-
case "INT4":
|
|
46
|
-
case "INTEGER":
|
|
47
|
-
return "INTEGER";
|
|
48
|
-
case "SMALLINT":
|
|
49
|
-
case "INT2":
|
|
50
|
-
return "SMALLINT";
|
|
51
|
-
case "BIGINT":
|
|
52
|
-
case "INT8":
|
|
53
|
-
return "BIGINT";
|
|
54
|
-
case "DECIMAL":
|
|
55
|
-
case "NUMERIC":
|
|
56
|
-
return "DECIMAL";
|
|
57
|
-
case "FLOAT":
|
|
58
|
-
case "FLOAT4":
|
|
59
|
-
case "REAL":
|
|
60
|
-
return "REAL";
|
|
61
|
-
case "DOUBLE":
|
|
62
|
-
case "FLOAT8":
|
|
63
|
-
case "DOUBLE PRECISION":
|
|
64
|
-
return "DOUBLE PRECISION";
|
|
65
|
-
case "BOOLEAN":
|
|
66
|
-
case "BOOL":
|
|
67
|
-
return "BOOLEAN";
|
|
68
|
-
case "DATE":
|
|
69
|
-
return "DATE";
|
|
70
|
-
case "DATETIME":
|
|
71
|
-
case "TIMESTAMP":
|
|
72
|
-
case "NOW":
|
|
73
|
-
case "CURRENTTIMESTAMP":
|
|
74
|
-
return "TIMESTAMP";
|
|
75
|
-
case "TIMESTAMPTZ":
|
|
76
|
-
case "TIMESTAMP WITH TIME ZONE":
|
|
77
|
-
return "TIMESTAMPTZ";
|
|
78
|
-
case "OBJECT":
|
|
79
|
-
case "JSON":
|
|
80
|
-
case "JSONB":
|
|
81
|
-
return "JSONB";
|
|
82
|
-
case "UUID":
|
|
83
|
-
return "UUID";
|
|
84
|
-
default:
|
|
85
|
-
throw new Error(`Unsupported PostgreSQL type: ${rawType}`);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
defaultSql(value) {
|
|
90
|
-
if (value === undefined) return "";
|
|
91
|
-
if (value === null) return "DEFAULT NULL";
|
|
92
|
-
if (typeof value === "number") return `DEFAULT ${value}`;
|
|
93
|
-
if (typeof value === "boolean") return `DEFAULT ${value ? "TRUE" : "FALSE"}`;
|
|
94
|
-
if (typeof value === "string") {
|
|
95
|
-
if (/^(CURRENT_TIMESTAMP|CURRENT_DATE|CURRENT_TIME)\b/i.test(value)) {
|
|
96
|
-
return `DEFAULT ${value}`;
|
|
97
|
-
}
|
|
98
|
-
return `DEFAULT '${value.replace(/'/g, "''")}'`;
|
|
99
|
-
}
|
|
100
|
-
return `DEFAULT '${String(value).replace(/'/g, "''")}'`;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
columnDefinition(name, field) {
|
|
104
|
-
if (field.primary_key && field.unique) {
|
|
105
|
-
throw new Error(`Field '${name}' cannot be both PRIMARY KEY and UNIQUE.`);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
let sql = `${this.escape(name)} ${this.mapType(field)}`;
|
|
109
|
-
|
|
110
|
-
if (field.required || field.primary_key) sql += " NOT NULL";
|
|
111
|
-
if (field.default !== undefined) sql += ` ${this.defaultSql(field.default)}`;
|
|
112
|
-
if (field.unique) sql += " UNIQUE";
|
|
113
|
-
if (field.primary_key) sql += " PRIMARY KEY";
|
|
114
|
-
if (field.auto_increment) {
|
|
115
|
-
// Explicit auto-increment is represented with identity syntax.
|
|
116
|
-
sql = sql.replace(/\s+(INTEGER|BIGINT|SMALLINT)(?=\s|$)/i, " $1 GENERATED BY DEFAULT AS IDENTITY");
|
|
117
|
-
}
|
|
118
|
-
if (typeof field.customize === "string" && field.customize.trim()) {
|
|
119
|
-
sql += ` ${field.customize.trim()}`;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return sql;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
createTable(table, schema) {
|
|
126
|
-
const columns = [];
|
|
127
|
-
const foreignKeys = [];
|
|
128
|
-
|
|
129
|
-
for (const [name, field] of Object.entries(schema)) {
|
|
130
|
-
columns.push(this.columnDefinition(name, field));
|
|
131
|
-
|
|
132
|
-
if (field.foreignKey) {
|
|
133
|
-
const match = /^([A-Za-z_][A-Za-z0-9_$]*)(?:\s*\(\s*([A-Za-z_][A-Za-z0-9_$]*)\s*\)|\s*\.\s*([A-Za-z_][A-Za-z0-9_$]*)\s*)$/.exec(field.foreignKey);
|
|
134
|
-
if (!match) throw new Error(`Invalid foreign key definition for ${name}.`);
|
|
135
|
-
const refColumn = match[2] || match[3];
|
|
136
|
-
foreignKeys.push(
|
|
137
|
-
`FOREIGN KEY (${this.escape(name)}) REFERENCES ${this.escape(match[1])} (${this.escape(refColumn)})`
|
|
138
|
-
);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return `CREATE TABLE IF NOT EXISTS ${this.escape(table)} (${columns.concat(foreignKeys).join(", ")});`;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
alterColumn(table, name, field) {
|
|
146
|
-
const clauses = [
|
|
147
|
-
`ALTER COLUMN ${this.escape(name)} TYPE ${this.mapType(field)}`
|
|
148
|
-
];
|
|
149
|
-
|
|
150
|
-
if (field.required || field.primary_key) {
|
|
151
|
-
clauses.push(`ALTER COLUMN ${this.escape(name)} SET NOT NULL`);
|
|
152
|
-
} else {
|
|
153
|
-
clauses.push(`ALTER COLUMN ${this.escape(name)} DROP NOT NULL`);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (field.default === undefined) {
|
|
157
|
-
clauses.push(`ALTER COLUMN ${this.escape(name)} DROP DEFAULT`);
|
|
158
|
-
} else {
|
|
159
|
-
clauses.push(`ALTER COLUMN ${this.escape(name)} SET ${this.defaultSql(field.default)}`);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return `ALTER TABLE ${this.escape(table)} ${clauses.join(", ")};`;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
introspectionQueries() {
|
|
166
|
-
return {
|
|
167
|
-
tables: `
|
|
168
|
-
SELECT table_name
|
|
169
|
-
FROM information_schema.tables
|
|
170
|
-
WHERE table_schema = current_schema()
|
|
171
|
-
AND table_type = 'BASE TABLE'
|
|
172
|
-
`,
|
|
173
|
-
columns: `
|
|
174
|
-
SELECT
|
|
175
|
-
column_name,
|
|
176
|
-
data_type,
|
|
177
|
-
udt_name,
|
|
178
|
-
is_nullable,
|
|
179
|
-
column_default,
|
|
180
|
-
character_maximum_length
|
|
181
|
-
FROM information_schema.columns
|
|
182
|
-
WHERE table_schema = current_schema()
|
|
183
|
-
AND table_name = $1
|
|
184
|
-
ORDER BY ordinal_position
|
|
185
|
-
`
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
normalizeDatabaseColumn(row) {
|
|
190
|
-
const type = String(row.udt_name || row.data_type || "").toLowerCase();
|
|
191
|
-
const isSerial = typeof row.column_default === "string" &&
|
|
192
|
-
row.column_default.includes("nextval(");
|
|
193
|
-
|
|
194
|
-
return {
|
|
195
|
-
type,
|
|
196
|
-
length: row.character_maximum_length ?? undefined,
|
|
197
|
-
required: row.is_nullable === "NO",
|
|
198
|
-
primary_key: false,
|
|
199
|
-
unique: false,
|
|
200
|
-
auto_increment: isSerial,
|
|
201
|
-
default: row.column_default
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
createMigrationTable() {
|
|
206
|
-
return `
|
|
207
|
-
CREATE TABLE IF NOT EXISTS ${this.escape("sql_connector_migrations")} (
|
|
208
|
-
${this.escape("id")} VARCHAR(255) NOT NULL PRIMARY KEY,
|
|
209
|
-
${this.escape("checksum")} VARCHAR(64) NOT NULL,
|
|
210
|
-
${this.escape("applied_at")} TIMESTAMPTZ NOT NULL,
|
|
211
|
-
${this.escape("created_at")} TIMESTAMPTZ NOT NULL
|
|
212
|
-
);
|
|
213
|
-
`;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
insertMigration() {
|
|
217
|
-
return `
|
|
218
|
-
INSERT INTO ${this.escape("sql_connector_migrations")}
|
|
219
|
-
(${this.escape("id")}, ${this.escape("checksum")}, ${this.escape("applied_at")}, ${this.escape("created_at")})
|
|
220
|
-
VALUES ($1, $2, $3, $4)
|
|
221
|
-
`;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
selectMigrations() {
|
|
225
|
-
return `SELECT ${this.escape("id")} AS id, ${this.escape("checksum")} AS checksum, ${this.escape("applied_at")} AS applied_at FROM ${this.escape("sql_connector_migrations")} ORDER BY ${this.escape("id")}`;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
deleteMigration() {
|
|
229
|
-
return `DELETE FROM ${this.escape("sql_connector_migrations")} WHERE ${this.escape("id")} = $1`;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
module.exports = { PostgreSQLDialect };
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
const { MySQLDialect } = require("./MySQLDialect");
|
|
2
|
-
const { PostgreSQLDialect } = require("./PostgreSQLDialect");
|
|
3
|
-
|
|
4
|
-
function createDialect(name) {
|
|
5
|
-
switch (String(name || "mysql").toLowerCase()) {
|
|
6
|
-
case "mysql":
|
|
7
|
-
case "mariadb":
|
|
8
|
-
return new MySQLDialect();
|
|
9
|
-
case "postgres":
|
|
10
|
-
case "postgresql":
|
|
11
|
-
return new PostgreSQLDialect();
|
|
12
|
-
default:
|
|
13
|
-
throw new Error(`Unsupported migration dialect: ${name}`);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
module.exports = {
|
|
18
|
-
createDialect,
|
|
19
|
-
MySQLDialect,
|
|
20
|
-
PostgreSQLDialect
|
|
21
|
-
};
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
const { getDialect } = require("../../db/dialects");
|
|
2
|
-
|
|
3
|
-
module.exports = (field) => {
|
|
4
|
-
if (typeof field === 'string') return `COUNT(${getDialect().escape(field)})`;
|
|
5
|
-
if (field instanceof Array && field !== null) return field.map(col => `COUNT(${getDialect().escape(col)})`).join(' + ');
|
|
6
|
-
if (field instanceof Object) {
|
|
7
|
-
const [key, value] = Object.entries(field)[0];
|
|
8
|
-
return `COUNT(CASE WHEN ${getDialect().escape(key)} = ${getDialect().escapeValue(value)} THEN 1 END)`;
|
|
9
|
-
}
|
|
10
|
-
throw new Error("Invalid field type for COUNT. Must be a string, array, or object.");
|
|
11
|
-
}
|