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