mirror of
https://github.com/stackrender/stackrender.git
synced 2026-09-11 03:35:42 +00:00
Field Modifiers integrated , extra options added to the field settings based on the data type of that field
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
import { FieldType } from "@/lib/schemas/field-schema"
|
||||
import { FieldType } from "@/lib/schemas/field-schema"
|
||||
import { Cardinality, RelationshipType } from "@/lib/schemas/relationship-schema"
|
||||
import { TableType } from "@/lib/schemas/table-schema"
|
||||
|
||||
export const getRelationshipSourceAndTarget = (sourceTableId: string, sourceField: FieldType, targetTableId: string, targetField: FieldType) => {
|
||||
if (sourceField.isPrimary || targetField.isPrimary) {
|
||||
@@ -47,4 +49,26 @@ export const getRelationshipSourceAndTarget = (sourceTableId: string, sourceFiel
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const getForeignRelationships = (table: TableType): RelationshipType[] => {
|
||||
|
||||
let foreignRelationships = (table.sourceRelations ? table.sourceRelations?.filter((relationship: RelationshipType) =>
|
||||
relationship.cardinality == Cardinality.many_to_one
|
||||
) : []).map((relationship: RelationshipType) => {
|
||||
return {
|
||||
...relationship ,
|
||||
sourceTable : relationship.targetTable ,
|
||||
targetTable : relationship.sourceTable ,
|
||||
sourceField : relationship.targetField ,
|
||||
targetField : relationship.sourceField ,
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
foreignRelationships = foreignRelationships.concat(
|
||||
table.targetRelations ? table.targetRelations?.filter((relationship: RelationshipType) =>
|
||||
relationship.cardinality == Cardinality.one_to_many || relationship.cardinality == Cardinality.one_to_one
|
||||
) : []);
|
||||
|
||||
return foreignRelationships;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
|
||||
|
||||
import { DatabaseDialect } from "@/lib/database";
|
||||
import { DataType } from "@/lib/schemas/data-type-schema";
|
||||
import { DatabaseType } from "@/lib/schemas/database-schema";
|
||||
import { FieldType } from "@/lib/schemas/field-schema";
|
||||
@@ -7,29 +8,40 @@ import { FieldIndexType } from "@/lib/schemas/field_index-schema";
|
||||
import { IndexType } from "@/lib/schemas/index-schema";
|
||||
import { Cardinality, RelationshipType } from "@/lib/schemas/relationship-schema";
|
||||
import { TableType } from "@/lib/schemas/table-schema";
|
||||
import { RenderableTable, SortableTable, toRenderableTable, toSortableTable } from "@/lib/table";
|
||||
import { getForeignRelationships } from "@/utils/relationship";
|
||||
import { orderTables } from "@/utils/tables";
|
||||
|
||||
|
||||
export const DatabaseToAst = (database: DatabaseType, data_types: DataType[]) => {
|
||||
let dbAst: any = [];
|
||||
if (!data_types || data_types.length == 0)
|
||||
return dbAst;
|
||||
for (const table of database.tables) {
|
||||
dbAst.push(TableToAst({
|
||||
...table,
|
||||
}, data_types));
|
||||
|
||||
if (table.indices && table.indices.length > 0)
|
||||
for (const index of table.indices)
|
||||
dbAst.push(IndexToAst({
|
||||
...index,
|
||||
fields: index.fieldIndices.map((fieldIndex: FieldIndexType) => table.fields.find((field: FieldType) => field.id == fieldIndex.fieldId)) as FieldType[]
|
||||
}, table));
|
||||
};
|
||||
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 sortedTables: TableType[] = sortedTablesIds.map((id: string) =>
|
||||
renderableTables.find((table: TableType) => table.id == id) as TableType
|
||||
);
|
||||
|
||||
for (const relationship of database.relationships) {
|
||||
dbAst.push(relationshipToAst(relationship))
|
||||
}
|
||||
//console.log (dbAst)
|
||||
|
||||
for (const table of sortedTables) {
|
||||
dbAst.push(TableToAst(table, data_types));
|
||||
|
||||
if (table.indices && table.indices.length > 0)
|
||||
for (const index of table.indices)
|
||||
dbAst.push(IndexToAst({
|
||||
...index,
|
||||
fields: index.fieldIndices.map((fieldIndex: FieldIndexType) =>
|
||||
table.fields.find((field: FieldType) => field.id == fieldIndex.fieldId)
|
||||
) as FieldType[],
|
||||
}, table));
|
||||
};
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
return dbAst;
|
||||
}
|
||||
|
||||
@@ -47,12 +59,10 @@ export const TableToAst = (table: TableType, data_types: DataType[]) => {
|
||||
}))
|
||||
}] : [];
|
||||
|
||||
let foreignRelationships = getForeignRelationships(table);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const constraints: any[] = [...primaryKeysConstraints];
|
||||
const foreignKeysConstraints = foreignRelationships.map((relationship: RelationshipType) => relationshipToAst(relationship));
|
||||
const constraints: any[] = [...primaryKeysConstraints, ...foreignKeysConstraints];
|
||||
const filed_definitions = table.fields.map((field: FieldType) => FieldToAst({
|
||||
...field,
|
||||
type: data_types.find((dataType: DataType) => dataType.id == field.typeId) as DataType,
|
||||
@@ -84,8 +94,8 @@ export const FieldToAst = (field: FieldType, ignorePrimaryKey: boolean = false)
|
||||
},
|
||||
definition: {
|
||||
dataType: field.type?.name?.toLocaleUpperCase(),
|
||||
length : field.type.name == "varchar" && field.type.dialect == "mysql" ? 255 : null ,
|
||||
|
||||
length: field.type?.name == "varchar" && (field.type?.dialect == DatabaseDialect.MYSQL || field.type?.dialect == DatabaseDialect.MARIADB) ? 255 : null,
|
||||
|
||||
|
||||
},
|
||||
primary_key: field.isPrimary && !ignorePrimaryKey ? "primary key" : null,
|
||||
@@ -112,52 +122,53 @@ export const IndexToAst = (index: IndexType, table: TableType) => {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export const relationshipToAst = (relationship: RelationshipType) => {
|
||||
|
||||
console.log(relationship);
|
||||
let primaryKey: FieldType = relationship.sourceField;
|
||||
let foreignKey: FieldType = relationship.targetField;
|
||||
|
||||
let sourceTable : TableType = relationship.sourceTable ;
|
||||
let targetTable : TableType = relationship.targetTable ;
|
||||
let sourceTable: TableType = relationship.sourceTable;
|
||||
let targetTable: TableType = relationship.targetTable;
|
||||
|
||||
if (relationship.cardinality == Cardinality.many_to_one) {
|
||||
primaryKey = relationship.targetField;
|
||||
foreignKey = relationship.sourceField;
|
||||
sourceTable = relationship.targetTable ;
|
||||
targetTable = relationship.sourceTable ;
|
||||
sourceTable = relationship.targetTable;
|
||||
targetTable = relationship.sourceTable;
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
table: [{
|
||||
table: targetTable.name,
|
||||
}],
|
||||
type: "alter",
|
||||
expr: [{
|
||||
action: "add",
|
||||
resource: "constraint",
|
||||
type: "alter",
|
||||
create_definitions: {
|
||||
constraint: relationship.name ? relationship.name : null,
|
||||
constraint_type: "FOREIGN KEY",
|
||||
index: null,
|
||||
keyword: null,
|
||||
resource: "constraint",
|
||||
definition: [{
|
||||
column: foreignKey.name,
|
||||
type: "column_ref"
|
||||
}],
|
||||
reference_definition: {
|
||||
keyword: "references",
|
||||
on_action: [],
|
||||
table: [{ table: sourceTable.name }],
|
||||
definition: [{
|
||||
column: primaryKey.name,
|
||||
type: "column_ref"
|
||||
}],
|
||||
}
|
||||
constraint: null,
|
||||
definition: [
|
||||
{
|
||||
type: "column_ref",
|
||||
column: foreignKey.name,
|
||||
|
||||
}
|
||||
}]
|
||||
],
|
||||
constraint_type: "FOREIGN KEY",
|
||||
resource: "constraint",
|
||||
reference_definition: {
|
||||
definition: [
|
||||
{
|
||||
type: "column_ref",
|
||||
column: primaryKey.name,
|
||||
}
|
||||
],
|
||||
table: [
|
||||
{
|
||||
table: sourceTable.name,
|
||||
}
|
||||
],
|
||||
keyword: "references",
|
||||
on_action: []
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+109
-18
@@ -7,6 +7,7 @@ import ELK from "elkjs/lib/elk.bundled.js";
|
||||
import { cloneField } from "./field";
|
||||
import { v4 } from "uuid";
|
||||
import { getTimestamp } from "./utils";
|
||||
import { SortableTable } from "@/lib/table";
|
||||
|
||||
const elk = new ELK();
|
||||
|
||||
@@ -16,7 +17,7 @@ const adjustTablesPositions = async (
|
||||
): Promise<TableType[]> => {
|
||||
|
||||
// Extract tables (same as before)
|
||||
const tables: TableType[] = nodes.map((node: Node) => (
|
||||
const tables: TableType[] = nodes.map((node: Node) => (
|
||||
{ ...node.data.table as TableType }
|
||||
)) as TableType[];
|
||||
|
||||
@@ -40,7 +41,7 @@ const adjustTablesPositions = async (
|
||||
targets: [rel.targetTableId],
|
||||
})),
|
||||
};
|
||||
console.log (graph.children ) ;
|
||||
console.log(graph.children);
|
||||
// Run ELK layout (async)
|
||||
const layoutedGraph = await elk.layout(graph);
|
||||
|
||||
@@ -49,8 +50,8 @@ const adjustTablesPositions = async (
|
||||
const node = layoutedGraph?.children?.find((n) => n.id === table.id);
|
||||
if (node) {
|
||||
// ELK positions are top-left, adjust to center like before
|
||||
table.posX = node.x || 0 + (node.width / 2) + 112;
|
||||
table.posY = node.y || 0 + (node.height / 2) + 75;
|
||||
table.posX = node.x || 0 + (node.width / 2) + 112;
|
||||
table.posY = node.y || 0 + (node.height / 2) + 75;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -59,7 +60,7 @@ const adjustTablesPositions = async (
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const isTablesOverlapping = (tableA: TableType, tableB: TableType) => {
|
||||
@@ -69,7 +70,7 @@ const isTablesOverlapping = (tableA: TableType, tableB: TableType) => {
|
||||
|
||||
const tableBWidth: number = 224;
|
||||
const tableBHeight: number = tableB.fields.length * 32 + 36;
|
||||
|
||||
|
||||
const a = {
|
||||
left: tableA.posX,
|
||||
right: tableA.posX + tableAWidth,
|
||||
@@ -90,7 +91,7 @@ const isTablesOverlapping = (tableA: TableType, tableB: TableType) => {
|
||||
|
||||
const getDefaultTableOverlapping = (table: TableType, tables: TableType[]): boolean => {
|
||||
for (let index: number = 0; index < tables.length; index++) {
|
||||
if ( table.id == tables[index].id) continue ;
|
||||
if (table.id == tables[index].id) continue;
|
||||
if (isTablesOverlapping(table, tables[index]))
|
||||
return true;
|
||||
}
|
||||
@@ -99,25 +100,115 @@ const getDefaultTableOverlapping = (table: TableType, tables: TableType[]): bool
|
||||
|
||||
|
||||
|
||||
const cloneTable = ( table: TableType) : TableType => {
|
||||
const cloneTable = (table: TableType): TableType => {
|
||||
|
||||
return {
|
||||
...table ,
|
||||
id : v4() ,
|
||||
name : `${table.name}_copy` ,
|
||||
fields : table.fields.map((field : FieldType) => cloneField(field)) ,
|
||||
posX : table.posX - 360 ,
|
||||
|
||||
createdAt : getTimestamp()
|
||||
...table,
|
||||
id: v4(),
|
||||
name: `${table.name}_copy`,
|
||||
fields: table.fields.map((field: FieldType) => cloneField(field)),
|
||||
posX: table.posX - 360,
|
||||
|
||||
createdAt: getTimestamp()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function orderTables(tables: SortableTable[]): string[] {
|
||||
// Build adjacency list and in-degree count
|
||||
const graph = new Map();
|
||||
const inDegree = new Map();
|
||||
|
||||
// Initialize graph and in-degree map
|
||||
tables.forEach(({ tableId, relationships }) => {
|
||||
graph.set(tableId, new Set());
|
||||
inDegree.set(tableId, 0);
|
||||
});
|
||||
|
||||
// Populate the graph and in-degree map
|
||||
tables.forEach(({ tableId, relationships }) => {
|
||||
relationships.forEach(fk => {
|
||||
if (graph.has(fk)) {
|
||||
graph.get(fk).add(tableId);
|
||||
inDegree.set(tableId, (inDegree.get(tableId) || 0) + 1);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Find tables with no dependencies (in-degree = 0)
|
||||
const queue: string[] = [];
|
||||
inDegree.forEach((count, table) => {
|
||||
if (count === 0) queue.push(table);
|
||||
});
|
||||
|
||||
// Process tables in topological order
|
||||
const sortedOrder: string[] = [];
|
||||
while (queue.length > 0) {
|
||||
const table = queue.shift();
|
||||
sortedOrder.push(table as string);
|
||||
|
||||
// Reduce in-degree of dependent tables
|
||||
graph.get(table).forEach((dependent: string) => {
|
||||
inDegree.set(dependent, inDegree.get(dependent) - 1);
|
||||
if (inDegree.get(dependent) === 0) {
|
||||
queue.push(dependent);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// If not all tables are processed, a cycle exists
|
||||
if (sortedOrder.length !== tables.length) {
|
||||
const visited = new Set<string>();
|
||||
const onStack = new Set<string>();
|
||||
const path: string[] = [];
|
||||
let cycle: string[] = [];
|
||||
|
||||
function dfs(node: string): boolean {
|
||||
visited.add(node);
|
||||
onStack.add(node);
|
||||
path.push(node);
|
||||
|
||||
for (const neighbor of graph.get(node)!) {
|
||||
if (!visited.has(neighbor)) {
|
||||
if (dfs(neighbor)) return true;
|
||||
} else if (onStack.has(neighbor)) {
|
||||
// Found the cycle
|
||||
const cycleStartIndex = path.indexOf(neighbor);
|
||||
cycle = path.slice(cycleStartIndex);
|
||||
cycle.push(neighbor); // close the loop
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
onStack.delete(node);
|
||||
path.pop();
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const node of graph.keys()) {
|
||||
if (!visited.has(node)) {
|
||||
if (dfs(node)) break;
|
||||
}
|
||||
}
|
||||
|
||||
throw {
|
||||
success: false,
|
||||
message: "Cycle detected",
|
||||
cycle: cycle,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
return sortedOrder;
|
||||
}
|
||||
|
||||
|
||||
export {
|
||||
adjustTablesPositions,
|
||||
getDefaultTableOverlapping ,
|
||||
isTablesOverlapping ,
|
||||
cloneTable
|
||||
getDefaultTableOverlapping,
|
||||
isTablesOverlapping,
|
||||
cloneTable,
|
||||
orderTables
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user