mirror of
https://github.com/stackrender/stackrender.git
synced 2026-09-10 03:05:42 +00:00
Column Modifier for Postgres Implemented
This commit is contained in:
@@ -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",
|
||||
|
||||
|
||||
+8
-2
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const MYSQL_MAX_VAR_LENGTH = 255 ;
|
||||
@@ -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)
|
||||
|
||||
+87
-11
@@ -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<FieldDefaultValueProps> = ({ field }) => {
|
||||
});
|
||||
|
||||
const defaultValueRef: Ref<HTMLInputElement> = useRef<HTMLInputElement>(null);
|
||||
const [values, setValues] = useState<string[]>([]);
|
||||
|
||||
|
||||
const [timeSelection, setTimeSelection] = useState<string[]>(() => {
|
||||
|
||||
@@ -68,6 +73,41 @@ const fieldDefautlValue: React.FC<FieldDefaultValueProps> = ({ 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<string[]>(() => {
|
||||
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<FieldDefaultValueProps> = ({ 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<FieldDefaultValueProps> = ({ 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<FieldDefaultValueProps> = ({ 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<FieldDefaultValueProps> = ({ field }) => {
|
||||
</>
|
||||
|
||||
}
|
||||
{
|
||||
(defaultValueType.select || defaultValueType.multiSelect) &&
|
||||
<Select
|
||||
className="w-full"
|
||||
size="sm"
|
||||
variant="bordered"
|
||||
aria-label="Enum Values"
|
||||
placeholder={t('db_controller.field_settings.pick_value')}
|
||||
selectedKeys={selectedValues}
|
||||
onSelectionChange={enumValueChange}
|
||||
selectionMode={!defaultValueType.multiSelect ? 'single' : "multiple"}
|
||||
classNames={{
|
||||
trigger: "border-divider group-hover:border-primary",
|
||||
}}
|
||||
>
|
||||
{
|
||||
values.map((value: string) => (
|
||||
<SelectItem key={value}>{value}</SelectItem>
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
</Select>
|
||||
|
||||
}
|
||||
</>
|
||||
|
||||
)
|
||||
|
||||
+15
-9
@@ -175,6 +175,7 @@ const FieldSetting: React.FC<FieldSettingProps> = ({ field }) => {
|
||||
|
||||
|
||||
const updateValues = useCallback((values: string[]) => {
|
||||
|
||||
const jsonValues = JSON.stringify(values);
|
||||
|
||||
if (jsonValues != field.values)
|
||||
@@ -183,6 +184,7 @@ const FieldSetting: React.FC<FieldSettingProps> = ({ 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<FieldSettingProps> = ({ field }) => {
|
||||
}, [precisionRef, scaleRef])
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div className="w-full flex flex-col gap-2 p-2 min-w-[260px] max-w-[260px]">
|
||||
<h3 className="font-semibold text-sm text-font/90">
|
||||
{t("db_controller.field_settings.title")}
|
||||
</h3>
|
||||
<hr className="border-divider" />
|
||||
|
||||
<div className="flex w-full justify-between">
|
||||
<span className="text-xs text-font/70 font-medium dark:text-font/90">
|
||||
{t("db_controller.field_settings.unique")}
|
||||
</span>
|
||||
<Checkbox defaultSelected={field.unique as boolean} size="md" onValueChange={toggleUnqiue} />
|
||||
</div>
|
||||
{
|
||||
!modifiers.includes(Modifiers.NO_UNIQUE) &&
|
||||
<div className="flex w-full justify-between">
|
||||
<span className="text-xs text-font/70 font-medium dark:text-font/90">
|
||||
{t("db_controller.field_settings.unique")}
|
||||
</span>
|
||||
<Checkbox defaultSelected={field.unique as boolean} size="md" onValueChange={toggleUnqiue} />
|
||||
</div>
|
||||
}
|
||||
{
|
||||
showNumericModifiers && <>
|
||||
<h3 className="font-semibold text-sm text-font/90">
|
||||
@@ -517,9 +522,10 @@ const FieldSetting: React.FC<FieldSettingProps> = ({ field }) => {
|
||||
</>
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
!modifiers.includes(Modifiers.NO_DEFAULT) &&
|
||||
<FieldDefaultValue field={field} />
|
||||
|
||||
}
|
||||
<label className="text-xs font-medium text-font/70 dark:text-font/90">
|
||||
{t("db_controller.field_settings.note")}
|
||||
</label>
|
||||
|
||||
@@ -30,7 +30,6 @@ const TablesController: React.FC<Props> = ({ }) => {
|
||||
const { focusedTableId } = useDiagram();
|
||||
const nameRef: Ref<HTMLInputElement> = useRef<HTMLInputElement>(null);
|
||||
|
||||
|
||||
useEffect(() => setTables(allTables), [allTables]);
|
||||
|
||||
const addNewTable = useCallback(async () => {
|
||||
@@ -52,13 +51,13 @@ const TablesController: React.FC<Props> = ({ }) => {
|
||||
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(() => {
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Props> = ({ 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<Props> = ({ 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<Props> = ({ children }) => {
|
||||
}}>
|
||||
<DatabaseOperationsContext.Provider value={databaseOpsValue}>
|
||||
{
|
||||
!database && !isLoading &&
|
||||
!database && !isLoading &&
|
||||
<>
|
||||
{children}
|
||||
</>
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user