@ronin/compiler 0.10.3 → 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +8 -5
- package/dist/index.d.ts +47 -5
- package/dist/index.js +221 -143
- package/package.json +1 -1
package/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# RONIN Compiler
|
2
2
|
|
3
|
-
This package compiles [RONIN queries](https://ronin.co/docs/queries) to
|
3
|
+
This package compiles [RONIN queries](https://ronin.co/docs/queries) to [SQLite](https://www.sqlite.org) statements.
|
4
4
|
|
5
5
|
## Setup
|
6
6
|
|
@@ -65,9 +65,11 @@ Once the RONIN queries have been compiled down to SQL statements, the statements
|
|
65
65
|
executed and their results can be formatted by the compiler as well:
|
66
66
|
|
67
67
|
```typescript
|
68
|
-
// `
|
68
|
+
// Passing `rawResults` (rows being of arrays of values) provided by the database (ideal)
|
69
|
+
const results: Array<Result> = transaction.formatResults(rawResults);
|
69
70
|
|
70
|
-
|
71
|
+
// Passing `objectResults` (rows being of objects) provided by a driver
|
72
|
+
const results: Array<Result> = transaction.formatResults(objectResults, false);
|
71
73
|
```
|
72
74
|
|
73
75
|
#### Root Model
|
@@ -133,12 +135,13 @@ new Transaction(queries, {
|
|
133
135
|
//
|
134
136
|
// If the driver being used instead returns an object for every row, the driver must
|
135
137
|
// ensure the uniqueness of every key in that object, which means prefixing duplicated
|
136
|
-
// column names with the name of the respective table, if multiple tables are joined
|
138
|
+
// column names with the name of the respective table, if multiple tables are joined
|
139
|
+
// (example for an object key: "table_name.column_name").
|
137
140
|
//
|
138
141
|
// Drivers that return objects for rows offer this behavior as an option that is
|
139
142
|
// usually called "expand columns". If the driver being used does not offer such an
|
140
143
|
// option, you can instead activate the option in the compiler, which results in longer
|
141
|
-
// SQL statements because
|
144
|
+
// SQL statements because all column names are aliased.
|
142
145
|
expandColumns: true
|
143
146
|
});
|
144
147
|
```
|
package/dist/index.d.ts
CHANGED
@@ -5867,7 +5867,7 @@ type ModelFieldBasics = {
|
|
5867
5867
|
increment?: boolean;
|
5868
5868
|
};
|
5869
5869
|
type ModelFieldNormal = ModelFieldBasics & {
|
5870
|
-
type: 'string' | 'number' | 'boolean' | 'date' | 'json'
|
5870
|
+
type: 'string' | 'number' | 'boolean' | 'date' | 'json';
|
5871
5871
|
};
|
5872
5872
|
type ModelFieldReferenceAction = 'CASCADE' | 'RESTRICT' | 'SET NULL' | 'SET DEFAULT' | 'NO ACTION';
|
5873
5873
|
type ModelFieldReference = ModelFieldBasics & {
|
@@ -5984,7 +5984,8 @@ type PublicModel<T extends Array<ModelField> = Array<ModelField>> = Omit<Partial
|
|
5984
5984
|
identifiers?: Partial<Model['identifiers']>;
|
5985
5985
|
};
|
5986
5986
|
|
5987
|
-
type
|
5987
|
+
type RawRow = Array<unknown>;
|
5988
|
+
type ObjectRow = Record<string, unknown>;
|
5988
5989
|
type NativeRecord = Record<string, unknown> & {
|
5989
5990
|
id: string;
|
5990
5991
|
ronin: {
|
@@ -6005,6 +6006,45 @@ type AmountResult = {
|
|
6005
6006
|
};
|
6006
6007
|
type Result = SingleRecordResult | MultipleRecordResult | AmountResult;
|
6007
6008
|
|
6009
|
+
/**
|
6010
|
+
* A list of placeholders that can be located inside queries after those queries were
|
6011
|
+
* serialized into JSON objects.
|
6012
|
+
*
|
6013
|
+
* These placeholders are used to represent special keys and values. For example, if a
|
6014
|
+
* query is nested into a query, the nested query will be marked with `__RONIN_QUERY`,
|
6015
|
+
* which allows for distinguishing that nested query from an object of instructions.
|
6016
|
+
*/
|
6017
|
+
declare const QUERY_SYMBOLS: {
|
6018
|
+
readonly QUERY: "__RONIN_QUERY";
|
6019
|
+
readonly EXPRESSION: "__RONIN_EXPRESSION";
|
6020
|
+
readonly FIELD: "__RONIN_FIELD_";
|
6021
|
+
readonly FIELD_PARENT: "__RONIN_FIELD_PARENT_";
|
6022
|
+
readonly FIELD_PARENT_OLD: "__RONIN_FIELD_PARENT_OLD_";
|
6023
|
+
readonly FIELD_PARENT_NEW: "__RONIN_FIELD_PARENT_NEW_";
|
6024
|
+
readonly VALUE: "__RONIN_VALUE";
|
6025
|
+
};
|
6026
|
+
type RoninErrorCode = 'MODEL_NOT_FOUND' | 'FIELD_NOT_FOUND' | 'INDEX_NOT_FOUND' | 'TRIGGER_NOT_FOUND' | 'PRESET_NOT_FOUND' | 'INVALID_WITH_VALUE' | 'INVALID_TO_VALUE' | 'INVALID_INCLUDING_VALUE' | 'INVALID_FOR_VALUE' | 'INVALID_BEFORE_OR_AFTER_INSTRUCTION' | 'INVALID_MODEL_VALUE' | 'MUTUALLY_EXCLUSIVE_INSTRUCTIONS' | 'MISSING_INSTRUCTION' | 'MISSING_FIELD';
|
6027
|
+
interface Issue {
|
6028
|
+
message: string;
|
6029
|
+
path: Array<string | number>;
|
6030
|
+
}
|
6031
|
+
interface Details {
|
6032
|
+
message: string;
|
6033
|
+
code: RoninErrorCode;
|
6034
|
+
field?: string;
|
6035
|
+
fields?: Array<string>;
|
6036
|
+
issues?: Array<Issue>;
|
6037
|
+
queries?: Array<Query> | null;
|
6038
|
+
}
|
6039
|
+
declare class RoninError extends Error {
|
6040
|
+
code: Details['code'];
|
6041
|
+
field?: Details['field'];
|
6042
|
+
fields?: Details['fields'];
|
6043
|
+
issues?: Details['issues'];
|
6044
|
+
queries?: Details['queries'];
|
6045
|
+
constructor(details: Details);
|
6046
|
+
}
|
6047
|
+
|
6008
6048
|
interface TransactionOptions {
|
6009
6049
|
/** A list of models that already exist in the database. */
|
6010
6050
|
models?: Array<PublicModel>;
|
@@ -6020,6 +6060,7 @@ declare class Transaction {
|
|
6020
6060
|
statements: Array<Statement>;
|
6021
6061
|
models: Array<Model>;
|
6022
6062
|
private queries;
|
6063
|
+
private fields;
|
6023
6064
|
constructor(queries: Array<Query>, options?: TransactionOptions);
|
6024
6065
|
/**
|
6025
6066
|
* Composes SQL statements for the provided RONIN queries.
|
@@ -6031,10 +6072,11 @@ declare class Transaction {
|
|
6031
6072
|
* @returns The composed SQL statements.
|
6032
6073
|
*/
|
6033
6074
|
private compileQueries;
|
6034
|
-
private
|
6035
|
-
|
6075
|
+
private formatRow;
|
6076
|
+
formatResults(results: Array<Array<RawRow>>, raw?: true): Array<Result>;
|
6077
|
+
formatResults(results: Array<Array<ObjectRow>>, raw?: false): Array<Result>;
|
6036
6078
|
}
|
6037
6079
|
|
6038
6080
|
declare const CLEAN_ROOT_MODEL: PublicModel;
|
6039
6081
|
|
6040
|
-
export { type PublicModel as Model, type ModelField, type ModelIndex, type ModelPreset, type ModelTrigger, type Query, CLEAN_ROOT_MODEL as ROOT_MODEL, type Result, type Statement, Transaction };
|
6082
|
+
export { type PublicModel as Model, type ModelField, type ModelIndex, type ModelPreset, type ModelTrigger, QUERY_SYMBOLS, type Query, CLEAN_ROOT_MODEL as ROOT_MODEL, type Result, RoninError, type Statement, Transaction };
|
package/dist/index.js
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
// src/utils/helpers.ts
|
2
2
|
import { init as cuid } from "@paralleldrive/cuid2";
|
3
|
-
var
|
3
|
+
var QUERY_SYMBOLS = {
|
4
4
|
// Represents a sub query.
|
5
5
|
QUERY: "__RONIN_QUERY",
|
6
6
|
// Represents an expression that should be evaluated.
|
@@ -17,9 +17,10 @@ var RONIN_MODEL_SYMBOLS = {
|
|
17
17
|
VALUE: "__RONIN_VALUE"
|
18
18
|
};
|
19
19
|
var RONIN_MODEL_FIELD_REGEX = new RegExp(
|
20
|
-
`${
|
20
|
+
`${QUERY_SYMBOLS.FIELD}[_a-zA-Z0-9.]+`,
|
21
21
|
"g"
|
22
22
|
);
|
23
|
+
var composeIncludedTableAlias = (fieldSlug) => `including_${fieldSlug}`;
|
23
24
|
var MODEL_ENTITY_ERROR_CODES = {
|
24
25
|
field: "FIELD_NOT_FOUND",
|
25
26
|
index: "INDEX_NOT_FOUND",
|
@@ -65,6 +66,23 @@ var convertToCamelCase = (str) => {
|
|
65
66
|
return sanitize(str).split(SPLIT_REGEX).map((part, index) => index === 0 ? part.toLowerCase() : capitalize(part)).join("");
|
66
67
|
};
|
67
68
|
var isObject = (value) => value != null && typeof value === "object" && Array.isArray(value) === false;
|
69
|
+
var getSymbol = (value) => {
|
70
|
+
if (!isObject(value)) return null;
|
71
|
+
const objectValue = value;
|
72
|
+
if (QUERY_SYMBOLS.QUERY in objectValue) {
|
73
|
+
return {
|
74
|
+
type: "query",
|
75
|
+
value: objectValue[QUERY_SYMBOLS.QUERY]
|
76
|
+
};
|
77
|
+
}
|
78
|
+
if (QUERY_SYMBOLS.EXPRESSION in objectValue) {
|
79
|
+
return {
|
80
|
+
type: "expression",
|
81
|
+
value: objectValue[QUERY_SYMBOLS.EXPRESSION]
|
82
|
+
};
|
83
|
+
}
|
84
|
+
return null;
|
85
|
+
};
|
68
86
|
var findInObject = (obj, pattern, replacer) => {
|
69
87
|
let found = false;
|
70
88
|
for (const key in obj) {
|
@@ -85,10 +103,11 @@ var findInObject = (obj, pattern, replacer) => {
|
|
85
103
|
var flatten = (obj, prefix = "", res = {}) => {
|
86
104
|
for (const key in obj) {
|
87
105
|
const path = prefix ? `${prefix}.${key}` : key;
|
88
|
-
|
89
|
-
|
106
|
+
const value = obj[key];
|
107
|
+
if (typeof value === "object" && value !== null && !getSymbol(value)) {
|
108
|
+
flatten(value, path, res);
|
90
109
|
} else {
|
91
|
-
res[path] =
|
110
|
+
res[path] = value;
|
92
111
|
}
|
93
112
|
}
|
94
113
|
return res;
|
@@ -99,7 +118,11 @@ var omit = (obj, properties) => Object.fromEntries(
|
|
99
118
|
var expand = (obj) => {
|
100
119
|
return Object.entries(obj).reduce((res, [key, val]) => {
|
101
120
|
key.split(".").reduce((acc, part, i, arr) => {
|
102
|
-
|
121
|
+
if (i === arr.length - 1) {
|
122
|
+
acc[part] = val;
|
123
|
+
} else {
|
124
|
+
acc[part] = typeof acc[part] === "object" && acc[part] !== null ? acc[part] : {};
|
125
|
+
}
|
103
126
|
return acc[part];
|
104
127
|
}, res);
|
105
128
|
return res;
|
@@ -133,15 +156,15 @@ var prepareStatementValue = (statementParams, value) => {
|
|
133
156
|
};
|
134
157
|
var parseFieldExpression = (model, instructionName, expression, parentModel) => {
|
135
158
|
return expression.replace(RONIN_MODEL_FIELD_REGEX, (match) => {
|
136
|
-
let toReplace =
|
159
|
+
let toReplace = QUERY_SYMBOLS.FIELD;
|
137
160
|
let rootModel = model;
|
138
|
-
if (match.startsWith(
|
161
|
+
if (match.startsWith(QUERY_SYMBOLS.FIELD_PARENT)) {
|
139
162
|
rootModel = parentModel;
|
140
|
-
toReplace =
|
141
|
-
if (match.startsWith(
|
142
|
-
rootModel.tableAlias = toReplace =
|
143
|
-
} else if (match.startsWith(
|
144
|
-
rootModel.tableAlias = toReplace =
|
163
|
+
toReplace = QUERY_SYMBOLS.FIELD_PARENT;
|
164
|
+
if (match.startsWith(QUERY_SYMBOLS.FIELD_PARENT_OLD)) {
|
165
|
+
rootModel.tableAlias = toReplace = QUERY_SYMBOLS.FIELD_PARENT_OLD;
|
166
|
+
} else if (match.startsWith(QUERY_SYMBOLS.FIELD_PARENT_NEW)) {
|
167
|
+
rootModel.tableAlias = toReplace = QUERY_SYMBOLS.FIELD_PARENT_NEW;
|
145
168
|
}
|
146
169
|
}
|
147
170
|
const fieldSlug = match.replace(toReplace, "");
|
@@ -190,47 +213,52 @@ var composeConditions = (models, model, statementParams, instructionName, value,
|
|
190
213
|
return conditions.join(" AND ");
|
191
214
|
}
|
192
215
|
if (options.fieldSlug) {
|
193
|
-
const
|
194
|
-
|
195
|
-
|
196
|
-
if (!
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
if (
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
216
|
+
const childField = model.fields.some(({ slug }) => {
|
217
|
+
return slug.includes(".") && slug.split(".")[0] === options.fieldSlug;
|
218
|
+
});
|
219
|
+
if (!childField) {
|
220
|
+
const fieldDetails = getFieldFromModel(model, options.fieldSlug, instructionName);
|
221
|
+
const { field: modelField } = fieldDetails || {};
|
222
|
+
const consumeJSON = modelField?.type === "json" && instructionName === "to";
|
223
|
+
if (modelField && !(isObject(value) || Array.isArray(value)) || getSymbol(value) || consumeJSON) {
|
224
|
+
return composeFieldValues(
|
225
|
+
models,
|
226
|
+
model,
|
227
|
+
statementParams,
|
228
|
+
instructionName,
|
229
|
+
value,
|
230
|
+
{ ...options, fieldSlug: options.fieldSlug }
|
231
|
+
);
|
232
|
+
}
|
233
|
+
if (modelField?.type === "link" && isNested) {
|
234
|
+
const keys = Object.keys(value);
|
235
|
+
const values = Object.values(value);
|
236
|
+
let recordTarget;
|
237
|
+
if (keys.length === 1 && keys[0] === "id") {
|
238
|
+
recordTarget = values[0];
|
239
|
+
} else {
|
240
|
+
const relatedModel = getModelBySlug(models, modelField.target);
|
241
|
+
const subQuery = {
|
242
|
+
get: {
|
243
|
+
[relatedModel.slug]: {
|
244
|
+
with: value,
|
245
|
+
selecting: ["id"]
|
246
|
+
}
|
219
247
|
}
|
220
|
-
}
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
}
|
248
|
+
};
|
249
|
+
recordTarget = {
|
250
|
+
[QUERY_SYMBOLS.QUERY]: subQuery
|
251
|
+
};
|
252
|
+
}
|
253
|
+
return composeConditions(
|
254
|
+
models,
|
255
|
+
model,
|
256
|
+
statementParams,
|
257
|
+
instructionName,
|
258
|
+
recordTarget,
|
259
|
+
options
|
260
|
+
);
|
225
261
|
}
|
226
|
-
return composeConditions(
|
227
|
-
models,
|
228
|
-
model,
|
229
|
-
statementParams,
|
230
|
-
instructionName,
|
231
|
-
recordTarget,
|
232
|
-
options
|
233
|
-
);
|
234
262
|
}
|
235
263
|
}
|
236
264
|
if (isNested) {
|
@@ -278,23 +306,6 @@ var formatIdentifiers = ({ identifiers }, queryInstructions) => {
|
|
278
306
|
[type]: newNestedInstructions
|
279
307
|
};
|
280
308
|
};
|
281
|
-
var getSymbol = (value) => {
|
282
|
-
if (!isObject(value)) return null;
|
283
|
-
const objectValue = value;
|
284
|
-
if (RONIN_MODEL_SYMBOLS.QUERY in objectValue) {
|
285
|
-
return {
|
286
|
-
type: "query",
|
287
|
-
value: objectValue[RONIN_MODEL_SYMBOLS.QUERY]
|
288
|
-
};
|
289
|
-
}
|
290
|
-
if (RONIN_MODEL_SYMBOLS.EXPRESSION in objectValue) {
|
291
|
-
return {
|
292
|
-
type: "expression",
|
293
|
-
value: objectValue[RONIN_MODEL_SYMBOLS.EXPRESSION]
|
294
|
-
};
|
295
|
-
}
|
296
|
-
return null;
|
297
|
-
};
|
298
309
|
|
299
310
|
// src/instructions/with.ts
|
300
311
|
var getMatcher = (value, negative) => {
|
@@ -347,7 +358,7 @@ var getModelBySlug = (models, slug) => {
|
|
347
358
|
};
|
348
359
|
var composeAssociationModelSlug = (model, field) => convertToCamelCase(`ronin_link_${model.slug}_${field.slug}`);
|
349
360
|
var getFieldSelector = (model, field, fieldPath, instructionName) => {
|
350
|
-
const symbol = model.tableAlias?.startsWith(
|
361
|
+
const symbol = model.tableAlias?.startsWith(QUERY_SYMBOLS.FIELD_PARENT) ? `${model.tableAlias.replace(QUERY_SYMBOLS.FIELD_PARENT, "").slice(0, -1)}.` : "";
|
351
362
|
const tablePrefix = symbol || (model.tableAlias ? `"${model.tableAlias}".` : "");
|
352
363
|
if (field.type === "json" && instructionName !== "to") {
|
353
364
|
const dotParts = fieldPath.split(".");
|
@@ -357,7 +368,7 @@ var getFieldSelector = (model, field, fieldPath, instructionName) => {
|
|
357
368
|
}
|
358
369
|
return `${tablePrefix}"${fieldPath}"`;
|
359
370
|
};
|
360
|
-
|
371
|
+
function getFieldFromModel(model, fieldPath, instructionName, shouldThrow = true) {
|
361
372
|
const errorPrefix = `Field "${fieldPath}" defined for \`${instructionName}\``;
|
362
373
|
const modelFields = model.fields || [];
|
363
374
|
let modelField;
|
@@ -375,16 +386,19 @@ var getFieldFromModel = (model, fieldPath, instructionName) => {
|
|
375
386
|
}
|
376
387
|
modelField = modelFields.find((field) => field.slug === fieldPath);
|
377
388
|
if (!modelField) {
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
389
|
+
if (shouldThrow) {
|
390
|
+
throw new RoninError({
|
391
|
+
message: `${errorPrefix} does not exist in model "${model.name}".`,
|
392
|
+
code: "FIELD_NOT_FOUND",
|
393
|
+
field: fieldPath,
|
394
|
+
queries: null
|
395
|
+
});
|
396
|
+
}
|
397
|
+
return null;
|
384
398
|
}
|
385
399
|
const fieldSelector = getFieldSelector(model, modelField, fieldPath, instructionName);
|
386
400
|
return { field: modelField, fieldSelector };
|
387
|
-
}
|
401
|
+
}
|
388
402
|
var slugToName = (slug) => {
|
389
403
|
const name = slug.replace(/([a-z])([A-Z])/g, "$1 $2");
|
390
404
|
return title(name);
|
@@ -440,11 +454,6 @@ var SYSTEM_FIELDS = [
|
|
440
454
|
slug: "id",
|
441
455
|
displayAs: "single-line"
|
442
456
|
},
|
443
|
-
{
|
444
|
-
name: "RONIN",
|
445
|
-
type: "group",
|
446
|
-
slug: "ronin"
|
447
|
-
},
|
448
457
|
{
|
449
458
|
name: "RONIN - Locked",
|
450
459
|
type: "boolean",
|
@@ -488,7 +497,6 @@ var ROOT_MODEL = {
|
|
488
497
|
{ slug: "pluralSlug", type: "string" },
|
489
498
|
{ slug: "idPrefix", type: "string" },
|
490
499
|
{ slug: "table", type: "string" },
|
491
|
-
{ slug: "identifiers", type: "group" },
|
492
500
|
{ slug: "identifiers.name", type: "string" },
|
493
501
|
{ slug: "identifiers.slug", type: "string" },
|
494
502
|
// Providing an empty object as a default value allows us to use `json_insert`
|
@@ -543,14 +551,14 @@ var addDefaultModelPresets = (list, model) => {
|
|
543
551
|
instructions: {
|
544
552
|
including: {
|
545
553
|
[field.slug]: {
|
546
|
-
[
|
554
|
+
[QUERY_SYMBOLS.QUERY]: {
|
547
555
|
get: {
|
548
556
|
[relatedModel.slug]: {
|
549
557
|
with: {
|
550
558
|
// Compare the `id` field of the related model to the link field on
|
551
559
|
// the root model (`field.slug`).
|
552
560
|
id: {
|
553
|
-
[
|
561
|
+
[QUERY_SYMBOLS.EXPRESSION]: `${QUERY_SYMBOLS.FIELD_PARENT}${field.slug}`
|
554
562
|
}
|
555
563
|
}
|
556
564
|
}
|
@@ -578,12 +586,12 @@ var addDefaultModelPresets = (list, model) => {
|
|
578
586
|
instructions: {
|
579
587
|
including: {
|
580
588
|
[presetSlug]: {
|
581
|
-
[
|
589
|
+
[QUERY_SYMBOLS.QUERY]: {
|
582
590
|
get: {
|
583
591
|
[pluralSlug]: {
|
584
592
|
with: {
|
585
593
|
[childField.slug]: {
|
586
|
-
[
|
594
|
+
[QUERY_SYMBOLS.EXPRESSION]: `${QUERY_SYMBOLS.FIELD_PARENT}id`
|
587
595
|
}
|
588
596
|
}
|
589
597
|
}
|
@@ -610,7 +618,6 @@ var typesInSQLite = {
|
|
610
618
|
json: "TEXT"
|
611
619
|
};
|
612
620
|
var getFieldStatement = (models, model, field) => {
|
613
|
-
if (field.type === "group") return null;
|
614
621
|
let statement = `"${field.slug}" ${typesInSQLite[field.type]}`;
|
615
622
|
if (field.slug === "id") statement += " PRIMARY KEY";
|
616
623
|
if (field.unique === true) statement += " UNIQUE";
|
@@ -850,11 +857,11 @@ var transformMetaQuery = (models, dependencyStatements, statementParams, query)
|
|
850
857
|
statementParts.push(`OF (${fieldSelectors.join(", ")})`);
|
851
858
|
}
|
852
859
|
statementParts.push("ON", `"${existingModel.table}"`);
|
853
|
-
if (trigger.filter || trigger.effects.some((query2) => findInObject(query2,
|
860
|
+
if (trigger.filter || trigger.effects.some((query2) => findInObject(query2, QUERY_SYMBOLS.FIELD))) {
|
854
861
|
statementParts.push("FOR EACH ROW");
|
855
862
|
}
|
856
863
|
if (trigger.filter) {
|
857
|
-
const tableAlias = trigger.action === "DELETE" ?
|
864
|
+
const tableAlias = trigger.action === "DELETE" ? QUERY_SYMBOLS.FIELD_PARENT_OLD : QUERY_SYMBOLS.FIELD_PARENT_NEW;
|
858
865
|
const withStatement = handleWith(
|
859
866
|
models,
|
860
867
|
{ ...existingModel, tableAlias },
|
@@ -876,7 +883,7 @@ var transformMetaQuery = (models, dependencyStatements, statementParams, query)
|
|
876
883
|
}
|
877
884
|
dependencyStatements.push({ statement, params });
|
878
885
|
}
|
879
|
-
const field = `${
|
886
|
+
const field = `${QUERY_SYMBOLS.FIELD}${pluralType}`;
|
880
887
|
let json;
|
881
888
|
switch (action) {
|
882
889
|
case "create": {
|
@@ -940,7 +947,7 @@ var transformMetaQuery = (models, dependencyStatements, statementParams, query)
|
|
940
947
|
model: {
|
941
948
|
with: { slug: modelSlug },
|
942
949
|
to: {
|
943
|
-
[pluralType]: { [
|
950
|
+
[pluralType]: { [QUERY_SYMBOLS.EXPRESSION]: json }
|
944
951
|
}
|
945
952
|
}
|
946
953
|
}
|
@@ -1059,8 +1066,8 @@ var handleFor = (model, instructions) => {
|
|
1059
1066
|
if (arg !== null) {
|
1060
1067
|
findInObject(
|
1061
1068
|
replacedForFilter,
|
1062
|
-
|
1063
|
-
(match) => match.replace(
|
1069
|
+
QUERY_SYMBOLS.VALUE,
|
1070
|
+
(match) => match.replace(QUERY_SYMBOLS.VALUE, arg)
|
1064
1071
|
);
|
1065
1072
|
}
|
1066
1073
|
for (const subInstruction in replacedForFilter) {
|
@@ -1102,7 +1109,7 @@ var handleIncluding = (models, model, statementParams, instruction) => {
|
|
1102
1109
|
const relatedModel = getModelBySlug(models, queryModel);
|
1103
1110
|
let joinType = "LEFT";
|
1104
1111
|
let relatedTableSelector = `"${relatedModel.table}"`;
|
1105
|
-
const tableAlias =
|
1112
|
+
const tableAlias = composeIncludedTableAlias(ephemeralFieldSlug);
|
1106
1113
|
const single = queryModel !== relatedModel.pluralSlug;
|
1107
1114
|
if (!modifiableQueryInstructions?.with) {
|
1108
1115
|
joinType = "CROSS";
|
@@ -1183,36 +1190,70 @@ var handleOrderedBy = (model, instruction) => {
|
|
1183
1190
|
};
|
1184
1191
|
|
1185
1192
|
// src/instructions/selecting.ts
|
1186
|
-
var handleSelecting = (model, statementParams, instructions) => {
|
1193
|
+
var handleSelecting = (models, model, statementParams, instructions, options) => {
|
1194
|
+
let loadedFields = [];
|
1195
|
+
let statement = "*";
|
1187
1196
|
let isJoining = false;
|
1188
|
-
let statement = instructions.selecting ? instructions.selecting.map((slug) => {
|
1189
|
-
return getFieldFromModel(model, slug, "selecting").fieldSelector;
|
1190
|
-
}).join(", ") : "*";
|
1191
1197
|
if (instructions.including) {
|
1192
|
-
const
|
1198
|
+
const flatObject = flatten(instructions.including);
|
1199
|
+
instructions.including = {};
|
1200
|
+
for (const [key, value] of Object.entries(flatObject)) {
|
1193
1201
|
const symbol = getSymbol(value);
|
1194
|
-
if (symbol) {
|
1195
|
-
|
1196
|
-
|
1197
|
-
|
1198
|
-
|
1199
|
-
|
1200
|
-
|
1202
|
+
if (symbol?.type === "query") {
|
1203
|
+
isJoining = true;
|
1204
|
+
const { queryModel, queryInstructions } = splitQuery(symbol.value);
|
1205
|
+
const subQueryModel = getModelBySlug(models, queryModel);
|
1206
|
+
const tableName = composeIncludedTableAlias(key);
|
1207
|
+
const queryModelFields = queryInstructions?.selecting ? subQueryModel.fields.filter((field) => {
|
1208
|
+
return queryInstructions.selecting?.includes(field.slug);
|
1209
|
+
}) : subQueryModel.fields;
|
1210
|
+
for (const field of queryModelFields) {
|
1211
|
+
loadedFields.push({ ...field, parentField: key });
|
1212
|
+
if (options?.expandColumns) {
|
1213
|
+
const newValue2 = parseFieldExpression(
|
1214
|
+
{ ...subQueryModel, tableAlias: tableName },
|
1215
|
+
"including",
|
1216
|
+
`${QUERY_SYMBOLS.FIELD}${field.slug}`
|
1217
|
+
);
|
1218
|
+
instructions.including[`${tableName}.${field.slug}`] = newValue2;
|
1219
|
+
}
|
1201
1220
|
}
|
1221
|
+
continue;
|
1202
1222
|
}
|
1203
|
-
|
1204
|
-
|
1205
|
-
|
1206
|
-
|
1207
|
-
|
1208
|
-
|
1209
|
-
|
1210
|
-
return `(${value}) as "${key}"`;
|
1211
|
-
return `${prepareStatementValue(statementParams, value)} as "${key}"`;
|
1212
|
-
}).join(", ");
|
1223
|
+
let newValue = value;
|
1224
|
+
if (symbol?.type === "expression") {
|
1225
|
+
newValue = `(${parseFieldExpression(model, "including", symbol.value)})`;
|
1226
|
+
} else {
|
1227
|
+
newValue = prepareStatementValue(statementParams, value);
|
1228
|
+
}
|
1229
|
+
instructions.including[key] = newValue;
|
1213
1230
|
}
|
1214
1231
|
}
|
1215
|
-
|
1232
|
+
const expandColumns = isJoining && options?.expandColumns;
|
1233
|
+
if (expandColumns) {
|
1234
|
+
instructions.selecting = model.fields.map((field) => field.slug);
|
1235
|
+
}
|
1236
|
+
if (instructions.selecting) {
|
1237
|
+
const usableModel = expandColumns ? { ...model, tableAlias: model.tableAlias || model.table } : model;
|
1238
|
+
const selectedFields = [];
|
1239
|
+
statement = instructions.selecting.map((slug) => {
|
1240
|
+
const { field, fieldSelector } = getFieldFromModel(
|
1241
|
+
usableModel,
|
1242
|
+
slug,
|
1243
|
+
"selecting"
|
1244
|
+
);
|
1245
|
+
selectedFields.push(field);
|
1246
|
+
return fieldSelector;
|
1247
|
+
}).join(", ");
|
1248
|
+
loadedFields = [...selectedFields, ...loadedFields];
|
1249
|
+
} else {
|
1250
|
+
loadedFields = [...model.fields, ...loadedFields];
|
1251
|
+
}
|
1252
|
+
if (instructions.including && Object.keys(instructions.including).length > 0) {
|
1253
|
+
statement += ", ";
|
1254
|
+
statement += Object.entries(instructions.including).map(([key, value]) => `${value} as "${key}"`).join(", ");
|
1255
|
+
}
|
1256
|
+
return { columns: statement, isJoining, loadedFields };
|
1216
1257
|
};
|
1217
1258
|
|
1218
1259
|
// src/instructions/to.ts
|
@@ -1279,8 +1320,8 @@ var handleTo = (models, model, statementParams, queryType, dependencyStatements,
|
|
1279
1320
|
Object.assign(toInstruction, defaultFields);
|
1280
1321
|
for (const fieldSlug in toInstruction) {
|
1281
1322
|
const fieldValue = toInstruction[fieldSlug];
|
1282
|
-
const fieldDetails = getFieldFromModel(model, fieldSlug, "to");
|
1283
|
-
if (fieldDetails
|
1323
|
+
const fieldDetails = getFieldFromModel(model, fieldSlug, "to", false);
|
1324
|
+
if (fieldDetails?.field.type === "link" && fieldDetails.field.kind === "many") {
|
1284
1325
|
delete toInstruction[fieldSlug];
|
1285
1326
|
const associativeModelSlug = composeAssociationModelSlug(model, fieldDetails.field);
|
1286
1327
|
const composeStatement = (subQueryType, value) => {
|
@@ -1345,7 +1386,8 @@ var compileQueryInput = (defaultQuery, models, statementParams, options) => {
|
|
1345
1386
|
statementParams,
|
1346
1387
|
defaultQuery
|
1347
1388
|
);
|
1348
|
-
if (query === null)
|
1389
|
+
if (query === null)
|
1390
|
+
return { dependencies: [], main: dependencyStatements[0], loadedFields: [] };
|
1349
1391
|
const parsedQuery = splitQuery(query);
|
1350
1392
|
const { queryType, queryModel, queryInstructions } = parsedQuery;
|
1351
1393
|
const model = getModelBySlug(models, queryModel);
|
@@ -1355,10 +1397,16 @@ var compileQueryInput = (defaultQuery, models, statementParams, options) => {
|
|
1355
1397
|
if (instructions && Object.hasOwn(instructions, "for")) {
|
1356
1398
|
instructions = handleFor(model, instructions);
|
1357
1399
|
}
|
1358
|
-
const { columns, isJoining } = handleSelecting(
|
1359
|
-
|
1360
|
-
|
1361
|
-
|
1400
|
+
const { columns, isJoining, loadedFields } = handleSelecting(
|
1401
|
+
models,
|
1402
|
+
model,
|
1403
|
+
statementParams,
|
1404
|
+
{
|
1405
|
+
selecting: instructions?.selecting,
|
1406
|
+
including: instructions?.including
|
1407
|
+
},
|
1408
|
+
options
|
1409
|
+
);
|
1362
1410
|
let statement = "";
|
1363
1411
|
switch (queryType) {
|
1364
1412
|
case "get":
|
@@ -1478,7 +1526,8 @@ var compileQueryInput = (defaultQuery, models, statementParams, options) => {
|
|
1478
1526
|
if (returning) mainStatement.returning = true;
|
1479
1527
|
return {
|
1480
1528
|
dependencies: dependencyStatements,
|
1481
|
-
main: mainStatement
|
1529
|
+
main: mainStatement,
|
1530
|
+
loadedFields
|
1482
1531
|
};
|
1483
1532
|
};
|
1484
1533
|
|
@@ -1487,6 +1536,7 @@ var Transaction = class {
|
|
1487
1536
|
statements;
|
1488
1537
|
models = [];
|
1489
1538
|
queries;
|
1539
|
+
fields = [];
|
1490
1540
|
constructor(queries, options) {
|
1491
1541
|
const models = options?.models || [];
|
1492
1542
|
this.statements = this.compileQueries(queries, models, options);
|
@@ -1518,46 +1568,72 @@ var Transaction = class {
|
|
1518
1568
|
const result = compileQueryInput(
|
1519
1569
|
query,
|
1520
1570
|
modelListWithPresets,
|
1521
|
-
options?.inlineParams ? null : []
|
1571
|
+
options?.inlineParams ? null : [],
|
1572
|
+
{ expandColumns: options?.expandColumns }
|
1522
1573
|
);
|
1523
1574
|
dependencyStatements.push(...result.dependencies);
|
1524
1575
|
mainStatements.push(result.main);
|
1576
|
+
this.fields.push(result.loadedFields);
|
1525
1577
|
}
|
1526
1578
|
this.models = modelListWithPresets;
|
1527
1579
|
return [...dependencyStatements, ...mainStatements];
|
1528
1580
|
};
|
1529
|
-
|
1530
|
-
const
|
1531
|
-
for (
|
1532
|
-
const
|
1581
|
+
formatRow(fields, row) {
|
1582
|
+
const record = {};
|
1583
|
+
for (let index = 0; index < row.length; index++) {
|
1584
|
+
const value = row[index];
|
1585
|
+
const field = fields[index];
|
1586
|
+
let newSlug = field.slug;
|
1587
|
+
let newValue = value;
|
1588
|
+
const parentFieldSlug = field.parentField;
|
1589
|
+
if (parentFieldSlug) {
|
1590
|
+
newSlug = `${parentFieldSlug}.${field.slug}`;
|
1591
|
+
}
|
1533
1592
|
if (field.type === "json") {
|
1534
|
-
|
1535
|
-
continue;
|
1593
|
+
newValue = JSON.parse(value);
|
1536
1594
|
}
|
1537
|
-
|
1595
|
+
record[newSlug] = newValue;
|
1538
1596
|
}
|
1539
|
-
return expand(
|
1597
|
+
return expand(record);
|
1540
1598
|
}
|
1541
|
-
|
1599
|
+
/**
|
1600
|
+
* Format the results returned from the database into RONIN records.
|
1601
|
+
*
|
1602
|
+
* @param results - A list of results from the database, where each result is an array
|
1603
|
+
* of rows.
|
1604
|
+
* @param raw - By default, rows are expected to be arrays of values, which is how SQL
|
1605
|
+
* databases return rows by default. If the driver being used returns rows as objects
|
1606
|
+
* instead, this option should be set to `false`.
|
1607
|
+
*
|
1608
|
+
* @returns A list of formatted RONIN results, where each result is either a single
|
1609
|
+
* RONIN record, an array of RONIN records, or a RONIN count result.
|
1610
|
+
*/
|
1611
|
+
formatResults(results, raw = true) {
|
1542
1612
|
const relevantResults = results.filter((_, index) => {
|
1543
1613
|
return this.statements[index].returning;
|
1544
1614
|
});
|
1545
|
-
|
1615
|
+
const normalizedResults = raw ? relevantResults : relevantResults.map((rows) => {
|
1616
|
+
return rows.map((row) => {
|
1617
|
+
if (Array.isArray(row)) return row;
|
1618
|
+
if (row["COUNT(*)"]) return [row["COUNT(*)"]];
|
1619
|
+
return Object.values(row);
|
1620
|
+
});
|
1621
|
+
});
|
1622
|
+
return normalizedResults.map((rows, index) => {
|
1546
1623
|
const query = this.queries.at(-index);
|
1624
|
+
const fields = this.fields.at(-index);
|
1547
1625
|
const { queryType, queryModel, queryInstructions } = splitQuery(query);
|
1548
1626
|
const model = getModelBySlug(this.models, queryModel);
|
1549
1627
|
if (queryType === "count") {
|
1550
|
-
return { amount:
|
1628
|
+
return { amount: rows[0][0] };
|
1551
1629
|
}
|
1552
1630
|
const single = queryModel !== model.pluralSlug;
|
1553
1631
|
if (single) {
|
1554
|
-
return { record: this.
|
1632
|
+
return { record: this.formatRow(fields, rows[0]) };
|
1555
1633
|
}
|
1556
1634
|
const pageSize = queryInstructions?.limitedTo;
|
1557
1635
|
const output = {
|
1558
|
-
records:
|
1559
|
-
return this.formatRecord(model, resultItem);
|
1560
|
-
})
|
1636
|
+
records: rows.map((row) => this.formatRow(fields, row))
|
1561
1637
|
};
|
1562
1638
|
if (pageSize && output.records.length > 0) {
|
1563
1639
|
if (queryInstructions?.before || queryInstructions?.after) {
|
@@ -1585,6 +1661,8 @@ var Transaction = class {
|
|
1585
1661
|
};
|
1586
1662
|
var CLEAN_ROOT_MODEL = omit(ROOT_MODEL, ["system"]);
|
1587
1663
|
export {
|
1664
|
+
QUERY_SYMBOLS,
|
1588
1665
|
CLEAN_ROOT_MODEL as ROOT_MODEL,
|
1666
|
+
RoninError,
|
1589
1667
|
Transaction
|
1590
1668
|
};
|