mirror of
https://github.com/stackrender/stackrender.git
synced 2026-09-10 19:25:44 +00:00
Import Tables and relationships from Postgres SQL code is implemented successfully
This commit is contained in:
@@ -17,7 +17,6 @@ const DatabaseCheckbox: React.FC<DatabaseCheckboxProps> = ({ database }) => {
|
||||
className="database-checkbox"
|
||||
classNames={{
|
||||
base: "flex min-w-[128px] min-h-[128px] max-w-[128px] max-h-[128px] hover:bg-default rounded-md relative",
|
||||
|
||||
wrapper: "absolute top-2 left-2 " ,
|
||||
label : "flex items-center justify-center w-full h-full p-2 "
|
||||
}}
|
||||
@@ -25,7 +24,6 @@ const DatabaseCheckbox: React.FC<DatabaseCheckboxProps> = ({ database }) => {
|
||||
<div className="min-w-full h-full flex items-center justify-center ">
|
||||
<Image
|
||||
src={database.logo}
|
||||
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import { ImportDatabaseMethod, ImportDatabaseOption } from "@/lib/database"
|
||||
import { Avatar, Checkbox, Chip } from "@heroui/react";
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
interface OptionCheckboxProps {
|
||||
value: string;
|
||||
label: string ;
|
||||
icon?: React.ReactNode;
|
||||
logo?: string
|
||||
|
||||
}
|
||||
|
||||
|
||||
const OptionCheckbox: React.FC<OptionCheckboxProps> = ({ value, icon, logo , label }) => {
|
||||
|
||||
return (
|
||||
<Checkbox
|
||||
aria-label={value}
|
||||
value={value}
|
||||
size="sm"
|
||||
className="option-checkbox"
|
||||
classNames={{
|
||||
wrapper: "hidden",
|
||||
label: "flex items-center justify-center w-full h-full"
|
||||
}}
|
||||
>
|
||||
<Chip radius="sm" variant="bordered" color="default" className="option-span px-2 border-1 transition-all duration-300 border-divider"
|
||||
avatar={logo ? <Avatar src={logo} /> : undefined}
|
||||
startContent={
|
||||
icon
|
||||
}
|
||||
>
|
||||
<span className="text-sm text-font font-semibold">
|
||||
{label}
|
||||
</span>
|
||||
</Chip>
|
||||
</Checkbox>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export default OptionCheckbox;
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
import { useTheme } from "next-themes";
|
||||
import DropdownMenu, { MenuDropdownProps } from "./menu-dropdown";
|
||||
import React, { useMemo } from "react";
|
||||
import React, { useEffect, useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useModal } from "@/providers/modal-provider/modal-provider";
|
||||
import { Modals } from "@/providers/modal-provider/modal-contxet";
|
||||
@@ -23,6 +23,10 @@ const Menu: React.FC<MenuProps> = ({ }) => {
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { open } = useModal();
|
||||
|
||||
useEffect(() => {
|
||||
open(Modals.IMPORT_DATABASE)
|
||||
} , [])
|
||||
const menu: MenuDropdownProps[] = useMemo(() => [
|
||||
{
|
||||
title: t("menu.file"),
|
||||
@@ -43,12 +47,9 @@ const Menu: React.FC<MenuProps> = ({ }) => {
|
||||
{
|
||||
title: t("menu.import"),
|
||||
divide: true,
|
||||
children: [
|
||||
{ title: t("menu.json") },
|
||||
{ title: t("menu.dbml") },
|
||||
{ title: t("menu.mysql") },
|
||||
{ title: t("menu.postgresql") },
|
||||
],
|
||||
clickHandler: () => {
|
||||
open(Modals.IMPORT_DATABASE)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t("menu.export_sql"),
|
||||
|
||||
@@ -14,7 +14,7 @@ import { useTranslation } from "react-i18next";
|
||||
|
||||
export interface ModalProps {
|
||||
isOpen?: boolean,
|
||||
onOpenChange: (open: boolean) => void,
|
||||
onOpenChange? : (open: boolean) => void,
|
||||
className?: string,
|
||||
backdrop?: "blur" | "transparent" | "opaque",
|
||||
title: string,
|
||||
@@ -45,7 +45,7 @@ const Modal: React.FC<ModalProps> = ({ isOpen, onOpenChange, className, backdrop
|
||||
<HeroUiModal
|
||||
ref={targetRef}
|
||||
isOpen={isOpen}
|
||||
onOpenChange={onOpenChange}
|
||||
onOpenChange={onOpenChange ? onOpenChange : undefined}
|
||||
className={className}
|
||||
backdrop={backdrop}
|
||||
radius="sm"
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Parser } from "node-sql-parser";
|
||||
import { DatabaseType } from "@/lib/schemas/database-schema";
|
||||
import { format } from 'sql-formatter';
|
||||
import { DatabaseDialect, getDatabaseByDialect } from "@/lib/database";
|
||||
import { CircularDependencyError, fixCharsetPlacement, fixSQLiteColumnOrder } from "@/utils/render/parsers/render-uttils";
|
||||
import { CircularDependencyError, fixCharsetPlacement, fixSQLiteColumnOrder } from "@/utils/render/render-uttils";
|
||||
import { areArraysEqual } from "@/utils/utils";
|
||||
|
||||
const parser = new Parser();
|
||||
@@ -17,7 +17,7 @@ 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);
|
||||
@@ -34,18 +34,22 @@ export const useRenderSql = (database: DatabaseType) => {
|
||||
setSql(
|
||||
(formattedSqlCode)
|
||||
);
|
||||
setCircularDependency( undefined ) ;
|
||||
setCircularDependency(undefined);
|
||||
} catch (error) {
|
||||
|
||||
setCircularDependency((previousError) => {
|
||||
if ( !previousError || !areArraysEqual( previousError.cycle , (error as CircularDependencyError).cycle) )
|
||||
return error as CircularDependencyError ;
|
||||
return previousError ;
|
||||
})
|
||||
|
||||
|
||||
if ((error as CircularDependencyError)?.cycle)
|
||||
setCircularDependency((previousError) => {
|
||||
if (!previousError)
|
||||
return error as CircularDependencyError;
|
||||
else if (Array.isArray(previousError.cycle) && Array.isArray((error as CircularDependencyError).cycle) && !(areArraysEqual(previousError.cycle, (error as CircularDependencyError).cycle)))
|
||||
|
||||
return error as CircularDependencyError;
|
||||
return previousError;
|
||||
})
|
||||
|
||||
}
|
||||
}, [database]);
|
||||
|
||||
return {sql , circularDependency};
|
||||
return { sql, circularDependency };
|
||||
|
||||
}
|
||||
@@ -180,7 +180,13 @@ export const en = {
|
||||
open_database_header: "Open a database by selecting one from the list.",
|
||||
delete_database: "Delete Database",
|
||||
delete_database_content: "This action is irreversible and will permanently remove the diagram.",
|
||||
delete: "Delete"
|
||||
delete: "Delete" ,
|
||||
|
||||
import_database : {
|
||||
title : "Import your Database" ,
|
||||
import : "Import" ,
|
||||
import_options : "Would you like to import using :"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -38,4 +38,25 @@ export const DBTypes: DatabaseType[] = [
|
||||
export const getDatabaseByDialect = (dialect: DatabaseDialect): DatabaseType => {
|
||||
const dbType : DatabaseType | undefined = DBTypes.find((dbType : DatabaseType) => dbType.dialect == dialect) ;
|
||||
return dbType ? dbType : DBTypes[0] ;
|
||||
}
|
||||
|
||||
|
||||
export enum ImportMethodType {
|
||||
DUMP = "DUMP" ,
|
||||
DB_CLIENT = "DB_CLIENT" ,
|
||||
|
||||
}
|
||||
|
||||
export interface ImportDatabaseMethod {
|
||||
id : string ;
|
||||
name : string ;
|
||||
logo? : string ;
|
||||
icon? : React.ReactNode ;
|
||||
instructions? : string ;
|
||||
type : ImportMethodType ;
|
||||
}
|
||||
|
||||
export interface ImportDatabaseOption {
|
||||
dialect : DatabaseDialect ,
|
||||
methods : ImportDatabaseMethod []
|
||||
}
|
||||
@@ -0,0 +1,451 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export const PostgresSqlExample = `
|
||||
--
|
||||
-- PostgreSQL database dump
|
||||
--
|
||||
|
||||
-- Dumped from database version 17.4
|
||||
-- Dumped by pg_dump version 17.4
|
||||
|
||||
SET statement_timeout = 0;
|
||||
SET lock_timeout = 0;
|
||||
SET idle_in_transaction_session_timeout = 0;
|
||||
SET transaction_timeout = 0;
|
||||
SET client_encoding = 'UTF8';
|
||||
SET standard_conforming_strings = on;
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
SET check_function_bodies = false;
|
||||
SET xmloption = content;
|
||||
SET client_min_messages = warning;
|
||||
SET row_security = off;
|
||||
|
||||
--
|
||||
-- Name: public; Type: SCHEMA; Schema: -; Owner: postgres
|
||||
--
|
||||
|
||||
-- *not* creating schema, since initdb creates it
|
||||
|
||||
|
||||
ALTER SCHEMA public OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- Name: SCHEMA public; Type: COMMENT; Schema: -; Owner: postgres
|
||||
--
|
||||
|
||||
COMMENT ON SCHEMA public IS '';
|
||||
|
||||
|
||||
--
|
||||
-- Name: citext; Type: EXTENSION; Schema: -; Owner: -
|
||||
--
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS citext WITH SCHEMA public;
|
||||
|
||||
|
||||
--
|
||||
-- Name: EXTENSION citext; Type: COMMENT; Schema: -; Owner:
|
||||
--
|
||||
|
||||
COMMENT ON EXTENSION citext IS 'data type for case-insensitive character strings';
|
||||
|
||||
|
||||
--
|
||||
-- Name: type_enum; Type: TYPE; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
CREATE TYPE public.type_enum AS ENUM (
|
||||
'user',
|
||||
'admin',
|
||||
'manager'
|
||||
);
|
||||
|
||||
|
||||
ALTER TYPE public.type_enum OWNER TO postgres;
|
||||
|
||||
SET default_tablespace = '';
|
||||
|
||||
SET default_table_access_method = heap;
|
||||
|
||||
--
|
||||
-- Name: category; Type: TABLE; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
CREATE TABLE public.category (
|
||||
id bigint NOT NULL,
|
||||
name character varying
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.category OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- Name: category_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.category_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
ALTER SEQUENCE public.category_id_seq OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- Name: category_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.category_id_seq OWNED BY public.category.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: field_2_seq; Type: SEQUENCE; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.field_2_seq
|
||||
START WITH 12
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
ALTER SEQUENCE public.field_2_seq OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- Name: products; Type: TABLE; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
CREATE TABLE public.products (
|
||||
id bigint NOT NULL,
|
||||
name character varying NOT NULL,
|
||||
descripttion text,
|
||||
sale_price money NOT NULL,
|
||||
cost_price money DEFAULT 0 NOT NULL,
|
||||
quantity integer DEFAULT 0,
|
||||
store_id bigint NOT NULL
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.products OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- Name: products_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.products_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
ALTER SEQUENCE public.products_id_seq OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- Name: products_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.products_id_seq OWNED BY public.products.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: stores; Type: TABLE; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
CREATE TABLE public.stores (
|
||||
id bigint NOT NULL,
|
||||
title character varying NOT NULL,
|
||||
bio text,
|
||||
logo bytea,
|
||||
rating numeric(3,2) DEFAULT 0 NOT NULL,
|
||||
user_id bigint NOT NULL,
|
||||
category_id bigint NOT NULL
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.stores OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- Name: stores_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.stores_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
ALTER SEQUENCE public.stores_id_seq OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- Name: stores_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.stores_id_seq OWNED BY public.stores.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: stores_user_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.stores_user_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
ALTER SEQUENCE public.stores_user_id_seq OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- Name: stores_user_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.stores_user_id_seq OWNED BY public.stores.user_id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: users; Type: TABLE; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
CREATE TABLE public.users (
|
||||
id bigint NOT NULL,
|
||||
name character varying NOT NULL,
|
||||
lastname character varying NOT NULL,
|
||||
email character varying(100) NOT NULL,
|
||||
password character varying(100) NOT NULL,
|
||||
type public.type_enum DEFAULT 'user'::public.type_enum NOT NULL,
|
||||
created_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.users OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- Name: users_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.users_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
ALTER SEQUENCE public.users_id_seq OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- Name: users_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.users_id_seq OWNED BY public.users.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: category id; Type: DEFAULT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.category ALTER COLUMN id SET DEFAULT nextval('public.category_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: products id; Type: DEFAULT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.products ALTER COLUMN id SET DEFAULT nextval('public.products_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: stores id; Type: DEFAULT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.stores ALTER COLUMN id SET DEFAULT nextval('public.stores_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: stores user_id; Type: DEFAULT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.stores ALTER COLUMN user_id SET DEFAULT nextval('public.stores_user_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: users id; Type: DEFAULT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.users ALTER COLUMN id SET DEFAULT nextval('public.users_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: category; Type: TABLE DATA; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
COPY public.category (id, name) FROM stdin;
|
||||
\.
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: products; Type: TABLE DATA; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
COPY public.products (id, name, descripttion, sale_price, cost_price, quantity, store_id) FROM stdin;
|
||||
\.
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: stores; Type: TABLE DATA; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
COPY public.stores (id, title, bio, logo, rating, user_id, category_id) FROM stdin;
|
||||
\.
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: users; Type: TABLE DATA; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
COPY public.users (id, name, lastname, email, password, type, created_at) FROM stdin;
|
||||
\.
|
||||
|
||||
|
||||
--
|
||||
-- Name: category_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
SELECT pg_catalog.setval('public.category_id_seq', 1, false);
|
||||
|
||||
|
||||
--
|
||||
-- Name: field_2_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
SELECT pg_catalog.setval('public.field_2_seq', 12, false);
|
||||
|
||||
|
||||
--
|
||||
-- Name: products_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
SELECT pg_catalog.setval('public.products_id_seq', 1, false);
|
||||
|
||||
|
||||
--
|
||||
-- Name: stores_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
SELECT pg_catalog.setval('public.stores_id_seq', 1, false);
|
||||
|
||||
|
||||
--
|
||||
-- Name: stores_user_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
SELECT pg_catalog.setval('public.stores_user_id_seq', 1, false);
|
||||
|
||||
|
||||
--
|
||||
-- Name: users_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
SELECT pg_catalog.setval('public.users_id_seq', 1, false);
|
||||
|
||||
|
||||
--
|
||||
-- Name: category category_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.category
|
||||
ADD CONSTRAINT category_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: products products_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.products
|
||||
ADD CONSTRAINT products_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: stores stores_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.stores
|
||||
ADD CONSTRAINT stores_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: users users_email_key; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.users
|
||||
ADD CONSTRAINT users_email_key UNIQUE (email);
|
||||
|
||||
|
||||
--
|
||||
-- Name: users users_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.users
|
||||
ADD CONSTRAINT users_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: email_index; Type: INDEX; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
CREATE UNIQUE INDEX email_index ON public.users USING btree (email);
|
||||
|
||||
|
||||
--
|
||||
-- Name: products products_store_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.products
|
||||
ADD CONSTRAINT products_store_id_fkey FOREIGN KEY (store_id) REFERENCES public.stores(id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: stores stores_category_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.stores
|
||||
ADD CONSTRAINT stores_category_id_fkey FOREIGN KEY (category_id) REFERENCES public.category(id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: stores stores_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.stores
|
||||
ADD CONSTRAINT stores_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: SCHEMA public; Type: ACL; Schema: -; Owner: postgres
|
||||
--
|
||||
|
||||
REVOKE USAGE ON SCHEMA public FROM PUBLIC;
|
||||
|
||||
|
||||
--
|
||||
-- PostgreSQL database dump complete
|
||||
--
|
||||
|
||||
|
||||
|
||||
|
||||
`
|
||||
|
||||
|
||||
@@ -12,7 +12,8 @@ export const data_types = sqliteTable('data_types', {
|
||||
}).notNull().default("postgres"),
|
||||
|
||||
type: text('type').notNull(),
|
||||
modifiers: text("modifiers")
|
||||
modifiers: text("modifiers"),
|
||||
synonyms: text("synonyms")
|
||||
});
|
||||
|
||||
|
||||
@@ -24,6 +25,7 @@ export const dataTypeRelations = relations(fields, ({ many }) => ({
|
||||
|
||||
export interface DataType extends InferSelectModel<typeof data_types> {
|
||||
|
||||
modifiers: string | null | any ;
|
||||
modifiers: string | null | any;
|
||||
synonyms: string | null | any;
|
||||
|
||||
};
|
||||
@@ -2,7 +2,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/tooltip/to
|
||||
import { RelationshipType } from "@/lib/schemas/relationship-schema";
|
||||
import { useDatabase, useDatabaseOperations } from "@/providers/database-provider/database-provider";
|
||||
import { useDiagramOps } from "@/providers/diagram-provider/diagram-provider";
|
||||
import { CircularDependencyError } from "@/utils/render/parsers/render-uttils";
|
||||
import { CircularDependencyError } from "@/utils/render/render-uttils";
|
||||
import { addToast, Alert, Button, Listbox, ListboxItem, toast } from "@heroui/react";
|
||||
import { AlertTriangle, Trash } from "lucide-react";
|
||||
import React, { useCallback, useEffect, useMemo } from "react";
|
||||
@@ -40,7 +40,7 @@ const CircularDependencyAlert: React.FC<CircularDependencyAlertProps> = ({ error
|
||||
|
||||
|
||||
const focus = (key: any) => {
|
||||
console.log(key)
|
||||
|
||||
focusOnRelationship(
|
||||
key, true, false
|
||||
)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useRenderSql } from "@/hooks/user-render-sql";
|
||||
import { useDatabase } from "@/providers/database-provider/database-provider";
|
||||
import React, { useEffect, useMemo } from "react";
|
||||
import { useDatabase, useDatabaseOperations } from "@/providers/database-provider/database-provider";
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
|
||||
import CodeMirror, { EditorView } from '@uiw/react-codemirror';
|
||||
import { sql } from '@codemirror/lang-sql';
|
||||
@@ -11,7 +11,7 @@ import { overrideDarkTheme, overrideLightTheme } from "@/lib/colors";
|
||||
import { DatabaseType } from "@/lib/schemas/database-schema";
|
||||
import CircularDependencyAlert from "./circular-dependecy-alert";
|
||||
import { addToast } from "@heroui/react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
|
||||
|
||||
@@ -19,20 +19,20 @@ import { useTranslation } from "react-i18next";
|
||||
const SqlPreview: React.FC = ({ }) => {
|
||||
|
||||
const { database } = useDatabase();
|
||||
|
||||
|
||||
const { sql: sqlCode, circularDependency } = useRenderSql(database as DatabaseType);
|
||||
const { resolvedTheme } = useTheme();
|
||||
const { t } = useTranslation() ;
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
useEffect(() => {
|
||||
if (circularDependency)
|
||||
addToast({
|
||||
title: t("db_controller.circular_dependency.title"),
|
||||
description: t("db_controller.circular_dependency.description") ,
|
||||
description: t("db_controller.circular_dependency.description"),
|
||||
color: "danger",
|
||||
variant: "solid"
|
||||
});
|
||||
}, [circularDependency])
|
||||
}, [circularDependency]);
|
||||
|
||||
if (circularDependency)
|
||||
return <CircularDependencyAlert error={circularDependency} />
|
||||
@@ -40,7 +40,8 @@ const SqlPreview: React.FC = ({ }) => {
|
||||
return (
|
||||
<div className="flex w-full h-full ">
|
||||
{
|
||||
<CodeMirror
|
||||
<CodeMirror
|
||||
defaultValue={sqlCode}
|
||||
value={sqlCode}
|
||||
className="flex flex-1 w-full"
|
||||
extensions={[sql()]}
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
import Modal, { ModalProps } from "@/components/modal/modal"
|
||||
import ReactCodeMirror, { oneDark } from "@uiw/react-codemirror";
|
||||
import { useCallback, useMemo, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { overrideDarkTheme, overrideLightTheme } from "@/lib/colors";
|
||||
|
||||
import { sql } from '@codemirror/lang-sql';
|
||||
import { useTheme } from "next-themes";
|
||||
import { DatabaseDialect, ImportDatabaseMethod, ImportDatabaseOption, ImportMethodType } from "@/lib/database";
|
||||
import { useDatabase, useDatabaseOperations } from "@/providers/database-provider/database-provider";
|
||||
import { CheckboxGroup } from "@heroui/react";
|
||||
import OptionCheckbox from "@/components/checkbox/option-checkbox";
|
||||
import { Code } from "lucide-react";
|
||||
import { Code as CodeSection } from "@heroui/react";
|
||||
import { SqlToDatabase } from "@/utils/render/parsers/sql_to_database";
|
||||
import { adjustTablesPositions } from "@/utils/tables";
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const options: ImportDatabaseOption[] = [{
|
||||
dialect: DatabaseDialect.POSTGRES,
|
||||
methods: [{
|
||||
id: "pg_dump",
|
||||
name: "pg_dump",
|
||||
icon: <Code className="size-4 text-font/90" />,
|
||||
type: ImportMethodType.DUMP
|
||||
|
||||
}, {
|
||||
id: "pg_admin",
|
||||
name: "pg Admin",
|
||||
logo: "/postgresql_logo.png",
|
||||
type: ImportMethodType.DB_CLIENT
|
||||
}]
|
||||
}]
|
||||
|
||||
const ImportDatabaseModal: React.FC<ModalProps> = ({ isOpen, onOpenChange }) => {
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { resolvedTheme } = useTheme();
|
||||
const { database } = useDatabase();
|
||||
const { data_types, createTable , createRelationship} = useDatabaseOperations();
|
||||
const [sqlCode, setSqlCode] = useState<string>("");
|
||||
let currentOption: ImportDatabaseOption | undefined = useMemo(() => {
|
||||
return options.find((option: ImportDatabaseOption) => option.dialect == database?.dialect)
|
||||
}, [database]);
|
||||
|
||||
|
||||
const [selectedMethodId, setSelectedMethodId] = useState<string[]>(currentOption ? [currentOption.methods[0].id] : []);
|
||||
|
||||
if (!currentOption) {
|
||||
onOpenChange && onOpenChange(false);
|
||||
}
|
||||
|
||||
const onImportMethodChange = (types: string[]) => {
|
||||
const selectedType: string | undefined = types.pop();
|
||||
if (selectedType && selectedType != selectedMethodId?.[0])
|
||||
setSelectedMethodId([selectedType]);
|
||||
}
|
||||
|
||||
|
||||
const selectedImportMethod: ImportDatabaseMethod = useMemo(() => {
|
||||
return currentOption?.methods.find((method: ImportDatabaseMethod) => method.id == selectedMethodId?.[0]) as ImportDatabaseMethod;
|
||||
}, [selectedMethodId, currentOption])
|
||||
|
||||
const importDatabase = useCallback( async () => {
|
||||
|
||||
const {tables , relationships } = SqlToDatabase(sqlCode, data_types, database?.dialect as DatabaseDialect);
|
||||
|
||||
|
||||
for (const table of tables) {
|
||||
await createTable(table)
|
||||
}
|
||||
|
||||
for ( const relationship of relationships ) {
|
||||
await createRelationship(relationship) ;
|
||||
}
|
||||
}, [sqlCode, database?.dialect, data_types]);
|
||||
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onOpenChange={onOpenChange}
|
||||
title={t("modals.import_database.title")}
|
||||
actionName={t("modals.import_database.import")}
|
||||
className="min-w-[860px] max-w-[860px]"
|
||||
actionHandler={importDatabase}
|
||||
>
|
||||
<div className="flex flex-col gap-4 ">
|
||||
<div className="w-full ">
|
||||
<p className="text-sm text-font/90">
|
||||
{t("modals.import_database.import_options")}
|
||||
</p>
|
||||
<CheckboxGroup
|
||||
classNames={{
|
||||
base: "w-full p-0 ",
|
||||
wrapper: "flex flex-row p-2 gap-2 px-0 "
|
||||
}}
|
||||
aria-label="Select Database"
|
||||
value={selectedMethodId}
|
||||
onChange={onImportMethodChange}
|
||||
>
|
||||
{
|
||||
currentOption?.methods.map((method: ImportDatabaseMethod) => (
|
||||
<OptionCheckbox
|
||||
label={method.name}
|
||||
value={method.id}
|
||||
icon={method.icon}
|
||||
logo={method.logo}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</CheckboxGroup>
|
||||
{
|
||||
selectedImportMethod.type == ImportMethodType.DUMP &&
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-semibold">
|
||||
Instructions :
|
||||
</label>
|
||||
<ul className="list-decimal list-outside px-4 text-font/90 text-sm space-y-2 marker:font-semibold ">
|
||||
<li>
|
||||
install <span className="font-semibold text-font/90">{selectedImportMethod.name}</span> .
|
||||
</li>
|
||||
|
||||
<li>
|
||||
Run the following command in your terminal :
|
||||
<ReactCodeMirror
|
||||
className="flex flex-1 w-full border-1 my-2 rounded-md border-divider overflow-hidden "
|
||||
|
||||
editable={false}
|
||||
|
||||
value={`pg_dump -h <host> -p <port> -d <database_name>
|
||||
-U <username> -s -F p -E UTF-8
|
||||
-f <output_file_path>`}
|
||||
theme={resolvedTheme == "light" ? overrideLightTheme : [overrideDarkTheme, oneDark]}
|
||||
/>
|
||||
Example :
|
||||
<ReactCodeMirror
|
||||
className="flex flex-1 w-full border-1 my-2 rounded-md border-divider overflow-hidden "
|
||||
value={`pg_dump -h localhost -p 5432 -d my_db
|
||||
-U postgres -s -F p -E UTF-8
|
||||
-f schema_export.sql`}
|
||||
editable={false}
|
||||
theme={resolvedTheme == "light" ? overrideLightTheme : [overrideDarkTheme, oneDark]}
|
||||
/>
|
||||
|
||||
</li>
|
||||
<li>
|
||||
Drag and drop the output .sql file in code section or copy it content
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
selectedImportMethod.type == ImportMethodType.DB_CLIENT &&
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-semibold">
|
||||
Instructions :
|
||||
</label>
|
||||
<ul className="list-decimal list-outside px-4 text-font/90 text-sm space-y-3 marker:font-semibold ">
|
||||
<li>
|
||||
Open <span className="font-semibold text-font/90">{selectedImportMethod.name}</span> .
|
||||
</li>
|
||||
<li>
|
||||
Right-click your database and select <span className="font-semibold text-font/90">Backup</span> from the context menu.
|
||||
</li>
|
||||
<li>
|
||||
Name your <CodeSection className="p-1" color="default" size="sm"> .sql </CodeSection> file, set Format to <span className="font-semibold text-font/90">Plain</span>, and choose <span className="font-semibold text-font/90">Encoding: UTF8.</span>
|
||||
</li>
|
||||
<li>
|
||||
Make sure <span className="font-semibold text-font/90">Only schema</span> is checked and <span className="font-semibold text-font/90">Only data</span> is unchecked in the <span className="font-semibold text-font/90">Data Options tab</span>.
|
||||
</li>
|
||||
<li>
|
||||
Click <span className="font-semibold text-font/90">Backup</span> to export the file, then copy its content into the code editor section.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
|
||||
</div>
|
||||
<div className="flex flex-1 ">
|
||||
{
|
||||
|
||||
<ReactCodeMirror
|
||||
className="flex w-full min-h-[360px] max-h-[360px] border-1 rounded-md border-divider overflow-hidden"
|
||||
extensions={[sql()]}
|
||||
value={sqlCode}
|
||||
onChange={setSqlCode}
|
||||
theme={resolvedTheme == "light" ? overrideLightTheme : [oneDark, overrideDarkTheme]}
|
||||
/>
|
||||
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export default ImportDatabaseModal
|
||||
@@ -5,7 +5,8 @@ export enum Modals {
|
||||
CREATE_RELATIONSHIP = "CREATE_RELATIONSHIP",
|
||||
CREATE_DATABASE = "CREATE_DATABASE" ,
|
||||
OPEN_DATABASE = "OPEN_DATABASE" ,
|
||||
DELETE_DATABASE = "DELETE_DATABASE"
|
||||
DELETE_DATABASE = "DELETE_DATABASE" ,
|
||||
IMPORT_DATABASE = "IMPOR_DATABASE"
|
||||
}
|
||||
|
||||
interface ModalContextType {
|
||||
|
||||
@@ -7,6 +7,7 @@ import CreateRelationshipModal from "@/pages/database/modals/create-relationship
|
||||
import { CreateDatabaseModal } from "@/pages/database/modals/create-database-modal";
|
||||
import OpenDatabaseModal from "@/pages/database/modals/open-database-modal";
|
||||
import DeleteDatabaseModal from "@/pages/database/modals/delete-database-modal";
|
||||
import ImportDatabaseModal from "@/pages/database/modals/import-database";
|
||||
|
||||
|
||||
interface Props { children: React.ReactNode }
|
||||
@@ -54,8 +55,12 @@ export const ModalProvider: React.FC<Props> = ({ children }) => {
|
||||
||
|
||||
currentModal.modal == Modals.DELETE_DATABASE &&
|
||||
<DeleteDatabaseModal {...currentModal.props} onOpenChange={onOpenChange} isOpen={isOpen} />
|
||||
||
|
||||
(currentModal.modal == Modals.IMPORT_DATABASE ) &&
|
||||
<ImportDatabaseModal {...currentModal.props} onOpenChange={onOpenChange} isOpen={isOpen} />
|
||||
) : undefined
|
||||
}
|
||||
|
||||
}
|
||||
{children}
|
||||
</ModalContext.Provider>
|
||||
)
|
||||
|
||||
+28
-13
@@ -187,29 +187,44 @@ tbody[role="rowgroup"] tr td:first-child {
|
||||
|
||||
button[data-testid="remove"] {
|
||||
|
||||
top : 1px ;
|
||||
top: 1px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
button[data-testid="remove"]:after {
|
||||
content: '×' ;
|
||||
color : white;
|
||||
font-size : 11px ;
|
||||
content: '×';
|
||||
color: white;
|
||||
font-size: 11px;
|
||||
position: relative;
|
||||
top: -2px ;
|
||||
|
||||
top: -2px;
|
||||
|
||||
|
||||
}
|
||||
|
||||
button[data-testid="remove"] svg {
|
||||
|
||||
|
||||
|
||||
display: none ;
|
||||
|
||||
display: none;
|
||||
|
||||
}
|
||||
|
||||
[data-invalid = "true"] div[data-slot="input-wrapper"] {
|
||||
border : 1px solid red !important ;
|
||||
background-color: transparent ;
|
||||
outline: none ;
|
||||
}
|
||||
[data-invalid="true"] div[data-slot="input-wrapper"] {
|
||||
border: 1px solid red !important;
|
||||
background-color: transparent;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.option-checkbox[data-selected="true"] .option-span {
|
||||
border-color: hsl(var(--heroui-divider));
|
||||
background-color: hsl(var(--heroui-default));
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,286 @@
|
||||
import { Modifiers, TimeDefaultValues } from "@/lib/field";
|
||||
import { DataType } from "@/lib/schemas/data-type-schema";
|
||||
import { FieldInsertType, FieldType } from "@/lib/schemas/field-schema";
|
||||
import { TableInsertType } from "@/lib/schemas/table-schema";
|
||||
import { Parser } from "node-sql-parser";
|
||||
import { v4 } from "uuid";
|
||||
import { parse } from 'pgsql-ast-parser';
|
||||
import { DatabaseDialect } from "@/lib/database";
|
||||
import { randomColor } from "@/lib/colors";
|
||||
import { Cardinality, RelationshipInsertType } from "@/lib/schemas/relationship-schema";
|
||||
|
||||
|
||||
export const SqlToDatabase = (sql: string, data_types: DataType[], dialect: DatabaseDialect) => {
|
||||
const parser = new Parser();
|
||||
const createTableStatements: string[] = [];
|
||||
const alterTableStatements: string[] = [];
|
||||
const createIndexStatements: string[] = [];
|
||||
const createPostgresTypesStatements: string[] = [];
|
||||
const tables: TableInsertType[] = [];
|
||||
let relationships: RelationshipInsertType[] = [];
|
||||
const postgresTypes: any[] = [];
|
||||
// Clean up SQL: remove comments and normalize
|
||||
const cleanedSql = sql
|
||||
.replace(/--.*$/gm, '') // remove single-line comments
|
||||
.replace(/\/\*[\s\S]*?\*\//g, '') // remove multi-line comments
|
||||
.replace(/\s+/g, ' ') // normalize whitespace
|
||||
.replace(/;\s*/g, ';\n'); // separate statements
|
||||
|
||||
// Split into individual statements
|
||||
const statements = cleanedSql
|
||||
.split('\n')
|
||||
.map(s => s.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
for (const stmt of statements) {
|
||||
const upper = stmt.toUpperCase();
|
||||
|
||||
if (upper.startsWith('CREATE TABLE')) {
|
||||
createTableStatements.push(stmt);
|
||||
} else if (upper.startsWith('ALTER TABLE')) {
|
||||
alterTableStatements.push(stmt);
|
||||
} else if (upper.startsWith('CREATE INDEX')) {
|
||||
createIndexStatements.push(stmt);
|
||||
} else if (upper.startsWith('CREATE TYPE')) {
|
||||
createPostgresTypesStatements.push(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
if (dialect == DatabaseDialect.POSTGRES)
|
||||
for (const postgresType of createPostgresTypesStatements) {
|
||||
try {
|
||||
const instructionAst = parse(postgresType);
|
||||
if (Array.isArray(instructionAst) && instructionAst.length > 0) {
|
||||
postgresTypes.push(instructionAst[0]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
if (dialect == DatabaseDialect.POSTGRES)
|
||||
for (const createTable of createTableStatements) {
|
||||
|
||||
try {
|
||||
const instructionAst = parse(createTable);
|
||||
if (Array.isArray(instructionAst) && instructionAst.length > 0) {
|
||||
tables.push(postgresAstToTable(instructionAst[0], data_types, postgresTypes));
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
const instructionAst = parser.astify(createTable, {
|
||||
database: "MySql"
|
||||
});
|
||||
if (Array.isArray(instructionAst) && instructionAst.length > 0) {
|
||||
tables.push(astToTable(instructionAst[0], data_types));
|
||||
}*/
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
for (const alterTable of alterTableStatements) {
|
||||
try {
|
||||
const instructionAst = parse(alterTable);
|
||||
if (Array.isArray(instructionAst) && instructionAst.length > 0) {
|
||||
const extractedRelationships: RelationshipInsertType[] = astToRelationship((instructionAst[0] as any), tables);
|
||||
relationships = relationships.concat(extractedRelationships);
|
||||
}
|
||||
} catch (error) {
|
||||
if ((error as any).relationships && (error as any).relationships.length > 0)
|
||||
relationships = relationships.concat((error as any).relationships);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return {tables , relationships};
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export const postgresAstToTable = (ast: any, data_types: DataType[], postgresTypes: any[]): TableInsertType => {
|
||||
// console.log(ast);
|
||||
return {
|
||||
id: v4(),
|
||||
name: ast.name.name,
|
||||
fields: ast.columns.filter((column: any) => column.kind == "column")
|
||||
.map((fieldAst: any, index: number) => postgresAstToField(fieldAst, data_types, index, postgresTypes)),
|
||||
color: randomColor(),
|
||||
} as TableInsertType;
|
||||
}
|
||||
|
||||
|
||||
export const postgresAstToField = (ast: any, data_types: DataType[], sequence: number, postgresTypes: any[]): FieldInsertType => {
|
||||
|
||||
let dataType: DataType | undefined = data_types.find((dataType: DataType) => {
|
||||
const synonyms: string[] = dataType.synonyms ? JSON.parse(dataType.synonyms) : [];
|
||||
return dataType.name == ast.dataType.name?.toLowerCase() || synonyms.includes(ast.dataType.name?.toLowerCase())
|
||||
});
|
||||
|
||||
let values: string | undefined;
|
||||
|
||||
if (!dataType && postgresTypes && postgresTypes.length > 0) {
|
||||
|
||||
const postgresType: any = postgresTypes.find((type: any) => type.name.name == ast.dataType.name);
|
||||
if (postgresType) {
|
||||
dataType = data_types.find((dataType: DataType) => dataType.name == "enum") as DataType;
|
||||
values = JSON.stringify(postgresType.values.map((value: any) => value.value));
|
||||
};
|
||||
}
|
||||
|
||||
const modifiers: string[] = dataType?.modifiers ? JSON.parse(dataType.modifiers) : [];
|
||||
|
||||
let length: number | undefined;
|
||||
let scale: number | undefined;
|
||||
|
||||
if (ast.dataType.config && ast.dataType.config.length > 0)
|
||||
if (ast.dataType.config.length == 1)
|
||||
length = ast.dataType.config[0];
|
||||
else if (ast.dataType.config.length == 2)
|
||||
scale = ast.dataType.config[1];
|
||||
|
||||
|
||||
let maxLength: number | null = null;
|
||||
let precision: number | null = null;
|
||||
|
||||
if (modifiers.includes(Modifiers.LENGTH) && length)
|
||||
maxLength = length;
|
||||
|
||||
if (modifiers.includes(Modifiers.PRECISION) && length)
|
||||
precision = length;
|
||||
|
||||
let defaultValue: string | undefined;
|
||||
let nullable: boolean = true;;
|
||||
|
||||
const constraints: any[] | undefined = ast.constraints;
|
||||
if (constraints && constraints.length > 0) {
|
||||
|
||||
const nullableConstraints: any | undefined = constraints.find((c: any) => c.type == "not null");
|
||||
if (nullableConstraints)
|
||||
nullable = false;
|
||||
|
||||
const defaultValueConstraints: any | undefined = constraints.find((c: any) => c.type == "default");
|
||||
if (defaultValueConstraints) {
|
||||
if (defaultValueConstraints.default.type == "keyword" && defaultValueConstraints.default.keyword == "current_timestamp")
|
||||
defaultValue = TimeDefaultValues.NOW;
|
||||
else if (defaultValueConstraints.default.type == "cast" && defaultValueConstraints.default.operand)
|
||||
defaultValue = String(defaultValueConstraints.default.operand.value)
|
||||
else
|
||||
defaultValue = String(defaultValueConstraints.default.value);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: v4(),
|
||||
name: ast.name.name,
|
||||
defaultValue,
|
||||
typeId: dataType?.id,
|
||||
nullable,
|
||||
unique: ast.unique,
|
||||
maxLength,
|
||||
precision,
|
||||
scale,
|
||||
sequence,
|
||||
values
|
||||
} as FieldInsertType;
|
||||
}
|
||||
|
||||
|
||||
const astToTable = (ast: any, data_types: DataType[]): TableInsertType => {
|
||||
|
||||
|
||||
return {
|
||||
id: v4(),
|
||||
name: ast.table[0]?.table,
|
||||
fields: ast.create_definitions.filter((column: any) => column.resource == "column")
|
||||
.map((fieldAst: any) => astToField(fieldAst, data_types))
|
||||
} as TableInsertType;
|
||||
}
|
||||
|
||||
|
||||
export const astToField = (ast: any, data_types: DataType[]): FieldInsertType => {
|
||||
const dataType: DataType | undefined = data_types.find((dataType: DataType) => dataType.name == ast.definition.dataType?.toLowerCase());
|
||||
const modifiers: string[] = dataType?.modifiers ? JSON.parse(dataType.modifiers) : [];
|
||||
|
||||
const { length, scale } = ast.definition;
|
||||
|
||||
let maxLength: number | null = null;
|
||||
let precision: number | null = null;
|
||||
|
||||
if (modifiers.includes(Modifiers.LENGTH) && length)
|
||||
maxLength = length;
|
||||
|
||||
if (modifiers.includes(Modifiers.PRECISION) && length)
|
||||
precision = length;
|
||||
|
||||
|
||||
return {
|
||||
id: v4(),
|
||||
name: ast.column.column,
|
||||
defaultValue: ast.default_val,
|
||||
typeId: dataType?.id,
|
||||
nullable: ast.nullable?.value == "not null",
|
||||
unique: ast.unique,
|
||||
maxLength,
|
||||
precision,
|
||||
scale: scale,
|
||||
|
||||
} as FieldInsertType;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const astToRelationship = (ast: any, tables: TableInsertType[]): RelationshipInsertType[] => {
|
||||
|
||||
const relationships: RelationshipInsertType[] = [];
|
||||
const changes = ast.changes;
|
||||
|
||||
|
||||
|
||||
const targetTable: TableInsertType | undefined = tables.find((table: TableInsertType) => table.name == ast.table.name);
|
||||
|
||||
if (!targetTable)
|
||||
throw Error("source table not found");
|
||||
|
||||
const foreignKeyConstraints = changes.filter((change: any) => change.type == 'add constraint' && change.constraint && change.constraint.type == "foreign key").map((change: any) => change.constraint);
|
||||
|
||||
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)
|
||||
|
||||
|
||||
|
||||
if (!sourceField || !targetField || !sourceTable)
|
||||
continue;
|
||||
|
||||
relationships.push({
|
||||
id: v4(),
|
||||
sourceTableId: sourceTable.id,
|
||||
targetTableId: targetTable.id,
|
||||
sourceFieldId: sourceField.id,
|
||||
targetFieldId: targetField.id,
|
||||
cardinality: targetField.unique ? Cardinality.one_to_one : Cardinality.one_to_many
|
||||
} as RelationshipInsertType)
|
||||
}
|
||||
|
||||
|
||||
if (relationships.length == foreignKeyConstraints.length)
|
||||
return relationships;
|
||||
else
|
||||
throw Error({
|
||||
success: false,
|
||||
message: "Failed to Extract all relationships",
|
||||
relationships
|
||||
} as any)
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user