@mlagie/sql-connector 3.0.3 → 3.1.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/README.md +985 -316
- package/docs/API.md +240 -0
- package/docs/DIALECTS.md +56 -0
- package/docs/MIGRATIONS.md +277 -0
- package/docs/QUERY.md +281 -0
- package/docs/README.md +20 -0
- package/docs/SCHEMA.md +167 -0
- package/docs/fr/README_FR.md +549 -305
- package/index.d.ts +2002 -137
- package/index.js +39 -9
- package/package.json +7 -6
- package/src/db/dialects.js +1 -1
- package/src/migration/BackupManager.js +42 -0
- package/src/migration/Migration.js +163 -0
- package/src/migration/MigrationPlan.js +62 -0
- package/src/migration/MigrationRunner.js +194 -0
- package/src/migration/MigrationStore.js +137 -0
- package/src/migration/SchemaDiffer.js +261 -0
- package/src/migration/SchemaInspector.js +44 -0
- package/src/migration/SchemaSnapshot.js +53 -0
- package/src/migration/crypto.js +79 -0
- package/src/migration/dialects/Dialect.js +112 -0
- package/src/migration/dialects/MySQLDialect.js +205 -0
- package/src/migration/dialects/PostgreSQLDialect.js +233 -0
- package/src/migration/dialects/index.js +21 -0
- package/src/migration/errors.js +28 -0
- package/src/migration/index.js +18 -0
- package/src/migration/utils.js +89 -0
- package/src/models/Model.js +8 -2
- package/src/models/Schema.js +20 -20
- package/src/utils/buildQuery/buildQuery.js +51 -11
- package/src/utils/sqlTypeMap.js +8 -0
|
@@ -61,7 +61,14 @@ function buildSelect(select = []) {
|
|
|
61
61
|
.join(',\n');
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
const COMPARISON_OPERATORS = ["=", "!=", ">", "<", ">=", "<=", "LIKE", "NOT LIKE", "ILIKE", "NOT ILIKE", "IN", "NOT IN", "BETWEEN", "NOT BETWEEN"];
|
|
65
|
+
|
|
66
|
+
function buildCaseInsensitiveLike(dialect, key, placeholder, negate) {
|
|
67
|
+
if (dialect.name === "postgres") return null;
|
|
68
|
+
return `${negate ? "NOT " : ""}LOWER(${dialect.escape(key)}) LIKE LOWER(${placeholder})`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function buildWhere(where, values, offset) {
|
|
65
72
|
const conditions = [];
|
|
66
73
|
|
|
67
74
|
for (const [key, value] of Object.entries(where)) {
|
|
@@ -95,28 +102,49 @@ function buildWhere(where, values, offset = 0) {
|
|
|
95
102
|
continue;
|
|
96
103
|
}
|
|
97
104
|
|
|
105
|
+
if (key === "NOT") {
|
|
106
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
107
|
+
throw new Error("NOT condition must be an object");
|
|
108
|
+
}
|
|
109
|
+
const clause = buildWhere(value, values, offset);
|
|
110
|
+
if (clause) conditions.push(`NOT (${clause})`);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
|
|
98
114
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
99
115
|
const operators = Object.keys(value);
|
|
100
116
|
|
|
101
|
-
const isOperatorObject = operators.some((op) =>
|
|
102
|
-
["=", "!=", ">", "<", ">=", "<=", "LIKE", "IN", "NOT IN"].includes(op)
|
|
103
|
-
);
|
|
117
|
+
const isOperatorObject = operators.some((op) => COMPARISON_OPERATORS.includes(op));
|
|
104
118
|
|
|
105
119
|
if (isOperatorObject) {
|
|
120
|
+
const dialect = getDialect();
|
|
121
|
+
|
|
106
122
|
for (const [operator, operatorValue] of Object.entries(value)) {
|
|
107
123
|
if (operator === "IN" || operator === "NOT IN") {
|
|
108
124
|
const placeholders = operatorValue
|
|
109
|
-
.map((_, i) =>
|
|
125
|
+
.map((_, i) => dialect.getPlaceholder(offset + values.length + i))
|
|
110
126
|
.join(",");
|
|
111
127
|
|
|
112
|
-
conditions.push(
|
|
113
|
-
`${getDialect().escape(key)} ${operator} (${placeholders})`
|
|
114
|
-
);
|
|
128
|
+
conditions.push(`${dialect.escape(key)} ${operator} (${placeholders})`);
|
|
115
129
|
values.push(...operatorValue);
|
|
130
|
+
} else if (operator === "BETWEEN" || operator === "NOT BETWEEN") {
|
|
131
|
+
const [min, max] = operatorValue;
|
|
132
|
+
const lowPlaceholder = dialect.getPlaceholder(offset + values.length);
|
|
133
|
+
const highPlaceholder = dialect.getPlaceholder(offset + values.length + 1);
|
|
134
|
+
|
|
135
|
+
conditions.push(`${dialect.escape(key)} ${operator} ${lowPlaceholder} AND ${highPlaceholder}`);
|
|
136
|
+
values.push(min, max);
|
|
137
|
+
} else if (operator === "ILIKE" || operator === "NOT ILIKE") {
|
|
138
|
+
const placeholder = dialect.getPlaceholder(offset + values.length);
|
|
139
|
+
const negate = operator === "NOT ILIKE";
|
|
140
|
+
const fallback = buildCaseInsensitiveLike(dialect, key, placeholder, negate);
|
|
141
|
+
|
|
142
|
+
conditions.push(fallback ?? `${dialect.escape(key)} ${operator} ${placeholder}`);
|
|
143
|
+
values.push(operatorValue);
|
|
116
144
|
} else if (operatorValue === null && (operator === "=" || operator === "!=")) {
|
|
117
|
-
conditions.push(`${
|
|
145
|
+
conditions.push(`${dialect.escape(key)} IS ${operator === "!=" ? "NOT " : ""}NULL`);
|
|
118
146
|
} else {
|
|
119
|
-
conditions.push(`${
|
|
147
|
+
conditions.push(`${dialect.escape(key)} ${operator} ${dialect.getPlaceholder(offset + values.length)}`);
|
|
120
148
|
values.push(operatorValue);
|
|
121
149
|
}
|
|
122
150
|
}
|
|
@@ -161,7 +189,10 @@ function buildQueryParts(options, valueOffset = 0) {
|
|
|
161
189
|
}
|
|
162
190
|
|
|
163
191
|
if (options.having) {
|
|
164
|
-
|
|
192
|
+
if (typeof options.having !== 'object' || Array.isArray(options.having)) {
|
|
193
|
+
throw new Error("Raw string HAVING clauses are not allowed. Use a structured filter instead.");
|
|
194
|
+
}
|
|
195
|
+
parts.push(`HAVING ${buildWhere(options.having, values, valueOffset)}`);
|
|
165
196
|
}
|
|
166
197
|
|
|
167
198
|
if (options.orderBy) {
|
|
@@ -182,6 +213,15 @@ function buildQueryParts(options, valueOffset = 0) {
|
|
|
182
213
|
values.push(options.limit);
|
|
183
214
|
}
|
|
184
215
|
|
|
216
|
+
if (options.offset !== undefined) {
|
|
217
|
+
if (!Number.isInteger(options.offset) || options.offset < 0) {
|
|
218
|
+
throw new Error("Invalid OFFSET value");
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
parts.push(`OFFSET ${getDialect().getPlaceholder(valueOffset + values.length)}`);
|
|
222
|
+
values.push(options.offset);
|
|
223
|
+
}
|
|
224
|
+
|
|
185
225
|
return {
|
|
186
226
|
sql: parts.join('\n\n'),
|
|
187
227
|
values: values
|
package/src/utils/sqlTypeMap.js
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
const sqlTypeMap = {
|
|
2
2
|
String: 'VARCHAR',
|
|
3
|
+
Char: 'CHAR',
|
|
3
4
|
Number: 'INT',
|
|
5
|
+
SmallInt: 'SMALLINT',
|
|
6
|
+
BigInt: 'BIGINT',
|
|
7
|
+
Decimal: 'DECIMAL',
|
|
4
8
|
Boolean: 'BOOLEAN',
|
|
5
9
|
Date: 'DATETIME',
|
|
6
10
|
Object: 'JSON',
|
|
7
11
|
Array: 'VARCHAR',
|
|
8
12
|
Now: 'NOW()',
|
|
9
13
|
Float: 'FLOAT',
|
|
14
|
+
Double: 'DOUBLE',
|
|
10
15
|
Text: 'TEXT',
|
|
16
|
+
Blob: 'BLOB',
|
|
17
|
+
Binary: 'VARBINARY',
|
|
18
|
+
Uuid: 'CHAR',
|
|
11
19
|
DateTime: "DATETIME",
|
|
12
20
|
Timestamp: "TIMESTAMP",
|
|
13
21
|
CurrentTimestamp: "CURRENT_TIMESTAMP"
|