Field Modifiers integrated , extra options added to the field settings based on the data type of that field

This commit is contained in:
KarimTamani
2025-06-12 21:29:54 +01:00
parent 071bd5eecc
commit 12d2ac693e
33 changed files with 1157 additions and 335 deletions
+26 -2
View File
@@ -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;
}
+66 -55
View File
@@ -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
View File
@@ -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
}