@mlagie/sql-connector 3.0.3 → 3.2.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 +35 -9
- package/package.json +9 -7
- package/src/db/dialects.js +640 -2
- package/src/migration/BackupManager.js +42 -0
- package/src/migration/Migration.js +162 -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/errors.js +28 -0
- package/src/migration/index.js +15 -0
- package/src/migration/utils.js +89 -0
- package/src/models/Model.js +13 -7
- package/src/models/Schema.js +20 -20
- package/src/utils/buildQuery/buildQuery.js +52 -13
- package/src/utils/sqlTypeMap.js +8 -0
|
@@ -10,7 +10,7 @@ function buildGroupByItem(group) {
|
|
|
10
10
|
if (typeof group === 'object') {
|
|
11
11
|
if (group.dateFormat) {
|
|
12
12
|
const [col, format] = group.dateFormat;
|
|
13
|
-
sql =
|
|
13
|
+
sql = getDialect().dateFormat(getDialect().escape(col), format);
|
|
14
14
|
}
|
|
15
15
|
if (group.col) {
|
|
16
16
|
sql = getDialect().escape(group.col);
|
|
@@ -36,7 +36,7 @@ function buildField(field) {
|
|
|
36
36
|
sql = `SUM(${getDialect().escape(field.sum)})`;
|
|
37
37
|
else if (field.dateFormat) {
|
|
38
38
|
const [col, format] = field.dateFormat;
|
|
39
|
-
sql =
|
|
39
|
+
sql = getDialect().dateFormat(getDialect().escape(col), format);
|
|
40
40
|
}
|
|
41
41
|
else if (field.col)
|
|
42
42
|
sql = getDialect().escape(field.col);
|
|
@@ -61,7 +61,13 @@ 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
|
+
return dialect.caseInsensitiveLike(dialect.escape(key), placeholder, negate);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function buildWhere(where, values, offset) {
|
|
65
71
|
const conditions = [];
|
|
66
72
|
|
|
67
73
|
for (const [key, value] of Object.entries(where)) {
|
|
@@ -95,28 +101,49 @@ function buildWhere(where, values, offset = 0) {
|
|
|
95
101
|
continue;
|
|
96
102
|
}
|
|
97
103
|
|
|
104
|
+
if (key === "NOT") {
|
|
105
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
106
|
+
throw new Error("NOT condition must be an object");
|
|
107
|
+
}
|
|
108
|
+
const clause = buildWhere(value, values, offset);
|
|
109
|
+
if (clause) conditions.push(`NOT (${clause})`);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
|
|
98
113
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
99
114
|
const operators = Object.keys(value);
|
|
100
115
|
|
|
101
|
-
const isOperatorObject = operators.some((op) =>
|
|
102
|
-
["=", "!=", ">", "<", ">=", "<=", "LIKE", "IN", "NOT IN"].includes(op)
|
|
103
|
-
);
|
|
116
|
+
const isOperatorObject = operators.some((op) => COMPARISON_OPERATORS.includes(op));
|
|
104
117
|
|
|
105
118
|
if (isOperatorObject) {
|
|
119
|
+
const dialect = getDialect();
|
|
120
|
+
|
|
106
121
|
for (const [operator, operatorValue] of Object.entries(value)) {
|
|
107
122
|
if (operator === "IN" || operator === "NOT IN") {
|
|
108
123
|
const placeholders = operatorValue
|
|
109
|
-
.map((_, i) =>
|
|
124
|
+
.map((_, i) => dialect.getPlaceholder(offset + values.length + i))
|
|
110
125
|
.join(",");
|
|
111
126
|
|
|
112
|
-
conditions.push(
|
|
113
|
-
`${getDialect().escape(key)} ${operator} (${placeholders})`
|
|
114
|
-
);
|
|
127
|
+
conditions.push(`${dialect.escape(key)} ${operator} (${placeholders})`);
|
|
115
128
|
values.push(...operatorValue);
|
|
129
|
+
} else if (operator === "BETWEEN" || operator === "NOT BETWEEN") {
|
|
130
|
+
const [min, max] = operatorValue;
|
|
131
|
+
const lowPlaceholder = dialect.getPlaceholder(offset + values.length);
|
|
132
|
+
const highPlaceholder = dialect.getPlaceholder(offset + values.length + 1);
|
|
133
|
+
|
|
134
|
+
conditions.push(`${dialect.escape(key)} ${operator} ${lowPlaceholder} AND ${highPlaceholder}`);
|
|
135
|
+
values.push(min, max);
|
|
136
|
+
} else if (operator === "ILIKE" || operator === "NOT ILIKE") {
|
|
137
|
+
const placeholder = dialect.getPlaceholder(offset + values.length);
|
|
138
|
+
const negate = operator === "NOT ILIKE";
|
|
139
|
+
const fallback = buildCaseInsensitiveLike(dialect, key, placeholder, negate);
|
|
140
|
+
|
|
141
|
+
conditions.push(fallback);
|
|
142
|
+
values.push(operatorValue);
|
|
116
143
|
} else if (operatorValue === null && (operator === "=" || operator === "!=")) {
|
|
117
|
-
conditions.push(`${
|
|
144
|
+
conditions.push(`${dialect.escape(key)} IS ${operator === "!=" ? "NOT " : ""}NULL`);
|
|
118
145
|
} else {
|
|
119
|
-
conditions.push(`${
|
|
146
|
+
conditions.push(`${dialect.escape(key)} ${operator} ${dialect.getPlaceholder(offset + values.length)}`);
|
|
120
147
|
values.push(operatorValue);
|
|
121
148
|
}
|
|
122
149
|
}
|
|
@@ -161,7 +188,10 @@ function buildQueryParts(options, valueOffset = 0) {
|
|
|
161
188
|
}
|
|
162
189
|
|
|
163
190
|
if (options.having) {
|
|
164
|
-
|
|
191
|
+
if (typeof options.having !== 'object' || Array.isArray(options.having)) {
|
|
192
|
+
throw new Error("Raw string HAVING clauses are not allowed. Use a structured filter instead.");
|
|
193
|
+
}
|
|
194
|
+
parts.push(`HAVING ${buildWhere(options.having, values, valueOffset)}`);
|
|
165
195
|
}
|
|
166
196
|
|
|
167
197
|
if (options.orderBy) {
|
|
@@ -182,6 +212,15 @@ function buildQueryParts(options, valueOffset = 0) {
|
|
|
182
212
|
values.push(options.limit);
|
|
183
213
|
}
|
|
184
214
|
|
|
215
|
+
if (options.offset !== undefined) {
|
|
216
|
+
if (!Number.isInteger(options.offset) || options.offset < 0) {
|
|
217
|
+
throw new Error("Invalid OFFSET value");
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
parts.push(`OFFSET ${getDialect().getPlaceholder(valueOffset + values.length)}`);
|
|
221
|
+
values.push(options.offset);
|
|
222
|
+
}
|
|
223
|
+
|
|
185
224
|
return {
|
|
186
225
|
sql: parts.join('\n\n'),
|
|
187
226
|
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"
|