mirror of
https://github.com/chartdb/chartdb.git
synced 2025-11-04 14:03:15 +00:00
Compare commits
1 Commits
v1.15.1
...
jf/add_sup
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
21ba816a6d |
@@ -143,6 +143,7 @@ export const en = {
|
|||||||
title: 'Field Attributes',
|
title: 'Field Attributes',
|
||||||
unique: 'Unique',
|
unique: 'Unique',
|
||||||
auto_increment: 'Auto Increment',
|
auto_increment: 'Auto Increment',
|
||||||
|
array: 'Declare Array',
|
||||||
character_length: 'Max Length',
|
character_length: 'Max Length',
|
||||||
precision: 'Precision',
|
precision: 'Precision',
|
||||||
scale: 'Scale',
|
scale: 'Scale',
|
||||||
|
|||||||
@@ -165,3 +165,21 @@ export const supportsAutoIncrementDataType = (
|
|||||||
'decimal',
|
'decimal',
|
||||||
].includes(dataTypeName.toLocaleLowerCase());
|
].includes(dataTypeName.toLocaleLowerCase());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const supportsArrayDataType = (dataTypeName: string): boolean => {
|
||||||
|
// Types that do NOT support arrays in PostgreSQL
|
||||||
|
const unsupportedTypes = [
|
||||||
|
'serial',
|
||||||
|
'bigserial',
|
||||||
|
'smallserial',
|
||||||
|
'serial2',
|
||||||
|
'serial4',
|
||||||
|
'serial8',
|
||||||
|
'xml',
|
||||||
|
'money',
|
||||||
|
];
|
||||||
|
|
||||||
|
// Check if the type is in the unsupported list
|
||||||
|
const normalizedType = dataTypeName.toLowerCase();
|
||||||
|
return !unsupportedTypes.includes(normalizedType);
|
||||||
|
};
|
||||||
|
|||||||
@@ -286,10 +286,14 @@ export function exportPostgreSQL({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle array types (check if the type name ends with '[]')
|
// Handle array types (check if the field has array property or type name ends with '[]')
|
||||||
if (typeName.endsWith('[]')) {
|
if (field.array || typeName.endsWith('[]')) {
|
||||||
typeWithSize =
|
if (!typeName.endsWith('[]')) {
|
||||||
typeWithSize.replace('[]', '') + '[]';
|
typeWithSize = typeWithSize + '[]';
|
||||||
|
} else {
|
||||||
|
typeWithSize =
|
||||||
|
typeWithSize.replace('[]', '') + '[]';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const notNull = field.nullable ? '' : ' NOT NULL';
|
const notNull = field.nullable ? '' : ' NOT NULL';
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export interface SQLColumn {
|
|||||||
comment?: string;
|
comment?: string;
|
||||||
default?: string;
|
default?: string;
|
||||||
increment?: boolean;
|
increment?: boolean;
|
||||||
|
array?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SQLTable {
|
export interface SQLTable {
|
||||||
@@ -612,6 +613,7 @@ export function convertToChartDBDiagram(
|
|||||||
default: column.default || '',
|
default: column.default || '',
|
||||||
createdAt: Date.now(),
|
createdAt: Date.now(),
|
||||||
increment: column.increment,
|
increment: column.increment,
|
||||||
|
array: column.array,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add type arguments if present
|
// Add type arguments if present
|
||||||
|
|||||||
@@ -373,6 +373,13 @@ function extractColumnsFromSQL(sql: string): SQLColumn[] {
|
|||||||
'SMALLSERIAL',
|
'SMALLSERIAL',
|
||||||
].includes(upperType.split('(')[0]);
|
].includes(upperType.split('(')[0]);
|
||||||
|
|
||||||
|
// Check if it's an array type
|
||||||
|
let isArrayType = false;
|
||||||
|
if (columnType.endsWith('[]')) {
|
||||||
|
isArrayType = true;
|
||||||
|
columnType = columnType.slice(0, -2);
|
||||||
|
}
|
||||||
|
|
||||||
// Normalize the type
|
// Normalize the type
|
||||||
columnType = normalizePostgreSQLType(columnType);
|
columnType = normalizePostgreSQLType(columnType);
|
||||||
|
|
||||||
@@ -395,6 +402,7 @@ function extractColumnsFromSQL(sql: string): SQLColumn[] {
|
|||||||
trimmedLine.includes('uuid_generate_v4()') ||
|
trimmedLine.includes('uuid_generate_v4()') ||
|
||||||
trimmedLine.includes('GENERATED ALWAYS AS IDENTITY') ||
|
trimmedLine.includes('GENERATED ALWAYS AS IDENTITY') ||
|
||||||
trimmedLine.includes('GENERATED BY DEFAULT AS IDENTITY'),
|
trimmedLine.includes('GENERATED BY DEFAULT AS IDENTITY'),
|
||||||
|
array: isArrayType,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -782,6 +790,16 @@ export async function fromPostgres(
|
|||||||
normalizePostgreSQLType(rawDataType);
|
normalizePostgreSQLType(rawDataType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if it's an array type
|
||||||
|
let isArrayType = false;
|
||||||
|
if (normalizedBaseType.endsWith('[]')) {
|
||||||
|
isArrayType = true;
|
||||||
|
normalizedBaseType = normalizedBaseType.slice(
|
||||||
|
0,
|
||||||
|
-2
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Now handle parameters - but skip for integer types that shouldn't have them
|
// Now handle parameters - but skip for integer types that shouldn't have them
|
||||||
let finalDataType = normalizedBaseType;
|
let finalDataType = normalizedBaseType;
|
||||||
|
|
||||||
@@ -874,6 +892,7 @@ export async function fromPostgres(
|
|||||||
stmt.sql
|
stmt.sql
|
||||||
.toUpperCase()
|
.toUpperCase()
|
||||||
.includes('IDENTITY')),
|
.includes('IDENTITY')),
|
||||||
|
array: isArrayType,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (def.resource === 'constraint') {
|
} else if (def.resource === 'constraint') {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export interface DBField {
|
|||||||
unique: boolean;
|
unique: boolean;
|
||||||
nullable: boolean;
|
nullable: boolean;
|
||||||
increment?: boolean | null;
|
increment?: boolean | null;
|
||||||
|
array?: boolean | null;
|
||||||
createdAt: number;
|
createdAt: number;
|
||||||
characterMaximumLength?: string | null;
|
characterMaximumLength?: string | null;
|
||||||
precision?: number | null;
|
precision?: number | null;
|
||||||
@@ -36,6 +37,7 @@ export const dbFieldSchema: z.ZodType<DBField> = z.object({
|
|||||||
unique: z.boolean(),
|
unique: z.boolean(),
|
||||||
nullable: z.boolean(),
|
nullable: z.boolean(),
|
||||||
increment: z.boolean().or(z.null()).optional(),
|
increment: z.boolean().or(z.null()).optional(),
|
||||||
|
array: z.boolean().or(z.null()).optional(),
|
||||||
createdAt: z.number(),
|
createdAt: z.number(),
|
||||||
characterMaximumLength: z.string().or(z.null()).optional(),
|
characterMaximumLength: z.string().or(z.null()).optional(),
|
||||||
precision: z.number().or(z.null()).optional(),
|
precision: z.number().or(z.null()).optional(),
|
||||||
@@ -71,13 +73,48 @@ export const createFieldsFromMetadata = ({
|
|||||||
pk.column.trim()
|
pk.column.trim()
|
||||||
);
|
);
|
||||||
|
|
||||||
return sortedColumns.map(
|
return sortedColumns.map((col: ColumnInfo): DBField => {
|
||||||
(col: ColumnInfo): DBField => ({
|
// Check if type is an array (ends with [])
|
||||||
|
const isArrayType = col.type.endsWith('[]');
|
||||||
|
let baseType = col.type;
|
||||||
|
|
||||||
|
// Extract base type and any parameters if it's an array
|
||||||
|
if (isArrayType) {
|
||||||
|
baseType = col.type.slice(0, -2); // Remove the [] suffix
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract parameters from types like "character varying(100)" or "numeric(10,2)"
|
||||||
|
let charMaxLength = col.character_maximum_length;
|
||||||
|
let precision = col.precision?.precision;
|
||||||
|
let scale = col.precision?.scale;
|
||||||
|
|
||||||
|
// Handle types with single parameter like varchar(100)
|
||||||
|
const singleParamMatch = baseType.match(/^(.+?)\((\d+)\)$/);
|
||||||
|
if (singleParamMatch) {
|
||||||
|
baseType = singleParamMatch[1];
|
||||||
|
if (!charMaxLength || charMaxLength === 'null') {
|
||||||
|
charMaxLength = singleParamMatch[2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle types with two parameters like numeric(10,2)
|
||||||
|
const twoParamMatch = baseType.match(/^(.+?)\((\d+),\s*(\d+)\)$/);
|
||||||
|
if (twoParamMatch) {
|
||||||
|
baseType = twoParamMatch[1];
|
||||||
|
if (!precision) {
|
||||||
|
precision = parseInt(twoParamMatch[2]);
|
||||||
|
}
|
||||||
|
if (!scale) {
|
||||||
|
scale = parseInt(twoParamMatch[3]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
id: generateId(),
|
id: generateId(),
|
||||||
name: col.name,
|
name: col.name,
|
||||||
type: {
|
type: {
|
||||||
id: col.type.split(' ').join('_').toLowerCase(),
|
id: baseType.split(' ').join('_').toLowerCase(),
|
||||||
name: col.type.toLowerCase(),
|
name: baseType.toLowerCase(),
|
||||||
},
|
},
|
||||||
primaryKey: tablePrimaryKeysColumns.includes(col.name),
|
primaryKey: tablePrimaryKeysColumns.includes(col.name),
|
||||||
unique: Object.values(aggregatedIndexes).some(
|
unique: Object.values(aggregatedIndexes).some(
|
||||||
@@ -87,20 +124,18 @@ export const createFieldsFromMetadata = ({
|
|||||||
idx.columns[0].name === col.name
|
idx.columns[0].name === col.name
|
||||||
),
|
),
|
||||||
nullable: Boolean(col.nullable),
|
nullable: Boolean(col.nullable),
|
||||||
...(col.character_maximum_length &&
|
...(isArrayType ? { array: true } : {}),
|
||||||
col.character_maximum_length !== 'null'
|
...(charMaxLength && charMaxLength !== 'null'
|
||||||
? { characterMaximumLength: col.character_maximum_length }
|
? { characterMaximumLength: charMaxLength }
|
||||||
: {}),
|
: {}),
|
||||||
...(col.precision?.precision
|
...(precision ? { precision } : {}),
|
||||||
? { precision: col.precision.precision }
|
...(scale ? { scale } : {}),
|
||||||
: {}),
|
|
||||||
...(col.precision?.scale ? { scale: col.precision.scale } : {}),
|
|
||||||
...(col.default ? { default: col.default } : {}),
|
...(col.default ? { default: col.default } : {}),
|
||||||
...(col.collation ? { collation: col.collation } : {}),
|
...(col.collation ? { collation: col.collation } : {}),
|
||||||
createdAt: Date.now(),
|
createdAt: Date.now(),
|
||||||
comments: col.comment ? col.comment : undefined,
|
comments: col.comment ? col.comment : undefined,
|
||||||
})
|
};
|
||||||
);
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const generateDBFieldSuffix = (
|
export const generateDBFieldSuffix = (
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import type { FieldAttributeRange } from '@/lib/data/data-types/data-types';
|
|||||||
import {
|
import {
|
||||||
findDataTypeDataById,
|
findDataTypeDataById,
|
||||||
supportsAutoIncrementDataType,
|
supportsAutoIncrementDataType,
|
||||||
|
supportsArrayDataType,
|
||||||
} from '@/lib/data/data-types/data-types';
|
} from '@/lib/data/data-types/data-types';
|
||||||
import {
|
import {
|
||||||
Popover,
|
Popover,
|
||||||
@@ -87,6 +88,7 @@ export const TableFieldPopover: React.FC<TableFieldPopoverProps> = ({
|
|||||||
unique: localField.unique,
|
unique: localField.unique,
|
||||||
default: localField.default,
|
default: localField.default,
|
||||||
increment: localField.increment,
|
increment: localField.increment,
|
||||||
|
array: localField.array,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
prevFieldRef.current = localField;
|
prevFieldRef.current = localField;
|
||||||
@@ -102,6 +104,13 @@ export const TableFieldPopover: React.FC<TableFieldPopoverProps> = ({
|
|||||||
[field.type.name]
|
[field.type.name]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const supportsArray = useMemo(
|
||||||
|
() =>
|
||||||
|
databaseType === 'postgresql' &&
|
||||||
|
supportsArrayDataType(field.type.name),
|
||||||
|
[field.type.name, databaseType]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover
|
<Popover
|
||||||
open={isOpen}
|
open={isOpen}
|
||||||
@@ -168,6 +177,27 @@ export const TableFieldPopover: React.FC<TableFieldPopoverProps> = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
{supportsArray ? (
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<Label
|
||||||
|
htmlFor="array"
|
||||||
|
className="text-subtitle"
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
'side_panel.tables_section.table.field_actions.array'
|
||||||
|
)}
|
||||||
|
</Label>
|
||||||
|
<Checkbox
|
||||||
|
checked={localField.array ?? false}
|
||||||
|
onCheckedChange={(value) =>
|
||||||
|
setLocalField((current) => ({
|
||||||
|
...current,
|
||||||
|
array: !!value,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<Label htmlFor="default" className="text-subtitle">
|
<Label htmlFor="default" className="text-subtitle">
|
||||||
{t(
|
{t(
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import type { DataTypeData } from '@/lib/data/data-types/data-types';
|
|||||||
import {
|
import {
|
||||||
dataTypeDataToDataType,
|
dataTypeDataToDataType,
|
||||||
sortedDataTypeMap,
|
sortedDataTypeMap,
|
||||||
|
supportsArrayDataType,
|
||||||
} from '@/lib/data/data-types/data-types';
|
} from '@/lib/data/data-types/data-types';
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
@@ -175,6 +176,13 @@ export const TableField: React.FC<TableFieldProps> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the new type supports arrays - if not, clear the array property
|
||||||
|
const newTypeName = dataType?.name ?? (value as string);
|
||||||
|
const shouldClearArray =
|
||||||
|
databaseType === 'postgresql' &&
|
||||||
|
!supportsArrayDataType(newTypeName) &&
|
||||||
|
field.array;
|
||||||
|
|
||||||
updateField({
|
updateField({
|
||||||
characterMaximumLength,
|
characterMaximumLength,
|
||||||
precision,
|
precision,
|
||||||
@@ -185,6 +193,7 @@ export const TableField: React.FC<TableFieldProps> = ({
|
|||||||
name: value as string,
|
name: value as string,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
...(shouldClearArray ? { array: false } : {}),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
@@ -193,6 +202,7 @@ export const TableField: React.FC<TableFieldProps> = ({
|
|||||||
field.characterMaximumLength,
|
field.characterMaximumLength,
|
||||||
field.precision,
|
field.precision,
|
||||||
field.scale,
|
field.scale,
|
||||||
|
field.array,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user