Foriegn Key Actions (onDelete , onUpdate) Implemnted

This commit is contained in:
KarimTamani
2025-07-05 23:18:34 +01:00
parent e968dcdd96
commit 7bf3e6e74e
13 changed files with 368 additions and 66 deletions
+1
View File
@@ -36,6 +36,7 @@ export const useRenderSql = (database: DatabaseType) => {
);
setCircularDependency(undefined);
} catch (error) {
console.log (error)
if ((error as CircularDependencyError)?.cycle)
setCircularDependency((previousError) => {
+12
View File
@@ -59,6 +59,18 @@ export const en = {
many_to_many: "Many to Many",
},
foreign_key_actions : {
title : "Foreign key Actions" ,
on_delete : "On Delete" ,
on_update : "On Update" ,
actions : {
no_action : "No Action" ,
cascade : "Cascade" ,
set_null : "Set Null" ,
set_default : "Set Default" ,
restrict : "Restrict"
}
} ,
field_settings: {
title: "Field Setting",
unique: "Unique",
+14 -7
View File
@@ -9,7 +9,7 @@ export enum Modifiers {
CHARSET = "charset",
COLLATE = "collate",
VALUES = "values",
NO_DEFAULT = "no_default" ,
NO_DEFAULT = "no_default",
NO_UNIQUE = "no_unique"
}
@@ -77,8 +77,6 @@ export enum SQLiteCollation {
RTrim = 'RTRIM',
}
export enum DataTypes {
INTEGER = "integer",
NUMERIC = "numeric",
@@ -87,16 +85,25 @@ export enum DataTypes {
TEXT = "text",
BINARY = "binary",
JSON = "JSON",
GEOMETRY = "geometry",
GEOMETRY = "geometry",
ENUM = "enum"
}
export enum TimeDefaultValues {
NO_VALUE = "no_value",
CUSTOM = "custom" ,
NOW = "now"
CUSTOM = "custom",
NOW = "now"
}
export const MYSQL_MAX_VAR_LENGTH = 255 ;
export enum ForeignKeyActions {
NO_ACTION = "no_action",
CASCADE = "cascade",
SET_NULL = "set_null",
SET_DEFAULT = "set_default",
RESTRICT = "restrict",
}
export const MYSQL_MAX_VAR_LENGTH = 255;
+21 -3
View File
@@ -35,9 +35,27 @@ export const relationships = sqliteTable('relationships', {
'many_to_one',
'many_to_many',
],
})
.notNull()
.default('one_to_many'),
}).notNull().default('one_to_many'),
onDelete: text('onDelete', {
enum: [
"no_action",
"cascade",
"set_null",
"set_default",
"restrict",
],
}).default('no_action'),
onUpdate: text('onUpdate', {
enum: [
"no_action",
"cascade",
"set_null",
"set_default",
"restrict",
],
}).default('no_action'),
+3 -3
View File
@@ -15,12 +15,12 @@ export interface RenderableTable extends TableType {
export const toSortableTable = (table: RenderableTable): SortableTable => {
return {
tableId: table.id,
relationships: table.foreignRelationships.filter(
relationships: Array.from(new Set( table.foreignRelationships.filter(
(relationship : RelationshipType) => !(relationship.targetTable.id == relationship.sourceTable.id && relationship.sourceTable.id == table.id)
).map((relationship: RelationshipType) => relationship.sourceTable.id)
).map((relationship: RelationshipType) => relationship.sourceTable.id)))
}
}
+3 -3
View File
@@ -42,12 +42,12 @@ import { getRelationshipSourceAndTarget } from "@/utils/relationship";
import { useModal } from "@/providers/modal-provider/modal-provider";
import { Modals } from "@/providers/modal-provider/modal-contxet";
import { Loading } from "@/components/modal/loading-modal";
import { usePowerSync, usePowerSyncStatus } from "@powersync/react";
import { usePowerSync } from "@powersync/react";
import { CardinalityStyle } from "@/lib/database";
import debounce from 'lodash.debounce';
const DatabasePage: React.FC = () => {
const { open } = useModal();
const syncStatus = usePowerSync();
@@ -205,7 +205,6 @@ const DatabasePage: React.FC = () => {
useHighlightedEdges(nodes, relationships, edges);
const { isOverlapping, puls } = useOverlappingTables(tables);
return (
<div className="w-full h-screen flex relative overflow-hidden">
@@ -325,3 +324,4 @@ export default DatabasePage;
@@ -1,10 +1,11 @@
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/tooltip/tooltip";
import { ForeignKeyActions } from "@/lib/field";
import { Cardinality, RelationshipInsertType, RelationshipType } from "@/lib/schemas/relationship-schema";
import { useDatabaseOperations } from "@/providers/database-provider/database-provider";
import { Button, Select, SelectItem, SharedSelection } from "@heroui/react";
import { ChevronsLeftRightEllipsis, FileMinus2, FileOutput, Trash2 } from "lucide-react";
import { ChevronsLeftRightEllipsis, FileCog, FileKey, FileMinus2, FileOutput, Trash2 } from "lucide-react";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
@@ -14,39 +15,69 @@ interface RelationshipAccordionBodyProps {
}
const RelationshipAccordionBody: React.FC<RelationshipAccordionBodyProps> = ({ relationship }) => {
const [cardinality, setCardinality] = useState(new Set([relationship.cardinality]));
const [onDeleteAction, setOnDeleteAction] = useState<Set<ForeignKeyActions>>(new Set([]));
const [onUpdateAction, setOnUpdateAction] = useState<Set<ForeignKeyActions>>(new Set([]));
const { editRelationship, deleteRelationship } = useDatabaseOperations();
const { t } = useTranslation();
const changeCardinality = (keys: SharedSelection) => {
if (keys.anchorKey && (keys.anchorKey != relationship.cardinality)) {
editRelationship({
id: relationship.id,
cardinality: keys.anchorKey as Cardinality,
} as RelationshipInsertType);
setCardinality(keys as any);
}
}
const changeOnDelete = (keys: SharedSelection) => {
if (keys.anchorKey && (keys.anchorKey != relationship.onDelete)) {
editRelationship({
id: relationship.id,
onDelete: keys.anchorKey as ForeignKeyActions,
} as RelationshipInsertType);
setOnDeleteAction(keys as any);
}
}
const changeOnUpdate = (keys: SharedSelection) => {
if (keys.anchorKey && (keys.anchorKey != relationship.onUpdate)) {
editRelationship({
id: relationship.id,
onUpdate: keys.anchorKey as ForeignKeyActions,
} as RelationshipInsertType);
setOnUpdateAction(keys as any);
}
}
const removeRelationship = () => {
deleteRelationship(relationship.id);
}
}
useEffect(() => {
setCardinality(new Set([relationship.cardinality]));
}, [relationship.cardinality])
useEffect(() => {
if (!relationship.onDelete)
setOnDeleteAction(new Set([ForeignKeyActions.NO_ACTION]))
else
setOnDeleteAction(new Set([relationship.onDelete as ForeignKeyActions]))
}, [relationship.onDelete]);
useEffect(() => {
if (!relationship.onUpdate)
setOnUpdateAction(new Set([ForeignKeyActions.NO_ACTION]))
else
setOnUpdateAction(new Set([relationship.onUpdate as ForeignKeyActions]))
}, [relationship.onUpdate]);
if (!relationship.sourceTable || !relationship.targetTable)
return;
return (
<div className="w-full p-2 space-y-4">
<div className="flex">
@@ -71,7 +102,6 @@ const RelationshipAccordionBody: React.FC<RelationshipAccordionBodyProps> = ({ r
<label className="font-medium flex text-font/90 flex items-center gap-1 text-sm ">
<FileMinus2 className="size-4" />
{t("db_controller.referenced_table")}
</label>
<Tooltip>
@@ -111,6 +141,67 @@ const RelationshipAccordionBody: React.FC<RelationshipAccordionBodyProps> = ({ r
<SelectItem key={Cardinality.many_to_many}>{t("db_controller.cardinality.many_to_many")}</SelectItem>
</Select>
</div>
{
cardinality.values().next().value != Cardinality.many_to_many &&
<div className="space-y-2">
<label className="flex text-sm font-medium flex text-font/90 items-center gap-1 ">
<FileCog className="size-4 " />
{t('db_controller.foreign_key_actions.title')}
</label>
<div className="flex gap-2">
<div className="w-full space-y-2">
<span className="truncate text-left text-sm text-font/70">
{t('db_controller.foreign_key_actions.on_delete')}
</span>
<Select
size="sm"
variant="bordered"
aria-label="Oon Delete actions"
selectedKeys={onDeleteAction}
onSelectionChange={changeOnDelete}
className="h-8 w-full focus-visible:ring-0 shadow-none "
classNames={{
trigger: "border-divider group-hover:border-primary data-[focus=true]:border-primary data-[open=true]:border-primary",
selectorIcon: "text-icon",
popoverContent: "rounded-md "
}}
>
{
Object.values(ForeignKeyActions).map((value: string) => (
<SelectItem key={value}>{t(`db_controller.foreign_key_actions.actions.${value}`)}</SelectItem>
))
}
</Select>
</div>
<div className="w-full space-y-2">
<div className="w-full space-y-2">
<span className="truncate text-left text-sm text-font/70">
{t('db_controller.foreign_key_actions.on_update')}
</span>
<Select
size="sm"
variant="bordered"
aria-label="Oon Update actions"
selectedKeys={onUpdateAction}
onSelectionChange={changeOnUpdate}
className="h-8 w-full focus-visible:ring-0 shadow-none "
classNames={{
trigger: "border-divider group-hover:border-primary data-[focus=true]:border-primary data-[open=true]:border-primary",
selectorIcon: "text-icon",
popoverContent: "rounded-md "
}}
>
{
Object.values(ForeignKeyActions).map((value: string) => (
<SelectItem key={value}>{t(`db_controller.foreign_key_actions.actions.${value}`)}</SelectItem>
))
}
</Select>
</div>
</div>
</div>
</div>
}
<div className="flex justify-center">
<Button
variant="solid"
@@ -74,11 +74,12 @@ const RelationshipController: React.FC = ({ }) => {
const selectedRelationshipId = selectedRelationship.values().next().value;
const collapseAll = useCallback(() => {
setSelectedRelationship(new Set([]));
}, [])
return (
<div className="w-full h-full flex flex-col gap-2">
<div className="flex items-center justify-between gap-4 py-1">
@@ -15,6 +15,7 @@ import { useTranslation } from "react-i18next";
import Clipboard from "@/components/clipboard/clipboard";
import { TableType } from "@/lib/schemas/table-schema";
import { RelationshipType } from "@/lib/schemas/relationship-schema";
import { Parser } from "node-sql-parser";
interface SqlPreviewProps {
@@ -22,6 +23,23 @@ interface SqlPreviewProps {
}
const sqlExample : string = `
CREATE TABLE projects (
id INTEGER PRIMARY KEY,
name VARCHAR(100) NOT NULL
);
CREATE TABLE tasks (
id INTEGER PRIMARY KEY,
title VARCHAR(100) NOT NULL,
project_id INT,
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE RESTRICT
);
`
const SqlPreview: React.FC<SqlPreviewProps> = ({ tableFilterIds }) => {
const { database: currentDatabase } = useDatabase();
@@ -42,6 +60,13 @@ const SqlPreview: React.FC<SqlPreviewProps> = ({ tableFilterIds }) => {
const { t } = useTranslation();
useEffect(() => {
const parser = new Parser() ;
const ast = parser.astify(sqlExample , {
database : "MySql"
}) ;
console.log (ast)
if (circularDependency)
addToast({
title: t("db_controller.circular_dependency.title"),
@@ -138,6 +138,7 @@ const ImportDatabaseModal: React.FC<ModalProps> = ({ isOpen, onOpenChange }) =>
const validateSql = useCallback((code: string) => {
try {
const parsedDatabase = SqlToDatabase(code, data_types, database?.dialect as DatabaseDialect);
console.log ( parsedDatabase) ;
setParsedDatabase(parsedDatabase);
setError(false);
+49 -3
View File
@@ -1,7 +1,7 @@
import { DatabaseDialect } from "@/lib/database";
import { DataTypes, Modifiers, MYSQL_MAX_VAR_LENGTH, TimeDefaultValues } from "@/lib/field";
import { DataTypes, ForeignKeyActions, Modifiers, MYSQL_MAX_VAR_LENGTH, TimeDefaultValues } from "@/lib/field";
import { DataType } from "@/lib/schemas/data-type-schema";
import { DatabaseType } from "@/lib/schemas/database-schema";
import { FieldType } from "@/lib/schemas/field-schema";
@@ -22,7 +22,8 @@ export const DatabaseToAst = (database: DatabaseType, data_types: DataType[]) =>
let renderableTables: RenderableTable[] = database.tables.map((table: TableType) => toRenderableTable(table, database));
let sortableTables: SortableTable[] = renderableTables.map((table: RenderableTable) => toSortableTable(table));
try {
const sortedTablesIds: string[] = orderTables(sortableTables)
const sortedTablesIds: string[] = orderTables(sortableTables);
const sortedTables: TableType[] = sortedTablesIds.map((id: string) =>
renderableTables.find((table: TableType) => table.id == id) as TableType
);
@@ -48,6 +49,7 @@ export const DatabaseToAst = (database: DatabaseType, data_types: DataType[]) =>
}, table));
};
} catch (error) {
throw error;
}
@@ -299,6 +301,23 @@ export const PotgresEnumToAst = (field: FieldType) => {
}
const 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;
}
@@ -317,7 +336,34 @@ export const relationshipToAst = (relationship: RelationshipType) => {
targetTable = relationship.sourceTable;
}
let on_action: any[] = [];
if (relationship.onDelete) {
const value: string | null = foreignKeyActionToAst(relationship.onDelete as ForeignKeyActions);
if (value)
on_action.push({
type: "on delete",
value: {
type: "origin",
value
}
})
}
if (relationship.onUpdate) {
const value: string | null = foreignKeyActionToAst(relationship.onUpdate as ForeignKeyActions);
if (value)
on_action.push({
type: "on update",
value: {
type: "origin",
value
}
})
}
return {
constraint: null,
definition: [
@@ -342,7 +388,7 @@ export const relationshipToAst = (relationship: RelationshipType) => {
}
],
keyword: "references",
on_action: []
on_action
}
}
+132 -36
View File
@@ -1,4 +1,4 @@
import { DataTypes, Modifiers, TimeDefaultValues } from "@/lib/field";
import { DataTypes, ForeignKeyActions, Modifiers, TimeDefaultValues } from "@/lib/field";
import { DataType } from "@/lib/schemas/data-type-schema";
import { FieldInsertType, FieldType } from "@/lib/schemas/field-schema";
import { TableInsertType } from "@/lib/schemas/table-schema";
@@ -15,8 +15,8 @@ import { DatabaseInsertType } from "@/lib/schemas/database-schema";
export const SqlToDatabase = (sql: string, data_types: DataType[], dialect: DatabaseDialect) => {
const parser = new Parser();
let errors : Error[] = [] ;
let errors: Error[] = [];
const createTableStatements: string[] = [];
const alterTableStatements: string[] = [];
const createIndexStatements: string[] = [];
@@ -59,10 +59,11 @@ export const SqlToDatabase = (sql: string, data_types: DataType[], dialect: Data
for (const postgresType of createPostgresTypesStatements) {
try {
const instructionAst = parse(postgresType);
if (Array.isArray(instructionAst) && instructionAst.length > 0) {
postgresTypes.push(instructionAst[0]);
}
} catch (error ) {
} catch (error) {
errors.push(error as Error);
}
}
@@ -71,22 +72,52 @@ export const SqlToDatabase = (sql: string, data_types: DataType[], dialect: Data
for (const createTable of createTableStatements) {
try {
const instructionAst = parse(createTable);
if (Array.isArray(instructionAst) && instructionAst.length > 0) {
const table: TableInsertType = postgresAstToTable(instructionAst[0], data_types, postgresTypes);
tables.push(table);
for (const column of (instructionAst[0] as any).columns)
if (column.constraints?.length > 0) {
let referenceColumn: any | undefined = column.constraints.find((contraint: any) => contraint.type == "reference")
if (!referenceColumn)
continue
referenceColumn = {
...referenceColumn, localColumns: [
{ name: column.name.name }
]
}
const relationshipAst: any = foreignKeyConstraintToAlterTableAst([referenceColumn], table);
try {
const newRelationships = postgresAstToRelationship(relationshipAst, tables);
relationships = relationships.concat(newRelationships);
} catch (error) {
errors.push(error as Error);
if ((error as any).relationships && (error as any).relationships.length > 0)
relationships = relationships.concat((error as any).relationships);
}
}
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 = postgresAstToRelationship(relationshipAst, tables);
relationships = relationships.concat(newRelationships);
} catch (error) {
errors.push(error as Error);
if ((error as any).relationships && (error as any).relationships.length > 0)
relationships = relationships.concat((error as any).relationships);
}
}
}
} catch (error) {
errors.push(error as Error);
}
@@ -98,7 +129,6 @@ export const SqlToDatabase = (sql: string, data_types: DataType[], dialect: Data
indices.push(postgresAstToIndex(instructionAst[0], tables));
}
} catch (error) {
errors.push(error as Error);
}
}
@@ -110,8 +140,8 @@ export const SqlToDatabase = (sql: string, data_types: DataType[], dialect: Data
relationships = relationships.concat(extractedRelationships);
}
} catch (error) {
errors.push(error as Error);
if ((error as any).relationships && (error as any).relationships.length > 0)
relationships = relationships.concat((error as any).relationships);
}
@@ -124,38 +154,35 @@ export const SqlToDatabase = (sql: string, data_types: DataType[], dialect: Data
try {
if (dialect == DatabaseDialect.SQLITE)
if (dialect == DatabaseDialect.SQLITE)
createTable = createTable.replace(/\btext\s*\(\s*\d+\s*\)/gi, 'TEXT');
let instructionAst = parser.astify(createTable, {
database: dialect == DatabaseDialect.MARIADB ? getDatabaseByDialect(DatabaseDialect.MYSQL).name : getDatabaseByDialect(dialect).name
});
if (Array.isArray(instructionAst) && instructionAst.length > 0) {
instructionAst = instructionAst[0];
}
}
if (instructionAst) {
const table: TableInsertType = astToTable(instructionAst, data_types);
tables.push(table);
const tableForeignKeyConstraints = (instructionAst as any).create_definitions.filter((definition: any) => definition.constraint_type == "FOREIGN KEY");
const tableReferenceDefinitions = (instructionAst as any).create_definitions.filter((definition: any) => definition.resource == "column" && definition.reference_definition);
foreignKeyConstraints = foreignKeyConstraints.concat(
tableForeignKeyConstraints.map((constraint: any) => ({ ...constraint, table: (instructionAst as any).table }))
)
referenceDefinitions = referenceDefinitions.concat(
tableReferenceDefinitions.map((constraint: any) => ({ ...constraint, table: (instructionAst as any).table }))
)
}
} catch (error) {
errors.push(error as Error);
continue;
}
@@ -163,7 +190,7 @@ export const SqlToDatabase = (sql: string, data_types: DataType[], dialect: Data
for (const foreignKeyConstraint of foreignKeyConstraints) {
try {
relationships.push(astToRelationship(tables, foreignKeyConstraint) as RelationshipInsertType);
} catch (error) {
} catch (error) {
errors.push(error as Error);
continue;
}
@@ -192,7 +219,7 @@ export const SqlToDatabase = (sql: string, data_types: DataType[], dialect: Data
}
} catch (error) {
errors.push(error as Error);
if ((error as any).relationships && (error as any).relationships.length > 0)
relationships = relationships.concat((error as any).relationships);
@@ -211,23 +238,35 @@ export const SqlToDatabase = (sql: string, data_types: DataType[], dialect: Data
indices.push(astToIndex(instructionAst, tables));
} catch (error) {
errors.push(error as Error);
continue;
}
}
}
if ( tables.length == 0 )
throw Error ("Error while Parsing")
if (tables.length == 0)
throw Error("Error while Parsing")
return { tables, relationships, indices , errors };
return { tables, relationships, indices, errors };
}
const astToForiegnKeyAction = (ast: string): ForeignKeyActions => {
switch (ast) {
case "cascade":
return ForeignKeyActions.CASCADE;
case "set null":
return ForeignKeyActions.SET_NULL;
case 'restrict':
return ForeignKeyActions.RESTRICT;
case "set default":
return ForeignKeyActions.SET_DEFAULT;
}
return ForeignKeyActions.NO_ACTION
}
export const astToRelationship = (tables: TableInsertType[], constraintAst?: any, columnAst?: any, alterTableAst?: any): RelationshipInsertType | RelationshipInsertType[] => {
@@ -236,36 +275,74 @@ export const astToRelationship = (tables: TableInsertType[], constraintAst?: any
let sourceField: FieldInsertType | undefined;
let targetTable: TableInsertType | undefined;
let relationships: RelationshipInsertType[] = [];
let onDelete: ForeignKeyActions | undefined;
let onUpdate: ForeignKeyActions | undefined;
let on_action: any | undefined;
if (constraintAst) {
targetTable = tables.find((table: TableInsertType) => table.name == constraintAst.table?.[0].table);
targetField = targetTable?.fields?.find((field: FieldInsertType) => field.name == constraintAst.definition?.[0].column);
sourceTable = tables.find((table: TableInsertType) => table.name == constraintAst.reference_definition?.table?.[0].table);
sourceField = sourceTable?.fields?.find((field: FieldInsertType) => field.name == constraintAst.reference_definition?.definition?.[0].column);
on_action = constraintAst.reference_definition.on_action;
}
if (columnAst) {
targetTable = tables.find((table: TableInsertType) => table.name == columnAst.table?.[0].table);
targetField = targetTable?.fields?.find((field: FieldInsertType) => field.name == columnAst.column?.column);
sourceTable = tables.find((table: TableInsertType) => table.name == columnAst.reference_definition?.table?.[0].table);
sourceField = sourceTable?.fields?.find((field: FieldInsertType) => field.name == columnAst.reference_definition?.definition?.[0].column);
on_action = columnAst.reference_definition.on_action;
}
if (on_action) {
const onDeleteAst: any | undefined = on_action.find((action: any) => action.type == "on delete");
const onUpdateAst: any | undefined = on_action.find((action: any) => action.type == "on update");
if (onDeleteAst)
onDelete = astToForiegnKeyAction(onDeleteAst.value.value);
if (onUpdateAst)
onUpdate = astToForiegnKeyAction(onUpdateAst.value.value);
}
if (alterTableAst) {
const expressions = alterTableAst.expr;
const foreignKeyExpressions = expressions.filter((expression: any) => expression.resource == "constraint" && expression.create_definitions?.constraint_type == "FOREIGN KEY")
targetTable = tables.find((table: TableInsertType) => table.name == alterTableAst.table?.[0].table);
targetTable = tables.find((table: TableInsertType) => table.name == alterTableAst.table);
for (const expression of foreignKeyExpressions) {
const targetField: FieldInsertType | undefined = targetTable?.fields?.find((field: FieldInsertType) => field.name == expression.create_definitions?.definition?.[0].column);
const sourceTable: TableInsertType | undefined = tables.find((table: TableInsertType) => table.name == expression.create_definitions?.reference_definition?.table?.[0].table)
const sourceField: FieldInsertType | undefined = sourceTable?.fields?.find((field: FieldInsertType) => field.name == expression.create_definitions?.reference_definition?.definition?.[0].column);
const on_action: any | undefined = expression.create_definitions?.reference_definition?.on_action;
let onDelete: ForeignKeyActions | undefined;
let onUpdate: ForeignKeyActions | undefined;
if (on_action) {
const onDeleteAst: any | undefined = on_action.find((action: any) => action.type == "on delete");
const onUpdateAst: any | undefined = on_action.find((action: any) => action.type == "on update");
if (onDeleteAst)
onDelete = astToForiegnKeyAction(onDeleteAst.value.value);
if (onUpdateAst)
onUpdate = astToForiegnKeyAction(onUpdateAst.value.value);
}
if (!targetField || !sourceField || !sourceTable)
continue;
@@ -276,7 +353,9 @@ export const astToRelationship = (tables: TableInsertType[], constraintAst?: any
targetFieldId: targetField.id,
sourceTableId: sourceTable.id,
sourceFieldId: sourceField.id,
cardinality: targetField.unique ? Cardinality.one_to_one : Cardinality.one_to_many
cardinality: targetField.unique ? Cardinality.one_to_one : Cardinality.one_to_many,
onDelete,
onUpdate
} as RelationshipInsertType)
}
@@ -300,7 +379,9 @@ export const astToRelationship = (tables: TableInsertType[], constraintAst?: any
targetFieldId: targetField.id,
sourceTableId: sourceTable.id,
sourceFieldId: sourceField.id,
cardinality: targetField.unique ? Cardinality.one_to_one : Cardinality.one_to_many
cardinality: targetField.unique ? Cardinality.one_to_one : Cardinality.one_to_many,
onDelete,
onUpdate
} as RelationshipInsertType;
}
@@ -370,19 +451,22 @@ export const astToField = (ast: any, data_types: DataType[], sequence: number):
ast.default_val?.value?.name?.name[0].value == "CURRENT_TIMESTAMP")
defaultValue = TimeDefaultValues.NOW;
const isPrimary: boolean = ast.primary_key == "primary key";
const nullable: boolean = ast.nullable?.value ? ast.nullable?.value != "not null" : !isPrimary;
return {
id: v4(),
name: ast.column.column,
defaultValue,
typeId: dataType?.id,
nullable: ast.nullable?.value != "not null",
nullable,
unique: ast.unique == "unique",
maxLength,
precision,
scale,
sequence,
autoIncrement: ast.auto_increment == "auto_increment",
isPrimary: ast.primary_key == "primary key",
isPrimary,
values,
charset,
collate,
@@ -457,15 +541,13 @@ export const postgresAstToField = (ast: any, data_types: DataType[], sequence: n
precision = length;
let defaultValue: string | undefined;
let nullable: boolean = true;;
let nullable: boolean = true;
const constraints: any[] | undefined = ast.constraints;
if (constraints && constraints.length > 0) {
const nullableConstraints: any | undefined = constraints.find((c: any) => c.type == "not null");
if (nullableConstraints)
nullable = false;
const nullableConstraints: any | undefined = constraints.find((c: any) => c.type == "not null");
const defaultValueConstraints: any | undefined = constraints.find((c: any) => c.type == "default");
if (defaultValueConstraints) {
if (defaultValueConstraints.default.type == "keyword" && defaultValueConstraints.default.keyword == "current_timestamp")
@@ -483,8 +565,13 @@ export const postgresAstToField = (ast: any, data_types: DataType[], sequence: n
if (primryKeyConstraints)
primaryKey = true;
if (nullableConstraints || primaryKey)
nullable = false;
}
return {
id: v4(),
name: ast.name.name,
@@ -519,12 +606,18 @@ export const postgresAstToRelationship = (ast: any, tables: TableInsertType[]):
for (const foreignKeyConstraint of foreignKeyConstraints) {
const sourceTable: TableInsertType | undefined = tables.find((table: TableInsertType) => table.name == foreignKeyConstraint.foreignTable.name);
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 onDelete = foreignKeyConstraint.onDelete ? astToForiegnKeyAction( foreignKeyConstraint.onDelete) : undefined ;
const onUpdate = foreignKeyConstraint.onUpdate ? astToForiegnKeyAction( foreignKeyConstraint.onUpdate) : undefined ;
if (!sourceField || !targetField || !sourceTable)
continue;
@@ -534,7 +627,8 @@ export const postgresAstToRelationship = (ast: any, tables: TableInsertType[]):
targetTableId: targetTable.id,
sourceFieldId: sourceField.id,
targetFieldId: targetField.id,
cardinality: targetField.unique ? Cardinality.one_to_one : Cardinality.one_to_many
cardinality: targetField.unique ? Cardinality.one_to_one : Cardinality.one_to_many ,
onDelete , onUpdate
} as RelationshipInsertType)
}
@@ -570,7 +664,9 @@ const foreignKeyConstraintToAlterTableAst = (constraints: any[], table: TableIns
{
name: constraint.foreignColumns?.[0].name
}
]
],
onDelete : constraint.onDelete ,
onUpdate : constraint.onUpdate ,
}
}))
+4
View File
@@ -108,6 +108,7 @@ const cloneTable = (table: TableType): TableType => {
function orderTables(tables: SortableTable[]): string[] {
// Build adjacency list and in-degree count
const graph = new Map();
const inDegree = new Map();
@@ -149,6 +150,8 @@ function orderTables(tables: SortableTable[]): string[] {
});
}
// If not all tables are processed, a cycle exists
if (sortedOrder.length !== tables.length) {
const visited = new Set<string>();
@@ -184,6 +187,7 @@ function orderTables(tables: SortableTable[]): string[] {
}
}
throw {
success: false,
message: "Cycle detected",