fix(custom-types): fetch directly via the smart-query the custom types (#729)

This commit is contained in:
Jonathan Fishner
2025-05-27 19:26:07 +03:00
committed by GitHub
parent 3894a22174
commit cf1e141837
6 changed files with 52 additions and 213 deletions

View File

@@ -165,7 +165,7 @@ cols AS (
'","table":"', cols.table_name,
'","name":"', cols.column_name,
'","ordinal_position":', cols.ordinal_position,
',"type":"', LOWER(replace(cols.data_type, '"', '')),
',"type":"', case when LOWER(replace(cols.data_type, '"', '')) = 'user-defined' then pg_type.typname else LOWER(replace(cols.data_type, '"', '')) end,
'","character_maximum_length":"', COALESCE(cols.character_maximum_length::text, 'null'),
'","precision":',
CASE
@@ -184,8 +184,12 @@ cols AS (
ON c.relname = cols.table_name
JOIN pg_catalog.pg_namespace n
ON n.oid = c.relnamespace AND n.nspname = cols.table_schema
LEFT JOIN pg_catalog.pg_description dsc ON dsc.objoid = c.oid
AND dsc.objsubid = cols.ordinal_position
LEFT JOIN pg_catalog.pg_description dsc
ON dsc.objoid = c.oid AND dsc.objsubid = cols.ordinal_position
LEFT JOIN pg_catalog.pg_attribute attr
ON attr.attrelid = c.oid AND attr.attname = cols.column_name
LEFT JOIN pg_catalog.pg_type
ON pg_type.oid = attr.atttypid
WHERE cols.table_schema NOT IN ('information_schema', 'pg_catalog')${
databaseEdition === DatabaseEdition.POSTGRESQL_TIMESCALE
? timescaleColFilter

View File

@@ -58,6 +58,3 @@ export const customTypeKindToLabel: Record<DBCustomTypeKind, string> = {
enum: 'Enum',
composite: 'Composite',
};
export const getCustomTypeId = (name: string) =>
`custom-type-${name.toLowerCase().trim()}`;

View File

@@ -7,7 +7,6 @@ import type { PrimaryKeyInfo } from '../data/import-metadata/metadata-types/prim
import type { TableInfo } from '../data/import-metadata/metadata-types/table-info';
import type { DBCustomTypeInfo } from '../data/import-metadata/metadata-types/custom-type-info';
import { schemaNameToDomainSchemaName } from './db-schema';
import { getCustomTypeId } from './db-custom-type';
export interface DBField {
id: string;
@@ -43,56 +42,12 @@ export const dbFieldSchema: z.ZodType<DBField> = z.object({
comments: z.string().optional(),
});
// Helper function to find the best matching custom type for a column
const findMatchingCustomType = (
columnName: string,
customTypes: DBCustomTypeInfo[]
): DBCustomTypeInfo | undefined => {
// 1. Exact name match (highest priority)
const exactMatch = customTypes.find((ct) => ct.type === columnName);
if (exactMatch) return exactMatch;
// 2. Check if column name is the base of a custom type (e.g., 'role' matches 'role_enum')
const prefixMatch = customTypes.find((ct) =>
ct.type.startsWith(columnName + '_')
);
if (prefixMatch) return prefixMatch;
// 3. Check if a custom type name is the base of the column name (e.g., 'user_role' matches 'role_enum')
const baseTypeMatch = customTypes.find((ct) => {
// Extract base name by removing common suffixes
const baseTypeName = ct.type.replace(/_enum$|_type$/, '');
return (
columnName.includes(baseTypeName) ||
baseTypeName.includes(columnName)
);
});
if (baseTypeMatch) return baseTypeMatch;
// 4. For composite types, check if any field matches the column name
const compositeMatch = customTypes.find(
(ct) =>
ct.kind === 'composite' &&
ct.fields?.some((f) => f.field === columnName)
);
if (compositeMatch) return compositeMatch;
// 5. Special case for name/fullname relationship which is common
if (columnName === 'name') {
const fullNameType = customTypes.find((ct) => ct.type === 'full_name');
if (fullNameType) return fullNameType;
}
return undefined;
};
export const createFieldsFromMetadata = ({
columns,
tableSchema,
tableInfo,
primaryKeys,
aggregatedIndexes,
customTypes = [],
}: {
columns: ColumnInfo[];
tableSchema?: string;
@@ -126,50 +81,14 @@ export const createFieldsFromMetadata = ({
)
.map((pk) => pk.column.trim());
// Create a mapping between column names and custom types
const typeMap: Record<string, DBCustomTypeInfo> = {};
if (customTypes && customTypes.length > 0) {
// Filter to custom types in this schema
const schemaCustomTypes = customTypes.filter(
(ct) => schemaNameToDomainSchemaName(ct.schema) === tableSchema
);
// Process user-defined columns to find matching custom types
for (const column of sortedColumns) {
if (column.type.toLowerCase() === 'user-defined') {
const matchingType = findMatchingCustomType(
column.name,
schemaCustomTypes
);
if (matchingType) {
typeMap[column.name] = matchingType;
}
}
}
}
return sortedColumns.map((col: ColumnInfo): DBField => {
let type: DataType;
// Use custom type if available, otherwise use standard type
if (col.type.toLowerCase() === 'user-defined' && typeMap[col.name]) {
const customType = typeMap[col.name];
type = {
id: getCustomTypeId(customType.type),
name: customType.type,
};
} else {
type = {
id: col.type.split(' ').join('_').toLowerCase(),
name: col.type.toLowerCase(),
};
}
return {
return sortedColumns.map(
(col: ColumnInfo): DBField => ({
id: generateId(),
name: col.name,
type,
type: {
id: col.type.split(' ').join('_').toLowerCase(),
name: col.type.toLowerCase(),
},
primaryKey: tablePrimaryKeys.includes(col.name),
unique: Object.values(aggregatedIndexes).some(
(idx) =>
@@ -190,6 +109,6 @@ export const createFieldsFromMetadata = ({
...(col.collation ? { collation: col.collation } : {}),
createdAt: Date.now(),
comments: col.comment ? col.comment : undefined,
};
});
})
);
};

View File

@@ -102,7 +102,6 @@ export const createTablesFromMetadata = ({
columns,
indexes,
views: views,
custom_types: customTypes,
} = databaseMetadata;
return tableInfos.map((tableInfo: TableInfo) => {
@@ -121,10 +120,6 @@ export const createTablesFromMetadata = ({
primaryKeys,
tableInfo,
tableSchema,
customTypes:
databaseType === DatabaseType.POSTGRESQL
? customTypes
: undefined,
});
const dbIndexes = createIndexesFromMetadata({

View File

@@ -55,7 +55,7 @@ export const CustomTypeCompositeFields: React.FC<
const customDataTypes = useMemo<DataTypeData[]>(
() =>
customTypes.map<DataTypeData>((type) => ({
id: `custom-type-${type.id}`,
id: type.name,
name: type.name,
})),
[customTypes]

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useMemo } from 'react';
import React, { useCallback } from 'react';
import { GripVertical, KeyRound } from 'lucide-react';
import { Input } from '@/components/input/input';
import type { DBField } from '@/lib/domain/db-field';
@@ -6,7 +6,6 @@ import { useChartDB } from '@/hooks/use-chartdb';
import {
dataTypeDataToDataType,
sortedDataTypeMap,
type DataType,
} from '@/lib/data/data-types/data-types';
import {
Tooltip,
@@ -35,98 +34,51 @@ export const TableField: React.FC<TableFieldProps> = ({
updateField,
removeField,
}) => {
const { databaseType, customTypes } = useChartDB();
const { databaseType } = useChartDB();
const { t } = useTranslation();
const { attributes, listeners, setNodeRef, transform, transition } =
useSortable({ id: field.id });
const dataFieldOptions = useMemo(() => {
// Start with the built-in data types
const standardTypes: SelectBoxOption[] = sortedDataTypeMap[
databaseType
].map((type) => ({
label: type.name,
value: type.id,
regex: type.hasCharMaxLength
? `^${type.name}\\(\\d+\\)$`
: undefined,
extractRegex: type.hasCharMaxLength ? /\((\d+)\)/ : undefined,
group: 'Standard Types',
}));
if (!customTypes?.length) {
return standardTypes;
}
// Add custom types as options
const customTypeOptions: SelectBoxOption[] = customTypes.map(
(type) => ({
label: type.name,
value: `custom-type-${type.name}`,
// Add additional info to distinguish custom types
description:
type.kind === 'enum' ? `${type.values?.join(' | ')}` : '',
// Group custom types together
group: 'Custom Types',
})
);
return [...standardTypes, ...customTypeOptions];
}, [databaseType, customTypes]);
const dataFieldOptions: SelectBoxOption[] = sortedDataTypeMap[
databaseType
].map((type) => ({
label: type.name,
value: type.id,
regex: type.hasCharMaxLength ? `^${type.name}\\(\\d+\\)$` : undefined,
extractRegex: type.hasCharMaxLength ? /\((\d+)\)/ : undefined,
}));
const onChangeDataType = useCallback<
NonNullable<SelectBoxProps['onChange']>
>(
(value, regexMatches) => {
// Check if this is a custom type
const isCustomType =
typeof value === 'string' && value.startsWith('custom-type-');
let newType: DataType;
if (isCustomType && typeof value === 'string') {
// For custom types, create a custom DataType object
const typeName = value.replace('custom-type-', '');
newType = {
id: value as string,
name: typeName,
};
} else {
// For standard types, use the existing logic
const dataType = sortedDataTypeMap[databaseType].find(
(v) => v.id === value
) ?? {
id: value as string,
name: value as string,
};
newType = dataTypeDataToDataType(dataType);
}
const dataType = sortedDataTypeMap[databaseType].find(
(v) => v.id === value
) ?? {
id: value as string,
name: value as string,
};
let characterMaximumLength: string | undefined = undefined;
if (regexMatches?.length && !isCustomType) {
const dataType = sortedDataTypeMap[databaseType].find(
(v) => v.id === value
);
if (dataType?.hasCharMaxLength) {
characterMaximumLength = regexMatches[1];
}
} else if (field.characterMaximumLength && !isCustomType) {
const dataType = sortedDataTypeMap[databaseType].find(
(v) => v.id === value
);
if (dataType?.hasCharMaxLength) {
characterMaximumLength = field.characterMaximumLength;
}
if (regexMatches?.length && dataType?.hasCharMaxLength) {
characterMaximumLength = regexMatches[1];
} else if (
field.characterMaximumLength &&
dataType?.hasCharMaxLength
) {
characterMaximumLength = field.characterMaximumLength;
}
updateField({
characterMaximumLength,
type: newType,
type: dataTypeDataToDataType(
dataType ?? {
id: value as string,
name: value as string,
}
),
});
},
[updateField, databaseType, field.characterMaximumLength]
@@ -137,21 +89,6 @@ export const TableField: React.FC<TableFieldProps> = ({
transition,
};
// Helper to get the display value for custom types
const getCustomTypeDisplayValue = () => {
if (field.type.id.startsWith('custom-type-')) {
const typeName = field.type.name;
const customType = customTypes?.find((ct) => ct.name === typeName);
if (customType) {
return typeName;
}
}
return undefined;
};
const customTypeDisplayValue = getCustomTypeDisplayValue();
return (
<div
className="flex flex-1 touch-none flex-row justify-between p-1"
@@ -198,22 +135,11 @@ export const TableField: React.FC<TableFieldProps> = ({
)}
value={field.type.id}
valueSuffix={
customTypeDisplayValue
? ``
: field.characterMaximumLength
? `(${field.characterMaximumLength})`
: ''
field.characterMaximumLength
? `(${field.characterMaximumLength})`
: ''
}
optionSuffix={(option) => {
// For custom types, we don't need to add a suffix
if (
option.value
.toString()
.startsWith('custom-type-')
) {
return '';
}
const type = sortedDataTypeMap[
databaseType
].find((v) => v.id === option.value);
@@ -236,12 +162,10 @@ export const TableField: React.FC<TableFieldProps> = ({
</span>
</TooltipTrigger>
<TooltipContent>
{customTypeDisplayValue ||
`${field.type.name}${
field.characterMaximumLength
? `(${field.characterMaximumLength})`
: ''
}`}
{field.type.name}
{field.characterMaximumLength
? `(${field.characterMaximumLength})`
: ''}
</TooltipContent>
</Tooltip>
</div>