diff --git a/src/i18/languages/en.ts b/src/i18/languages/en.ts index 6d84e0c..af17323 100644 --- a/src/i18/languages/en.ts +++ b/src/i18/languages/en.ts @@ -91,7 +91,8 @@ export const en = { scale : "Scale must be positive number, no decimals." , scale_max_value : "Scale must be ≤ precision." - } + } , + pick_value : "Pick value" }, delete: "Delete", diff --git a/src/lib/field.ts b/src/lib/field.ts index 48010b0..0d45df1 100644 --- a/src/lib/field.ts +++ b/src/lib/field.ts @@ -9,6 +9,8 @@ export enum Modifiers { CHARSET = "charset", COLLATE = "collate", VALUES = "values", + NO_DEFAULT = "no_default" , + NO_UNIQUE = "no_unique" } export enum MySQLCharset { @@ -85,7 +87,8 @@ export enum DataTypes { TEXT = "text", BINARY = "binary", JSON = "JSON", - GEOMETRY = "geometry" + GEOMETRY = "geometry", + ENUM = "enum" } @@ -93,4 +96,7 @@ export enum TimeDefaultValues { NO_VALUE = "no_value", CUSTOM = "custom" , NOW = "now" -} \ No newline at end of file +} + + +export const MYSQL_MAX_VAR_LENGTH = 255 ; \ No newline at end of file diff --git a/src/pages/database/db-controller/sql-preview.tsx b/src/pages/database/db-controller/sql-preview.tsx index c77ca25..ec1bf3b 100644 --- a/src/pages/database/db-controller/sql-preview.tsx +++ b/src/pages/database/db-controller/sql-preview.tsx @@ -11,15 +11,7 @@ import { overrideDarkTheme, overrideLightTheme } from "@/lib/colors"; import { DatabaseType } from "@/lib/schemas/database-schema"; const parser = new Parser(); const code = ` -CREATE TABLE events ( - id INT AUTO_INCREMENT PRIMARY KEY, - - - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - - - logged_at DATETIME DEFAULT CURRENT_TIMESTAMP -); + ` @@ -33,7 +25,7 @@ const SqlPreview: React.FC = ({ }) => { useEffect(() => { const ast = parser.astify(code, { - database: "Mysql" + database: "Postgresql" }) ; console.log(ast) diff --git a/src/pages/database/db-controller/tables-controller/table-accordion-item/field/field-default-value.tsx b/src/pages/database/db-controller/tables-controller/table-accordion-item/field/field-default-value.tsx index b92fa81..d556150 100644 --- a/src/pages/database/db-controller/tables-controller/table-accordion-item/field/field-default-value.tsx +++ b/src/pages/database/db-controller/tables-controller/table-accordion-item/field/field-default-value.tsx @@ -1,6 +1,6 @@ import { FieldInsertType, FieldType } from "@/lib/schemas/field-schema" import { Checkbox, DatePicker, Input, Select, SelectItem, Switch, TimeInput, Tooltip } from "@heroui/react"; -import React, { Ref, useCallback, useMemo, useRef, useState } from "react"; +import React, { Ref, useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { ModifierValidation } from "./field-setting"; import { DataTypes, TimeDefaultValues } from "@/lib/field"; @@ -8,12 +8,15 @@ import { Calendar, Clock, TriangleAlert } from "lucide-react"; import { useDatabaseOperations } from "@/providers/database-provider/database-provider"; import dayjs from 'dayjs'; import { now, parseAbsoluteToLocal, Time } from "@internationalized/date"; +import { DatabaseDialect } from "@/lib/database"; interface DefaultValueType { number?: boolean; string?: boolean; boolean?: boolean; time?: boolean; + select?: boolean; + multiSelect?: boolean } interface FieldDefaultValueProps { @@ -31,6 +34,8 @@ const fieldDefautlValue: React.FC = ({ field }) => { }); const defaultValueRef: Ref = useRef(null); + const [values, setValues] = useState([]); + const [timeSelection, setTimeSelection] = useState(() => { @@ -68,6 +73,41 @@ const fieldDefautlValue: React.FC = ({ field }) => { } }); + const defaultValueType: DefaultValueType = useMemo(() => { + return { + number: field.type?.type == DataTypes.INTEGER || field.type?.type == DataTypes.NUMERIC, + string: field.type?.type == DataTypes.TEXT || field.type?.name == "year" , + boolean: field.type?.type == DataTypes.BOOLEAN, + time: field.type?.type == DataTypes.TIME && field.type?.name != "year", + select: field.type?.type == DataTypes.ENUM && field.type?.name != "set", + multiSelect: field.type?.type == DataTypes.ENUM && field.type?.name == "set" + } + }, [field]); + const [selectedValues, setSelectedValues] = useState(() => { + if (!field.defaultValue) + return [] + if (defaultValueType.multiSelect) + return field.defaultValue.split(",") + + return [field.defaultValue as string]; + + }); + + + useEffect(() => { + if (field.values && field.values.length > 0) { + try { + const jsonValues: string[] = JSON.parse(field.values); + + setValues(jsonValues); + } catch (error) { + setValues([]); + } + } + }, [field.values]); + + + const defaultValueChange = useCallback((value: any) => { if (value && value.trim().length > 0) { if (field.type?.type == DataTypes.INTEGER) { @@ -105,14 +145,7 @@ const fieldDefautlValue: React.FC = ({ field }) => { }, [defaultValueRef, field, defaultValueValidation]); - const defaultValueType: DefaultValueType = useMemo(() => { - return { - number: field.type?.type == DataTypes.INTEGER || field.type?.type == DataTypes.NUMERIC, - string: field.type.type == DataTypes.TEXT || field.type.name == "year", - boolean: field.type.type == DataTypes.BOOLEAN, - time: field.type.type == DataTypes.TIME && field.type.name != "year" - } - }, [field]) + const changeTimeDefaultValue = (selection: any) => { let value: string | undefined; @@ -165,8 +198,26 @@ const fieldDefautlValue: React.FC = ({ field }) => { id: field.id, defaultValue: value ? String(value) : null }) as FieldInsertType); - }, [field, defaultDateTime]) + }, [field, defaultDateTime]); + const enumValueChange = useCallback((selection: any) => { + let values: string | null = null; + + const arraySelection = Array.from(selection); + + if (arraySelection.length > 0) { + values = arraySelection.join(','); + } else { + values = null; + } + + editField({ + id: field.id, + defaultValue: values + } as FieldInsertType); + + setSelectedValues(selection); + }, [field]) return ( @@ -226,7 +277,7 @@ const fieldDefautlValue: React.FC = ({ field }) => { className="w-full" size="sm" variant="bordered" - aria-label="cardinality" + aria-label="Time" selectedKeys={timeSelection} onSelectionChange={changeTimeDefaultValue} classNames={{ @@ -295,6 +346,31 @@ const fieldDefautlValue: React.FC = ({ field }) => { } + { + (defaultValueType.select || defaultValueType.multiSelect) && + + + } ) diff --git a/src/pages/database/db-controller/tables-controller/table-accordion-item/field/field-setting.tsx b/src/pages/database/db-controller/tables-controller/table-accordion-item/field/field-setting.tsx index d55c93b..c474c64 100644 --- a/src/pages/database/db-controller/tables-controller/table-accordion-item/field/field-setting.tsx +++ b/src/pages/database/db-controller/tables-controller/table-accordion-item/field/field-setting.tsx @@ -175,6 +175,7 @@ const FieldSetting: React.FC = ({ field }) => { const updateValues = useCallback((values: string[]) => { + const jsonValues = JSON.stringify(values); if (jsonValues != field.values) @@ -183,6 +184,7 @@ const FieldSetting: React.FC = ({ field }) => { values: jsonValues, } as FieldInsertType); + }, [field]) const showNumericModifiers: boolean = modifiers.includes(Modifiers.AUTO_INCREMENT) || modifiers.includes(Modifiers.UNSIGNED) || modifiers.includes(Modifiers.ZEROFILL) @@ -257,19 +259,22 @@ const FieldSetting: React.FC = ({ field }) => { }, [precisionRef, scaleRef]) + return (

{t("db_controller.field_settings.title")}


- -
- - {t("db_controller.field_settings.unique")} - - -
+ { + !modifiers.includes(Modifiers.NO_UNIQUE) && +
+ + {t("db_controller.field_settings.unique")} + + +
+ } { showNumericModifiers && <>

@@ -517,9 +522,10 @@ const FieldSetting: React.FC = ({ field }) => { } - + { + !modifiers.includes(Modifiers.NO_DEFAULT) && - + } diff --git a/src/pages/database/db-controller/tables-controller/tables-controller.tsx b/src/pages/database/db-controller/tables-controller/tables-controller.tsx index 21ac8bc..aa092b4 100644 --- a/src/pages/database/db-controller/tables-controller/tables-controller.tsx +++ b/src/pages/database/db-controller/tables-controller/tables-controller.tsx @@ -30,7 +30,6 @@ const TablesController: React.FC = ({ }) => { const { focusedTableId } = useDiagram(); const nameRef: Ref = useRef(null); - useEffect(() => setTables(allTables), [allTables]); const addNewTable = useCallback(async () => { @@ -52,13 +51,13 @@ const TablesController: React.FC = ({ }) => { name: "id", isPrimary: true, unique: true, - typeId: getDefaultPrimaryKeyType()?.id + typeId: getDefaultPrimaryKeyType(database?.dialect)?.id }] } as TableInsertType); setSelectedTable(new Set([newTableId]) as any); - }, [tables, getViewport, getDefaultPrimaryKeyType]); + }, [database , tables, getViewport, getDefaultPrimaryKeyType]); useEffect(() => { diff --git a/src/providers/database-provider/database-context.ts b/src/providers/database-provider/database-context.ts index 09dbb39..da0f633 100644 --- a/src/providers/database-provider/database-context.ts +++ b/src/providers/database-provider/database-context.ts @@ -1,4 +1,5 @@ +import { DatabaseDialect } from "@/lib/database"; import { DataType } from "@/lib/schemas/data-type-schema"; import { DatabaseInsertType, DatabaseType } from "@/lib/schemas/database-schema"; import { FieldInsertType, FieldType } from "@/lib/schemas/field-schema"; @@ -20,7 +21,7 @@ interface DatabaseDataContextType { isLoading: boolean, isSwitchingDatabase: boolean, getField: (tableId: string, id: string) => FieldType | undefined, - getDefaultPrimaryKeyType : () => DataType | undefined + getDefaultPrimaryKeyType : (dialect? : DatabaseDialect) => DataType | undefined } diff --git a/src/providers/database-provider/database-provider.tsx b/src/providers/database-provider/database-provider.tsx index d9bedf9..de0019c 100644 --- a/src/providers/database-provider/database-provider.tsx +++ b/src/providers/database-provider/database-provider.tsx @@ -17,6 +17,7 @@ import { field_indices } from "@/lib/schemas/field_index-schema"; import { v4 } from "uuid"; import { DataType } from "@/lib/schemas/data-type-schema"; import { Modifiers } from "@/lib/field"; +import { DatabaseDialect } from "@/lib/database"; @@ -90,16 +91,16 @@ const DatabaseProvider: React.FC = ({ children }) => { )); - const charsetOrCollationDataTypes = data_types.filter((dataType : DataType) => { - const modifiers : string[] | undefined = dataType.modifiers ? JSON.parse(dataType.modifiers) : undefined ; - if ( modifiers) { - return modifiers.includes(Modifiers.COLLATE || Modifiers.CHARSET) + const charsetOrCollationDataTypes = data_types.filter((dataType: DataType) => { + const modifiers: string[] | undefined = dataType.modifiers ? JSON.parse(dataType.modifiers) : undefined; + if (modifiers) { + return modifiers.includes(Modifiers.COLLATE || Modifiers.CHARSET) } - }).map((dataType : DataType) => dataType.name?.toUpperCase()) ; + }).map((dataType: DataType) => dataType.name?.toUpperCase()); + + - - const grouped_data_types: any = useMemo(() => { return groupBy(data_types, "type"); }, [data_types]); @@ -371,8 +372,11 @@ const DatabaseProvider: React.FC = ({ children }) => { }, [db, currentDatabaseId]); - const getDefaultPrimaryKeyType = useCallback(() => { - return data_types.find((dataType: DataType) => dataType.name == "bigint") + const getDefaultPrimaryKeyType = useCallback((dialect?: DatabaseDialect) => { + if (!dialect || dialect != DatabaseDialect.POSTGRES) + return data_types.find((dataType: DataType) => dataType.name == "bigint"); + else if (dialect == DatabaseDialect.POSTGRES) + return data_types.find((dataType: DataType) => dataType.name == "bigserial"); }, [data_types]); const databaseOpsValue = useMemo(() => ({ @@ -441,7 +445,7 @@ const DatabaseProvider: React.FC = ({ children }) => { }}> { - !database && !isLoading && + !database && !isLoading && <> {children} diff --git a/src/utils/render/parsers/database_to_ast.ts b/src/utils/render/parsers/database_to_ast.ts index 7469d56..c5fb519 100644 --- a/src/utils/render/parsers/database_to_ast.ts +++ b/src/utils/render/parsers/database_to_ast.ts @@ -1,7 +1,7 @@ import { DatabaseDialect } from "@/lib/database"; -import { DataTypes, Modifiers, TimeDefaultValues } from "@/lib/field"; +import { DataTypes, Modifiers, MYSQL_MAX_VAR_LENGTH, TimeDefaultValues } from "@/lib/field"; import { DataType } from "@/lib/schemas/data-type-schema"; import { DatabaseType } from "@/lib/schemas/database-schema"; import { FieldType } from "@/lib/schemas/field-schema"; @@ -97,6 +97,8 @@ export const FieldToAst = (field: FieldType, ignorePrimaryKey: boolean = false) let valuesExpr: any | null; let default_val: any | null = null; + let dataType : string | undefined = field.type?.name?.toLocaleUpperCase() ; + if (modifiers.includes(Modifiers.PRECISION) && field.precision) { length = field.precision; @@ -109,7 +111,8 @@ export const FieldToAst = (field: FieldType, ignorePrimaryKey: boolean = false) if (modifiers.includes(Modifiers.LENGTH) && field.maxLength) length = field.maxLength; - + else if (modifiers.includes(Modifiers.LENGTH) && field.type.dialect == DatabaseDialect.MYSQL && field.type.name?.startsWith('var') && !field.maxLength) + length = MYSQL_MAX_VAR_LENGTH ; if (modifiers.includes(Modifiers.CHARSET) && field.charset) character_set = { @@ -172,7 +175,6 @@ export const FieldToAst = (field: FieldType, ignorePrimaryKey: boolean = false) } else if (field.defaultValue == "true" || field.defaultValue == "false") { - default_val.value.type = "bool" default_val.value.value = field.defaultValue == "true" ? true : false; } @@ -184,6 +186,9 @@ export const FieldToAst = (field: FieldType, ignorePrimaryKey: boolean = false) } } + + + // field.type?.name == "varchar" && (field.type?.dialect == DatabaseDialect.MYSQL || field.type?.dialect == DatabaseDialect.MARIADB) ? 255 : null return { column: { @@ -198,14 +203,14 @@ export const FieldToAst = (field: FieldType, ignorePrimaryKey: boolean = false) character_set, default_val, unique: field.unique ? "unique" : null, - auto_increment: (modifiers.includes(Modifiers.AUTO_INCREMENT) && field.autoIncrement) ? "auto_increment" : null, + auto_increment: (modifiers.includes(Modifiers.AUTO_INCREMENT) && field.autoIncrement && field.type.dialect != DatabaseDialect.POSTGRES) ? "auto_increment" : null, nullable: { - type: field.nullable ? "null" : "not null", + type: field.nullable ? "null" : "not null", value: field.nullable ? "null" : "not null", }, definition: { - dataType: field.type?.name?.toLocaleUpperCase(), + dataType , length, scale, suffix,