fix: PostgreSQL serial type parsing (#1016)

* fix: PostgreSQL serial type parsing

* fix
This commit is contained in:
Guy Ben-Aharon
2025-12-17 12:39:18 +02:00
committed by GitHub
parent 6293511373
commit db1cf45a8a
17 changed files with 1645 additions and 2439 deletions

3959
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,8 @@ import { cn } from '@/lib/utils';
import { badgeVariants } from './badge-variants';
export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
extends
React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {

View File

@@ -27,7 +27,8 @@ export interface ButtonAlternative {
}
export interface ButtonWithAlternativesProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
extends
React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
alternatives: Array<ButtonAlternative>;

View File

@@ -6,7 +6,8 @@ import { cn } from '@/lib/utils';
import { buttonVariants } from './button-variants';
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
extends
React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}

View File

@@ -12,8 +12,7 @@ import {
import type { DatabaseType } from '@/lib/domain/database-type';
import { cn } from '@/lib/utils';
export interface DiagramIconProps
extends React.ComponentPropsWithoutRef<'div'> {
export interface DiagramIconProps extends React.ComponentPropsWithoutRef<'div'> {
databaseType: DatabaseType;
databaseEdition?: DatabaseEdition;
imgClassName?: string;

View File

@@ -45,7 +45,8 @@ const sheetVariants = cva(
);
interface SheetContentProps
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
extends
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
VariantProps<typeof sheetVariants> {}
const SheetContent = React.forwardRef<

View File

@@ -30,7 +30,8 @@ const loaderVariants = cva('animate-spin text-primary', {
});
interface SpinnerContentProps
extends VariantProps<typeof spinnerVariants>,
extends
VariantProps<typeof spinnerVariants>,
VariantProps<typeof loaderVariants> {
className?: string;
children?: React.ReactNode;

View File

@@ -2,8 +2,7 @@ import React from 'react';
import { cn } from '@/lib/utils';
export interface TextareaProps
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
export interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
({ className, ...props }, ref) => {

View File

@@ -2,8 +2,7 @@ import React from 'react';
import { Skeleton } from '../skeleton/skeleton';
import { cn } from '@/lib/utils';
export interface TreeItemSkeletonProps
extends React.HTMLAttributes<HTMLDivElement> {}
export interface TreeItemSkeletonProps extends React.HTMLAttributes<HTMLDivElement> {}
export const TreeItemSkeleton: React.FC<TreeItemSkeletonProps> = ({
className,

View File

@@ -64,9 +64,8 @@ export const SmartQueryInstructions: React.FC<SmartQueryInstructionsProps> = ({
useEffect(() => {
const loadScripts = async () => {
const { importMetadataScripts } = await import(
'@/lib/data/import-metadata/scripts/scripts'
);
const { importMetadataScripts } =
await import('@/lib/data/import-metadata/scripts/scripts');
setImportMetadataScripts(importMetadataScripts);
};
loadScripts();

View File

@@ -1,4 +1,4 @@
import { describe, it, expect } from 'vitest';
import { describe, it, expect, vi } from 'vitest';
import { fixMetadataJson, isStringMetadataJson } from '../utils';
describe('fixMetadataJson', () => {
@@ -280,6 +280,10 @@ describe('isStringMetadataJson', () => {
});
it('should return false for valid JSON but missing required fields', () => {
const consoleErrorSpy = vi
.spyOn(console, 'error')
.mockImplementation(() => {});
const incompleteMetadata = JSON.stringify({
fk_info: [],
pk_info: [],
@@ -287,6 +291,8 @@ describe('isStringMetadataJson', () => {
});
expect(isStringMetadataJson(incompleteMetadata)).toBe(false);
consoleErrorSpy.mockRestore();
});
it('should return false for empty string', () => {
@@ -294,8 +300,14 @@ describe('isStringMetadataJson', () => {
});
it('should return false for null-like values', () => {
const consoleErrorSpy = vi
.spyOn(console, 'error')
.mockImplementation(() => {});
expect(isStringMetadataJson('null')).toBe(false);
expect(isStringMetadataJson('undefined')).toBe(false);
consoleErrorSpy.mockRestore();
});
});

View File

@@ -1,4 +1,4 @@
import { describe, it, expect } from 'vitest';
import { describe, it, expect, vi } from 'vitest';
import { fromMySQL } from '../mysql';
describe('MySQL Default Value Import', () => {
@@ -173,6 +173,10 @@ describe('MySQL Default Value Import', () => {
describe('Complex Real-World Example', () => {
it('should handle complex table with multiple default types', async () => {
const consoleErrorSpy = vi
.spyOn(console, 'error')
.mockImplementation(() => {});
const sql = `
CREATE TABLE adventurer_profiles (
adventurer_id BIGINT NOT NULL AUTO_INCREMENT,
@@ -223,6 +227,8 @@ describe('MySQL Default Value Import', () => {
(c) => c.name === 'inventory_data'
);
expect(inventoryColumn?.default).toBe('NULL');
consoleErrorSpy.mockRestore();
});
});
});

View File

@@ -433,8 +433,7 @@ export async function fromMySQL(sqlContent: string): Promise<SQLParserResult> {
colName
);
if (col) {
col.primaryKey =
true;
col.primaryKey = true;
}
}

View File

@@ -130,7 +130,7 @@ describe('PostgreSQL ALTER TABLE ADD COLUMN Tests', () => {
(col) => col.name === 'price'
);
expect(priceColumn).toBeDefined();
expect(priceColumn?.default).toBe('0');
expect(priceColumn?.default).toBe('0.00');
});
it('should not add duplicate columns', async () => {

View File

@@ -843,6 +843,12 @@ export async function fromPostgres(
normalizedBaseType = 'INTEGER';
isSerialType = true;
}
} else if (upperType === 'SMALLSERIAL') {
normalizedBaseType = 'SMALLINT';
isSerialType = true;
} else if (upperType === 'BIGSERIAL') {
normalizedBaseType = 'BIGINT';
isSerialType = true;
} else if (upperType === 'INT') {
// Use length to determine the actual int type
if (typeLength === 2) {
@@ -862,12 +868,17 @@ export async function fromPostgres(
// Now handle parameters - but skip for integer types that shouldn't have them
let finalDataType = normalizedBaseType;
// Don't add parameters to INTEGER types that come from int4, int8, etc.
// Don't add parameters to INTEGER types that come from int4, int8, serial types, etc.
const isNormalizedIntegerType =
['INTEGER', 'BIGINT', 'SMALLINT'].includes(
normalizedBaseType
) &&
(upperType === 'INT' || upperType === 'SERIAL');
[
'INT',
'SERIAL',
'SMALLSERIAL',
'BIGSERIAL',
].includes(upperType);
if (!isSerialType && !isNormalizedIntegerType) {
// Include precision/scale/length in the type string if available

View File

@@ -9,9 +9,8 @@ const routes: RouteObject[] = [
...['', 'diagrams/:diagramId'].map((path) => ({
path,
async lazy() {
const { EditorPage } = await import(
'./pages/editor-page/editor-page'
);
const { EditorPage } =
await import('./pages/editor-page/editor-page');
return {
element: <EditorPage />,
@@ -21,9 +20,8 @@ const routes: RouteObject[] = [
{
path: 'examples',
async lazy() {
const { ExamplesPage } = await import(
'./pages/examples-page/examples-page'
);
const { ExamplesPage } =
await import('./pages/examples-page/examples-page');
return {
element: <ExamplesPage />,
};
@@ -33,9 +31,8 @@ const routes: RouteObject[] = [
id: 'templates',
path: 'templates',
async lazy() {
const { TemplatesPage } = await import(
'./pages/templates-page/templates-page'
);
const { TemplatesPage } =
await import('./pages/templates-page/templates-page');
return {
element: <TemplatesPage />,
};
@@ -54,9 +51,8 @@ const routes: RouteObject[] = [
id: 'templates_featured',
path: 'templates/featured',
async lazy() {
const { TemplatesPage } = await import(
'./pages/templates-page/templates-page'
);
const { TemplatesPage } =
await import('./pages/templates-page/templates-page');
return {
element: <TemplatesPage />,
};
@@ -76,9 +72,8 @@ const routes: RouteObject[] = [
id: 'templates_tags',
path: 'templates/tags/:tag',
async lazy() {
const { TemplatesPage } = await import(
'./pages/templates-page/templates-page'
);
const { TemplatesPage } =
await import('./pages/templates-page/templates-page');
return {
element: <TemplatesPage />,
};
@@ -98,17 +93,15 @@ const routes: RouteObject[] = [
id: 'templates_templateSlug',
path: 'templates/:templateSlug',
async lazy() {
const { TemplatePage } = await import(
'./pages/template-page/template-page'
);
const { TemplatePage } =
await import('./pages/template-page/template-page');
return {
element: <TemplatePage />,
};
},
loader: async ({ params }): Promise<TemplatePageLoaderData> => {
const { templates } = await import(
'./templates-data/templates-data'
);
const { templates } =
await import('./templates-data/templates-data');
return {
template: templates.find(
(template) => template.slug === params.templateSlug
@@ -120,17 +113,15 @@ const routes: RouteObject[] = [
id: 'templates_load',
path: 'templates/clone/:templateSlug',
async lazy() {
const { CloneTemplatePage } = await import(
'./pages/clone-template-page/clone-template-page'
);
const { CloneTemplatePage } =
await import('./pages/clone-template-page/clone-template-page');
return {
element: <CloneTemplatePage />,
};
},
loader: async ({ params }) => {
const { templates } = await import(
'./templates-data/templates-data'
);
const { templates } =
await import('./templates-data/templates-data');
return {
template: templates.find(
(template) => template.slug === params.templateSlug
@@ -141,9 +132,8 @@ const routes: RouteObject[] = [
{
path: '*',
async lazy() {
const { NotFoundPage } = await import(
'./pages/not-found-page/not-found-page'
);
const { NotFoundPage } =
await import('./pages/not-found-page/not-found-page');
return {
element: <NotFoundPage />,
};

View File

@@ -2,9 +2,9 @@
"compilerOptions": {
"composite": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
"target": "ES2022",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,