@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.
@@ -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 = `DATE_FORMAT(${getDialect().escape(col)}, ${getDialect().escapeValue(format)})`;
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 = `DATE_FORMAT(${getDialect().escape(col)}, ${getDialect().escapeValue(format)})`;
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
- function buildWhere(where, values, offset = 0) {
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) => getDialect().getPlaceholder(offset + values.length + 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(`${getDialect().escape(key)} IS ${operator === "!=" ? "NOT " : ""}NULL`);
144
+ conditions.push(`${dialect.escape(key)} IS ${operator === "!=" ? "NOT " : ""}NULL`);
118
145
  } else {
119
- conditions.push(`${getDialect().escape(key)} ${operator} ${getDialect().getPlaceholder(offset + values.length)}`);
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
- throw new Error("Raw string HAVING clauses are not allowed. Use a structured filter instead.");
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
@@ -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"