mirror of
https://github.com/stackrender/stackrender.git
synced 2026-09-10 11:15:42 +00:00
Foriegn Key Actions (onDelete , onUpdate) Implemnted
This commit is contained in:
@@ -36,6 +36,7 @@ export const useRenderSql = (database: DatabaseType) => {
|
||||
);
|
||||
setCircularDependency(undefined);
|
||||
} catch (error) {
|
||||
console.log (error)
|
||||
|
||||
if ((error as CircularDependencyError)?.cycle)
|
||||
setCircularDependency((previousError) => {
|
||||
|
||||
@@ -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
@@ -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;
|
||||
@@ -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
@@ -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)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+101
-10
@@ -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"
|
||||
|
||||
+2
-1
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 ,
|
||||
}
|
||||
}))
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user