Datatypes Seeded and Not Found page added

This commit is contained in:
KarimTamani
2025-07-20 16:04:51 +01:00
parent 7bf3e6e74e
commit ba81bab88c
39 changed files with 1225 additions and 262 deletions
+4 -1
View File
@@ -10,6 +10,7 @@ import DiagramProvider from "./providers/diagram-provider/diagram-provider";
import { ToastProvider } from "@heroui/react";
import { ModalProvider } from "./providers/modal-provider/modal-provider";
import DatabaseHotkeysProvider from "./providers/database-hotkeys/database-hotkeys-provider";
@@ -26,7 +27,9 @@ function App() {
<TooltipProvider delayDuration={0}>
<ModalProvider>
{appRoutes}
<DatabaseHotkeysProvider>
{appRoutes}
</DatabaseHotkeysProvider>
</ModalProvider>
</TooltipProvider>
+18
View File
@@ -0,0 +1,18 @@
import { SVGProps } from "react";
export function Discord(props: SVGProps<SVGSVGElement>) {
return (
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="20" height="20" viewBox="0 0 50 50"
className="fill-transparent"
{...props}
>
<path
className="stroke-font/90"
strokeWidth="3"
fill="transparent"
d="M 41.625 10.769531 C 37.644531 7.566406 31.347656 7.023438 31.078125 7.003906 C 30.660156 6.96875 30.261719 7.203125 30.089844 7.589844 C 30.074219 7.613281 29.9375 7.929688 29.785156 8.421875 C 32.417969 8.867188 35.652344 9.761719 38.578125 11.578125 C 39.046875 11.867188 39.191406 12.484375 38.902344 12.953125 C 38.710938 13.261719 38.386719 13.429688 38.050781 13.429688 C 37.871094 13.429688 37.6875 13.378906 37.523438 13.277344 C 32.492188 10.15625 26.210938 10 25 10 C 23.789063 10 17.503906 10.15625 12.476563 13.277344 C 12.007813 13.570313 11.390625 13.425781 11.101563 12.957031 C 10.808594 12.484375 10.953125 11.871094 11.421875 11.578125 C 14.347656 9.765625 17.582031 8.867188 20.214844 8.425781 C 20.0625 7.929688 19.925781 7.617188 19.914063 7.589844 C 19.738281 7.203125 19.34375 6.960938 18.921875 7.003906 C 18.652344 7.023438 12.355469 7.566406 8.320313 10.8125 C 6.214844 12.761719 2 24.152344 2 34 C 2 34.175781 2.046875 34.34375 2.132813 34.496094 C 5.039063 39.605469 12.972656 40.941406 14.78125 41 C 14.789063 41 14.800781 41 14.8125 41 C 15.132813 41 15.433594 40.847656 15.621094 40.589844 L 17.449219 38.074219 C 12.515625 36.800781 9.996094 34.636719 9.851563 34.507813 C 9.4375 34.144531 9.398438 33.511719 9.765625 33.097656 C 10.128906 32.683594 10.761719 32.644531 11.175781 33.007813 C 11.234375 33.0625 15.875 37 25 37 C 34.140625 37 38.78125 33.046875 38.828125 33.007813 C 39.242188 32.648438 39.871094 32.683594 40.238281 33.101563 C 40.601563 33.515625 40.5625 34.144531 40.148438 34.507813 C 40.003906 34.636719 37.484375 36.800781 32.550781 38.074219 L 34.378906 40.589844 C 34.566406 40.847656 34.867188 41 35.1875 41 C 35.199219 41 35.210938 41 35.21875 41 C 37.027344 40.941406 44.960938 39.605469 47.867188 34.496094 C 47.953125 34.34375 48 34.175781 48 34 C 48 24.152344 43.785156 12.761719 41.625 10.769531 Z M 18.5 30 C 16.566406 30 15 28.210938 15 26 C 15 23.789063 16.566406 22 18.5 22 C 20.433594 22 22 23.789063 22 26 C 22 28.210938 20.433594 30 18.5 30 Z M 31.5 30 C 29.566406 30 28 28.210938 28 26 C 28 23.789063 29.566406 22 31.5 22 C 33.433594 22 35 23.789063 35 26 C 35 28.210938 33.433594 30 31.5 30 Z"></path>
</svg>
)
}
+26
View File
@@ -0,0 +1,26 @@
import { SVGProps } from "react";
export function X(props: SVGProps<SVGSVGElement>) {
return (
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
width="100"
height="100"
viewBox="0 0 50 50"
{...props}>
<path
className="stroke-font/90"
strokeWidth="3"
fill="transparent"
d="M 5.9199219 6 L 20.582031 27.375 L 6.2304688 44 L 9.4101562 44 L 21.986328 29.421875 L 31.986328 44 L 44 44 L 28.681641 21.669922 L 42.199219 6 L 39.029297 6 L 27.275391 19.617188 L 17.933594 6 L 5.9199219 6 z M 9.7167969 8 L 16.880859 8 L 40.203125 42 L 33.039062 42 L 9.7167969 8 z"></path>
</svg>
)
}
+5 -3
View File
@@ -146,9 +146,11 @@ const Menu: React.FC = ({ }) => {
id: "menu.help",
title: t("menu.help"),
children: [
{ id: "menu.show_docs", title: t("menu.show_docs") },
{ id: "menu.join_discord", title: t("menu.join_discord") },
{ id: "menu.join_discord", title: t("menu.join_discord") ,
clickHandler : () => {
window.open("https://discord.gg/DsN8RcPR6Y", "_blank")
}
},
],
},
], [t, canRedo, canUndo, undo, redo, deleteMultiTables, database, showController, openController ,changeCardinalityStyle , cardinalityStyle, resolvedTheme]);
+3
View File
@@ -48,6 +48,9 @@ const Modal: React.FC<ModalProps> = ({ isOpen, onOpenChange, className, backdrop
}
}
return (
<HeroUiModal
ref={targetRef}
-1
View File
@@ -29,7 +29,6 @@ const Navbar: React.FC<Props> = ({ }) => {
<div className=" w-full h-full flex items-center justify-center">
{database && <RenameDatabase database={database} />}
</div>
<ConnectionStatus />
</div>
</div>
+29 -8
View File
@@ -10,29 +10,50 @@ export interface SidebarItemProps {
title?: string;
icon?: React.ReactNode;
href?: string;
isActive? : boolean
isActive?: boolean,
newTab?: boolean
}
const SidebarItem: React.FC<SidebarItemProps> = ({ title, icon, href , type = "item" , isActive}) => {
const SidebarItem: React.FC<SidebarItemProps> = ({ title, icon, href, type = "item", isActive, newTab }) => {
if (type == "item")
return (
<Tooltip delayDuration={0} >
<TooltipTrigger asChild>
<Link to={href as string} className="p-2 rounded-md hover:bg-default-100 dark:hover:bg-background-50 sidebar-item " data-active={isActive}>
{icon}
</Link>
</TooltipTrigger>
{
!newTab &&
<TooltipTrigger asChild>
<Link
to={href as string}
target=""
className="p-2 rounded-md hover:bg-default-100 dark:hover:bg-background-50 sidebar-item " data-active={isActive}>
{icon}
</Link>
</TooltipTrigger>
}
{
newTab &&
<TooltipTrigger asChild>
<a
href={href as string}
target="_blank"
className="p-2 rounded-md hover:bg-default-100 dark:hover:bg-background-50 sidebar-item " data-active={isActive}>
{icon}
</a>
</TooltipTrigger>
}
<TooltipContent
side="left"
align="center"
>
{title}
</TooltipContent>
</Tooltip>
</Tooltip >
)
else {
+20 -54
View File
@@ -1,8 +1,10 @@
import { BookOpen, EarthLock, GitCommitHorizontal, Github, Settings, Sparkles, TableProperties, Waypoints, Workflow, Zap } from "lucide-react";
import { Github, TableProperties, Workflow } from "lucide-react";
import SidebarItem, { SidebarItemProps } from "./sidebar-item";
import { useMemo } from "react";
import { useLocation } from "react-router-dom";
import { useTranslation } from 'react-i18next';
import { Discord } from "../icon/discord";
import { X } from "../icon/x";
interface SidebarProps {
children?: React.ReactNode
@@ -11,9 +13,8 @@ interface SidebarProps {
const sidebarItemClass: string = "text-font size-4 data-[active=true]:text-primary-900";
const Sidebar: React.FC<SidebarProps> = ({ }) => {
const { t } = useTranslation();
const location = useLocation();
const sidebarItems: SidebarItemProps[] = useMemo(() => [
@@ -29,68 +30,34 @@ const Sidebar: React.FC<SidebarProps> = ({ }) => {
href: "/database/relationships",
isActive: location.pathname.endsWith("/database/relationships")
},
{
title: "AI",
icon: <Sparkles className={sidebarItemClass}></Sparkles>,
href: "/database/bot",
isActive: location.pathname.endsWith("/database/bot")
},
{
type: "divider"
},
{
title: "API",
icon: <Zap className={sidebarItemClass}></Zap>,
href: "/database/ai-bot",
},
{
title: "Authentication",
icon: <EarthLock className={sidebarItemClass}></EarthLock>,
href: "/database/ai-bot",
},
{
title: "Controllers",
icon: <Waypoints className={sidebarItemClass}></Waypoints>,
href: "/database/ai-bot",
},
{
type: "divider"
},
{
title: "Git",
icon: <GitCommitHorizontal className={sidebarItemClass}></GitCommitHorizontal>,
href: "/database/ai-bot",
},
{
type: "divider"
},
{
title: "Project Setting",
icon: <Settings className={sidebarItemClass}></Settings>,
href: "/database/ai-bot",
},
], [location])
const bottomSidebarItems: SidebarItemProps[] = useMemo(() => [
{
title: "Github",
icon: <Github className={sidebarItemClass}></Github>,
href: "/database/ai-bot",
title: "X",
icon: <X className={sidebarItemClass}></X>,
href: "https://github.com/KarimTamani/stackrender",
newTab : true ,
},
{
title: "Docs",
icon: <BookOpen className={sidebarItemClass}></BookOpen>,
href: "/database/ai-bot",
title: "Discord",
icon: <Discord className={sidebarItemClass}></Discord>,
href: "https://discord.gg/DsN8RcPR6Y",
newTab : true ,
},
{
title: "Github",
icon: <Github className={sidebarItemClass}></Github>,
href: "https://github.com/KarimTamani/stackrender",
newTab : true ,
},
], []);
return (
<aside className="h-full z-[20] flex flex-col items-between py-2 justify-between sticky top-0 duration-500 w-12 bg-sidebar pt-[56px] dark:bg-background">
<div className="flex flex-col items-center gap-2">
{
sidebarItems.map((item: SidebarItemProps , index : number) => (
sidebarItems.map((item: SidebarItemProps, index: number) => (
<SidebarItem
key={`top-${index}`}
{...item}
@@ -100,10 +67,9 @@ const Sidebar: React.FC<SidebarProps> = ({ }) => {
</div>
<div className="flex flex-col items-center gap-2 ">
{
bottomSidebarItems.map((item: SidebarItemProps, index : number) => (
bottomSidebarItems.map((item: SidebarItemProps, index: number) => (
<SidebarItem
key={`bottom-${index}`}
{...item}
/>
))
+10 -3
View File
@@ -10,6 +10,7 @@ import { format } from 'sql-formatter';
import { DatabaseDialect, getDatabaseByDialect } from "@/lib/database";
import { CircularDependencyError, fixCharsetPlacement, fixSQLiteColumnOrder } from "@/utils/render/render-uttils";
import { areArraysEqual } from "@/utils/utils";
import { decomposeManyToMany } from "@/utils/relationship";
const parser = new Parser();
@@ -17,13 +18,19 @@ const parser = new Parser();
export const useRenderSql = (database: DatabaseType) => {
const [sql, setSql] = useState<string>("");
const { data_types } = useDatabaseOperations();
const [circularDependency, setCircularDependency] = useState<CircularDependencyError | undefined>(undefined)
const [circularDependency, setCircularDependency] = useState<CircularDependencyError | undefined>(undefined) ;
useEffect(() => {
try {
const dbAst: any = DatabaseToAst(database, data_types);
const decomposedDatabase = decomposeManyToMany(database) ;
const dbAst: any = DatabaseToAst(decomposedDatabase, data_types);
let sql: string = parser.sqlify(dbAst, {
database: getDatabaseByDialect(database.dialect).name
});
if (database.dialect != DatabaseDialect.SQLITE)
sql = fixCharsetPlacement(format(sql, { language: "sql" }));
else
@@ -49,7 +56,7 @@ export const useRenderSql = (database: DatabaseType) => {
})
}
}, [database]);
}, [database , data_types]);
return { sql, circularDependency };
+303
View File
@@ -0,0 +1,303 @@
import { DataInsertType } from "../schemas/data-type-schema";
export const MariaDbDataType: Partial<DataInsertType>[] = [
// ===== integer =====
{
id: 'ce8a95f3-c0eb-4700-aefa-3147fa21377a',
name: 'bit',
type: 'integer',
modifiers: ['length'] // BIT(M) where M is number of bits (1-64)
},
{
id: 'e2ff64e3-96b1-4896-8f92-1dbe5d8bd442',
name: 'integer',
type: 'integer',
modifiers: ['unsigned', 'zerofill', 'length', "auto_increment"], // Same as INT
synonyms: ['int']
},
{
id: '5b4d8b34-5f33-4660-9443-03ea2eef34fd',
name: 'tinyint',
type: 'integer',
modifiers: ['unsigned', 'zerofill', 'length', "auto_increment"] // TINYINT[(M)] [UNSIGNED] [ZEROFILL]
},
{
id: '6548ff67-1800-447b-8fbd-87003a43dc05',
name: 'smallint',
type: 'integer',
modifiers: ['unsigned', 'zerofill', 'length', "auto_increment"] // SMALLINT[(M)] [UNSIGNED] [ZEROFILL]
},
{
id: '5991fbbb-6180-4f4c-9f3f-af8f94931bae',
name: 'mediumint',
type: 'integer',
modifiers: ['unsigned', 'zerofill', 'length', "auto_increment"] // MEDIUMINT[(M)] [UNSIGNED] [ZEROFILL]
},
{
id: 'e7b9e8f8-6a87-4ee7-a411-af5775cd0b88',
name: 'bigint',
type: 'integer',
modifiers: ['unsigned', 'zerofill', 'length', "auto_increment"] // BIGINT[(M)] [UNSIGNED] [ZEROFILL]
},
// ===== numeric =====
{
id: '59abc150-6796-46ee-940c-88eb34eae619',
name: 'float',
type: 'numeric',
modifiers: ['precision', "scale", 'unsigned', 'zerofill'] // FLOAT[(M,D)] [UNSIGNED] [ZEROFILL]
},
{
id: '1ccdde6d-45ae-4ab8-9020-d0d123175037',
name: 'double',
type: 'numeric',
modifiers: ['precision', "scale", 'unsigned', 'zerofill'], // DOUBLE[(M,D)] [UNSIGNED] [ZEROFILL]
synonyms: ['double precision', 'real']
},
{
id: '3089e057-7ea0-4422-9f8b-b03c1c4aadb1',
name: 'decimal',
type: 'numeric',
modifiers: ['precision', "scale", 'unsigned', 'zerofill'], // DECIMAL[(M[,D])] [UNSIGNED] [ZEROFILL]
synonyms: ['dec', 'numeric', 'fixed']
},
{
id: '70c98043-ce75-4e17-8157-b4060b600cc0',
name: 'boolean',
type: 'boolean',
synonyms: ['bool'] // BOOLEAN and BOOL are both aliases for TINYINT(1)
// Alias for TINYINT(1)
},
// ===== date/time =====
{
id: 'bd2d44eb-d4dc-45bc-b1c8-39bff5c703a6',
name: 'date',
type: 'time',
// No modifiers
},
{
id: '2344419d-c226-42a3-bdf4-77e4181a7f81',
name: 'datetime',
type: 'time',
modifiers: ['precision'] // DATETIME[(fsp)], fsp = fractional seconds precision (0-6)
},
{
id: '2b2d6921-b364-4953-a94d-048c43037e35',
name: 'timestamp',
type: 'time',
modifiers: ['precision'] // TIMESTAMP[(fsp)], fsp = fractional seconds precision (0-6)
},
{
id: '6b40d621-d7c5-408e-b784-c839b6c36bed',
name: 'time',
type: 'time',
modifiers: ['precision'] // TIME[(fsp)], fsp = fractional seconds precision (0-6)
},
{
id: '2da3d963-ee00-4472-8b61-38b3f22bdeb6',
name: 'year',
type: 'time',
// YEAR[(2|4)], but 2-digit YEAR is deprecated
},
// ===== text =====
{
id: '3d1c4ddc-a71b-42c6-b935-abb571999055',
name: 'char',
type: 'text',
modifiers: ['length', 'charset', 'collate'] // CHAR[(M)] [CHARACTER SET charset] [COLLATE collation]
},
{
id: 'dfba8595-d9da-4efa-b5e7-a759c07a7984',
name: 'varchar',
type: 'text',
modifiers: ['length', 'charset', 'collate'], // VARCHAR(M) [CHARACTER SET charset] [COLLATE collation]
synonyms: ['nchar', 'national char']
},
{
id: '2dc7ba21-e477-47ff-b736-836f7a6586c6',
name: 'text',
type: 'text',
modifiers: ['charset', 'collate', "no_default", "no_unique"], // TEXT [CHARACTER SET charset] [COLLATE collation]
synonyms: ['nvarchar', 'national varchar']
},
{
id: '51467e54-6257-4431-b161-477aa74857d5',
name: 'tinytext',
type: 'text',
modifiers: ['charset', 'collate', "no_default", "no_unique"] // Same as TEXT
},
{
id: '62399f45-0a58-4b7e-826e-d40278399b19',
name: 'mediumtext',
type: 'text',
modifiers: ['charset', 'collate', "no_default", "no_unique"] // Same as TEXT
},
{
id: 'd0a90fad-ed80-43c5-9388-2e416cfa07dd',
name: 'longtext',
type: 'text',
modifiers: ['charset', 'collate', "no_default", "no_unique"] // Same as TEXT
},
// ===== binary (bytea) =====
{
id: '4c08fd5b-3744-44d1-a57a-e81290a1a29b',
name: 'blob',
type: 'binary',
modifiers: ["no_default", "no_unique"]
},
{
id: '8183a869-7c46-4bee-9560-e5965b824aba',
name: 'tinyblob',
type: 'binary',
modifiers: ["no_default", "no_unique"]
},
{
id: '3edc6207-e595-45c9-af0d-056b4d991b21',
name: 'mediumblob',
type: 'binary',
modifiers: ["no_default", "no_unique"]
},
{
id: 'c3cbe13f-38bf-4ef5-b448-ed9bda3c673b',
name: 'longblob',
type: 'binary',
modifiers: ["no_default", "no_unique"]
},
{
id: 'f2930d56-1903-4710-9c4c-d635123bfaec',
name: 'binary',
type: 'binary',
modifiers: ["no_default", 'length'] // BINARY(M)
},
{
id: '646d8483-d585-4b7c-8255-5bd36bf9240e',
name: 'varbinary',
type: 'binary',
modifiers: ["no_default", 'length'] // VARBINARY(M)
},
// ===== enum/set =====
{
id: '0c9fca72-ead9-4abc-8726-3074bb49c878',
name: 'enum',
type: 'enum',
modifiers: ['values'] // ENUM('value1','value2',...) [CHARACTER SET charset] [COLLATE collation]
},
{
id: '378b77b6-d27c-4bb1-beea-6f0423bd1776',
name: 'set',
type: 'enum',
modifiers: ['values'] // SET('value1','value2',...) [CHARACTER SET charset] [COLLATE collation]
},
// ===== json =====
{
id: '40222e5e-40a6-43a5-b5c6-2a89601ee30b',
name: 'json',
type: 'json',
modifiers: ["no_default", "no_unique"]
// No modifiers
},
// ===== spatial =====
// Spatial types generally don't have modifiers
{
id: '06c24cc7-0ad8-4e05-a2bd-c1b7fb881fa3',
name: 'geometry',
type: 'geometry',
modifiers: ["no_default", "no_unique"]
},
{
id: '8a3363e8-ebce-4ec1-b022-b18cf52a4769',
name: 'point',
type: 'geometry',
modifiers: ["no_default", "no_unique"]
},
{
id: '5018b1a6-6a62-4f58-9c49-fc96efeb1b38',
name: 'linestring',
type: 'geometry',
modifiers: ["no_default", "no_unique"]
},
{
id: '0edf91e8-4f10-41b1-9fc9-3c024c5f90c4',
name: 'polygon',
type: 'geometry',
modifiers: ["no_default", "no_unique"]
},
{
id: '0be92649-f626-4dc8-a163-c86643ca6687',
name: 'multipoint',
type: 'geometry',
modifiers: ["no_default", "no_unique"]
},
{
id: 'c996968e-a83b-44af-aca0-755865cd8825',
name: 'multilinestring',
type: 'geometry',
modifiers: ["no_default", "no_unique"]
},
{
id: '3fa4450a-78b1-4243-9faf-178c607ac092',
name: 'multipolygon',
type: 'geometry',
modifiers: ["no_default", "no_unique"]
},
{
id: '584bf784-3193-4534-8078-392558fc625f',
name: 'geometrycollection',
type: 'geometry',
modifiers: ["no_default", "no_unique"]
},
// ===== MariaDB-specific additions =====
{
id: '7e092014-b75f-4a62-b447-0c5d4af91bc7',
name: 'uuid',
type: 'uuid',
// No modifiers
},
{
id: 'd6483b02-4eaf-418d-ab00-1818ca40d749',
name: 'inet4',
type: 'network',
modifiers: ["no_default"]
// No modifiers
},
{
id: '90adf29b-e993-4215-8241-38dfb31a1139',
name: 'inet6',
type: 'network',
modifiers: ["no_default"]
// No modifiers
}
];
+241
View File
@@ -0,0 +1,241 @@
import { DataInsertType } from "../schemas/data-type-schema";
export const MysqlDataType : Partial<DataInsertType>[] = [
// ===== integer =====
{
id: '53a71e01-345e-4894-bc99-15148dadd1f5',
name: 'integer',
type: 'integer',
modifiers: ['length', 'unsigned', 'zerofill', "auto_increment"], // Same as INT
synonyms: ["int"]
},
{
id: '838da726-648b-4b39-b589-f07c6f9ad249',
name: 'tinyint',
type: 'integer',
modifiers: ['length', 'unsigned', 'zerofill', "auto_increment"] // TINYINT[(M)] [UNSIGNED] [ZEROFILL]
},
{
id: '244e63ba-b172-4af5-8b82-ffbf7fb98b37',
name: 'smallint',
type: 'integer',
modifiers: ['length', 'unsigned', 'zerofill', "auto_increment"] // SMALLINT[(M)] [UNSIGNED] [ZEROFILL]
},
{
id: 'f34a507e-d99d-4129-967b-16436580e23a',
name: 'mediumint',
type: 'integer',
modifiers: ['length', 'unsigned', 'zerofill', "auto_increment"] // MEDIUMINT[(M)] [UNSIGNED] [ZEROFILL]
},
{
id: '37f5a67a-5366-4b0a-aa2d-b94f7cae0c09',
name: 'bigint',
type: 'integer',
modifiers: ['length', 'unsigned', 'zerofill', "auto_increment"] // BIGINT[(M)] [UNSIGNED] [ZEROFILL]
},
// ===== numeric =====
{
id: '40183231-7a46-4f40-9b54-69a573ce61d1',
name: 'float',
type: 'numeric',
modifiers: ['precision', "scale", 'unsigned', 'zerofill'],
synonyms: ['float4']
},
{
id: 'b1b1ccf9-b349-453e-a3dd-1ca7751d27f1',
name: 'double',
type: 'numeric',
modifiers: ['precision', "scale", 'unsigned', 'zerofill'],
synonyms: ['double precision', 'real', 'float8']
},
{
id: '094e7c4c-ae7f-414f-b7aa-6d44e7f357c2',
name: 'decimal',
type: 'numeric',
modifiers: ['precision', "scale", 'unsigned', 'zerofill'],
synonyms: ['dec', 'numeric', 'fixed']
},
{
id: '73eac02b-27e6-4d74-a161-00809c85be01',
name: 'boolean',
type: 'boolean',
synonyms: ['bool'],
},
// ===== date =====
{
id: '8fb4b59c-ab26-4377-a329-aca5bce94ef7',
name: 'date',
type: 'time',
// No modifiers
},
// ===== timestamp =====
{
id: 'c53ab7b7-a453-4669-80c8-cad828e4c95d',
name: 'datetime',
type: 'time',
modifiers: ['precision'] // DATETIME[(fsp)], fsp = fractional seconds (0-6)
},
{
id: 'c2c12ebd-9e71-45d9-979b-868335fbf29a',
name: 'timestamp',
type: 'time',
modifiers: ['precision'] // TIMESTAMP[(fsp)], fsp = fractional seconds (0-6)
},
// ===== time =====
{
id: '0097a5e9-2e8b-46f8-a27b-229e05c0ad3e',
name: 'time',
type: 'time',
modifiers: ['precision'] // TIME[(fsp)], fsp = fractional seconds (0-6)
},
{
id: 'd22941e8-c036-4fda-b7b7-003e5e9d92d5',
name: 'year',
type: 'time',
// YEAR[(2|4)], but 2-digit YEAR is deprecated
},
{
id: "98bf3653-ef15-4528-8319-9053f7ce0b9a",
name: "char",
type: "text",
modifiers: ["length", "charset", "collate"],
synonyms: ["nchar", "national char"] // CHAR = NCHAR = NATIONAL CHAR
},
{
id: "e9b4026d-67bb-4478-812a-760a1312dcb3",
name: "varchar",
type: "text",
modifiers: ["length", "charset", "collate"] ,
synonyms: ["nvarchar", "national varchar"] // VARCHAR = NVARCHAR = NATIONAL VARCHAR
},
{
id: "1caa0a4a-fa8e-4ca1-85af-ed3355f91789",
name: "text",
type: "text",
modifiers: ["charset", "collate", "no_default", "no_unique"]
},
{
id: "04cb1391-b46b-452f-8e36-cd90fbbd0c9b",
name: "tinytext",
type: "text",
modifiers: ["charset", "collate", "no_default", "no_unique"]
},
{
id: "0eb12f0c-d9b9-4355-b097-cffb5174ec48",
name: "mediumtext",
type: "text",
modifiers: ["charset", "collate", "no_default", "no_unique"]
},
{
id: "3f7402aa-d1e4-416a-9f0c-39063a5253ef",
name: "longtext",
type: "text",
modifiers: ["charset", "collate", "no_default", "no_unique"]
},
// ===== binary =====
{
id: '9437b53d-3ad0-4079-9b51-afd433e91085',
name: 'binary',
type: 'binary',
modifiers: ["no_default", 'length'] // BINARY(M)
},
{
id: 'ccbc6f5f-07d4-43e2-b3e2-ca802e373a7b',
name: 'varbinary',
type: 'binary',
modifiers: ["no_default", 'length'] // VARBINARY(M)
},
{
id: '5f03ac95-b931-444f-9837-519a785cc098',
name: 'blob',
type: 'binary',
modifiers: ["no_default", "no_unique"]
// BLOB
},
{
id: '9d677b18-2a4a-435f-ac8a-4ba0ceef9148',
name: 'tinyblob',
type: 'binary',
modifiers: ["no_default", "no_unique"]
// TINYBLOB
},
{
id: 'ab7f73d6-bdaf-4485-a4da-d94467f157d7',
name: 'mediumblob',
type: 'binary',
modifiers: ["no_default", "no_unique"]
// MEDIUMBLOB
},
{
id: '9367631e-3e05-4b85-9937-9b7e80ac7430',
name: 'longblob',
type: 'binary',
modifiers: ["no_default", "no_unique"]
// LONGBLOB
},
// ===== enum =====
{
id: '0e964e0c-f1f0-4bfd-84f7-dd82f07973aa',
name: 'enum',
type: 'enum',
modifiers: ['values'] // ENUM('value1','value2',...) [CHARACTER SET charset] [COLLATE collation]
},
{
id: '793c840c-b11e-484d-aaed-c9bbdee4b422',
name: 'set',
type: 'enum',
modifiers: ['values'] // SET('value1','value2',...) [CHARACTER SET charset] [COLLATE collation]
},
// ===== json =====
{
id: '9c9efa25-4db9-44f8-905b-948608f9119f',
name: 'json',
type: 'json',
modifiers: ["no_default", "no_unique"]
// No modifiers
},
{
id: '821e41e2-c7d0-4a89-86cf-501df764eb15',
name: 'geometry',
type: 'geometry',
modifiers: ["no_default", "no_unique"]
},
{
id: '5de94b34-3a89-4650-98f6-8779bf0370db',
name: 'point',
type: 'geometry',
modifiers: ["no_default", "no_unique"]
},
{
id: '353225e7-0da6-4bdc-8fd9-101bf3c5b8f3',
name: 'linestring',
type: 'geometry',
modifiers: ["no_default", "no_unique"]
},
{
id: '71f34268-df34-448a-bf4f-95a67e03653c',
name: 'polygon',
type: 'geometry',
modifiers: ["no_default", "no_unique"]
},
{
id: '3ed65825-6bd3-4d3a-82aa-be8ff168d450',
name: 'geometrycollection',
type: 'geometry',
modifiers: ["no_default", "no_unique"]
}
];
+62
View File
@@ -0,0 +1,62 @@
import { DataInsertType } from "../schemas/data-type-schema";
export const PostgresDataType: Partial<DataInsertType>[] = [
{
id: 'ee0289ee-8cc8-43c0-b67d-321cbc4585d1', name: 'smallint', type: 'integer', modifiers: ["auto_increment"],
synonyms: ['int2']
},
{
id: '96ca7315-2daf-489e-a5c6-1f6757dea9d9', name: 'integer', type: 'integer', modifiers: ["auto_increment"],
synonyms: ['int', 'int4']
},
{
id: 'e256e7b2-355a-45f4-aee7-6b39efa853a0', name: 'bigint', type: 'integer', modifiers: ["auto_increment"],
synonyms: ['int8']
},
{ id: 'b11e83d0-57c4-45fd-8f7d-c09a94758af0', name: 'numeric', type: 'numeric', modifiers: ['precision', 'scale'], synonyms: ['decimal'] },
{ id: '8c3e6086-6372-4ff1-a60c-7ca75f31d8e7', name: 'real', type: 'numeric', synonyms: ['float4', 'float'] },
{ id: '3774463e-1076-4dcb-95e7-f695fa769899', name: 'double precision', type: 'numeric', synonyms: ['float8'] },
{ id: 'd85b45d9-ea58-4d80-8589-b2489c64b6a9', name: 'money', type: 'numeric' },
{ id: '0173eda2-1e61-45bd-99cc-6a1a8df93efa', name: 'varchar', type: 'text', modifiers: ['length'], synonyms: ['character varying'] },
{ id: '87c39193-9592-488c-9864-8643cef6ce12', name: 'char', type: 'text', modifiers: ['length'], synonyms: ['character'] },
{ id: 'ec45a87a-00dc-422a-ac05-578f9bf74fdb', name: 'text', type: 'text' },
{ id: 'e75dbf56-41c4-4570-b141-e693b6fe1612', name: 'bytea', type: 'binary', modifiers: ["no_default"] },
{ id: '1d7f3282-e377-4252-bd49-95277c9c637c', name: 'bit', type: 'binary', modifiers: ['length', 'no_default'] },
{ id: 'bd35c546-89d2-407a-accc-101851f2b62d', name: 'varbit', type: 'binary', modifiers: ['length', "no_default"], synonyms: ['bit varying'] },
{
id: '11f5ce77-a0c8-44b0-9fbb-4a4b65140997', name: 'timestamp', type: 'time', modifiers: ['precision'], synonyms: [
'timestamp without time zone',
'timestamptz', // often mistaken but technically not equivalent
]
},
{ id: 'bd95631d-c1c4-4cc2-a715-b613369ab097', name: 'date', type: 'time', },
{ id: 'ea3b2c12-931e-4c88-bda5-07efa36ae178', name: 'time', type: 'time', modifiers: ['precision'], synonyms: ['time without time zone'] },
{ id: '3763db96-0ad8-45ef-8510-83000e398263', name: 'boolean', type: 'boolean', synonyms: ['bool'] },
{ id: '5baecfc7-f76d-4184-bb10-586dfb95ac37', name: 'enum', type: 'enum', modifiers: ['values'] },
{ id: "9660224d-cc98-4204-b4db-cbce58f1257d", name: 'point', type: 'geometric', modifiers: ["no_default"] },
{ id: 'e390e7a6-6fcb-4417-91ec-f663db80e47f', name: 'line', type: 'geometric', modifiers: ["no_default"] },
{ id: 'd6cc5854-f88c-4b92-98ce-7b4016022fe1', name: 'lseg', type: 'geometric', modifiers: ["no_default"] },
{ id: '4ce5a962-0194-4298-91f6-4647b0a12b13', name: 'box', type: 'geometric', modifiers: ["no_default"] },
{ id: 'b95eec3d-c6b1-40f0-8fe7-19b4a6212cfd', name: 'path', type: 'geometric', modifiers: ["no_default"] },
{ id: 'b8de3385-798b-4b3b-983f-f3cc61c3c3ac', name: 'polygon', type: 'geometric', modifiers: ["no_default"] },
{ id: '7b3db3bc-2334-4fe5-a5d7-1ad61f4cfb4f', name: 'circle', type: 'geometric', modifiers: ["no_default"] },
{ id: 'f5ad8fc7-4dfb-4e58-b5f5-348a08354056', name: 'uuid', type: 'uuid', },
{ id: 'a5dfb0db-377b-4ff7-b4c1-3c8e705d71aa', name: 'json', type: 'json', modifiers: ["no_default"] },
{ id: '1a8e41ae-fc5e-4bd9-9919-960e07d7b3d4', name: 'jsonb', type: 'json', modifiers: ["no_default"] },
{ id: 'e55e9dc4-4dd3-47f5-a9db-10c3b6f1295b', name: 'xml', type: 'xml', modifiers: ["no_default"] },
{ id: '2a52974a-2b00-4cd8-8264-fd41e2f77683', name: 'cidr', type: 'network', modifiers: ["no_default"] },
{ id: 'ac857b8e-8803-4635-ae2c-1be1c1aa8324', name: 'inet', type: 'network', modifiers: ["no_default"] },
{ id: '83f9691a-1b12-4663-a24a-f019bb5f04c2', name: 'macaddr', type: 'network', modifiers: ["no_default"] },
{ id: 'fdc52c94-6bd4-47d8-a5e7-6a43e5e517e9', name: 'tsvector', type: 'textsearch', modifiers: ["no_default"] },
{ id: '1b06287e-7e9f-4a3f-8c61-5f0a18f7f91c', name: 'tsquery', type: 'textsearch', modifiers: ["no_default"] },
];
+34
View File
@@ -0,0 +1,34 @@
import { PowerSyncSQLiteDatabase } from "@powersync/drizzle-driver";
import { data_types, DataInsertType } from "../schemas/data-type-schema";
import { MysqlDataType } from "./mysql_data_types";
import { PostgresDataType } from "./postgres_data_types";
import { SqliteDataTypes } from "./sqlite_data_types";
import { MariaDbDataType } from "./mariadb_data_types";
import { DatabaseDialect } from "../database";
export const seedDataTypes = async (db: any) => {
const existing = await db.query.data_types.findFirst();
if (!existing) {
// seeding data types .
return await db.insert(data_types).values([
...mapToDataType(MysqlDataType , DatabaseDialect.MYSQL) ,
...mapToDataType(PostgresDataType , DatabaseDialect.POSTGRES) ,
...mapToDataType(SqliteDataTypes , DatabaseDialect.SQLITE) ,
...mapToDataType(MariaDbDataType , DatabaseDialect.MARIADB)
] as DataInsertType[])
}
}
const mapToDataType = (dataTypes: Partial<DataInsertType>[], dialect: DatabaseDialect): DataInsertType[] => {
return dataTypes.map((dataType: Partial<DataInsertType>) => ({
...dataType,
dialect,
modifiers: dataType.modifiers ? JSON.stringify(dataType.modifiers) : undefined,
synonyms: dataType.synonyms ? JSON.stringify(dataType.synonyms) : undefined
} as DataInsertType))
}
+101
View File
@@ -0,0 +1,101 @@
import { DataInsertType } from "../schemas/data-type-schema";
export const SqliteDataTypes: Partial<DataInsertType>[] = [
// ===== INTEGER =====
{
id: 'dea77641-e050-4392-9c14-e53cafa1bcab', // using canonical 'integer'
name: 'integer',
type: 'integer',
modifiers: ['auto_increment'], // Only valid with PRIMARY KEY
synonyms: [
'int',
'tinyint',
'smallint',
'mediumint',
'bigint',
'unsigned big int',
'int2',
'int8'
]
},
// ===== TEXT =====
{
id: '862ee99c-a1d9-4652-a35a-a545712d39db', // 'text'
name: 'text',
type: 'text',
modifiers: ['length'], // Length ignored but can exist in UI
synonyms: [
'character',
'varchar',
'varying character',
'nchar',
'native character',
'nvarchar',
'clob'
]
},
// ===== REAL =====
{
id: '79f8e7a8-b36b-488a-9703-aa7593dcdfa4', // 'real'
name: 'real',
type: 'numeric',
synonyms: [
'float',
'double',
'double precision'
]
},
// ===== NUMERIC =====
{
id: '36ea1bc0-8f4f-4d9d-9a74-31458a65062c', // 'numeric'
name: 'numeric',
type: 'numeric',
modifiers: ['precision'], // Precision/scale ignored
synonyms: [
'decimal'
]
},
// ===== BLOB =====
{
id: '577161ce-7085-4484-af44-d25304c8c63b', // 'blob'
name: 'blob',
type: 'binary',
modifiers: ["no_default", "no_unique"]
},
// ===== BOOLEAN =====
{
id: '263a800f-e6c1-4287-91ad-d6688cb9f1e1', // 'boolean'
name: 'boolean',
type: 'boolean'
},
// ===== DATE / TIME =====
{
id: 'c438e00b-f7f8-4343-807d-96a6a3fff1c4', // 'date'
name: 'date',
type: 'time'
},
{
id: 'ef9455e5-3d56-48bd-8ba6-4c4d13a72fb9', // 'time'
name: 'time',
type: 'time'
},
{
id: '0e2a61ea-05dc-4b67-ab84-04caac829d57', // 'datetime'
name: 'datetime',
type: 'time'
},
// ===== JSON =====
{
id: 'a8cdd25f-dbd5-4582-b42d-641e5862de01', // 'json'
name: 'json',
type: 'json',
modifiers: ["no_default", "no_unique"]
}
];
+9
View File
@@ -1,6 +1,7 @@
import { InferSelectModel, relations } from 'drizzle-orm';
import { sqliteTable, text } from 'drizzle-orm/sqlite-core';
import { fields } from './field-schema';
import { DatabaseDialect } from '../database';
export const data_types = sqliteTable('data_types', {
@@ -28,4 +29,12 @@ export interface DataType extends InferSelectModel<typeof data_types> {
modifiers: string | null | any;
synonyms: string | null | any;
};
export interface DataInsertType extends InferSelectModel<typeof data_types> {
modifiers: string[] | null | any;
synonyms: string[] | null | any;
dialect: DatabaseDialect
};
@@ -4,11 +4,9 @@ import { Button, cn, Divider, Navbar } from "@heroui/react";
import { useOnViewportChange, useReactFlow } from "@xyflow/react";
import { LayoutGrid, Redo, Scan, Undo, ZoomIn, ZoomOut } from "lucide-react";
import { useCallback, useState } from "react";
import { useCallback, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
interface DbControlButtons {
adjustPositions: () => void,
}
@@ -16,8 +14,8 @@ interface DbControlButtons {
const ZOOM_DURATION = 100
const DatabaseControlButtons: React.FC<DbControlButtons> = ({ adjustPositions }) => {
const { zoomIn, zoomOut, fitView , getZoom} = useReactFlow();
console.log ()
const { zoomIn, zoomOut, fitView, getZoom } = useReactFlow();
const [zoom, setZoom] = useState<string>(
`${Math.round(getZoom() * 100)}%`
);
@@ -53,7 +51,12 @@ const DatabaseControlButtons: React.FC<DbControlButtons> = ({ adjustPositions })
maxZoom: 1,
})
}, []);
return (
<Navbar className="flex rounded-md border-1 border-default-200 bg-background dark:bg-transparent bg-default dark:border-divider " isBlurred
classNames={{
+15 -20
View File
@@ -42,20 +42,19 @@ 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 } from "@powersync/react";
import { usePowerSync } from "@powersync/react";
import { CardinalityStyle } from "@/lib/database";
const DatabasePage: React.FC = () => {
const { open } = useModal();
const syncStatus = usePowerSync();
const { t } = useTranslation();
const { resolvedTheme } = useTheme();
// Extract database state and operations
const { database, getField, databases, isSwitchingDatabase, isLoading, isFetching, isSyncing } = useDatabase();
const { database, getField, databases, isSwitchingDatabase, isLoading, currentDatabaseId } = useDatabase();
const { updateTablePositions, deleteMultiTables, deleteMultiRelationships, createRelationship } = useDatabaseOperations();
@@ -77,38 +76,36 @@ const DatabasePage: React.FC = () => {
const edgeTypes = useMemo(() => ({ 'relationship-edge': Relationship }), []);
useEffect(() => {
if (isSyncing)
if (isLoading)
return;
// no database selected , open (open database) Modal
if (!database && databases?.length > 0) {
open(Modals.OPEN_DATABASE, {
closable: false
})
}
else if (databases.length == 0) {
else if (databases.length == 0) {
// there is no databases in the first place to select from ,
// we have to create a new one .
open(Modals.CREATE_DATABASE, {
closable: false
});
}
}, [database?.id, isSyncing])
useEffect(() => {
if (isSwitchingDatabase) {
setNodes([]);
setEdges([]);
} else {
fitView({
duration: 500
});
}
}, [isSwitchingDatabase])
}, [database?.id, isLoading])
useEffect(() => {
setNodes([]);
setEdges([]);
}, [currentDatabaseId])
// Called when a connection is made between fields
const onConnect = useCallback(async (connection: Connection) => {
const sourceId: string | undefined = (connection.sourceHandle as string).split("_").pop();
const targetId: string = (connection.targetHandle as string).replace(TARGET_PREFIX, "");
@@ -125,7 +122,6 @@ const DatabasePage: React.FC = () => {
targetTableId,
sourceFieldId,
targetFieldId,
} as RelationshipInsertType);
} else {
@@ -150,9 +146,7 @@ const DatabasePage: React.FC = () => {
const nodeRemoveChanges: NodeRemoveChange[] = changes.filter((change: NodeChange) => change.type == "remove");
// Save new positions to the database
if (nodePositionChanges.length > 0)
updateTablePositions(nodePositionChanges.map((change: NodePositionChange) => ({
id: change.id,
posX: change.position?.x,
@@ -205,11 +199,12 @@ const DatabasePage: React.FC = () => {
useHighlightedEdges(nodes, relationships, edges);
const { isOverlapping, puls } = useOverlappingTables(tables);
return (
<div className="w-full h-screen flex relative overflow-hidden">
{
(isSwitchingDatabase || isLoading || !(syncStatus.currentStatus as any).options.hasSynced) && <Loading />
(isSwitchingDatabase || isLoading) && <Loading />
}
<div className="flex max-w-full">
<DBController />
@@ -23,22 +23,7 @@ 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 }) => {
@@ -60,13 +45,6 @@ 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"),
@@ -83,6 +83,7 @@ const fieldDefautlValue: React.FC<FieldDefaultValueProps> = ({ field }) => {
multiSelect: field.type?.type == DataTypes.ENUM && field.type?.name == "set"
}
}, [field]);
const [selectedValues, setSelectedValues] = useState<string[]>(() => {
if (!field.defaultValue)
return []
@@ -98,7 +99,6 @@ const fieldDefautlValue: React.FC<FieldDefaultValueProps> = ({ field }) => {
if (field.values && field.values.length > 0) {
try {
const jsonValues: string[] = JSON.parse(field.values);
setValues(jsonValues);
} catch (error) {
setValues([]);
@@ -51,11 +51,11 @@ const FieldItem: React.FC<Props> = ({ field }) => {
} as FieldType);
}
const updateFieldType = (key: Key | null) => {
const dataType: DataType | undefined = data_types.find((dataTypes: DataType) => dataTypes.id == key);
if (!dataType)
return;
if (key != null) {
editField({
id: field.id,
@@ -35,9 +35,8 @@ const IndexesList: React.FC<IndexesListProps> = ({ indices, fields, tableId }) =
return (
<div className="w-full flex-col space-y-2">
{
indices.map((index: IndexType) => <IndexItem index={index} fields={fields} />)
indices.map((index: IndexType) => <IndexItem index={index} fields={fields} key={index.id}/>)
}
<Button
variant="flat"
radius="sm"
@@ -32,7 +32,6 @@ const TablesController: React.FC = ({ }) => {
const nameRef: Ref<HTMLInputElement> = useRef<HTMLInputElement>(null);
const accordionRef = useRef<HTMLDivElement>(null);
useEffect(() => searchTables(), [allTables]);
const addNewTable = useCallback(async () => {
@@ -53,7 +52,6 @@ const TablesController: React.FC = ({ }) => {
id: v4(),
name: "id",
isPrimary: true,
unique: true,
typeId: getInteger()?.id,
autoIncrement: true,
@@ -15,9 +15,9 @@ import { v4 } from "uuid";
export const CreateDatabaseModal: React.FC<ModalProps> = (props) => {
const { t } = useTranslation();
const { isOpen, onOpenChange } = props ;
const { isOpen, onOpenChange } = props;
const [isValid, setIsValid] = useState<boolean>(false);
const [selectedDbType, setSelectedDbType] = useState<string[]>([DBTypes[0].dialect]);
const [dbName, setDbName] = useState<string>("db_example");
@@ -54,6 +54,7 @@ export const CreateDatabaseModal: React.FC<ModalProps> = (props) => {
return (
<Modal
{...props}
isOpen={isOpen}
onOpenChange={onOpenChange}
title={t("modals.pick_database")}
+24
View File
@@ -0,0 +1,24 @@
const NotFoundPage : React.FC = ({}) => {
return(
<section >
<div className="mx-auto max-w-screen-xl px-4 py-8 lg:px-6 lg:py-16">
<div className="mx-auto max-w-screen-sm text-center">
<h1 className="mb-4 text-7xl font-extrabold text-primary lg:text-9xl">
404
</h1>
<p className="mb-4 text-3xl font-bold md:text-4xl text-font/90">
Something's missing.
</p>
<p className="mb-4 text-lg font-light text-font/70">
Sorry, we can't find that page. You'll find lots to
explore on the home page.
</p>
</div>
</div>
</section>
)
}
export default NotFoundPage ;
@@ -12,7 +12,7 @@ interface Props { children: React.ReactNode };
const DatabaseHistoryProvider: React.FC<Props> = ({ children }) => {
const udpateDbFlag = useRef(false);
const { database, isLoading, isFetching } = useDatabase();
const { database, isLoading } = useDatabase();
const { executeDbDiffOps } = useDatabaseOperations();
const [isProcessing, setIsProcessing] = useState<boolean>(false);
@@ -24,7 +24,7 @@ const DatabaseHistoryProvider: React.FC<Props> = ({ children }) => {
}, [database?.id]);
useEffect(() => {
if (database && datatbaseState.present && !isLoading && !isFetching) {
if (database && datatbaseState.present && !isLoading ) {
udpateDbFlag.current = false;
const presentHash: string | undefined = hash(datatbaseState.present, { algorithm: 'sha1' });
@@ -39,18 +39,18 @@ const DatabaseHistoryProvider: React.FC<Props> = ({ children }) => {
const undo = useCallback(() => {
if (!isProcessing && !isLoading && !isFetching) {
if (!isProcessing && !isLoading ) {
udpateDbFlag.current = true;
undoChanges();
}
}, [undoChanges, udpateDbFlag, isProcessing , isLoading , isFetching]);
}, [undoChanges, udpateDbFlag, isProcessing , isLoading ]);
const redo = useCallback(() => {
if (!isProcessing && !isLoading && !isFetching) {
if (!isProcessing && !isLoading ) {
udpateDbFlag.current = true;
redoChanges();
}
}, [redoChanges, udpateDbFlag, isProcessing, isLoading , isFetching]);
}, [redoChanges, udpateDbFlag, isProcessing, isLoading]);
useEffect(() => {
@@ -0,0 +1,60 @@
import { createContext } from "react";
import useHotkeys from "@reecelucas/react-use-hotkeys";
import { useDatabaseHistory } from "../database-history/database-history-provider";
import { useReactFlow } from "@xyflow/react";
import { useModal } from "../modal-provider/modal-provider";
import { Modals } from "../modal-provider/modal-contxet";
import { useDiagramOps } from "../diagram-provider/diagram-provider";
const DatabaseHotkeysContext = createContext<null>(null);
interface Props {
children: React.ReactNode;
}
const DatabaseHotkeysProvider: React.FC<Props> = ({ children }) => {
const { canUndo, undo, canRedo, redo } = useDatabaseHistory();
const { openController, showController } = useDiagramOps();
const { fitView } = useReactFlow();
const { open } = useModal();
useHotkeys(["Control+z", "Meta+z"], (event: KeyboardEvent) => {
event.preventDefault()
canUndo && undo();
});
useHotkeys(["Control+Shift+z", "Meta+Shift+z", "Control+y"], (event: KeyboardEvent) => {
event.preventDefault()
canRedo && redo();
});
useHotkeys(["Control+a", "Meta+a"], (event: KeyboardEvent) => {
event.preventDefault()
fitView({
duration: 500,
padding: 0.1,
maxZoom: 0.9,
})
});
useHotkeys(["Control+o", "Meta+o"], (event: KeyboardEvent) => {
event.preventDefault()
open(Modals.OPEN_DATABASE);
});
useHotkeys(["Control+b", "Meta+b"], (event: KeyboardEvent) => {
event.preventDefault()
openController(!showController)
});
return (
<DatabaseHotkeysContext.Provider value={null}>
{children}
</DatabaseHotkeysContext.Provider>
);
};
export default DatabaseHotkeysProvider;
@@ -19,8 +19,8 @@ interface DatabaseDataContextType {
currentDatabaseId: string | undefined,
databases: DatabaseType[],
isLoading: boolean,
isFetching : boolean ,
isSyncing : boolean ,
isSwitchingDatabase: boolean,
getField: (tableId: string, id: string) => FieldType | undefined,
@@ -52,7 +52,7 @@ interface DatabaseOperationsContextType {
// index operations
createIndex: (index: IndexInsertType) => Promise<QueryResult>,
editIndex: (index: IndexInsertType) => Promise<QueryResult>,
deleteIndex: (id: string) => Promise<QueryResult>,
deleteIndex: (id: string) => Promise<void>,
editFieldIndices: (indexId: string, fieldIds: string[]) => Promise<void>,
// relationship operations
createRelationship: (relationship: RelationshipInsertType) => Promise<QueryResult>,
@@ -16,24 +16,25 @@ import { IndexInsertType, indices } from "@/lib/schemas/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 { deleteFieldsWithCascade, deleteTablesWithCascade } from "@/utils/cascade";
import { deleteFieldsWithCascade, deleteIndicesWithCascade, deleteTablesWithCascade } from "@/utils/cascade";
import DatabaseHotkeysProvider from "../database-hotkeys/database-hotkeys-provider";
interface Props { children: React.ReactNode }
const DatabaseProvider: React.FC<Props> = ({ children }) => {
const syncStatus = usePowerSync();
const [currentDatabaseId, setCurrentDatabaseId] = useState<string | undefined>(localStorage.getItem("database_id") as string | undefined);
// Fetch all databases
const { data: databases, isLoading: loadingDatabases, isFetching: fetchingDatabases } = useQuery(toCompilableQuery(
const { data: databases, isLoading: loadingDatabases } = useQuery(toCompilableQuery(
db.query.databases.findMany({
orderBy: desc(databaseModel.createdAt)
})
));
// Fetch the current database with nested tables, fields, and relationships
let { data: database, isLoading: loadingCurrentDatabase, isFetching: fetchingDatabase } = useQuery(
let { data: database, isLoading: loadingCurrentDatabase } = useQuery(
toCompilableQuery(
db.query.databases.findMany({
where: (databases, { eq }) => eq(databases.id, currentDatabaseId as string),
@@ -86,9 +87,8 @@ const DatabaseProvider: React.FC<Props> = ({ children }) => {
else
database = undefined as any;
// Fetch all data types
let { data: data_types, isLoading: loadingDataTypes, isFetching: fetchingDatatypes } = useQuery(toCompilableQuery(
let { data: data_types, isLoading: loadingDataTypes } = useQuery(toCompilableQuery(
db.query.data_types.findMany({
where: (data_types, { eq }) => eq(data_types.dialect, (database as any)?.dialect)
})
@@ -99,13 +99,10 @@ const DatabaseProvider: React.FC<Props> = ({ children }) => {
}, [data_types]);
const isLoading: boolean = loadingDataTypes || loadingDatabases || loadingCurrentDatabase;
const isFetching: boolean = fetchingDatabase || fetchingDatatypes || fetchingDatabases;
const isSyncing : boolean = isLoading ||isFetching || !(syncStatus.currentStatus as any).options.hasSynced ;
const isSwitchingDatabase: boolean = useMemo(() => {
return fetchingDatabase && currentDatabaseId != (database as any)?.id
}, [currentDatabaseId, database, fetchingDatabase]);
return isLoading && currentDatabaseId != (database as any)?.id
}, [currentDatabaseId, database, isLoading]);
// CRUD for database
const createDatabase = useCallback(async (database: DatabaseInsertType): Promise<QueryResult> => {
@@ -164,7 +161,7 @@ const DatabaseProvider: React.FC<Props> = ({ children }) => {
const deleteTable = useCallback(async (id: string): Promise<void> => {
if (currentDatabaseId) {
await db.transaction(async (tx) => {
await deleteFieldsWithCascade([id], tx);
await deleteTablesWithCascade([id], tx);
await updateDbNumTables(currentDatabaseId, tx);
});
} else {
@@ -228,8 +225,10 @@ const DatabaseProvider: React.FC<Props> = ({ children }) => {
}, [db]);
const deleteIndex = useCallback(async (id: string): Promise<QueryResult> => {
return await db.delete(indices).where(eq(indices.id, id))
const deleteIndex = useCallback(async (id: string): Promise<void> => {
return db.transaction(async (tx) => {
await deleteIndicesWithCascade([id], tx)
})
}, [db])
const editIndex = useCallback(async (index: IndexInsertType): Promise<QueryResult> => {
@@ -299,7 +298,7 @@ const DatabaseProvider: React.FC<Props> = ({ children }) => {
name: operation.chnages.name
}).where(eq(databaseModel.id, currentDatabaseId)));
}
else if (operation.type == "UPDATE_NUM_TABLES") {
else if (operation.type == "UPDATE_NUM_TABLES") {
if (currentDatabaseId)
operations.push(
tx.update(databaseModel).set({
@@ -369,7 +368,7 @@ const DatabaseProvider: React.FC<Props> = ({ children }) => {
}
else if (operation.type == "DELETE_INDEX") {
operations.push(
tx.delete(indices).where(eq(indices.id, operation.indexId))
deleteIndicesWithCascade([operation.indexId ] , tx)
);
}
else if (operation.type == "UPDATE_INDEX") {
@@ -393,7 +392,7 @@ const DatabaseProvider: React.FC<Props> = ({ children }) => {
return await Promise.all(operations);
})
} catch (error) {
throw error
}
}, [db, currentDatabaseId]);
@@ -512,13 +511,15 @@ const DatabaseProvider: React.FC<Props> = ({ children }) => {
databases: databases as DatabaseType[],
isLoading,
isSwitchingDatabase,
isFetching,
getField,
isSyncing
}}>
<DatabaseOperationsContext.Provider value={databaseOpsValue}>
<DatabaseHistoryProvider>
{children}
</DatabaseHistoryProvider>
</DatabaseOperationsContext.Provider>
</DatabaseDataContext.Provider>
@@ -23,7 +23,7 @@ export const ModalProvider: React.FC<Props> = ({ children }) => {
const [currentModal, setCurrentModal] = useState<CurrentModalProps | undefined>(undefined);
const { isOpen, onOpen, onClose } = useDisclosure();
const open = useCallback((modal: Modals, props?: any) => {
setCurrentModal({
modal,
props
+15 -18
View File
@@ -6,6 +6,15 @@ import { AppSchema, drizzleSchema } from '@/lib/schemas/app-schema';
import { StackRenderConnector } from '@/utils/stackrender-connector';
import { CircularProgress } from '@heroui/react';
import { PowerSyncSQLiteDatabase, wrapPowerSyncWithDrizzle } from '@powersync/drizzle-driver';
import { data_types, DataInsertType } from '@/lib/schemas/data-type-schema';
import { MysqlDataType } from '@/lib/data_types/mysql_data_types';
import { PostgresDataType } from '@/lib/data_types/postgres_data_types';
import { SqliteDataTypes } from '@/lib/data_types/sqlite_data_types';
import { MariaDbDataType } from '@/lib/data_types/mariadb_data_types';
import { DatabaseDialect } from '@/lib/database';
import { seedDataTypes } from '@/lib/data_types/seed_datatypes';
export const powerSyncDb = new PowerSyncDatabase({
database: {
@@ -17,9 +26,6 @@ export const powerSyncDb = new PowerSyncDatabase({
export const db: PowerSyncSQLiteDatabase<typeof drizzleSchema> = wrapPowerSyncWithDrizzle(powerSyncDb, {
schema: drizzleSchema,
});
const ConnectorContext = createContext<StackRenderConnector | null>(null);
export const useConnector = () => useContext(ConnectorContext);
@@ -30,26 +36,17 @@ interface SyncProviderProps {
export const SyncProvider: React.FC<SyncProviderProps> = ({ children }) => {
const [powerSync] = useState(powerSyncDb);
const [connector] = useState(new StackRenderConnector());
useEffect(() => {
const setup = async () => {
await powerSync.init();
await powerSync.execute("PRAGMA foreign_keys = ON;");
powerSync.connect(connector);
};
setup();
}, [powerSync, connector]);
const seed = async () => {
await seedDataTypes(db) ;
}
seed();
}, [db])
return (
<Suspense fallback={<CircularProgress />}>
<PowerSyncContext.Provider value={powerSync}>
<ConnectorContext.Provider value={connector}>
{children}
</ConnectorContext.Provider>
{children}
</PowerSyncContext.Provider>
</Suspense>
)
+4 -2
View File
@@ -1,11 +1,13 @@
import { Routes } from 'react-router-dom';
import { Route, Routes } from 'react-router-dom';
import useDashboardRoutes from './dashboard-route';
const useAppRoutes = () => {
import NotFoundPage from '@/pages/not-found/not-found-page';
const useAppRoutes = () => {
const dashboardRoutes = useDashboardRoutes();
return (
<Routes>
{dashboardRoutes}
<Route path="*" element={<NotFoundPage />} />
</Routes>
);
};
+5 -2
View File
@@ -1,16 +1,19 @@
import DashboardPage from '@/pages/dashboard/dashboard-page';
import { Route } from 'react-router-dom';
import { Navigate, Route } from 'react-router-dom';
import useDatabaseRoutes from './database-route';
import NotFoundPage from '@/pages/not-found/not-found-page';
const useDashboardRoutes = () => {
const databaseRoutes = useDatabaseRoutes() ;
const databaseRoutes = useDatabaseRoutes();
return (
<Route path="" element={<DashboardPage />}>
<Route index element={<Navigate to="database" replace />} />
{databaseRoutes}
</Route>
);
+3 -2
View File
@@ -2,16 +2,17 @@
import DatabasePage from '@/pages/database/database-page';
import RelationshipController from '@/pages/database/db-controller/relationship-controller/relationship-controller';
import TablesController from '@/pages/database/db-controller/tables-controller/tables-controller';
import { Route } from 'react-router-dom';
import { Navigate, Route } from 'react-router-dom';
const useDatabaseRoutes = () => {
return (
<Route path="/database" element={<DatabasePage />} >
<Route index element={<Navigate to="tables" replace />} />
<Route path='tables' element={<TablesController />}></Route>
<Route path='relationships' element={<RelationshipController />}></Route>
<Route path='bot' element={<DatabasePage />}></Route>
</Route>
);
};
+2 -11
View File
@@ -1,28 +1,19 @@
import { fields, FieldType } from "@/lib/schemas/field-schema";
import { field_indices } from "@/lib/schemas/field_index-schema";
import { relationships } from "@/lib/schemas/relationship-schema";
import { QueryResult } from "@powersync/web";
import { eq, ExtractTablesWithRelations, inArray, or } from "drizzle-orm";
import { SQLiteTransaction } from "drizzle-orm/sqlite-core";
import { FieldType } from "@/lib/schemas/field-schema";
import { v4 } from "uuid";
export const getNextSequence = (fields: FieldType[]): number => {
if (fields.length == 0)
return 0;
const maxSequenceItem = fields.reduce((max, field: FieldType) => {
return field.sequence > max.sequence ? field : max
});
return maxSequenceItem.sequence + 1;
}
export const cloneField = (field: FieldType): FieldType => {
return {
...field,
+130 -7
View File
@@ -1,7 +1,10 @@
import { ForeignKeyActions } from "@/lib/field"
import { DatabaseType } from "@/lib/schemas/database-schema"
import { FieldType } from "@/lib/schemas/field-schema"
import { Cardinality, RelationshipType } from "@/lib/schemas/relationship-schema"
import { TableType } from "@/lib/schemas/table-schema"
import { tablesRelations, TableType } from "@/lib/schemas/table-schema"
import { v4 } from "uuid"
import pluralize from 'pluralize';
export const getRelationshipSourceAndTarget = (sourceTableId: string, sourceField: FieldType, targetTableId: string, targetField: FieldType) => {
if (sourceField.isPrimary || targetField.isPrimary) {
if (sourceField.isPrimary) {
@@ -56,11 +59,11 @@ export const getForeignRelationships = (table: TableType): RelationshipType[] =>
relationship.cardinality == Cardinality.many_to_one
) : []).map((relationship: RelationshipType) => {
return {
...relationship ,
sourceTable : relationship.targetTable ,
targetTable : relationship.sourceTable ,
sourceField : relationship.targetField ,
targetField : relationship.sourceField ,
...relationship,
sourceTable: relationship.targetTable,
targetTable: relationship.sourceTable,
sourceField: relationship.targetField,
targetField: relationship.sourceField,
}
});
@@ -78,4 +81,124 @@ export const getDefaultRelationshipName = (relationship: RelationshipType) => {
if (!relationship.sourceTable || !relationship.targetTable || !relationship.sourceField || !relationship.targetField)
return "";
return `fk_${relationship.sourceTable?.name}_${relationship.targetTable?.name}`
}
export const decomposeManyToMany = (database: DatabaseType): DatabaseType => {
// Clone the tables and relationships to avoid mutating the original database object
let relationships: RelationshipType[] = [...database.relationships];
let tables: TableType[] = [...database.tables];
// Filter out all many-to-many relationships to process
let manyToManyRelationships = relationships.filter(
(relationship: RelationshipType) => relationship.cardinality == Cardinality.many_to_many
);
// Loop through each many-to-many relationship
for (const relationship of manyToManyRelationships) {
// Find the source and target tables
const sourceTable: TableType = tables.find(
(table: TableType) => table.id == relationship.sourceTableId
) as TableType;
const targetTable: TableType = tables.find(
(table: TableType) => table.id == relationship.targetTableId
) as TableType;
// Find the source and target fields involved in the relationship
const sourceField: FieldType = sourceTable.fields.find(
(field: FieldType) => field.id == relationship.sourceFieldId
) as FieldType;
const targetField: FieldType = targetTable.fields.find(
(field: FieldType) => field.id == relationship.targetFieldId
) as FieldType;
// Convert table names to singular if they are plural
const singularSourceTableName: string = pluralize.isPlural(sourceTable.name)
? pluralize.singular(sourceTable.name)
: sourceTable.name;
const singularTargetTableName: string = pluralize.isPlural(targetTable.name)
? pluralize.singular(targetTable.name)
: targetTable.name;
// Create a foreign key field pointing to the source table
const junctionSourceField: FieldType = {
id: v4(),
name: `${singularSourceTableName}_${sourceField.name}`,
nullable: false,
typeId: sourceField.typeId,
type: sourceField.type,
isPrimary: true
} as FieldType;
// Create a foreign key field pointing to the target table
const junctionTargetField: FieldType = {
id: v4(),
name: `${singularTargetTableName}_${targetField.name}`,
nullable: false,
typeId: targetField.typeId,
type: targetField.type,
isPrimary: true
} as FieldType;
// Define the new junction table combining both foreign keys
const junctionTable: TableType = {
id: v4(),
name: `${singularSourceTableName}_${targetTable.name}`,
fields: [
junctionSourceField,
junctionTargetField
]
} as TableType;
// Create a one-to-many relationship from the source table to the junction table
const junctionSourceRelationship: RelationshipType = {
id: v4(),
sourceTable: sourceTable,
targetTable: junctionTable,
sourceTableId: sourceTable.id,
targetTableId: junctionTable.id,
sourceField: sourceField,
sourceFieldId: sourceField.id,
targetField: junctionSourceField,
targetFieldId: junctionSourceField.id,
cardinality: Cardinality.one_to_many,
onDelete: ForeignKeyActions.CASCADE
} as RelationshipType;
// Create a one-to-many relationship from the target table to the junction table
const junctionTargetRelationship: RelationshipType = {
id: v4(),
sourceTable: targetTable,
targetTable: junctionTable,
sourceTableId: targetTable.id,
targetTableId: junctionTable.id,
sourceField: targetField,
sourceFieldId: targetField.id,
targetField: junctionTargetField,
targetFieldId: junctionTargetField.id,
cardinality: Cardinality.one_to_many,
onDelete: ForeignKeyActions.CASCADE
} as RelationshipType;
// Add the new junction table and the two relationships to the database
tables.push(junctionTable);
relationships.push(junctionSourceRelationship);
relationships.push(junctionTargetRelationship);
}
// Return the updated database object with the new junction tables and relationships added
return {
...database,
tables,
relationships
};
}
+17 -18
View File
@@ -21,6 +21,7 @@ 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);
@@ -32,8 +33,10 @@ export const DatabaseToAst = (database: DatabaseType, data_types: DataType[]) =>
if (database.dialect == DatabaseDialect.POSTGRES) {
postgresEnums = table.fields.filter((field: FieldType) => field.type?.name == "enum");
if (postgresEnums.length > 0) {
for (const postgresEnum of postgresEnums)
for (const postgresEnum of postgresEnums) {
(postgresEnum as any).table = table;
dbAst.push(PotgresEnumToAst(postgresEnum));
}
}
}
@@ -41,17 +44,16 @@ export const DatabaseToAst = (database: DatabaseType, data_types: DataType[]) =>
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));
if (index.fieldIndices?.length > 0)
dbAst.push(IndexToAst({
...index,
fields: index.fieldIndices.map((fieldIndex: FieldIndexType) =>
table.fields.find((field: FieldType) => field.id == fieldIndex.fieldId)
) as FieldType[],
}, table));
};
} catch (error) {
throw error;
}
return dbAst;
}
@@ -78,20 +80,17 @@ export const TableToAst = (table: TableType, data_types: DataType[], isPostgresD
...foreignKeysConstraints
];
const filed_definitions = table.fields.map((field: FieldType) => {
const filed_definitions = table.fields.filter((field: FieldType) => field.type).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,
{ name: `${table.name}_${field.name.toLowerCase()}_enum` } as DataType,
}, multiPrimaryKeys, !isPostgresEnum)
})
return {
keyword: "table",
type: "create",
@@ -286,7 +285,7 @@ export const PotgresEnumToAst = (field: FieldType) => {
resource: "enum",
name: {
schema: null,
name: `${field.name.toLowerCase()}_enum`
name: `${(field as any).table.name}_${field.name.toLowerCase()}_enum`
},
keyword: "type",
create_definitions: {
@@ -340,7 +339,7 @@ export const relationshipToAst = (relationship: RelationshipType) => {
if (relationship.onDelete) {
const value: string | null = foreignKeyActionToAst(relationship.onDelete as ForeignKeyActions);
if (value)
on_action.push({
type: "on delete",
@@ -363,7 +362,7 @@ export const relationshipToAst = (relationship: RelationshipType) => {
}
})
}
return {
constraint: null,
definition: [
@@ -388,7 +387,7 @@ export const relationshipToAst = (relationship: RelationshipType) => {
}
],
keyword: "references",
on_action
on_action
}
}
+24 -34
View File
@@ -9,8 +9,6 @@ import { DatabaseDialect, getDatabaseByDialect } 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) => {
@@ -89,9 +87,9 @@ export const SqlToDatabase = (sql: string, data_types: DataType[], dialect: Data
{ name: column.name.name }
]
}
const relationshipAst: any = foreignKeyConstraintToAlterTableAst([referenceColumn], table);
try {
const newRelationships = postgresAstToRelationship(relationshipAst, tables);
relationships = relationships.concat(newRelationships);
@@ -104,7 +102,7 @@ export const SqlToDatabase = (sql: string, data_types: DataType[], dialect: Data
}
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);
@@ -206,15 +204,16 @@ export const SqlToDatabase = (sql: string, data_types: DataType[], dialect: Data
for (const alterTable of alterTableStatements) {
try {
const instructionAst = parser.astify(alterTable, {
let instructionAst: any = parser.astify(alterTable, {
database: getDatabaseByDialect(dialect).name
});
if (instructionAst) {
const extractedRelationships: RelationshipInsertType[] = astToRelationship(tables, undefined, undefined, {
...instructionAst,
table: (instructionAst as any).table?.[0].table
...instructionAst[0],
table: instructionAst[0].table?.[0].table
}) as RelationshipInsertType[];
relationships = relationships.concat(extractedRelationships)
}
} catch (error) {
@@ -330,7 +329,7 @@ export const astToRelationship = (tables: TableInsertType[], constraintAst?: any
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) {
@@ -515,6 +514,7 @@ export const postgresAstToField = (ast: any, data_types: DataType[], sequence: n
});
autoIncrement = true;
}
const modifiers: string[] = dataType?.modifiers ? JSON.parse(dataType.modifiers) : [];
let length: number | undefined;
@@ -546,12 +546,17 @@ export const postgresAstToField = (ast: any, data_types: DataType[], sequence: n
const constraints: any[] | undefined = ast.constraints;
if (constraints && constraints.length > 0) {
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) {
console.log(defaultValueConstraints.default)
if (defaultValueConstraints.default.type == "keyword" && defaultValueConstraints.default.keyword == "current_timestamp")
defaultValue = TimeDefaultValues.NOW;
else if (defaultValueConstraints.default.type == "call" && defaultValueConstraints.default.function?.name == "now")
defaultValue = TimeDefaultValues.NOW;
else if (defaultValueConstraints.default.type == "cast" && defaultValueConstraints.default.operand)
defaultValue = String(defaultValueConstraints.default.operand.value)
else
@@ -589,11 +594,8 @@ export const postgresAstToField = (ast: any, data_types: DataType[], sequence: n
} as FieldInsertType;
}
export const postgresAstToRelationship = (ast: any, tables: TableInsertType[]): RelationshipInsertType[] => {
const relationships: RelationshipInsertType[] = [];
const changes = ast.changes;
@@ -606,18 +608,13 @@ 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 ;
const onDelete = foreignKeyConstraint.onDelete ? astToForiegnKeyAction(foreignKeyConstraint.onDelete) : undefined;
const onUpdate = foreignKeyConstraint.onUpdate ? astToForiegnKeyAction(foreignKeyConstraint.onUpdate) : undefined;
if (!sourceField || !targetField || !sourceTable)
continue;
@@ -627,12 +624,11 @@ 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 ,
onDelete , onUpdate
cardinality: targetField.unique ? Cardinality.one_to_one : Cardinality.one_to_many,
onDelete, onUpdate
} as RelationshipInsertType)
}
if (relationships.length == foreignKeyConstraints.length)
return relationships;
else
@@ -641,8 +637,6 @@ export const postgresAstToRelationship = (ast: any, tables: TableInsertType[]):
message: "Failed to Extract all relationships",
relationships
} as any)
}
@@ -664,9 +658,9 @@ const foreignKeyConstraintToAlterTableAst = (constraints: any[], table: TableIns
{
name: constraint.foreignColumns?.[0].name
}
],
onDelete : constraint.onDelete ,
onUpdate : constraint.onUpdate ,
],
onDelete: constraint.onDelete,
onUpdate: constraint.onUpdate,
}
}))
@@ -677,7 +671,6 @@ const foreignKeyConstraintToAlterTableAst = (constraints: any[], table: TableIns
name: table.name
},
changes
}
}
@@ -688,12 +681,9 @@ export const astToIndex = (ast: any, tables: TableInsertType[]): IndexInsertType
throw Error("table not found");
const fieldNames: string[] = ast.index_columns.map((column: any) => column.column);
const fieldIds: string[] | undefined = table.fields?.filter((field: FieldInsertType) => fieldNames.includes(field.name))
.map((field: FieldInsertType) => field.id);
return {
id: v4(),
name: ast.index,
+9 -9
View File
@@ -86,10 +86,10 @@ export function fixSQLiteColumnOrder(sql: string): string {
const lines = sql.split('\n');
let currentColumn = '';
const result: string[] = [];
for (const line of lines) {
const trimmed = line.trim();
if (isTableStructureLine(trimmed)) {
if (currentColumn) {
result.push(processSQLiteIntegerColumn(currentColumn));
@@ -98,7 +98,7 @@ export function fixSQLiteColumnOrder(sql: string): string {
result.push(line);
continue;
}
if (trimmed.endsWith(',')) {
currentColumn += ' ' + trimmed.slice(0, -1);
result.push(processSQLiteIntegerColumn(currentColumn) + ',');
@@ -107,7 +107,7 @@ export function fixSQLiteColumnOrder(sql: string): string {
currentColumn += ' ' + trimmed;
}
}
if (currentColumn) result.push(processSQLiteIntegerColumn(currentColumn));
return result.join('\n');
}
@@ -123,7 +123,7 @@ function processSQLiteIntegerColumn(columnDef: string): string {
const isPrimaryKey = rest.match(/\bPRIMARY\s+KEY\b/i);
const isAutoIncrement = rest.match(/\bAUTOINCREMENT\b/i);
const isNotNull = rest.match(/\bNOT\s+NULL\b/i);
if (!isPrimaryKey && !isAutoIncrement) {
return columnDef; // Leave non-PK INTEGER columns unchanged
}
@@ -138,7 +138,7 @@ function processSQLiteIntegerColumn(columnDef: string): string {
// Reconstruct with SQLite's required order
let reconstructed = `${colName} INTEGER`;
if (isPrimaryKey) reconstructed += ' PRIMARY KEY';
if (isAutoIncrement) reconstructed += ' AUTOINCREMENT';
if (isNotNull) reconstructed += ' NOT NULL';
@@ -148,7 +148,7 @@ function processSQLiteIntegerColumn(columnDef: string): string {
}
export interface CircularDependencyError {
cycle : string[] ;
success : boolean ;
message : string ;
cycle: string[];
success: boolean;
message: string;
}