Initial commit - Event Planner application
This commit is contained in:
360
node_modules/@mikro-orm/sql/schema/DatabaseSchema.js
generated
vendored
Normal file
360
node_modules/@mikro-orm/sql/schema/DatabaseSchema.js
generated
vendored
Normal file
@@ -0,0 +1,360 @@
|
||||
import { ReferenceKind, isRaw } from '@mikro-orm/core';
|
||||
import { DatabaseTable } from './DatabaseTable.js';
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export class DatabaseSchema {
|
||||
name;
|
||||
#tables = [];
|
||||
#views = [];
|
||||
#namespaces = new Set();
|
||||
#nativeEnums = {}; // for postgres
|
||||
#platform;
|
||||
constructor(platform, name) {
|
||||
this.name = name;
|
||||
this.#platform = platform;
|
||||
}
|
||||
addTable(name, schema, comment) {
|
||||
const namespaceName = schema ?? this.name;
|
||||
const table = new DatabaseTable(this.#platform, name, namespaceName);
|
||||
table.nativeEnums = this.#nativeEnums;
|
||||
table.comment = comment;
|
||||
this.#tables.push(table);
|
||||
if (namespaceName != null) {
|
||||
this.#namespaces.add(namespaceName);
|
||||
}
|
||||
return table;
|
||||
}
|
||||
getTables() {
|
||||
return this.#tables;
|
||||
}
|
||||
/** @internal */
|
||||
setTables(tables) {
|
||||
this.#tables = tables;
|
||||
}
|
||||
/** @internal */
|
||||
setNamespaces(namespaces) {
|
||||
this.#namespaces = namespaces;
|
||||
}
|
||||
getTable(name) {
|
||||
return this.#tables.find(t => t.name === name || `${t.schema}.${t.name}` === name);
|
||||
}
|
||||
hasTable(name) {
|
||||
return !!this.getTable(name);
|
||||
}
|
||||
addView(name, schema, definition, materialized, withData) {
|
||||
const namespaceName = schema ?? this.name;
|
||||
const view = { name, schema: namespaceName, definition, materialized, withData };
|
||||
this.#views.push(view);
|
||||
if (namespaceName != null) {
|
||||
this.#namespaces.add(namespaceName);
|
||||
}
|
||||
return view;
|
||||
}
|
||||
getViews() {
|
||||
return this.#views;
|
||||
}
|
||||
/** @internal */
|
||||
setViews(views) {
|
||||
this.#views = views;
|
||||
}
|
||||
getView(name) {
|
||||
return this.#views.find(v => v.name === name || `${v.schema}.${v.name}` === name);
|
||||
}
|
||||
hasView(name) {
|
||||
return !!this.getView(name);
|
||||
}
|
||||
setNativeEnums(nativeEnums) {
|
||||
this.#nativeEnums = nativeEnums;
|
||||
for (const nativeEnum of Object.values(nativeEnums)) {
|
||||
if (nativeEnum.schema && nativeEnum.schema !== '*') {
|
||||
this.#namespaces.add(nativeEnum.schema);
|
||||
}
|
||||
}
|
||||
}
|
||||
getNativeEnums() {
|
||||
return this.#nativeEnums;
|
||||
}
|
||||
getNativeEnum(name) {
|
||||
return this.#nativeEnums[name];
|
||||
}
|
||||
hasNamespace(namespace) {
|
||||
return this.#namespaces.has(namespace);
|
||||
}
|
||||
hasNativeEnum(name) {
|
||||
return name in this.#nativeEnums;
|
||||
}
|
||||
getNamespaces() {
|
||||
return [...this.#namespaces];
|
||||
}
|
||||
static async create(connection, platform, config, schemaName, schemas, takeTables, skipTables, skipViews) {
|
||||
const schema = new DatabaseSchema(platform, schemaName ?? config.get('schema') ?? platform.getDefaultSchemaName());
|
||||
const allTables = await platform.getSchemaHelper().getAllTables(connection, schemas);
|
||||
const parts = config.get('migrations').tableName.split('.');
|
||||
const migrationsTableName = parts[1] ?? parts[0];
|
||||
const migrationsSchemaName = parts.length > 1 ? parts[0] : config.get('schema', platform.getDefaultSchemaName());
|
||||
const tables = allTables.filter(
|
||||
t =>
|
||||
this.isTableNameAllowed(t.table_name, takeTables, skipTables) &&
|
||||
(t.table_name !== migrationsTableName || (t.schema_name && t.schema_name !== migrationsSchemaName)),
|
||||
);
|
||||
await platform
|
||||
.getSchemaHelper()
|
||||
.loadInformationSchema(schema, connection, tables, schemas && schemas.length > 0 ? schemas : undefined);
|
||||
// Load views from database
|
||||
await platform.getSchemaHelper().loadViews(schema, connection);
|
||||
// Load materialized views (PostgreSQL only)
|
||||
if (platform.supportsMaterializedViews()) {
|
||||
await platform.getSchemaHelper().loadMaterializedViews(schema, connection, schemaName);
|
||||
}
|
||||
// Filter out skipped views
|
||||
if (skipViews && skipViews.length > 0) {
|
||||
schema.#views = schema.#views.filter(v => this.isNameAllowed(v.name, skipViews));
|
||||
}
|
||||
return schema;
|
||||
}
|
||||
static fromMetadata(metadata, platform, config, schemaName, em) {
|
||||
const schema = new DatabaseSchema(platform, schemaName ?? config.get('schema'));
|
||||
const nativeEnums = {};
|
||||
const skipColumns = config.get('schemaGenerator').skipColumns || {};
|
||||
for (const meta of metadata) {
|
||||
// Skip view entities when collecting native enums
|
||||
if (meta.view) {
|
||||
continue;
|
||||
}
|
||||
for (const prop of meta.props) {
|
||||
if (prop.nativeEnumName) {
|
||||
let key = prop.nativeEnumName;
|
||||
let enumName = prop.nativeEnumName;
|
||||
let enumSchema = meta.schema ?? schema.name;
|
||||
if (key.includes('.')) {
|
||||
const [explicitSchema, ...parts] = prop.nativeEnumName.split('.');
|
||||
enumName = parts.join('.');
|
||||
key = enumName;
|
||||
enumSchema = explicitSchema;
|
||||
}
|
||||
if (enumSchema && enumSchema !== '*' && enumSchema !== platform.getDefaultSchemaName()) {
|
||||
key = enumSchema + '.' + key;
|
||||
}
|
||||
nativeEnums[key] = {
|
||||
name: enumName,
|
||||
schema: enumSchema,
|
||||
items: prop.items?.map(val => '' + val) ?? [],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
schema.setNativeEnums(nativeEnums);
|
||||
for (const meta of metadata) {
|
||||
// Handle view entities separately
|
||||
if (meta.view) {
|
||||
const viewDefinition = this.getViewDefinition(meta, em, platform);
|
||||
if (viewDefinition) {
|
||||
schema.addView(
|
||||
meta.collection,
|
||||
this.getSchemaName(meta, config, schemaName),
|
||||
viewDefinition,
|
||||
meta.materialized,
|
||||
meta.withData,
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const table = schema.addTable(meta.collection, this.getSchemaName(meta, config, schemaName));
|
||||
table.comment = meta.comment;
|
||||
// For TPT child entities, only use ownProps (properties defined in this entity only)
|
||||
// For all other entities (including TPT root), use all props
|
||||
const propsToProcess =
|
||||
meta.inheritanceType === 'tpt' && meta.tptParent && meta.ownProps ? meta.ownProps : meta.props;
|
||||
for (const prop of propsToProcess) {
|
||||
if (!this.shouldHaveColumn(meta, prop, skipColumns)) {
|
||||
continue;
|
||||
}
|
||||
table.addColumnFromProperty(prop, meta, config);
|
||||
}
|
||||
// For TPT child entities, always include the PK columns (they form the FK to parent)
|
||||
if (meta.inheritanceType === 'tpt' && meta.tptParent) {
|
||||
const pkProps = meta.primaryKeys.map(pk => meta.properties[pk]);
|
||||
for (const pkProp of pkProps) {
|
||||
// Only add if not already added (it might be in ownProps if defined in this entity)
|
||||
if (!propsToProcess.includes(pkProp)) {
|
||||
table.addColumnFromProperty(pkProp, meta, config);
|
||||
}
|
||||
// Child PK must not be autoincrement — it references the parent PK via FK
|
||||
for (const field of pkProp.fieldNames) {
|
||||
const col = table.getColumn(field);
|
||||
if (col) {
|
||||
col.autoincrement = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Add FK from child PK to parent PK with ON DELETE CASCADE
|
||||
this.addTPTForeignKey(table, meta, config, platform);
|
||||
}
|
||||
meta.indexes.forEach(index => table.addIndex(meta, index, 'index'));
|
||||
meta.uniques.forEach(index => table.addIndex(meta, index, 'unique'));
|
||||
// For TPT child entities, the PK is also defined here
|
||||
const pkPropsForIndex =
|
||||
meta.inheritanceType === 'tpt' && meta.tptParent
|
||||
? meta.primaryKeys.map(pk => meta.properties[pk])
|
||||
: meta.props.filter(prop => prop.primary);
|
||||
table.addIndex(meta, { properties: pkPropsForIndex.map(prop => prop.name) }, 'primary');
|
||||
for (const check of meta.checks) {
|
||||
const columnName = check.property ? meta.properties[check.property].fieldNames[0] : undefined;
|
||||
const expression = isRaw(check.expression)
|
||||
? platform.formatQuery(check.expression.sql, check.expression.params)
|
||||
: check.expression;
|
||||
table.addCheck({
|
||||
name: check.name,
|
||||
expression,
|
||||
definition: `check (${expression})`,
|
||||
columnName,
|
||||
});
|
||||
}
|
||||
}
|
||||
return schema;
|
||||
}
|
||||
static getViewDefinition(meta, em, platform) {
|
||||
if (typeof meta.expression === 'string') {
|
||||
return meta.expression;
|
||||
}
|
||||
// Expression is a function, need to evaluate it
|
||||
/* v8 ignore next */
|
||||
if (!em) {
|
||||
return undefined;
|
||||
}
|
||||
const result = meta.expression(em, {}, {});
|
||||
// Async expressions are not supported for view entities
|
||||
if (result && typeof result.then === 'function') {
|
||||
throw new Error(
|
||||
`View entity ${meta.className} expression returned a Promise. Async expressions are not supported for view entities.`,
|
||||
);
|
||||
}
|
||||
/* v8 ignore next */
|
||||
if (typeof result === 'string') {
|
||||
return result;
|
||||
}
|
||||
/* v8 ignore next */
|
||||
if (isRaw(result)) {
|
||||
return platform.formatQuery(result.sql, result.params);
|
||||
}
|
||||
// Check if it's a QueryBuilder (has getFormattedQuery method)
|
||||
if (result && typeof result.getFormattedQuery === 'function') {
|
||||
return result.getFormattedQuery();
|
||||
}
|
||||
/* v8 ignore next - fallback for unknown result types */
|
||||
return undefined;
|
||||
}
|
||||
static getSchemaName(meta, config, schema) {
|
||||
return (meta.schema === '*' ? schema : meta.schema) ?? config.get('schema');
|
||||
}
|
||||
/**
|
||||
* Add a foreign key from a TPT child entity's PK to its parent entity's PK.
|
||||
* This FK uses ON DELETE CASCADE to ensure child rows are deleted when parent is deleted.
|
||||
*/
|
||||
static addTPTForeignKey(table, meta, config, platform) {
|
||||
const parent = meta.tptParent;
|
||||
const pkColumnNames = meta.primaryKeys.flatMap(pk => meta.properties[pk].fieldNames);
|
||||
const parentPkColumnNames = parent.primaryKeys.flatMap(pk => parent.properties[pk].fieldNames);
|
||||
// Determine the parent table name with schema
|
||||
const parentSchema =
|
||||
parent.schema === '*' ? undefined : (parent.schema ?? config.get('schema', platform.getDefaultSchemaName()));
|
||||
const parentTableName = parentSchema ? `${parentSchema}.${parent.tableName}` : parent.tableName;
|
||||
// Create FK constraint name
|
||||
const constraintName = platform.getIndexName(table.name, pkColumnNames, 'foreign');
|
||||
// Add the foreign key to the table
|
||||
const fks = table.getForeignKeys();
|
||||
fks[constraintName] = {
|
||||
constraintName,
|
||||
columnNames: pkColumnNames,
|
||||
localTableName: table.getShortestName(false),
|
||||
referencedColumnNames: parentPkColumnNames,
|
||||
referencedTableName: parentTableName,
|
||||
deleteRule: 'cascade', // TPT always uses cascade delete
|
||||
updateRule: 'cascade', // TPT always uses cascade update
|
||||
};
|
||||
}
|
||||
static matchName(name, nameToMatch) {
|
||||
return typeof nameToMatch === 'string'
|
||||
? name.toLocaleLowerCase() === nameToMatch.toLocaleLowerCase()
|
||||
: nameToMatch.test(name);
|
||||
}
|
||||
static isNameAllowed(name, skipNames) {
|
||||
return !(skipNames?.some(pattern => this.matchName(name, pattern)) ?? false);
|
||||
}
|
||||
static isTableNameAllowed(tableName, takeTables, skipTables) {
|
||||
return (
|
||||
(takeTables?.some(tableNameToMatch => this.matchName(tableName, tableNameToMatch)) ?? true) &&
|
||||
this.isNameAllowed(tableName, skipTables)
|
||||
);
|
||||
}
|
||||
static shouldHaveColumn(meta, prop, skipColumns) {
|
||||
if (prop.persist === false || (prop.columnTypes?.length ?? 0) === 0) {
|
||||
return false;
|
||||
}
|
||||
// Check if column should be skipped
|
||||
if (skipColumns) {
|
||||
const tableName = meta.tableName;
|
||||
const tableSchema = meta.schema;
|
||||
const fullTableName = tableSchema ? `${tableSchema}.${tableName}` : tableName;
|
||||
// Check for skipColumns by table name or fully qualified table name
|
||||
const columnsToSkip = skipColumns[tableName] || skipColumns[fullTableName];
|
||||
if (columnsToSkip) {
|
||||
for (const fieldName of prop.fieldNames) {
|
||||
if (columnsToSkip.some(pattern => this.matchName(fieldName, pattern))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (prop.kind === ReferenceKind.EMBEDDED && prop.object) {
|
||||
return true;
|
||||
}
|
||||
const getRootProperty = prop => (prop.embedded ? getRootProperty(meta.properties[prop.embedded[0]]) : prop);
|
||||
const rootProp = getRootProperty(prop);
|
||||
if (rootProp.kind === ReferenceKind.EMBEDDED) {
|
||||
return prop === rootProp || !rootProp.object;
|
||||
}
|
||||
return (
|
||||
[ReferenceKind.SCALAR, ReferenceKind.MANY_TO_ONE].includes(prop.kind) ||
|
||||
(prop.kind === ReferenceKind.ONE_TO_ONE && prop.owner)
|
||||
);
|
||||
}
|
||||
toJSON() {
|
||||
return {
|
||||
name: this.name,
|
||||
namespaces: [...this.#namespaces],
|
||||
tables: this.#tables,
|
||||
views: this.#views,
|
||||
nativeEnums: this.#nativeEnums,
|
||||
};
|
||||
}
|
||||
prune(schema, wildcardSchemaTables) {
|
||||
const hasWildcardSchema = wildcardSchemaTables.length > 0;
|
||||
this.#tables = this.#tables.filter(table => {
|
||||
return (
|
||||
(!schema && !hasWildcardSchema) || // no schema specified and we don't have any multi-schema entity
|
||||
table.schema === schema || // specified schema matches the table's one
|
||||
(!schema && !wildcardSchemaTables.includes(table.name))
|
||||
); // no schema specified and the table has fixed one provided
|
||||
});
|
||||
this.#views = this.#views.filter(view => {
|
||||
/* v8 ignore next */
|
||||
return (
|
||||
(!schema && !hasWildcardSchema) ||
|
||||
view.schema === schema ||
|
||||
(!schema && !wildcardSchemaTables.includes(view.name))
|
||||
);
|
||||
});
|
||||
// remove namespaces of ignored tables and views
|
||||
for (const ns of this.#namespaces) {
|
||||
if (
|
||||
!this.#tables.some(t => t.schema === ns) &&
|
||||
!this.#views.some(v => v.schema === ns) &&
|
||||
!Object.values(this.#nativeEnums).some(e => e.schema === ns)
|
||||
) {
|
||||
this.#namespaces.delete(ns);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user