@ronin/compiler 0.2.2 → 0.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/dist/index.js +296 -204
- package/package.json +1 -1
package/dist/index.js
CHANGED
@@ -1,8 +1,15 @@
|
|
1
1
|
// src/utils/index.ts
|
2
2
|
import { init as cuid } from "@paralleldrive/cuid2";
|
3
3
|
var RONIN_SCHEMA_SYMBOLS = {
|
4
|
+
// Represents a sub query.
|
4
5
|
QUERY: "__RONIN_QUERY",
|
6
|
+
// Represents the value of a field in a schema.
|
5
7
|
FIELD: "__RONIN_FIELD_",
|
8
|
+
// Represents the old value of a field in a schema. Used for triggers.
|
9
|
+
FIELD_OLD: "__RONIN_FIELD_OLD_",
|
10
|
+
// Represents the new value of a field in a schema. Used for triggers.
|
11
|
+
FIELD_NEW: "__RONIN_FIELD_NEW_",
|
12
|
+
// Represents a value provided to a query preset.
|
6
13
|
VALUE: "__RONIN_VALUE"
|
7
14
|
};
|
8
15
|
var RoninError = class extends Error {
|
@@ -44,15 +51,22 @@ var convertToCamelCase = (str) => {
|
|
44
51
|
return sanitize(str).split(SPLIT_REGEX).map((part, index) => index === 0 ? part.toLowerCase() : capitalize(part)).join("");
|
45
52
|
};
|
46
53
|
var isObject = (value) => value != null && typeof value === "object" && Array.isArray(value) === false;
|
47
|
-
var
|
54
|
+
var findInObject = (obj, pattern, replacer) => {
|
55
|
+
let found = false;
|
48
56
|
for (const key in obj) {
|
49
57
|
const value = obj[key];
|
50
58
|
if (isObject(value)) {
|
51
|
-
|
59
|
+
found = findInObject(value, pattern, replacer);
|
52
60
|
} else if (typeof value === "string" && value.startsWith(pattern)) {
|
53
|
-
|
61
|
+
found = true;
|
62
|
+
if (replacer) {
|
63
|
+
obj[key] = value.replace(pattern, replacer);
|
64
|
+
} else {
|
65
|
+
return found;
|
66
|
+
}
|
54
67
|
}
|
55
68
|
}
|
69
|
+
return found;
|
56
70
|
};
|
57
71
|
var flatten = (obj, prefix = "", res = {}) => {
|
58
72
|
for (const key in obj) {
|
@@ -81,6 +95,209 @@ var splitQuery = (query) => {
|
|
81
95
|
return { queryType, querySchema, queryInstructions };
|
82
96
|
};
|
83
97
|
|
98
|
+
// src/utils/statement.ts
|
99
|
+
var prepareStatementValue = (statementValues, value, bindNull = false) => {
|
100
|
+
if (!bindNull && value === null) return "NULL";
|
101
|
+
let formattedValue = value;
|
102
|
+
if (Array.isArray(value) || isObject(value)) {
|
103
|
+
formattedValue = JSON.stringify(value);
|
104
|
+
} else if (typeof value === "boolean") {
|
105
|
+
formattedValue = value ? 1 : 0;
|
106
|
+
}
|
107
|
+
const index = statementValues.push(formattedValue);
|
108
|
+
return `?${index}`;
|
109
|
+
};
|
110
|
+
var composeFieldValues = (schemas, schema, statementValues, instructionName, value, options) => {
|
111
|
+
const { field: schemaField, fieldSelector: selector } = getFieldFromSchema(
|
112
|
+
schema,
|
113
|
+
options.fieldSlug,
|
114
|
+
instructionName,
|
115
|
+
options.rootTable
|
116
|
+
);
|
117
|
+
const isSubQuery = isObject(value) && Object.hasOwn(value, RONIN_SCHEMA_SYMBOLS.QUERY);
|
118
|
+
const collectStatementValue = options.type !== "fields";
|
119
|
+
let conditionSelector = selector;
|
120
|
+
let conditionValue = value;
|
121
|
+
if (isSubQuery && collectStatementValue) {
|
122
|
+
conditionValue = `(${compileQueryInput(
|
123
|
+
value[RONIN_SCHEMA_SYMBOLS.QUERY],
|
124
|
+
schemas,
|
125
|
+
{ statementValues }
|
126
|
+
).readStatement})`;
|
127
|
+
} else if (typeof value === "string" && value.startsWith(RONIN_SCHEMA_SYMBOLS.FIELD)) {
|
128
|
+
let targetTable = `"${options.rootTable}"`;
|
129
|
+
let toReplace = RONIN_SCHEMA_SYMBOLS.FIELD;
|
130
|
+
if (value.startsWith(RONIN_SCHEMA_SYMBOLS.FIELD_OLD)) {
|
131
|
+
targetTable = "OLD";
|
132
|
+
toReplace = RONIN_SCHEMA_SYMBOLS.FIELD_OLD;
|
133
|
+
} else if (value.startsWith(RONIN_SCHEMA_SYMBOLS.FIELD_NEW)) {
|
134
|
+
targetTable = "NEW";
|
135
|
+
toReplace = RONIN_SCHEMA_SYMBOLS.FIELD_NEW;
|
136
|
+
}
|
137
|
+
conditionSelector = `${options.customTable ? `"${options.customTable}".` : ""}"${schemaField.slug}"`;
|
138
|
+
conditionValue = `${targetTable}."${value.replace(toReplace, "")}"`;
|
139
|
+
} else if (schemaField.type === "json" && instructionName === "to") {
|
140
|
+
conditionSelector = `"${schemaField.slug}"`;
|
141
|
+
if (collectStatementValue) {
|
142
|
+
const preparedValue = prepareStatementValue(statementValues, value, false);
|
143
|
+
conditionValue = `IIF(${conditionSelector} IS NULL, ${preparedValue}, json_patch(${conditionSelector}, ${preparedValue}))`;
|
144
|
+
}
|
145
|
+
} else if (collectStatementValue) {
|
146
|
+
conditionValue = prepareStatementValue(statementValues, value, false);
|
147
|
+
}
|
148
|
+
if (options.type === "fields") return conditionSelector;
|
149
|
+
if (options.type === "values") return conditionValue;
|
150
|
+
return `${conditionSelector} ${WITH_CONDITIONS[options.condition || "being"](conditionValue, value)}`;
|
151
|
+
};
|
152
|
+
var composeConditions = (schemas, schema, statementValues, instructionName, value, options) => {
|
153
|
+
const isNested = isObject(value) && Object.keys(value).length > 0;
|
154
|
+
if (isNested && Object.keys(value).every((key) => key in WITH_CONDITIONS)) {
|
155
|
+
const conditions = Object.entries(value).map(
|
156
|
+
([conditionType, checkValue]) => composeConditions(schemas, schema, statementValues, instructionName, checkValue, {
|
157
|
+
...options,
|
158
|
+
condition: conditionType
|
159
|
+
})
|
160
|
+
);
|
161
|
+
return conditions.join(" AND ");
|
162
|
+
}
|
163
|
+
if (options.fieldSlug) {
|
164
|
+
const fieldDetails = getFieldFromSchema(
|
165
|
+
schema,
|
166
|
+
options.fieldSlug,
|
167
|
+
instructionName,
|
168
|
+
options.rootTable
|
169
|
+
);
|
170
|
+
const { field: schemaField } = fieldDetails;
|
171
|
+
const consumeJSON = schemaField.type === "json" && instructionName === "to";
|
172
|
+
const isSubQuery = isNested && Object.hasOwn(value, RONIN_SCHEMA_SYMBOLS.QUERY);
|
173
|
+
if (!(isObject(value) || Array.isArray(value)) || isSubQuery || consumeJSON) {
|
174
|
+
return composeFieldValues(
|
175
|
+
schemas,
|
176
|
+
schema,
|
177
|
+
statementValues,
|
178
|
+
instructionName,
|
179
|
+
value,
|
180
|
+
{ ...options, fieldSlug: options.fieldSlug }
|
181
|
+
);
|
182
|
+
}
|
183
|
+
if (schemaField.type === "reference" && isNested) {
|
184
|
+
const keys = Object.keys(value);
|
185
|
+
const values = Object.values(value);
|
186
|
+
let recordTarget;
|
187
|
+
if (keys.length === 1 && keys[0] === "id") {
|
188
|
+
recordTarget = values[0];
|
189
|
+
} else {
|
190
|
+
const relatedSchema = getSchemaBySlug(schemas, schemaField.target.slug);
|
191
|
+
const subQuery = {
|
192
|
+
get: {
|
193
|
+
[relatedSchema.slug]: {
|
194
|
+
with: value,
|
195
|
+
selecting: ["id"]
|
196
|
+
}
|
197
|
+
}
|
198
|
+
};
|
199
|
+
recordTarget = {
|
200
|
+
[RONIN_SCHEMA_SYMBOLS.QUERY]: subQuery
|
201
|
+
};
|
202
|
+
}
|
203
|
+
return composeConditions(
|
204
|
+
schemas,
|
205
|
+
schema,
|
206
|
+
statementValues,
|
207
|
+
instructionName,
|
208
|
+
recordTarget,
|
209
|
+
options
|
210
|
+
);
|
211
|
+
}
|
212
|
+
}
|
213
|
+
if (isNested) {
|
214
|
+
const conditions = Object.entries(value).map(([field, value2]) => {
|
215
|
+
const nestedFieldSlug = options.fieldSlug ? `${options.fieldSlug}.${field}` : field;
|
216
|
+
return composeConditions(schemas, schema, statementValues, instructionName, value2, {
|
217
|
+
...options,
|
218
|
+
fieldSlug: nestedFieldSlug
|
219
|
+
});
|
220
|
+
});
|
221
|
+
const joiner = instructionName === "to" ? ", " : " AND ";
|
222
|
+
if (instructionName === "to") return `${conditions.join(joiner)}`;
|
223
|
+
return conditions.length === 1 ? conditions[0] : options.fieldSlug ? `(${conditions.join(joiner)})` : conditions.join(joiner);
|
224
|
+
}
|
225
|
+
if (Array.isArray(value)) {
|
226
|
+
const conditions = value.map(
|
227
|
+
(filter) => composeConditions(
|
228
|
+
schemas,
|
229
|
+
schema,
|
230
|
+
statementValues,
|
231
|
+
instructionName,
|
232
|
+
filter,
|
233
|
+
options
|
234
|
+
)
|
235
|
+
);
|
236
|
+
return conditions.join(" OR ");
|
237
|
+
}
|
238
|
+
throw new RoninError({
|
239
|
+
message: `The \`with\` instruction must not contain an empty field. The following fields are empty: \`${options.fieldSlug}\`. If you meant to query by an empty field, try using \`null\` instead.`,
|
240
|
+
code: "INVALID_WITH_VALUE",
|
241
|
+
queries: null
|
242
|
+
});
|
243
|
+
};
|
244
|
+
var formatIdentifiers = ({ identifiers }, queryInstructions) => {
|
245
|
+
if (!queryInstructions) return queryInstructions;
|
246
|
+
const type = "with" in queryInstructions ? "with" : null;
|
247
|
+
if (!type) return queryInstructions;
|
248
|
+
const nestedInstructions = queryInstructions[type];
|
249
|
+
if (!nestedInstructions || Array.isArray(nestedInstructions))
|
250
|
+
return queryInstructions;
|
251
|
+
const newNestedInstructions = { ...nestedInstructions };
|
252
|
+
for (const oldKey of Object.keys(newNestedInstructions)) {
|
253
|
+
if (oldKey !== "titleIdentifier" && oldKey !== "slugIdentifier") continue;
|
254
|
+
const identifierName = oldKey === "titleIdentifier" ? "title" : "slug";
|
255
|
+
const value = newNestedInstructions[oldKey];
|
256
|
+
const newKey = identifiers?.[identifierName] || "id";
|
257
|
+
newNestedInstructions[newKey] = value;
|
258
|
+
delete newNestedInstructions[oldKey];
|
259
|
+
}
|
260
|
+
return {
|
261
|
+
...queryInstructions,
|
262
|
+
[type]: newNestedInstructions
|
263
|
+
};
|
264
|
+
};
|
265
|
+
|
266
|
+
// src/instructions/with.ts
|
267
|
+
var getMatcher = (value, negative) => {
|
268
|
+
if (negative) {
|
269
|
+
if (value === null) return "IS NOT";
|
270
|
+
return "!=";
|
271
|
+
}
|
272
|
+
if (value === null) return "IS";
|
273
|
+
return "=";
|
274
|
+
};
|
275
|
+
var WITH_CONDITIONS = {
|
276
|
+
being: (value, baseValue) => `${getMatcher(baseValue, false)} ${value}`,
|
277
|
+
notBeing: (value, baseValue) => `${getMatcher(baseValue, true)} ${value}`,
|
278
|
+
startingWith: (value) => `LIKE ${value}%`,
|
279
|
+
notStartingWith: (value) => `NOT LIKE ${value}%`,
|
280
|
+
endingWith: (value) => `LIKE %${value}`,
|
281
|
+
notEndingWith: (value) => `NOT LIKE %${value}`,
|
282
|
+
containing: (value) => `LIKE %${value}%`,
|
283
|
+
notContaining: (value) => `NOT LIKE %${value}%`,
|
284
|
+
greaterThan: (value) => `> ${value}`,
|
285
|
+
greaterOrEqual: (value) => `>= ${value}`,
|
286
|
+
lessThan: (value) => `< ${value}`,
|
287
|
+
lessOrEqual: (value) => `<= ${value}`
|
288
|
+
};
|
289
|
+
var handleWith = (schemas, schema, statementValues, instruction, rootTable) => {
|
290
|
+
const subStatement = composeConditions(
|
291
|
+
schemas,
|
292
|
+
schema,
|
293
|
+
statementValues,
|
294
|
+
"with",
|
295
|
+
instruction,
|
296
|
+
{ rootTable }
|
297
|
+
);
|
298
|
+
return `(${subStatement})`;
|
299
|
+
};
|
300
|
+
|
84
301
|
// src/utils/schema.ts
|
85
302
|
import title from "title";
|
86
303
|
var getSchemaBySlug = (schemas, slug) => {
|
@@ -101,7 +318,8 @@ var getTableForSchema = (schema) => {
|
|
101
318
|
var composeMetaSchemaSlug = (suffix) => convertToCamelCase(`ronin_${suffix}`);
|
102
319
|
var composeAssociationSchemaSlug = (schema, field) => composeMetaSchemaSlug(`${schema.pluralSlug}_${field.slug}`);
|
103
320
|
var getFieldSelector = (field, fieldPath, rootTable) => {
|
104
|
-
const
|
321
|
+
const symbol = rootTable?.startsWith(RONIN_SCHEMA_SYMBOLS.FIELD) ? `${rootTable.replace(RONIN_SCHEMA_SYMBOLS.FIELD, "").slice(0, -1)}.` : "";
|
322
|
+
const tablePrefix = symbol || (rootTable ? `"${rootTable}".` : "");
|
105
323
|
if (field.type === "json") {
|
106
324
|
const dotParts = fieldPath.split(".");
|
107
325
|
const columnName = tablePrefix + dotParts.shift();
|
@@ -234,6 +452,20 @@ var SYSTEM_SCHEMAS = [
|
|
234
452
|
{ slug: "unique", type: "boolean" },
|
235
453
|
{ slug: "filter", type: "json" }
|
236
454
|
]
|
455
|
+
},
|
456
|
+
{
|
457
|
+
name: "Trigger",
|
458
|
+
pluralName: "Triggers",
|
459
|
+
slug: "trigger",
|
460
|
+
pluralSlug: "triggers",
|
461
|
+
fields: [
|
462
|
+
...SYSTEM_FIELDS,
|
463
|
+
{ slug: "slug", type: "string", required: true },
|
464
|
+
{ slug: "schema", type: "reference", target: { slug: "schema" }, required: true },
|
465
|
+
{ slug: "cause", type: "string", required: true },
|
466
|
+
{ slug: "filter", type: "json" },
|
467
|
+
{ slug: "effects", type: "json", required: true }
|
468
|
+
]
|
237
469
|
}
|
238
470
|
];
|
239
471
|
var SYSTEM_SCHEMA_SLUGS = SYSTEM_SCHEMAS.flatMap(({ slug, pluralSlug }) => [
|
@@ -341,7 +573,7 @@ var getFieldStatement = (field) => {
|
|
341
573
|
}
|
342
574
|
return statement;
|
343
575
|
};
|
344
|
-
var addSchemaQueries = (queryDetails, writeStatements) => {
|
576
|
+
var addSchemaQueries = (schemas, statementValues, queryDetails, writeStatements) => {
|
345
577
|
const { queryType, querySchema, queryInstructions } = queryDetails;
|
346
578
|
if (!["create", "set", "drop"].includes(queryType)) return;
|
347
579
|
if (!SYSTEM_SCHEMA_SLUGS.includes(querySchema)) return;
|
@@ -352,7 +584,9 @@ var addSchemaQueries = (queryDetails, writeStatements) => {
|
|
352
584
|
let queryTypeReadable = null;
|
353
585
|
switch (queryType) {
|
354
586
|
case "create": {
|
355
|
-
if (kind === "schemas" || kind === "indexes"
|
587
|
+
if (kind === "schemas" || kind === "indexes" || kind === "triggers") {
|
588
|
+
tableAction = "CREATE";
|
589
|
+
}
|
356
590
|
queryTypeReadable = "creating";
|
357
591
|
break;
|
358
592
|
}
|
@@ -362,7 +596,9 @@ var addSchemaQueries = (queryDetails, writeStatements) => {
|
|
362
596
|
break;
|
363
597
|
}
|
364
598
|
case "drop": {
|
365
|
-
if (kind === "schemas" || kind === "indexes"
|
599
|
+
if (kind === "schemas" || kind === "indexes" || kind === "triggers") {
|
600
|
+
tableAction = "DROP";
|
601
|
+
}
|
366
602
|
queryTypeReadable = "deleting";
|
367
603
|
break;
|
368
604
|
}
|
@@ -388,8 +624,58 @@ var addSchemaQueries = (queryDetails, writeStatements) => {
|
|
388
624
|
if (kind === "indexes") {
|
389
625
|
const indexName = convertToSnakeCase(slug);
|
390
626
|
const unique = instructionList?.unique;
|
627
|
+
const filterQuery = instructionList?.filter;
|
391
628
|
let statement2 = `${tableAction}${unique ? " UNIQUE" : ""} INDEX "${indexName}"`;
|
392
|
-
if (queryType === "create")
|
629
|
+
if (queryType === "create") {
|
630
|
+
statement2 += ` ON "${tableName}"`;
|
631
|
+
if (filterQuery) {
|
632
|
+
const targetSchema = getSchemaBySlug(schemas, schemaSlug);
|
633
|
+
const withStatement = handleWith(
|
634
|
+
schemas,
|
635
|
+
targetSchema,
|
636
|
+
statementValues,
|
637
|
+
filterQuery
|
638
|
+
);
|
639
|
+
statement2 += ` WHERE (${withStatement})`;
|
640
|
+
}
|
641
|
+
}
|
642
|
+
writeStatements.push(statement2);
|
643
|
+
return;
|
644
|
+
}
|
645
|
+
if (kind === "triggers") {
|
646
|
+
const triggerName = convertToSnakeCase(slug);
|
647
|
+
let statement2 = `${tableAction} TRIGGER "${triggerName}"`;
|
648
|
+
if (queryType === "create") {
|
649
|
+
const cause = slugToName(instructionList?.cause).toUpperCase();
|
650
|
+
const statementParts = [cause, "ON", `"${tableName}"`];
|
651
|
+
const effectQueries = instructionList?.effects;
|
652
|
+
const filterQuery = instructionList?.filter;
|
653
|
+
if (filterQuery || effectQueries.some((query) => findInObject(query, RONIN_SCHEMA_SYMBOLS.FIELD))) {
|
654
|
+
statementParts.push("FOR EACH ROW");
|
655
|
+
}
|
656
|
+
if (filterQuery) {
|
657
|
+
const targetSchema = getSchemaBySlug(schemas, schemaSlug);
|
658
|
+
const tablePlaceholder = cause.endsWith("DELETE") ? RONIN_SCHEMA_SYMBOLS.FIELD_OLD : RONIN_SCHEMA_SYMBOLS.FIELD_NEW;
|
659
|
+
const withStatement = handleWith(
|
660
|
+
schemas,
|
661
|
+
targetSchema,
|
662
|
+
statementValues,
|
663
|
+
filterQuery,
|
664
|
+
tablePlaceholder
|
665
|
+
);
|
666
|
+
statementParts.push("WHEN", `(${withStatement})`);
|
667
|
+
}
|
668
|
+
const effectStatements = effectQueries.map((effectQuery) => {
|
669
|
+
return compileQueryInput(effectQuery, schemas, {
|
670
|
+
statementValues,
|
671
|
+
disableReturning: true
|
672
|
+
}).readStatement;
|
673
|
+
});
|
674
|
+
if (effectStatements.length > 1) statementParts.push("BEGIN");
|
675
|
+
statementParts.push(effectStatements.join("; "));
|
676
|
+
if (effectStatements.length > 1) statementParts.push("END");
|
677
|
+
statement2 += ` ${statementParts.join(" ")}`;
|
678
|
+
}
|
393
679
|
writeStatements.push(statement2);
|
394
680
|
return;
|
395
681
|
}
|
@@ -447,200 +733,6 @@ var pluralize = (word) => {
|
|
447
733
|
return `${word}s`;
|
448
734
|
};
|
449
735
|
|
450
|
-
// src/instructions/with.ts
|
451
|
-
var getMatcher = (value, negative) => {
|
452
|
-
if (negative) {
|
453
|
-
if (value === null) return "IS NOT";
|
454
|
-
return "!=";
|
455
|
-
}
|
456
|
-
if (value === null) return "IS";
|
457
|
-
return "=";
|
458
|
-
};
|
459
|
-
var WITH_CONDITIONS = {
|
460
|
-
being: (value, baseValue) => `${getMatcher(baseValue, false)} ${value}`,
|
461
|
-
notBeing: (value, baseValue) => `${getMatcher(baseValue, true)} ${value}`,
|
462
|
-
startingWith: (value) => `LIKE ${value}%`,
|
463
|
-
notStartingWith: (value) => `NOT LIKE ${value}%`,
|
464
|
-
endingWith: (value) => `LIKE %${value}`,
|
465
|
-
notEndingWith: (value) => `NOT LIKE %${value}`,
|
466
|
-
containing: (value) => `LIKE %${value}%`,
|
467
|
-
notContaining: (value) => `NOT LIKE %${value}%`,
|
468
|
-
greaterThan: (value) => `> ${value}`,
|
469
|
-
greaterOrEqual: (value) => `>= ${value}`,
|
470
|
-
lessThan: (value) => `< ${value}`,
|
471
|
-
lessOrEqual: (value) => `<= ${value}`
|
472
|
-
};
|
473
|
-
var handleWith = (schemas, schema, statementValues, instruction, rootTable) => {
|
474
|
-
const subStatement = composeConditions(
|
475
|
-
schemas,
|
476
|
-
schema,
|
477
|
-
statementValues,
|
478
|
-
"with",
|
479
|
-
instruction,
|
480
|
-
{ rootTable }
|
481
|
-
);
|
482
|
-
return `(${subStatement})`;
|
483
|
-
};
|
484
|
-
|
485
|
-
// src/utils/statement.ts
|
486
|
-
var prepareStatementValue = (statementValues, value, bindNull = false) => {
|
487
|
-
if (!bindNull && value === null) return "NULL";
|
488
|
-
let formattedValue = value;
|
489
|
-
if (Array.isArray(value) || isObject(value)) {
|
490
|
-
formattedValue = JSON.stringify(value);
|
491
|
-
} else if (typeof value === "boolean") {
|
492
|
-
formattedValue = value ? 1 : 0;
|
493
|
-
}
|
494
|
-
const index = statementValues.push(formattedValue);
|
495
|
-
return `?${index}`;
|
496
|
-
};
|
497
|
-
var composeFieldValues = (schemas, schema, statementValues, instructionName, value, options) => {
|
498
|
-
const { field: schemaField, fieldSelector: selector } = getFieldFromSchema(
|
499
|
-
schema,
|
500
|
-
options.fieldSlug,
|
501
|
-
instructionName,
|
502
|
-
options.rootTable
|
503
|
-
);
|
504
|
-
const isSubQuery = isObject(value) && Object.hasOwn(value, RONIN_SCHEMA_SYMBOLS.QUERY);
|
505
|
-
const collectStatementValue = options.type !== "fields";
|
506
|
-
let conditionSelector = selector;
|
507
|
-
let conditionValue = value;
|
508
|
-
if (isSubQuery && collectStatementValue) {
|
509
|
-
conditionValue = `(${compileQueryInput(
|
510
|
-
value[RONIN_SCHEMA_SYMBOLS.QUERY],
|
511
|
-
schemas,
|
512
|
-
{ statementValues }
|
513
|
-
).readStatement})`;
|
514
|
-
} else if (typeof value === "string" && value.startsWith(RONIN_SCHEMA_SYMBOLS.FIELD)) {
|
515
|
-
conditionSelector = `"${options.customTable}"."${schemaField.slug}"`;
|
516
|
-
conditionValue = `"${options.rootTable}"."${value.replace(RONIN_SCHEMA_SYMBOLS.FIELD, "")}"`;
|
517
|
-
} else if (schemaField.type === "json" && instructionName === "to") {
|
518
|
-
conditionSelector = `"${schemaField.slug}"`;
|
519
|
-
if (collectStatementValue) {
|
520
|
-
const preparedValue = prepareStatementValue(statementValues, value, false);
|
521
|
-
conditionValue = `IIF(${conditionSelector} IS NULL, ${preparedValue}, json_patch(${conditionSelector}, ${preparedValue}))`;
|
522
|
-
}
|
523
|
-
} else if (collectStatementValue) {
|
524
|
-
conditionValue = prepareStatementValue(statementValues, value, false);
|
525
|
-
}
|
526
|
-
if (options.type === "fields") return conditionSelector;
|
527
|
-
if (options.type === "values") return conditionValue;
|
528
|
-
return `${conditionSelector} ${WITH_CONDITIONS[options.condition || "being"](conditionValue, value)}`;
|
529
|
-
};
|
530
|
-
var composeConditions = (schemas, schema, statementValues, instructionName, value, options) => {
|
531
|
-
const isNested = isObject(value) && Object.keys(value).length > 0;
|
532
|
-
if (isNested && Object.keys(value).every((key) => key in WITH_CONDITIONS)) {
|
533
|
-
const conditions = Object.entries(value).map(
|
534
|
-
([conditionType, checkValue]) => composeConditions(schemas, schema, statementValues, instructionName, checkValue, {
|
535
|
-
...options,
|
536
|
-
condition: conditionType
|
537
|
-
})
|
538
|
-
);
|
539
|
-
return conditions.join(" AND ");
|
540
|
-
}
|
541
|
-
if (options.fieldSlug) {
|
542
|
-
const fieldDetails = getFieldFromSchema(
|
543
|
-
schema,
|
544
|
-
options.fieldSlug,
|
545
|
-
instructionName,
|
546
|
-
options.rootTable
|
547
|
-
);
|
548
|
-
const { field: schemaField } = fieldDetails;
|
549
|
-
const consumeJSON = schemaField.type === "json" && instructionName === "to";
|
550
|
-
const isSubQuery = isNested && Object.hasOwn(value, RONIN_SCHEMA_SYMBOLS.QUERY);
|
551
|
-
if (!(isObject(value) || Array.isArray(value)) || isSubQuery || consumeJSON) {
|
552
|
-
return composeFieldValues(
|
553
|
-
schemas,
|
554
|
-
schema,
|
555
|
-
statementValues,
|
556
|
-
instructionName,
|
557
|
-
value,
|
558
|
-
{ ...options, fieldSlug: options.fieldSlug }
|
559
|
-
);
|
560
|
-
}
|
561
|
-
if (schemaField.type === "reference" && isNested) {
|
562
|
-
const keys = Object.keys(value);
|
563
|
-
const values = Object.values(value);
|
564
|
-
let recordTarget;
|
565
|
-
if (keys.length === 1 && keys[0] === "id") {
|
566
|
-
recordTarget = values[0];
|
567
|
-
} else {
|
568
|
-
const relatedSchema = getSchemaBySlug(schemas, schemaField.target.slug);
|
569
|
-
const subQuery = {
|
570
|
-
get: {
|
571
|
-
[relatedSchema.slug]: {
|
572
|
-
with: value,
|
573
|
-
selecting: ["id"]
|
574
|
-
}
|
575
|
-
}
|
576
|
-
};
|
577
|
-
recordTarget = {
|
578
|
-
[RONIN_SCHEMA_SYMBOLS.QUERY]: subQuery
|
579
|
-
};
|
580
|
-
}
|
581
|
-
return composeConditions(
|
582
|
-
schemas,
|
583
|
-
schema,
|
584
|
-
statementValues,
|
585
|
-
instructionName,
|
586
|
-
recordTarget,
|
587
|
-
options
|
588
|
-
);
|
589
|
-
}
|
590
|
-
}
|
591
|
-
if (isNested) {
|
592
|
-
const conditions = Object.entries(value).map(([field, value2]) => {
|
593
|
-
const nestedFieldSlug = options.fieldSlug ? `${options.fieldSlug}.${field}` : field;
|
594
|
-
return composeConditions(schemas, schema, statementValues, instructionName, value2, {
|
595
|
-
...options,
|
596
|
-
fieldSlug: nestedFieldSlug
|
597
|
-
});
|
598
|
-
});
|
599
|
-
const joiner = instructionName === "to" ? ", " : " AND ";
|
600
|
-
if (instructionName === "to") return `${conditions.join(joiner)}`;
|
601
|
-
return conditions.length === 1 ? conditions[0] : options.fieldSlug ? `(${conditions.join(joiner)})` : conditions.join(joiner);
|
602
|
-
}
|
603
|
-
if (Array.isArray(value)) {
|
604
|
-
const conditions = value.map(
|
605
|
-
(filter) => composeConditions(
|
606
|
-
schemas,
|
607
|
-
schema,
|
608
|
-
statementValues,
|
609
|
-
instructionName,
|
610
|
-
filter,
|
611
|
-
options
|
612
|
-
)
|
613
|
-
);
|
614
|
-
return conditions.join(" OR ");
|
615
|
-
}
|
616
|
-
throw new RoninError({
|
617
|
-
message: `The \`with\` instruction must not contain an empty field. The following fields are empty: \`${options.fieldSlug}\`. If you meant to query by an empty field, try using \`null\` instead.`,
|
618
|
-
code: "INVALID_WITH_VALUE",
|
619
|
-
queries: null
|
620
|
-
});
|
621
|
-
};
|
622
|
-
var formatIdentifiers = ({ identifiers }, queryInstructions) => {
|
623
|
-
if (!queryInstructions) return queryInstructions;
|
624
|
-
const type = "with" in queryInstructions ? "with" : null;
|
625
|
-
if (!type) return queryInstructions;
|
626
|
-
const nestedInstructions = queryInstructions[type];
|
627
|
-
if (!nestedInstructions || Array.isArray(nestedInstructions))
|
628
|
-
return queryInstructions;
|
629
|
-
const newNestedInstructions = { ...nestedInstructions };
|
630
|
-
for (const oldKey of Object.keys(newNestedInstructions)) {
|
631
|
-
if (oldKey !== "titleIdentifier" && oldKey !== "slugIdentifier") continue;
|
632
|
-
const identifierName = oldKey === "titleIdentifier" ? "title" : "slug";
|
633
|
-
const value = newNestedInstructions[oldKey];
|
634
|
-
const newKey = identifiers?.[identifierName] || "id";
|
635
|
-
newNestedInstructions[newKey] = value;
|
636
|
-
delete newNestedInstructions[oldKey];
|
637
|
-
}
|
638
|
-
return {
|
639
|
-
...queryInstructions,
|
640
|
-
[type]: newNestedInstructions
|
641
|
-
};
|
642
|
-
};
|
643
|
-
|
644
736
|
// src/instructions/before-after.ts
|
645
737
|
var CURSOR_SEPARATOR = ",";
|
646
738
|
var CURSOR_NULL_PLACEHOLDER = "RONIN_NULL";
|
@@ -734,7 +826,7 @@ var handleFor = (schemas, schema, statementValues, instruction, rootTable) => {
|
|
734
826
|
});
|
735
827
|
}
|
736
828
|
const replacedForFilter = structuredClone(forFilter);
|
737
|
-
|
829
|
+
findInObject(
|
738
830
|
replacedForFilter,
|
739
831
|
RONIN_SCHEMA_SYMBOLS.VALUE,
|
740
832
|
(match) => match.replace(RONIN_SCHEMA_SYMBOLS.VALUE, args)
|
@@ -999,7 +1091,7 @@ var compileQueryInput = (query, defaultSchemas, options) => {
|
|
999
1091
|
let table = getTableForSchema(schema);
|
1000
1092
|
const statementValues = options?.statementValues || [];
|
1001
1093
|
const writeStatements = [];
|
1002
|
-
addSchemaQueries(parsedQuery, writeStatements);
|
1094
|
+
addSchemaQueries(schemas, statementValues, parsedQuery, writeStatements);
|
1003
1095
|
const columns = handleSelecting(schema, statementValues, {
|
1004
1096
|
selecting: instructions?.selecting,
|
1005
1097
|
including: instructions?.including
|