Compare commits

...

1 Commits

Author SHA1 Message Date
johnnyfish
c7d1c2ffa7 fix(import-sql): add dialect detection for import sql 2025-09-14 19:52:13 +03:00
6 changed files with 162 additions and 9 deletions

View File

@@ -57,11 +57,36 @@ export const SQLValidationStatus: React.FC<SQLValidationStatusProps> = ({
// If we have parser errors (errorMessage) after validation
if (errorMessage && !hasErrors) {
// Check if the error is related to parsing issues
const isParsingError =
errorMessage.toLowerCase().includes('error parsing') ||
errorMessage.toLowerCase().includes('unexpected');
return (
<>
<Separator className="mb-1 mt-2" />
<div className="mb-1 flex shrink-0 items-center gap-2">
<p className="text-xs text-red-700">{errorMessage}</p>
<div className="rounded-md border border-red-200 bg-red-50 dark:border-red-800 dark:bg-red-950">
<div className="space-y-3 p-3 pt-2 text-red-700 dark:text-red-300">
<div className="flex items-start gap-2">
<MessageCircleWarning className="mt-0.5 size-4 shrink-0 text-red-700 dark:text-red-300" />
<div className="flex-1 text-sm text-red-700 dark:text-red-300">
<div className="font-medium">
{isParsingError
? 'SQL Parsing Failed'
: 'SQL Import Error'}
</div>
<div className="mt-1 text-xs">
{errorMessage}
</div>
{isParsingError && (
<div className="mt-2 text-xs opacity-90">
This may indicate incompatible SQL
syntax for the selected database type.
</div>
)}
</div>
</div>
</div>
</div>
</>
);

View File

@@ -0,0 +1,105 @@
/**
* Shared utilities for detecting SQL dialect-specific syntax
* Used across all validators to identify incompatible SQL dialects
*/
import type { ValidationError } from './postgresql-validator';
interface DialectDetectionResult {
detected: boolean;
dialect: string;
lines: number[];
features: string[];
}
/**
* Detect Oracle-specific SQL syntax in the given SQL content
*/
export function detectOracleSQL(lines: string[]): DialectDetectionResult {
const oracleTypeLines: number[] = [];
const detectedFeatures = new Set<string>();
lines.forEach((line, index) => {
const upperLine = line.trim().toUpperCase();
// Check for Oracle-specific data types
if (upperLine.includes('VARCHAR2')) {
detectedFeatures.add('VARCHAR2');
oracleTypeLines.push(index + 1);
}
if (
upperLine.match(/\bNUMBER\s*\(/i) ||
upperLine.match(/\bNUMBER\b(?!\s*\()/i)
) {
detectedFeatures.add('NUMBER');
oracleTypeLines.push(index + 1);
}
// Could add more Oracle-specific features in the future:
// - CLOB, BLOB data types
// - ROWNUM pseudo-column
// - CONNECT BY for hierarchical queries
// - MINUS set operator (vs EXCEPT in other DBs)
});
return {
detected: oracleTypeLines.length > 0,
dialect: 'Oracle',
lines: oracleTypeLines,
features: Array.from(detectedFeatures),
};
}
/**
* Create an Oracle SQL error for the target database type
*/
export function createOracleError(
detection: DialectDetectionResult,
targetDatabase: 'MySQL' | 'PostgreSQL' | 'SQL Server' | 'SQLite'
): ValidationError {
const lineList = detection.lines.slice(0, 5).join(', ');
const moreLines =
detection.lines.length > 5
? ` and ${detection.lines.length - 5} more locations`
: '';
const featuresText = detection.features.join(', ');
// Database-specific conversion suggestions
const conversionMap = {
MySQL: 'VARCHAR2 → VARCHAR, NUMBER → INT/DECIMAL/NUMERIC',
PostgreSQL: 'VARCHAR2 → VARCHAR, NUMBER → NUMERIC/INTEGER',
'SQL Server': 'VARCHAR2 → VARCHAR, NUMBER → INT/DECIMAL/NUMERIC',
SQLite: 'VARCHAR2 → TEXT, NUMBER → INTEGER/REAL',
};
return {
line: detection.lines[0],
message: `Oracle SQL syntax detected (${featuresText} types found on lines: ${lineList}${moreLines})`,
type: 'syntax',
suggestion: `This appears to be Oracle SQL. Please convert to ${targetDatabase} syntax: ${conversionMap[targetDatabase]}`,
};
}
/**
* Detect any foreign SQL dialect in the given content
* Returns null if no foreign dialect is detected
*/
export function detectForeignDialect(
lines: string[],
targetDatabase: 'MySQL' | 'PostgreSQL' | 'SQL Server' | 'SQLite'
): ValidationError | null {
// Check for Oracle SQL
const oracleDetection = detectOracleSQL(lines);
if (oracleDetection.detected) {
return createOracleError(oracleDetection, targetDatabase);
}
// Future: Could add detection for other dialects
// - DB2 specific syntax
// - Teradata specific syntax
// - etc.
return null;
}

View File

@@ -8,6 +8,7 @@ import type {
ValidationError,
ValidationWarning,
} from './postgresql-validator';
import { detectForeignDialect } from './dialect-detection';
/**
* Validates MySQL SQL syntax
@@ -34,13 +35,16 @@ export function validateMySQLDialect(sql: string): ValidationResult {
};
}
// TODO: Implement MySQL-specific validation
// For now, just do basic checks
// Check for common MySQL syntax patterns
const lines = sql.split('\n');
let tableCount = 0;
// Check for foreign SQL dialects
const foreignDialectError = detectForeignDialect(lines, 'MySQL');
if (foreignDialectError) {
errors.push(foreignDialectError);
}
lines.forEach((line, index) => {
const trimmedLine = line.trim();

View File

@@ -3,6 +3,8 @@
* Provides user-friendly error messages for common SQL syntax issues
*/
import { detectForeignDialect } from './dialect-detection';
export interface ValidationResult {
isValid: boolean;
errors: ValidationError[];
@@ -212,7 +214,13 @@ export function validatePostgreSQLDialect(sql: string): ValidationResult {
});
}
// 9. Count CREATE TABLE statements
// 9. Check for foreign SQL dialects
const foreignDialectError = detectForeignDialect(lines, 'PostgreSQL');
if (foreignDialectError) {
errors.push(foreignDialectError);
}
// 10. Count CREATE TABLE statements
let tableCount = 0;
const createTableRegex =
/CREATE\s+TABLE(?:\s+IF\s+NOT\s+EXISTS)?(?:\s+ONLY)?\s+(?:"?[^"\s.]+?"?\.)?["'`]?[^"'`\s.(]+["'`]?/gi;

View File

@@ -8,6 +8,7 @@ import type {
ValidationError,
ValidationWarning,
} from './postgresql-validator';
import { detectForeignDialect } from './dialect-detection';
/**
* Validates SQLite SQL syntax
@@ -41,6 +42,12 @@ export function validateSQLiteDialect(sql: string): ValidationResult {
const lines = sql.split('\n');
let tableCount = 0;
// Check for foreign SQL dialects
const foreignDialectError = detectForeignDialect(lines, 'SQLite');
if (foreignDialectError) {
errors.push(foreignDialectError);
}
lines.forEach((line, index) => {
const trimmedLine = line.trim();

View File

@@ -8,6 +8,7 @@ import type {
ValidationError,
ValidationWarning,
} from './postgresql-validator';
import { detectForeignDialect } from './dialect-detection';
/**
* Validates SQL Server SQL syntax
@@ -34,13 +35,16 @@ export function validateSQLServerDialect(sql: string): ValidationResult {
};
}
// TODO: Implement SQL Server-specific validation
// For now, just do basic checks
// Check for common SQL Server syntax patterns
const lines = sql.split('\n');
let tableCount = 0;
// Check for foreign SQL dialects
const foreignDialectError = detectForeignDialect(lines, 'SQL Server');
if (foreignDialectError) {
errors.push(foreignDialectError);
}
lines.forEach((line, index) => {
const trimmedLine = line.trim();