mirror of
https://github.com/stackrender/stackrender.git
synced 2026-10-26 22:10:49 +00:00
307 lines
12 KiB
TypeScript
307 lines
12 KiB
TypeScript
import { DatabaseDialect, getDatabaseByDialect } from "@/lib/database";
|
|
import BaseDatabaseRenderer, { ASTStatment } from "./base-database-renderer";
|
|
import { AST } from "node-sql-parser";
|
|
import { format } from "sql-formatter";
|
|
import { TableType } from "@/lib/schemas/table-schema";
|
|
import { FieldType } from "@/lib/schemas/field-schema";
|
|
import { ForeignKeyActions, Modifiers, TimeDefaultValues } from "@/lib/field";
|
|
import { Cardinality, RelationshipType } from "@/lib/schemas/relationship-schema";
|
|
import { IndexType } from "@/lib/schemas/index-schema";
|
|
import { DataType } from "@/lib/schemas/data-type-schema";
|
|
|
|
|
|
|
|
export abstract class BaseSQLRenderer extends BaseDatabaseRenderer {
|
|
|
|
protected async astToSQL(ast: ASTStatment[]): Promise<string> {
|
|
// in the base SQL Render we use node-sql-parser .
|
|
// by simly passing the AST and formating the code .
|
|
// other renderers can override this method to handle the parsing based on the dielact
|
|
await this.readyPromise ;
|
|
|
|
let sql: string = this.parser.sqlify(ast as AST[], {
|
|
database: getDatabaseByDialect(this.dialect).name
|
|
});
|
|
return format(sql, { language: 'sql' });
|
|
}
|
|
|
|
protected createTableAst(table: TableType): ASTStatment[] | ASTStatment {
|
|
// get the tale defintion from parent render that contain only the field defintion and primary keys contraints
|
|
// this definition is commun amount (Mysql , MariaDB , Sqlite) , for postgres we need to override it and handle enums
|
|
const { field_definitions, constraints, indices_ast } = super.createTableAst(table) as any
|
|
// wrape those defintion with create table AST
|
|
return [{
|
|
keyword: "table",
|
|
type: "create",
|
|
table: [{
|
|
table: table.name
|
|
}],
|
|
create_definitions: [...field_definitions, ...constraints]
|
|
}, ...indices_ast] as any
|
|
}
|
|
|
|
|
|
protected getFieldDefinition(field: FieldType, table: TableType, ignorePkContraint: boolean): AST {
|
|
// we get the base field definition from the parent renderer
|
|
const definition: any = super.getFieldDefinition(field, table, ignorePkContraint)
|
|
// Mysql , MariaDB ,Have some additional attributes such as charset collation , the enum values are inline unlike Postgres , Unsigned , and ZERO FILL
|
|
// also we need default value expression that it is compatible with node-sql-parser
|
|
let valuesExpr: any | null;
|
|
// if the definition extracted values os this field is an enum .
|
|
// and for that we generate the values expression that's compatible with out parser
|
|
if (definition.values && definition.values.length > 0) {
|
|
const values = definition.values.map((value: string) => ({
|
|
type: "single_quote_string",
|
|
value
|
|
}));
|
|
|
|
valuesExpr = {
|
|
parentheses: true,
|
|
type: "expr_list",
|
|
value: values
|
|
}
|
|
}
|
|
|
|
const default_val = !definition.modifiers.includes(Modifiers.NO_DEFAULT) ? this.processDefaultValue(field) : undefined;
|
|
const unique: string | null = !definition.modifiers.includes(Modifiers.NO_UNIQUE) ? (field.unique ? "unique" : null) : null;
|
|
// return an AST statment for a field definition
|
|
return {
|
|
column: {
|
|
type: "column_ref",
|
|
column: (this.dialect == DatabaseDialect.MYSQL || this.dialect == DatabaseDialect.MARIADB) ? field.name : ({
|
|
expr: {
|
|
type: "double_quote_string", value: field.name,
|
|
}
|
|
}),
|
|
},
|
|
default_val,
|
|
unique,
|
|
auto_increment: definition.auto_increment ? "auto_increment" : undefined,
|
|
nullable: {
|
|
type: field.nullable ? "null" : "not null",
|
|
value: field.nullable ? "null" : "not null",
|
|
},
|
|
definition: {
|
|
dataType: definition.dataType,
|
|
length: definition.length,
|
|
scale: definition.scale,
|
|
expr: valuesExpr
|
|
},
|
|
primary_key: definition.primary_key ? "primary key" : null,
|
|
resource: "column"
|
|
} as any
|
|
}
|
|
|
|
protected createRelationshipAst(relationship: RelationshipType): ASTStatment {
|
|
// get the foriegn key , primary key source table and target table from the parent renderer
|
|
const { primaryKey, foreignKey, sourceTable, targetTable } = super.createRelationshipAst(relationship) as any;
|
|
let on_action: any[] = [];
|
|
// get the foriegn key actions for on delete and on cascade
|
|
if (relationship.onDelete) {
|
|
const value: string | null = this.foreignKeyActionToAst(relationship.onDelete as ForeignKeyActions);
|
|
|
|
if (value)
|
|
on_action.push({
|
|
type: "on delete",
|
|
value: {
|
|
type: "origin",
|
|
value
|
|
}
|
|
})
|
|
}
|
|
|
|
if (relationship.onUpdate) {
|
|
const value: string | null = this.foreignKeyActionToAst(relationship.onUpdate as ForeignKeyActions);
|
|
if (value)
|
|
on_action.push({
|
|
type: "on update",
|
|
value: {
|
|
type: "origin",
|
|
value
|
|
}
|
|
})
|
|
}
|
|
// return create foriegn key contraint for Mysql Dialect
|
|
return {
|
|
type: "alter",
|
|
keyword: "table",
|
|
table: [
|
|
{
|
|
|
|
table: targetTable.name
|
|
}
|
|
],
|
|
expr: [{
|
|
action: "add",
|
|
create_definitions: {
|
|
constraint: relationship.name,
|
|
definition: [
|
|
{
|
|
type: "column_ref",
|
|
table: null,
|
|
column: (this.dialect == DatabaseDialect.MYSQL || this.dialect == DatabaseDialect.MARIADB) ? foreignKey.name : ({
|
|
expr: {
|
|
type: "double_quote_string",
|
|
value: foreignKey.name
|
|
}
|
|
}),
|
|
}
|
|
],
|
|
constraint_type: "FOREIGN KEY",
|
|
keyword: "constraint",
|
|
resource: "constraint",
|
|
reference_definition: {
|
|
definition: [
|
|
{
|
|
type: "column_ref",
|
|
table: null,
|
|
column: (this.dialect == DatabaseDialect.MYSQL || this.dialect == DatabaseDialect.MARIADB) ? primaryKey.name : ({
|
|
expr: {
|
|
type: "double_quote_string",
|
|
value: primaryKey.name
|
|
}
|
|
})
|
|
}
|
|
],
|
|
table: [
|
|
{
|
|
db: null,
|
|
table: sourceTable.name
|
|
}
|
|
],
|
|
keyword: "references",
|
|
on_action
|
|
}
|
|
},
|
|
|
|
resource: "constraint",
|
|
type: "alter"
|
|
}]
|
|
} as any
|
|
}
|
|
|
|
|
|
|
|
protected foreignKeyActionToAst(action: ForeignKeyActions): string | null {
|
|
switch (action) {
|
|
case ForeignKeyActions.CASCADE:
|
|
return "cascade";
|
|
|
|
case ForeignKeyActions.SET_NULL:
|
|
return "set null";
|
|
|
|
case ForeignKeyActions.RESTRICT:
|
|
return "restrict";
|
|
|
|
case ForeignKeyActions.SET_DEFAULT:
|
|
return "set default";
|
|
}
|
|
return null;
|
|
}
|
|
|
|
protected getPrimaryKeyContraint(primaryKeys: FieldType[]): AST {
|
|
// primary key contraint
|
|
return {
|
|
constraint_type: "primary key",
|
|
resource: "constraint",
|
|
definition: primaryKeys.map((field: FieldType) => ({
|
|
column: field.name,
|
|
type: "column_ref"
|
|
}))
|
|
} as any
|
|
}
|
|
|
|
protected createIndexAst(table: TableType, index: IndexType): AST {
|
|
// create index AST for Mysql Dielect
|
|
return {
|
|
index: index.name,
|
|
type: "create",
|
|
table: { table: table.name },
|
|
keyword: "index",
|
|
on_kw: "on",
|
|
index_type: index.unique ? "unique" : null,
|
|
index_columns: index.fields.map((field: FieldType) => ({
|
|
column: field.name,
|
|
type: "column_ref"
|
|
}))
|
|
} as AST
|
|
}
|
|
|
|
protected processDefaultValue(field: FieldType): AST | null {
|
|
const definition = super.processDefaultValue(field) as any;
|
|
// if the field have a default value , then we generate an expression based on the type of the default value
|
|
if (definition && definition.default_value !== null) {
|
|
let default_val: any | null = null
|
|
if (definition.default_value_type == "string")
|
|
default_val = {
|
|
type: "default",
|
|
value: {
|
|
type: "single_quote_string",
|
|
value: field.defaultValue
|
|
}
|
|
}
|
|
else if (definition.default_value_type == "function" && field.defaultValue == TimeDefaultValues.NOW)
|
|
default_val = {
|
|
type: "default",
|
|
value: {
|
|
type: "function",
|
|
name: {
|
|
name: [
|
|
{
|
|
type: "origin",
|
|
value: "CURRENT_TIMESTAMP"
|
|
}
|
|
]
|
|
},
|
|
over: null
|
|
}
|
|
}
|
|
else if (definition.default_value_type == "function" && field.defaultValue == "random") {
|
|
default_val = {
|
|
type: "default",
|
|
value: {
|
|
type: "function",
|
|
name: {
|
|
name: [
|
|
{
|
|
type: "default",
|
|
value: this.dialect == DatabaseDialect.POSTGRES ? "gen_random_uuid" : "UUID"
|
|
}
|
|
]
|
|
},
|
|
args: {
|
|
type: "expr_list",
|
|
value: []
|
|
},
|
|
parentheses: this.dialect == DatabaseDialect.MARIADB
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
default_val = {
|
|
type: "default",
|
|
value: {
|
|
type: definition.default_value_type,
|
|
value: definition.default_value
|
|
}
|
|
}
|
|
}
|
|
return default_val;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
|
|
|
|
protected startTransaction(): string {
|
|
return "START TRANSACTION;";
|
|
}
|
|
|
|
protected commit(): string {
|
|
return "COMMIT;"
|
|
}
|
|
|
|
|
|
|
|
protected abstract getUsingAst(table: TableType, field: FieldType, dataType: DataType): AST | null;
|
|
} |