@ronin/compiler
Advanced tools
Comparing version 0.9.0-leo-ron-1083-experimental-204 to 0.9.0-leo-ron-1083-experimental-205
@@ -468,63 +468,63 @@ // src/utils/helpers.ts | ||
]; | ||
var SYSTEM_MODELS = [ | ||
{ | ||
slug: "model", | ||
identifiers: { | ||
name: "name", | ||
slug: "slug" | ||
}, | ||
// This name mimics the `sqlite_schema` table in SQLite. | ||
table: "ronin_schema", | ||
fields: [ | ||
{ slug: "name", type: "string" }, | ||
{ slug: "pluralName", type: "string" }, | ||
{ slug: "slug", type: "string" }, | ||
{ slug: "pluralSlug", type: "string" }, | ||
{ slug: "idPrefix", type: "string" }, | ||
{ slug: "table", type: "string" }, | ||
{ slug: "identifiers", type: "group" }, | ||
{ slug: "identifiers.name", type: "string" }, | ||
{ slug: "identifiers.slug", type: "string" }, | ||
// Providing an empty object as a default value allows us to use `json_insert` | ||
// without needing to fall back to an empty object in the insertion statement, | ||
// which makes the statement shorter. | ||
{ slug: "fields", type: "json", defaultValue: "{}" }, | ||
{ slug: "indexes", type: "json", defaultValue: "{}" }, | ||
{ slug: "triggers", type: "json", defaultValue: "{}" }, | ||
{ slug: "presets", type: "json", defaultValue: "{}" } | ||
] | ||
} | ||
]; | ||
var addSystemModels = (models) => { | ||
const associativeModels = models.flatMap((model) => { | ||
const addedModels = []; | ||
for (const field of model.fields || []) { | ||
if (field.type === "link" && !field.slug.startsWith("ronin.")) { | ||
const relatedModel = getModelBySlug(models, field.target); | ||
let fieldSlug = relatedModel.slug; | ||
if (field.kind === "many") { | ||
fieldSlug = composeAssociationModelSlug(model, field); | ||
addedModels.push({ | ||
pluralSlug: fieldSlug, | ||
slug: fieldSlug, | ||
associationSlug: field.slug, | ||
fields: [ | ||
{ | ||
slug: "source", | ||
type: "link", | ||
target: model.slug | ||
}, | ||
{ | ||
slug: "target", | ||
type: "link", | ||
target: relatedModel.slug | ||
} | ||
] | ||
}); | ||
} | ||
var ROOT_MODEL = { | ||
slug: "model", | ||
identifiers: { | ||
name: "name", | ||
slug: "slug" | ||
}, | ||
// This name mimics the `sqlite_schema` table in SQLite. | ||
table: "ronin_schema", | ||
// Indicates that the model was automatically generated by RONIN. | ||
system: { model: "root" }, | ||
fields: [ | ||
{ slug: "name", type: "string" }, | ||
{ slug: "pluralName", type: "string" }, | ||
{ slug: "slug", type: "string" }, | ||
{ slug: "pluralSlug", type: "string" }, | ||
{ slug: "idPrefix", type: "string" }, | ||
{ slug: "table", type: "string" }, | ||
{ slug: "identifiers", type: "group" }, | ||
{ slug: "identifiers.name", type: "string" }, | ||
{ slug: "identifiers.slug", type: "string" }, | ||
// Providing an empty object as a default value allows us to use `json_insert` | ||
// without needing to fall back to an empty object in the insertion statement, | ||
// which makes the statement shorter. | ||
{ slug: "fields", type: "json", defaultValue: "{}" }, | ||
{ slug: "indexes", type: "json", defaultValue: "{}" }, | ||
{ slug: "triggers", type: "json", defaultValue: "{}" }, | ||
{ slug: "presets", type: "json", defaultValue: "{}" } | ||
] | ||
}; | ||
var getSystemModels = (models, model) => { | ||
const addedModels = []; | ||
for (const field of model.fields || []) { | ||
if (field.type === "link" && !field.slug.startsWith("ronin.")) { | ||
const relatedModel = getModelBySlug(models, field.target); | ||
let fieldSlug = relatedModel.slug; | ||
if (field.kind === "many") { | ||
fieldSlug = composeAssociationModelSlug(model, field); | ||
addedModels.push({ | ||
pluralSlug: fieldSlug, | ||
slug: fieldSlug, | ||
system: { | ||
model: model.slug, | ||
associationSlug: field.slug | ||
}, | ||
fields: [ | ||
{ | ||
slug: "source", | ||
type: "link", | ||
target: model.slug | ||
}, | ||
{ | ||
slug: "target", | ||
type: "link", | ||
target: relatedModel.slug | ||
} | ||
] | ||
}); | ||
} | ||
} | ||
return addedModels; | ||
}); | ||
return [...SYSTEM_MODELS, ...associativeModels, ...models]; | ||
} | ||
return addedModels; | ||
}; | ||
@@ -571,3 +571,3 @@ var addDefaultModelPresets = (list, model) => { | ||
const pluralSlug = childModel.pluralSlug; | ||
const presetSlug = childModel.associationSlug || pluralSlug; | ||
const presetSlug = childModel.system?.associationSlug || pluralSlug; | ||
defaultPresets.push({ | ||
@@ -653,2 +653,10 @@ instructions: { | ||
}; | ||
var composeSystemModelStatement = (models, dependencyStatements, action, systemModel) => { | ||
const { system: _, ...systemModelClean } = systemModel; | ||
const query = { | ||
[action]: { model: action === "create" ? systemModelClean : systemModelClean.slug } | ||
}; | ||
const statement = compileQueryInput(query, models, []); | ||
dependencyStatements.push(...statement.dependencies); | ||
}; | ||
var transformMetaQuery = (models, dependencyStatements, statementParams, query) => { | ||
@@ -682,6 +690,3 @@ const { queryType } = splitQuery(query); | ||
if (!(modelSlug && slug)) return query; | ||
const tableAction = ["model", "index", "trigger"].includes(entity) ? action.toUpperCase() : "ALTER"; | ||
const tableName = convertToSnakeCase(pluralize(modelSlug)); | ||
const model = action === "create" && entity === "model" ? null : getModelBySlug(models, modelSlug); | ||
const statement = `${tableAction} TABLE "${tableName}"`; | ||
if (entity === "model") { | ||
@@ -692,3 +697,6 @@ let queryTypeDetails; | ||
const modelWithFields = addDefaultModelFields(newModel, true); | ||
const modelWithPresets = addDefaultModelPresets(models, modelWithFields); | ||
const modelWithPresets = addDefaultModelPresets( | ||
[...models, modelWithFields], | ||
modelWithFields | ||
); | ||
const entities = Object.fromEntries( | ||
@@ -702,3 +710,3 @@ Object.entries(PLURAL_MODEL_ENTITIES).map(([type, pluralType2]) => { | ||
dependencyStatements.push({ | ||
statement: `${statement} (${columns.join(", ")})`, | ||
statement: `CREATE TABLE "${modelWithPresets.table}" (${columns.join(", ")})`, | ||
params: [] | ||
@@ -712,2 +720,10 @@ }); | ||
queryTypeDetails = { to: finalModel }; | ||
getSystemModels(models, modelWithPresets).map((systemModel) => { | ||
return composeSystemModelStatement( | ||
models, | ||
dependencyStatements, | ||
"create", | ||
systemModel | ||
); | ||
}); | ||
} | ||
@@ -718,7 +734,6 @@ if (action === "alter" && model) { | ||
const modelWithPresets = addDefaultModelPresets(models, modelWithFields); | ||
const newSlug = modelWithPresets.pluralSlug; | ||
if (newSlug) { | ||
const newTable = convertToSnakeCase(newSlug); | ||
const newTableName = modelWithPresets.table; | ||
if (newTableName) { | ||
dependencyStatements.push({ | ||
statement: `${statement} RENAME TO "${newTable}"`, | ||
statement: `ALTER TABLE "${model.table}" RENAME TO "${newTableName}"`, | ||
params: [] | ||
@@ -737,4 +752,12 @@ }); | ||
models.splice(models.indexOf(model), 1); | ||
dependencyStatements.push({ statement, params: [] }); | ||
dependencyStatements.push({ statement: `DROP TABLE "${model.table}"`, params: [] }); | ||
queryTypeDetails = { with: { slug } }; | ||
models.filter(({ system }) => system?.model === model.slug).map((systemModel) => { | ||
return composeSystemModelStatement( | ||
models, | ||
dependencyStatements, | ||
"drop", | ||
systemModel | ||
); | ||
}); | ||
} | ||
@@ -749,3 +772,15 @@ const queryTypeAction = action === "create" ? "add" : action === "alter" ? "set" : "remove"; | ||
const existingModel = model; | ||
const pluralType = PLURAL_MODEL_ENTITIES[entity]; | ||
const targetEntityIndex = existingModel[pluralType]?.findIndex( | ||
(entity2) => entity2.slug === slug | ||
); | ||
if ((action === "alter" || action === "drop") && (typeof targetEntityIndex === "undefined" || targetEntityIndex === -1)) { | ||
throw new RoninError({ | ||
message: `No ${entity} with slug "${slug}" defined in model "${existingModel.name}".`, | ||
code: MODEL_ENTITY_ERROR_CODES[entity] | ||
}); | ||
} | ||
const existingEntity = existingModel[pluralType]?.[targetEntityIndex]; | ||
if (entity === "field") { | ||
const statement = `ALTER TABLE "${existingModel.table}"`; | ||
if (action === "create") { | ||
@@ -770,8 +805,12 @@ const field2 = jsonValue; | ||
} else if (action === "drop") { | ||
dependencyStatements.push({ | ||
statement: `${statement} DROP COLUMN "${slug}"`, | ||
params: [] | ||
}); | ||
const existingField = existingEntity; | ||
if (!(existingField.type === "link" && existingField.kind === "many")) { | ||
dependencyStatements.push({ | ||
statement: `${statement} DROP COLUMN "${slug}"`, | ||
params: [] | ||
}); | ||
} | ||
} | ||
} | ||
const statementAction = action.toUpperCase(); | ||
if (entity === "index") { | ||
@@ -781,3 +820,3 @@ const index = jsonValue; | ||
const params = []; | ||
let statement2 = `${tableAction}${index?.unique ? " UNIQUE" : ""} INDEX "${indexName}"`; | ||
let statement = `${statementAction}${index?.unique ? " UNIQUE" : ""} INDEX "${indexName}"`; | ||
if (action === "create") { | ||
@@ -795,9 +834,9 @@ const columns = index.fields.map((field2) => { | ||
}); | ||
statement2 += ` ON "${tableName}" (${columns.join(", ")})`; | ||
statement += ` ON "${existingModel.table}" (${columns.join(", ")})`; | ||
if (index.filter) { | ||
const withStatement = handleWith(models, existingModel, params, index.filter); | ||
statement2 += ` WHERE (${withStatement})`; | ||
statement += ` WHERE (${withStatement})`; | ||
} | ||
} | ||
dependencyStatements.push({ statement: statement2, params }); | ||
dependencyStatements.push({ statement, params }); | ||
} | ||
@@ -807,3 +846,3 @@ if (entity === "trigger") { | ||
const params = []; | ||
let statement2 = `${tableAction} TRIGGER "${triggerName}"`; | ||
let statement = `${statementAction} TRIGGER "${triggerName}"`; | ||
if (action === "create") { | ||
@@ -825,3 +864,3 @@ const trigger = jsonValue; | ||
} | ||
statementParts.push("ON", `"${tableName}"`); | ||
statementParts.push("ON", `"${existingModel.table}"`); | ||
if (trigger.filter || trigger.effects.some((query2) => findInObject(query2, RONIN_MODEL_SYMBOLS.FIELD))) { | ||
@@ -849,17 +888,7 @@ statementParts.push("FOR EACH ROW"); | ||
if (effectStatements.length > 1) statementParts.push("END"); | ||
statement2 += ` ${statementParts.join(" ")}`; | ||
statement += ` ${statementParts.join(" ")}`; | ||
} | ||
dependencyStatements.push({ statement: statement2, params }); | ||
dependencyStatements.push({ statement, params }); | ||
} | ||
const pluralType = PLURAL_MODEL_ENTITIES[entity]; | ||
const field = `${RONIN_MODEL_SYMBOLS.FIELD}${pluralType}`; | ||
const targetEntityIndex = existingModel[pluralType]?.findIndex( | ||
(entity2) => entity2.slug === slug | ||
); | ||
if ((action === "alter" || action === "drop") && (typeof targetEntityIndex === "undefined" || targetEntityIndex === -1)) { | ||
throw new RoninError({ | ||
message: `No ${entity} with slug "${slug}" defined in model "${existingModel.name}".`, | ||
code: MODEL_ENTITY_ERROR_CODES[entity] | ||
}); | ||
} | ||
let json; | ||
@@ -886,5 +915,19 @@ switch (action) { | ||
const targetEntity = existingModel[pluralType]; | ||
delete targetEntity[targetEntityIndex]; | ||
targetEntity.splice(targetEntityIndex, 1); | ||
} | ||
} | ||
const currentSystemModels = models.filter(({ system }) => { | ||
return system?.model === existingModel.slug; | ||
}); | ||
const newSystemModels = getSystemModels(models, existingModel); | ||
for (const systemModel of currentSystemModels) { | ||
const exists = newSystemModels.find((model2) => model2.slug === systemModel.slug); | ||
if (exists) continue; | ||
composeSystemModelStatement(models, dependencyStatements, "drop", systemModel); | ||
} | ||
for (const systemModel of newSystemModels) { | ||
const exists = currentSystemModels.find((model2) => model2.slug === systemModel.slug); | ||
if (exists) continue; | ||
composeSystemModelStatement(models, dependencyStatements, "create", systemModel); | ||
} | ||
return { | ||
@@ -1260,3 +1303,10 @@ set: { | ||
// src/utils/index.ts | ||
var compileQueryInput = (query, models, statementParams, options) => { | ||
var compileQueryInput = (defaultQuery, models, statementParams, options) => { | ||
const dependencyStatements = []; | ||
const query = transformMetaQuery( | ||
models, | ||
dependencyStatements, | ||
statementParams, | ||
defaultQuery | ||
); | ||
const parsedQuery = splitQuery(query); | ||
@@ -1267,3 +1317,2 @@ const { queryType, queryModel, queryInstructions } = parsedQuery; | ||
let instructions = formatIdentifiers(model, queryInstructions); | ||
const dependencyStatements = []; | ||
const returning = options?.returning ?? true; | ||
@@ -1420,3 +1469,7 @@ if (instructions && Object.hasOwn(instructions, "for")) { | ||
compileQueries = (queries, models, options) => { | ||
const modelList = addSystemModels(models).map((model) => { | ||
const modelList = [ | ||
ROOT_MODEL, | ||
...models.flatMap((model) => getSystemModels(models, model)), | ||
...models | ||
].map((model) => { | ||
return addDefaultModelFields(model, true); | ||
@@ -1430,13 +1483,6 @@ }); | ||
for (const query of queries) { | ||
const statementValues = options?.inlineParams ? null : []; | ||
const transformedQuery = transformMetaQuery( | ||
modelListWithPresets, | ||
dependencyStatements, | ||
statementValues, | ||
query | ||
); | ||
const result = compileQueryInput( | ||
transformedQuery, | ||
query, | ||
modelListWithPresets, | ||
statementValues | ||
options?.inlineParams ? null : [] | ||
); | ||
@@ -1443,0 +1489,0 @@ dependencyStatements.push(...result.dependencies); |
{ | ||
"name": "@ronin/compiler", | ||
"version": "0.9.0-leo-ron-1083-experimental-204", | ||
"version": "0.9.0-leo-ron-1083-experimental-205", | ||
"type": "module", | ||
@@ -5,0 +5,0 @@ "description": "Compiles RONIN queries to SQL statements.", |
Sorry, the diff of this file is too big to display
449022
7514