mirror of
https://github.com/stackrender/stackrender.git
synced 2026-09-10 03:05:42 +00:00
284 lines
8.9 KiB
TypeScript
284 lines
8.9 KiB
TypeScript
import { DataType } from "@/lib/schemas/data-type-schema";
|
|
import { DatabaseDialect } from "@/lib/database";
|
|
import { BaseSqlImporter, ExtractedStatments, ParsedDatabaase, ParsedField } from "./base-sql-importer";
|
|
import { init, parse, format, validate, Statement } from '@guanmingchiu/sqlparser-ts';
|
|
import { TableInsertType } from "@/lib/schemas/table-schema";
|
|
import { FieldInsertType } from "@/lib/schemas/field-schema";
|
|
|
|
interface PostgreSQLType {
|
|
name: string;
|
|
values: string[]
|
|
}
|
|
|
|
export class PostgreSqlImporter extends BaseSqlImporter {
|
|
|
|
private enums: PostgreSQLType[] = [];
|
|
|
|
public constructor(data_types: DataType[]) {
|
|
super(data_types);
|
|
this.dialect = DatabaseDialect.POSTGRES;
|
|
}
|
|
|
|
public parseSql(sql: string) {
|
|
try {
|
|
|
|
this.enums = [];
|
|
|
|
const cleaned: string = this.cleanSql(sql);
|
|
|
|
const { createPostgresTypesStatements, alterSequenceStatments } = this.extractStatments(cleaned);
|
|
if (createPostgresTypesStatements)
|
|
for (const statment of createPostgresTypesStatements) {
|
|
const astStatment: any = parse(statment, this.dialect as any)?.pop();
|
|
if (astStatment?.CreateType) {
|
|
this.enums.push(this.astToEnum(astStatment.CreateType));
|
|
}
|
|
}
|
|
|
|
const parsedDatabaase: ParsedDatabaase = super.parseSql(sql);
|
|
|
|
if (alterSequenceStatments) {
|
|
for (const statment of alterSequenceStatments) {
|
|
try {
|
|
this.processAlterSequenceStatements(statment, parsedDatabaase.tables);
|
|
} catch (error) {
|
|
parsedDatabaase.errors.push(error as Error);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
return parsedDatabaase;
|
|
} catch (error) {
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
|
|
protected processAlterSequenceStatements(statment: string, tables: TableInsertType[]) {
|
|
|
|
const regex =
|
|
/ALTER\s+SEQUENCE\s+[\w"]+\.[\w"]+\s+OWNED\s+BY\s+("?[\w]+"?)\.("?[\w]+"?)\.("?[\w]+"?)/i;
|
|
|
|
const match = statment.match(regex);
|
|
|
|
if (!match) {
|
|
return;
|
|
}
|
|
|
|
const schema = match[1].replace(/"/g, '');
|
|
const tableName = match[2].replace(/"/g, '');
|
|
const columnName = match[3].replace(/"/g, '');
|
|
|
|
|
|
const table: TableInsertType | undefined = tables.find((table: TableInsertType) => table.name == tableName);
|
|
if (!table) {
|
|
throw Error(`table ${tableName} not found in : ${statment}`);
|
|
}
|
|
|
|
const index = table.fields?.findIndex((field: FieldInsertType) => field.name == columnName) ?? -1;
|
|
if (index < 0) {
|
|
throw Error(`field ${columnName} not found in : ${statment}`);
|
|
}
|
|
|
|
(table.fields as FieldInsertType[])[index].autoIncrement = true ;
|
|
|
|
|
|
|
|
}
|
|
|
|
protected astToField(ast: any, sequence: number): ParsedField {
|
|
const { field, fk_constraint } = super.astToField(ast, sequence);
|
|
|
|
if (typeof ast.data_type === "object" && ast.data_type.Custom) {
|
|
const typeName = ast.data_type.Custom[0]?.[0]?.Identifier?.value.toLowerCase();
|
|
// it's a serial type
|
|
if (typeName?.toLowerCase().includes("serial")) {
|
|
|
|
field.autoIncrement = true;
|
|
}
|
|
else {
|
|
const postgreSqlType: PostgreSQLType | undefined = this.enums.find((postgreSqlType: PostgreSQLType) => postgreSqlType.name == typeName);
|
|
if (postgreSqlType && postgreSqlType.values.length > 0) {
|
|
try {
|
|
field.values = JSON.stringify(postgreSqlType.values);
|
|
} catch (error) {
|
|
console.error("failed to parse postgresql type values")
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|
|
return { field, fk_constraint };
|
|
}
|
|
|
|
protected processDefaultValue(ast: any, dataType: DataType): string | undefined {
|
|
|
|
let defaultValue: string | undefined = super.processDefaultValue(ast, dataType);
|
|
|
|
if (!defaultValue) {
|
|
if (ast.Function) {
|
|
if (ast.Function.name?.[0]?.Identifier?.value == "gen_random_uuid") {
|
|
return "random";
|
|
}
|
|
}
|
|
}
|
|
|
|
return defaultValue
|
|
}
|
|
|
|
|
|
protected processDataType(typeName: string): DataType | undefined {
|
|
let dataType: DataType | undefined = super.processDataType(typeName);
|
|
|
|
if (!dataType) {
|
|
if (typeName?.toLowerCase().includes("serial")) {
|
|
// if it's a serial type , then get the base integer type (integer , smallint , bigint)
|
|
let baseType: string = typeName.toLowerCase() == "serial" ? "integer" : typeName.toLowerCase().replace("serial", "int");
|
|
return super.processDataType(baseType);
|
|
}
|
|
else {
|
|
const isEnum: boolean = Boolean(this.enums.find((postgreSqlType: PostgreSQLType) => postgreSqlType.name == typeName));
|
|
if (isEnum) {
|
|
return this.data_types.find((dataType: DataType) => dataType.name == "enum")
|
|
}
|
|
}
|
|
|
|
}
|
|
return dataType
|
|
}
|
|
|
|
private astToEnum(ast: any): PostgreSQLType {
|
|
|
|
const values: string[] | undefined = ast.representation?.Enum?.labels?.map((label: any) => label.value);
|
|
|
|
return {
|
|
name: ast.name?.[0]?.Identifier?.value,
|
|
values
|
|
} as PostgreSQLType;
|
|
}
|
|
|
|
protected extractStatments(sql: string): ExtractedStatments {
|
|
|
|
const createPostgresTypesStatements: string[] = [];
|
|
const alterSequenceStatments: string[] = [];
|
|
const { createIndexStatements, createTableStatements, alterTableStatements } = super.extractStatments(sql);
|
|
|
|
// Split into individual statements
|
|
const statements = sql
|
|
.split('\n')
|
|
.map(s => s.trim())
|
|
.filter(Boolean);
|
|
|
|
for (const stmt of statements) {
|
|
const upper = stmt.toUpperCase();
|
|
if (upper.startsWith('CREATE TYPE')) {
|
|
createPostgresTypesStatements.push(stmt);
|
|
}
|
|
if (upper.startsWith("ALTER SEQUENCE")) {
|
|
alterSequenceStatments.push(stmt);
|
|
}
|
|
}
|
|
|
|
return {
|
|
createIndexStatements,
|
|
createTableStatements,
|
|
alterTableStatements,
|
|
createPostgresTypesStatements,
|
|
alterSequenceStatments
|
|
};
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
|
|
|
|
CREATE TYPE "users_user_type_enum" AS ENUM (
|
|
'individual',
|
|
'agency',
|
|
'developer',
|
|
'admin',
|
|
'employee'
|
|
);
|
|
|
|
CREATE TYPE "users_provider_enum" AS ENUM (
|
|
'google.com',
|
|
'facebook.com',
|
|
'password',
|
|
'anonymous'
|
|
);
|
|
|
|
CREATE TABLE "users" (
|
|
id INTEGER NOT NULL PRIMARY KEY,
|
|
full_name VARCHAR NOT NULL,
|
|
email VARCHAR NOT NULL,
|
|
picture_url VARCHAR NULL,
|
|
phone_number VARCHAR NULL,
|
|
password_hash VARCHAR NOT NULL,
|
|
user_type users_user_type_enum NOT NULL DEFAULT 'individual',
|
|
provider users_provider_enum NOT NULL DEFAULT 'password',
|
|
birthday TIMESTAMP(3) NULL,
|
|
gender BOOLEAN NULL,
|
|
created_at TIMESTAMPTZ (6) NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMPTZ (6) NULL,
|
|
is_email_verified BOOLEAN NOT NULL DEFAULT FALSE
|
|
);
|
|
|
|
CREATE TABLE all_postgres_types (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
|
|
-- Numeric types
|
|
col_smallint SMALLINT NOT NULL DEFAULT 1,
|
|
col_integer INTEGER NOT NULL UNIQUE DEFAULT 1000,
|
|
col_bigint BIGINT NULL DEFAULT 10000,
|
|
|
|
col_decimal DECIMAL(10,2) NOT NULL DEFAULT 99.99,
|
|
col_numeric NUMERIC(8,3) NULL DEFAULT 123.456,
|
|
|
|
col_real REAL NULL DEFAULT 1.23,
|
|
col_double DOUBLE PRECISION NOT NULL DEFAULT 2.3456,
|
|
|
|
col_boolean BOOLEAN NOT NULL DEFAULT TRUE,
|
|
|
|
-- Date & time types
|
|
col_date DATE NOT NULL DEFAULT DATE '2024-01-01',
|
|
col_timestamp TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
col_timestamptz TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
col_time TIME(6) NULL DEFAULT TIME '12:34:56.123456',
|
|
|
|
-- String types
|
|
col_char CHAR(10) NOT NULL DEFAULT 'charval',
|
|
col_varchar VARCHAR(255) NULL UNIQUE DEFAULT 'varchar value',
|
|
col_text TEXT NULL DEFAULT 'some text',
|
|
|
|
-- Binary
|
|
col_bytea BYTEA NULL,
|
|
|
|
-- Enum (PostgreSQL requires type creation first)
|
|
col_enum TEXT NOT NULL DEFAULT 'A',
|
|
|
|
-- JSON
|
|
col_json JSON NULL,
|
|
col_jsonb JSONB NULL,
|
|
|
|
-- UUID
|
|
col_uuid UUID NOT NULL UNIQUE DEFAULT gen_random_uuid(),
|
|
|
|
-- Array
|
|
col_text_array TEXT[] NULL DEFAULT ARRAY['a','b','c'],
|
|
|
|
-- Range types
|
|
col_int_range INT4RANGE NULL,
|
|
col_ts_range TSRANGE NULL,
|
|
|
|
-- XML
|
|
col_xml XML NULL,
|
|
|
|
-- Some extra UNIQUE inline
|
|
col_unique_text TEXT UNIQUE
|
|
);
|
|
|
|
*/ |