mirror of
https://github.com/stackrender/stackrender.git
synced 2026-09-10 11:15:42 +00:00
imoprt Postgres database features implemented
This commit is contained in:
@@ -26,6 +26,7 @@ export const databases = sqliteTable('databases', {
|
||||
export const databaseRelations = relations(databases, ({ many }) => ({
|
||||
tables: many(tables),
|
||||
relationships: many(relationships),
|
||||
|
||||
}));
|
||||
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ const ImportDatabaseModal: React.FC<ModalProps> = ({ isOpen, onOpenChange }) =>
|
||||
const { t } = useTranslation();
|
||||
const { resolvedTheme } = useTheme();
|
||||
const { database } = useDatabase();
|
||||
const { data_types, createTable , createRelationship} = useDatabaseOperations();
|
||||
const { data_types, importDatabase } = useDatabaseOperations();
|
||||
const [sqlCode, setSqlCode] = useState<string>("");
|
||||
let currentOption: ImportDatabaseOption | undefined = useMemo(() => {
|
||||
return options.find((option: ImportDatabaseOption) => option.dialect == database?.dialect)
|
||||
@@ -61,23 +61,13 @@ const ImportDatabaseModal: React.FC<ModalProps> = ({ isOpen, onOpenChange }) =>
|
||||
setSelectedMethodId([selectedType]);
|
||||
}
|
||||
|
||||
|
||||
const selectedImportMethod: ImportDatabaseMethod = useMemo(() => {
|
||||
return currentOption?.methods.find((method: ImportDatabaseMethod) => method.id == selectedMethodId?.[0]) as ImportDatabaseMethod;
|
||||
}, [selectedMethodId, currentOption])
|
||||
|
||||
const importDatabase = useCallback( async () => {
|
||||
|
||||
const {tables , relationships } = SqlToDatabase(sqlCode, data_types, database?.dialect as DatabaseDialect);
|
||||
|
||||
|
||||
for (const table of tables) {
|
||||
await createTable(table)
|
||||
}
|
||||
|
||||
for ( const relationship of relationships ) {
|
||||
await createRelationship(relationship) ;
|
||||
}
|
||||
const onImport = useCallback(async () => {
|
||||
const { tables, relationships, indices } = SqlToDatabase(sqlCode, data_types, database?.dialect as DatabaseDialect);
|
||||
return await importDatabase(tables, relationships, indices)
|
||||
}, [sqlCode, database?.dialect, data_types]);
|
||||
|
||||
|
||||
@@ -88,7 +78,7 @@ const ImportDatabaseModal: React.FC<ModalProps> = ({ isOpen, onOpenChange }) =>
|
||||
title={t("modals.import_database.title")}
|
||||
actionName={t("modals.import_database.import")}
|
||||
className="min-w-[860px] max-w-[860px]"
|
||||
actionHandler={importDatabase}
|
||||
actionHandler={onImport}
|
||||
>
|
||||
<div className="flex flex-col gap-4 ">
|
||||
<div className="w-full ">
|
||||
|
||||
@@ -23,6 +23,7 @@ interface DatabaseDataContextType {
|
||||
getField: (tableId: string, id: string) => FieldType | undefined,
|
||||
getDefaultPrimaryKeyType : (dialect? : DatabaseDialect) => DataType | undefined
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -58,6 +59,10 @@ interface DatabaseOperationsContextType {
|
||||
deleteMultiRelationships: (ids: string[]) => Promise<QueryResult>,
|
||||
// execute the diff operation whenver user click in undo or redo
|
||||
executeDbDiffOps: (operations: DBDiffOperation[]) => void,
|
||||
|
||||
// insert databse tables , relationships , indices in one operation
|
||||
importDatabase : (tables : TableInsertType[] , relationships : RelationshipInsertType[] , indices : IndexInsertType[]) => Promise<void> ;
|
||||
|
||||
}
|
||||
|
||||
export const DatabaseDataContext = createContext<DatabaseDataContextType>({} as DatabaseDataContextType);
|
||||
|
||||
@@ -13,7 +13,7 @@ import { DBDiffOperation } from "@/utils/database";
|
||||
import { DatabaseInsertType, DatabaseType, databases as databaseModel } from "@/lib/schemas/database-schema";
|
||||
import { getTimestamp, groupBy } from "@/utils/utils";
|
||||
import { IndexInsertType, indices } from "@/lib/schemas/index-schema";
|
||||
import { field_indices } from "@/lib/schemas/field_index-schema";
|
||||
import { field_indices, FieldIndexInsertType } from "@/lib/schemas/field_index-schema";
|
||||
import { v4 } from "uuid";
|
||||
import { DataType } from "@/lib/schemas/data-type-schema";
|
||||
import { Modifiers } from "@/lib/field";
|
||||
@@ -382,6 +382,51 @@ const DatabaseProvider: React.FC<Props> = ({ children }) => {
|
||||
|
||||
}, [data_types]);
|
||||
|
||||
|
||||
|
||||
const importDatabase = useCallback(async (importedTables: TableInsertType[], importedRelationships: RelationshipInsertType[], importedIndices: IndexInsertType[]) => {
|
||||
if (currentDatabaseId) {
|
||||
return await db.transaction(async (tx) => {
|
||||
for (const table of importedTables) {
|
||||
await tx.insert(tables).values({
|
||||
...table,
|
||||
databaseId: currentDatabaseId,
|
||||
createdAt: table.createdAt || getTimestamp()
|
||||
} as TableInsertType);
|
||||
await updateDbNumTables(currentDatabaseId, tx);
|
||||
if (table.fields) {
|
||||
await tx.insert(fields).values(
|
||||
table.fields.map((field: FieldInsertType) => ({ ...field, tableId: table.id }))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (const relationship of importedRelationships) {
|
||||
await tx.insert(relationships).values({
|
||||
...relationship,
|
||||
databaseId: currentDatabaseId,
|
||||
createdAt: relationship.createdAt || getTimestamp()
|
||||
});
|
||||
}
|
||||
|
||||
for (const index of importedIndices) {
|
||||
await tx.insert(indices).values({
|
||||
...index,
|
||||
createdAt: index.createdAt ? index.createdAt : getTimestamp()
|
||||
});
|
||||
|
||||
if (index.fieldIndices) {
|
||||
await tx.insert(field_indices).values(
|
||||
index.fieldIndices.map((fieldIndex: FieldIndexInsertType) => ({ ...fieldIndex, indexId: index.id }))
|
||||
)
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
throw Error("no database selected")
|
||||
}
|
||||
}, [currentDatabaseId])
|
||||
|
||||
const databaseOpsValue = useMemo(() => ({
|
||||
|
||||
createDatabase,
|
||||
@@ -406,6 +451,7 @@ const DatabaseProvider: React.FC<Props> = ({ children }) => {
|
||||
editIndex,
|
||||
deleteIndex,
|
||||
editFieldIndices,
|
||||
importDatabase,
|
||||
data_types,
|
||||
grouped_data_types
|
||||
}), [
|
||||
@@ -430,6 +476,7 @@ const DatabaseProvider: React.FC<Props> = ({ children }) => {
|
||||
editIndex,
|
||||
deleteIndex,
|
||||
editFieldIndices,
|
||||
importDatabase,
|
||||
data_types,
|
||||
grouped_data_types
|
||||
]);
|
||||
|
||||
@@ -48,7 +48,7 @@ export const DatabaseToAst = (database: DatabaseType, data_types: DataType[]) =>
|
||||
}, table));
|
||||
};
|
||||
} catch (error) {
|
||||
throw error ;
|
||||
throw error;
|
||||
|
||||
}
|
||||
return dbAst;
|
||||
@@ -78,13 +78,13 @@ export const TableToAst = (table: TableType, data_types: DataType[], isPostgresD
|
||||
|
||||
const filed_definitions = table.fields.map((field: FieldType) => {
|
||||
const isPostgresEnum: boolean = isPostgresDialect && field.type.name == "enum";
|
||||
|
||||
|
||||
return FieldToAst({
|
||||
...field,
|
||||
type: !(isPostgresEnum) ?
|
||||
data_types.find((dataType: DataType) => dataType.id == field.typeId) as DataType :
|
||||
{ name: `${field.name.toLowerCase()}_enum` } as DataType,
|
||||
}, multiPrimaryKeys , !isPostgresEnum)
|
||||
}, multiPrimaryKeys, !isPostgresEnum)
|
||||
})
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ export const FieldToAst = (field: FieldType, ignorePrimaryKey: boolean = false,
|
||||
let valuesExpr: any | null;
|
||||
|
||||
let default_val: any | null = null;
|
||||
let dataType: string | undefined = upperCaseType ? field.type?.name?.toLocaleUpperCase() : field.type?.name as string | undefined ;
|
||||
let dataType: string | undefined = upperCaseType ? field.type?.name?.toLocaleUpperCase() : field.type?.name as string | undefined;
|
||||
|
||||
if (modifiers.includes(Modifiers.PRECISION) && field.precision) {
|
||||
length = field.precision;
|
||||
@@ -211,7 +211,21 @@ export const FieldToAst = (field: FieldType, ignorePrimaryKey: boolean = false,
|
||||
}
|
||||
}
|
||||
|
||||
// field.type?.name == "varchar" && (field.type?.dialect == DatabaseDialect.MYSQL || field.type?.dialect == DatabaseDialect.MARIADB) ? 255 : null
|
||||
let auto_increment: string | undefined;
|
||||
if (modifiers.includes(Modifiers.AUTO_INCREMENT) && field.autoIncrement && field.type.dialect != DatabaseDialect.POSTGRES) {
|
||||
auto_increment = "auto_increment"
|
||||
} else if (modifiers.includes(Modifiers.AUTO_INCREMENT) && field.autoIncrement && field.type.dialect == DatabaseDialect.POSTGRES) {
|
||||
if (dataType == "INTEGER")
|
||||
dataType = "SERIAL";
|
||||
else
|
||||
dataType = dataType?.replace("INT", "SERIAL");
|
||||
|
||||
} else {
|
||||
auto_increment = undefined;
|
||||
}
|
||||
|
||||
|
||||
|
||||
return {
|
||||
column: {
|
||||
type: "column_ref",
|
||||
@@ -225,8 +239,7 @@ export const FieldToAst = (field: FieldType, ignorePrimaryKey: boolean = false,
|
||||
character_set,
|
||||
default_val,
|
||||
unique: field.unique ? "unique" : null,
|
||||
auto_increment: (modifiers.includes(Modifiers.AUTO_INCREMENT) && field.autoIncrement && field.type.dialect != DatabaseDialect.POSTGRES) ? "auto_increment" : null,
|
||||
|
||||
auto_increment,
|
||||
nullable: {
|
||||
type: field.nullable ? "null" : "not null",
|
||||
value: field.nullable ? "null" : "not null",
|
||||
@@ -264,9 +277,9 @@ export const IndexToAst = (index: IndexType, table: TableType) => {
|
||||
export const PotgresEnumToAst = (field: FieldType) => {
|
||||
|
||||
const jsonValues = field.values ? JSON.parse(field.values) : [];
|
||||
|
||||
|
||||
return {
|
||||
as : "as" ,
|
||||
as: "as",
|
||||
type: "create",
|
||||
resource: "enum",
|
||||
name: {
|
||||
|
||||
@@ -8,6 +8,9 @@ import { parse } from 'pgsql-ast-parser';
|
||||
import { DatabaseDialect } from "@/lib/database";
|
||||
import { randomColor } from "@/lib/colors";
|
||||
import { Cardinality, RelationshipInsertType } from "@/lib/schemas/relationship-schema";
|
||||
import { IndexInsertType } from "@/lib/schemas/index-schema";
|
||||
import { relationshipToAst } from "./database_to_ast";
|
||||
import { DatabaseInsertType } from "@/lib/schemas/database-schema";
|
||||
|
||||
|
||||
export const SqlToDatabase = (sql: string, data_types: DataType[], dialect: DatabaseDialect) => {
|
||||
@@ -16,8 +19,11 @@ export const SqlToDatabase = (sql: string, data_types: DataType[], dialect: Data
|
||||
const alterTableStatements: string[] = [];
|
||||
const createIndexStatements: string[] = [];
|
||||
const createPostgresTypesStatements: string[] = [];
|
||||
|
||||
const tables: TableInsertType[] = [];
|
||||
let relationships: RelationshipInsertType[] = [];
|
||||
const indices: IndexInsertType[] = []
|
||||
|
||||
const postgresTypes: any[] = [];
|
||||
// Clean up SQL: remove comments and normalize
|
||||
const cleanedSql = sql
|
||||
@@ -39,7 +45,7 @@ export const SqlToDatabase = (sql: string, data_types: DataType[], dialect: Data
|
||||
createTableStatements.push(stmt);
|
||||
} else if (upper.startsWith('ALTER TABLE')) {
|
||||
alterTableStatements.push(stmt);
|
||||
} else if (upper.startsWith('CREATE INDEX')) {
|
||||
} else if (upper.startsWith('CREATE INDEX') || upper.startsWith('CREATE UNIQUE INDEX')) {
|
||||
createIndexStatements.push(stmt);
|
||||
} else if (upper.startsWith('CREATE TYPE')) {
|
||||
createPostgresTypesStatements.push(stmt);
|
||||
@@ -60,27 +66,38 @@ export const SqlToDatabase = (sql: string, data_types: DataType[], dialect: Data
|
||||
|
||||
if (dialect == DatabaseDialect.POSTGRES)
|
||||
for (const createTable of createTableStatements) {
|
||||
|
||||
try {
|
||||
const instructionAst = parse(createTable);
|
||||
if (Array.isArray(instructionAst) && instructionAst.length > 0) {
|
||||
tables.push(postgresAstToTable(instructionAst[0], data_types, postgresTypes));
|
||||
const table: TableInsertType = postgresAstToTable(instructionAst[0], data_types, postgresTypes);
|
||||
tables.push(table);
|
||||
if ((instructionAst[0] as any).constraints && (instructionAst[0] as any).constraints.length > 0) {
|
||||
const relationshipAst: any = foreignKeyConstraintToAlterTableAst((instructionAst[0] as any).constraints, table)
|
||||
try {
|
||||
const newRelationships = astToRelationship(relationshipAst, tables);
|
||||
relationships = relationships.concat(newRelationships);
|
||||
} catch (error) {
|
||||
if ((error as any).relationships && (error as any).relationships.length > 0)
|
||||
relationships = relationships.concat((error as any).relationships);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
const instructionAst = parser.astify(createTable, {
|
||||
database: "MySql"
|
||||
});
|
||||
if (Array.isArray(instructionAst) && instructionAst.length > 0) {
|
||||
tables.push(astToTable(instructionAst[0], data_types));
|
||||
}*/
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
for (const createIndex of createIndexStatements) {
|
||||
try {
|
||||
const instructionAst = parse(createIndex);
|
||||
if (Array.isArray(instructionAst) && instructionAst.length > 0) {
|
||||
indices.push(astToIndex(instructionAst[0], tables));
|
||||
}
|
||||
} catch (error) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
for (const alterTable of alterTableStatements) {
|
||||
try {
|
||||
const instructionAst = parse(alterTable);
|
||||
@@ -93,9 +110,9 @@ export const SqlToDatabase = (sql: string, data_types: DataType[], dialect: Data
|
||||
relationships = relationships.concat((error as any).relationships);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return {tables , relationships};
|
||||
|
||||
return { tables, relationships, indices };
|
||||
|
||||
}
|
||||
|
||||
@@ -105,6 +122,7 @@ export const SqlToDatabase = (sql: string, data_types: DataType[], dialect: Data
|
||||
|
||||
export const postgresAstToTable = (ast: any, data_types: DataType[], postgresTypes: any[]): TableInsertType => {
|
||||
// console.log(ast);
|
||||
|
||||
return {
|
||||
id: v4(),
|
||||
name: ast.name.name,
|
||||
@@ -123,9 +141,9 @@ export const postgresAstToField = (ast: any, data_types: DataType[], sequence: n
|
||||
});
|
||||
|
||||
let values: string | undefined;
|
||||
let autoIncrement: boolean = false;
|
||||
|
||||
if (!dataType && postgresTypes && postgresTypes.length > 0) {
|
||||
|
||||
const postgresType: any = postgresTypes.find((type: any) => type.name.name == ast.dataType.name);
|
||||
if (postgresType) {
|
||||
dataType = data_types.find((dataType: DataType) => dataType.name == "enum") as DataType;
|
||||
@@ -133,16 +151,27 @@ export const postgresAstToField = (ast: any, data_types: DataType[], sequence: n
|
||||
};
|
||||
}
|
||||
|
||||
if (ast.dataType.name?.toLowerCase().includes("serial")) {
|
||||
|
||||
let baseType: string = ast.dataType.name?.toLowerCase() == "serial" ? "integer" : ast.dataType.name?.toLowerCase().replace("serial", "int");
|
||||
dataType = data_types.find((dataType: DataType) => {
|
||||
return dataType.name == baseType;
|
||||
});
|
||||
autoIncrement = true;
|
||||
}
|
||||
const modifiers: string[] = dataType?.modifiers ? JSON.parse(dataType.modifiers) : [];
|
||||
|
||||
let length: number | undefined;
|
||||
let scale: number | undefined;
|
||||
|
||||
if (ast.dataType.config && ast.dataType.config.length > 0)
|
||||
if (ast.dataType.config.length == 1)
|
||||
let unique: boolean = false;
|
||||
let primaryKey: boolean = false;
|
||||
if (ast.dataType.config && ast.dataType.config.length > 0) {
|
||||
if (ast.dataType.config.length >= 1)
|
||||
length = ast.dataType.config[0];
|
||||
else if (ast.dataType.config.length == 2)
|
||||
|
||||
if (ast.dataType.config.length == 2)
|
||||
scale = ast.dataType.config[1];
|
||||
}
|
||||
|
||||
|
||||
let maxLength: number | null = null;
|
||||
@@ -173,6 +202,14 @@ export const postgresAstToField = (ast: any, data_types: DataType[], sequence: n
|
||||
else
|
||||
defaultValue = String(defaultValueConstraints.default.value);
|
||||
}
|
||||
const uniqueConstraints: any | undefined = constraints.find((c: any) => c.type == "unique");
|
||||
const primryKeyConstraints: any | undefined = constraints.find((c: any) => c.type == "primary key");
|
||||
|
||||
if (uniqueConstraints)
|
||||
unique = true;
|
||||
if (primryKeyConstraints)
|
||||
primaryKey = true;
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -181,12 +218,14 @@ export const postgresAstToField = (ast: any, data_types: DataType[], sequence: n
|
||||
defaultValue,
|
||||
typeId: dataType?.id,
|
||||
nullable,
|
||||
unique: ast.unique,
|
||||
unique,
|
||||
maxLength,
|
||||
precision,
|
||||
scale,
|
||||
isPrimary: primaryKey,
|
||||
sequence,
|
||||
values
|
||||
values,
|
||||
autoIncrement
|
||||
} as FieldInsertType;
|
||||
}
|
||||
|
||||
@@ -204,6 +243,7 @@ const astToTable = (ast: any, data_types: DataType[]): TableInsertType => {
|
||||
|
||||
|
||||
export const astToField = (ast: any, data_types: DataType[]): FieldInsertType => {
|
||||
|
||||
const dataType: DataType | undefined = data_types.find((dataType: DataType) => dataType.name == ast.definition.dataType?.toLowerCase());
|
||||
const modifiers: string[] = dataType?.modifiers ? JSON.parse(dataType.modifiers) : [];
|
||||
|
||||
@@ -237,11 +277,10 @@ export const astToField = (ast: any, data_types: DataType[]): FieldInsertType =>
|
||||
|
||||
export const astToRelationship = (ast: any, tables: TableInsertType[]): RelationshipInsertType[] => {
|
||||
|
||||
|
||||
const relationships: RelationshipInsertType[] = [];
|
||||
const changes = ast.changes;
|
||||
|
||||
|
||||
|
||||
const targetTable: TableInsertType | undefined = tables.find((table: TableInsertType) => table.name == ast.table.name);
|
||||
|
||||
if (!targetTable)
|
||||
@@ -255,9 +294,7 @@ export const astToRelationship = (ast: any, tables: TableInsertType[]): Relation
|
||||
|
||||
const targetField: FieldInsertType | undefined = targetTable.fields?.find((field: FieldInsertType) => field.name == foreignKeyConstraint.localColumns[0]?.name)
|
||||
|
||||
const sourceField : FieldInsertType | undefined = sourceTable?.fields?.find((field: FieldInsertType) => field.name == foreignKeyConstraint.foreignColumns[0]?.name)
|
||||
|
||||
|
||||
const sourceField: FieldInsertType | undefined = sourceTable?.fields?.find((field: FieldInsertType) => field.name == foreignKeyConstraint.foreignColumns[0]?.name)
|
||||
|
||||
if (!sourceField || !targetField || !sourceTable)
|
||||
continue;
|
||||
@@ -283,4 +320,63 @@ export const astToRelationship = (ast: any, tables: TableInsertType[]): Relation
|
||||
} as any)
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const foreignKeyConstraintToAlterTableAst = (constraints: any[], table: TableInsertType) => {
|
||||
|
||||
const changes: any[] = constraints.map((constraint: any) => ({
|
||||
type: "add constraint",
|
||||
constraint: {
|
||||
type: "foreign key",
|
||||
localColumns: [
|
||||
{
|
||||
name: constraint.localColumns?.[0].name
|
||||
}
|
||||
],
|
||||
foreignTable: {
|
||||
name: constraint.foreignTable.name,
|
||||
},
|
||||
foreignColumns: [
|
||||
{
|
||||
name: constraint.foreignColumns?.[0].name
|
||||
}
|
||||
]
|
||||
}
|
||||
}))
|
||||
|
||||
return {
|
||||
type: "alter table",
|
||||
only: true,
|
||||
table: {
|
||||
name: table.name
|
||||
},
|
||||
changes
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export const astToIndex = (ast: any, tables: TableInsertType[]): IndexInsertType => {
|
||||
const table: TableInsertType | undefined = tables.find((table: TableInsertType) => table.name == ast.table.name);
|
||||
|
||||
if (!table)
|
||||
throw Error("table not found");
|
||||
|
||||
const fieldNames: string[] = ast.expressions.map((expression: any) => expression.expression.name);
|
||||
|
||||
|
||||
const fieldIds: string[] | undefined = table.fields?.filter((field: FieldInsertType) => fieldNames.includes(field.name))
|
||||
.map((field: FieldInsertType) => field.id);
|
||||
|
||||
return {
|
||||
id: v4(),
|
||||
name: ast.indexName.name,
|
||||
tableId: table.id,
|
||||
unique: ast.unique,
|
||||
fieldIndices: fieldIds?.map((id: string) => ({
|
||||
id: v4(),
|
||||
fieldId: id
|
||||
}))
|
||||
} as IndexInsertType
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user