import initSqlJs, { Database } from 'sql.js'; interface SqlValidationResult { isCorrect: boolean; feedback: string; suggestions: string; } interface TestCase { schema?: string; seed_data?: string; expected_rows?: any[][]; expected_columns?: string[]; expected_tables?: Record; validate_schema?: boolean; validate_view?: boolean; view_name?: string; } function normalizeValue(v: any): any { if (v === null || v === undefined) return null; if (typeof v === 'number') return v; return String(v); } function rowsMatch(actual: any[][], expected: any[][]): boolean { if (actual.length !== expected.length) return false; for (let i = 0; i < actual.length; i++) { const aRow = actual[i]; const eRow = expected[i]; if (!aRow || !eRow || aRow.length !== eRow.length) return false; for (let j = 0; j < aRow.length; j++) { const a = normalizeValue(aRow[j]); const b = normalizeValue(eRow[j]); if (a !== b) return false; } } return true; } function columnsMatch(actual: string[], expected: string[]): boolean { const norm = (s: string) => s.toLowerCase().trim(); const normActual = actual.map(norm).sort(); const normExpected = expected.map(norm).sort(); if (normActual.length !== normExpected.length) return false; return normActual.every((v, i) => v === normExpected[i]); } function getAllTablesInfo(db: Database): Record { const tables: Record = {}; try { const res = db.exec("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'"); if (res.length > 0 && res[0]) { for (const row of res[0].values) { const name = row[0] as string; try { const cols = db.exec(`PRAGMA table_info("${name}")`); if (cols.length > 0 && cols[0]) { tables[name] = cols[0].values.map((c: any) => c[1] as string); } } catch {} } } } catch {} return tables; } function getViewDef(db: Database, viewName: string): string | null { try { const res = db.exec(`SELECT sql FROM sqlite_master WHERE type='view' AND name='${viewName}'`); const first = res[0]; if (first && first.values.length > 0) { const row = first.values[0]; return row ? (row[0] as string) : null; } } catch {} return null; } function findTableName(userQuery: string): string { if (/^INSERT\s+INTO\s+(\w+)/i.test(userQuery)) { return userQuery.match(/^INSERT\s+INTO\s+(\w+)/i)?.[1] || ''; } if (/^UPDATE\s+(\w+)/i.test(userQuery)) { return userQuery.match(/^UPDATE\s+(\w+)/i)?.[1] || ''; } if (/^DELETE\s+FROM\s+(\w+)/i.test(userQuery)) { return userQuery.match(/^DELETE\s+FROM\s+(\w+)/i)?.[1] || ''; } if (/^ALTER\s+TABLE\s+(\w+)/i.test(userQuery)) { return userQuery.match(/^ALTER\s+TABLE\s+(\w+)/i)?.[1] || ''; } return ''; } function execStatements(db: Database, sql: string): void { const stmts = sql.split(';').filter(s => s.trim()); for (const stmt of stmts) { const trimmed = stmt.trim(); if (trimmed) { db.run(trimmed); } } } function formatRows(rows: any[][]): string { return rows.map(r => `[${r.map(v => v === null ? 'NULL' : JSON.stringify(v)).join(', ')}]`).join('\n'); } export async function validateSqlChallenge( testCases: TestCase, userQuery: string ): Promise { if (!userQuery || !userQuery.trim()) { return { isCorrect: false, feedback: 'اكتب استعلام SQL أولاً', suggestions: '' }; } try { const SQL = await initSqlJs(); const trimmed = userQuery.trim().toUpperCase(); // ═══ DDL challenges: CREATE TABLE ═══ if (testCases.validate_schema && testCases.expected_tables) { const db = new SQL.Database(); try { execStatements(db, userQuery); } catch (err: any) { db.close(); return { isCorrect: false, feedback: `خطأ في SQL: ${err.message}`, suggestions: 'تأكد من صحة بناء الجملة SQL' }; } const actual = getAllTablesInfo(db); db.close(); for (const [tableName, expectedCols] of Object.entries(testCases.expected_tables)) { const actualCols = actual[tableName]; if (!actualCols) { return { isCorrect: false, feedback: `الجدول "${tableName}" لم يتم إنشاؤه`, suggestions: 'تأكد من استخدام CREATE TABLE' }; } const norm = (s: string) => s.toLowerCase().replace(/\s+/g, ' ').trim(); const normActual = actualCols.map(norm).sort(); const normExpected = expectedCols.map(norm).sort(); if (normActual.length !== normExpected.length || !normActual.every((c, i) => c === normExpected[i])) { return { isCorrect: false, feedback: `أعمدة الجدول "${tableName}" غير صحيحة. المتوقع: ${expectedCols.join(', ')}`, suggestions: `تأكد من تضمين جميع الأعمدة: ${expectedCols.join(', ')}` }; } } return { isCorrect: true, feedback: 'ممتاز! تم إنشاء الجدول بشكل صحيح', suggestions: '' }; } // ═══ View challenges ═══ if (testCases.validate_view && testCases.view_name) { const db = new SQL.Database(); try { if (testCases.schema) execStatements(db, testCases.schema); if (testCases.seed_data) execStatements(db, testCases.seed_data); execStatements(db, userQuery); } catch (err: any) { db.close(); return { isCorrect: false, feedback: `خطأ في SQL: ${err.message}`, suggestions: 'تأكد من صحة بناء الجملة SQL' }; } const viewDef = getViewDef(db, testCases.view_name); if (!viewDef) { db.close(); return { isCorrect: false, feedback: `لم يتم إنشاء العرض "${testCases.view_name}"`, suggestions: 'تأكد من استخدام CREATE VIEW' }; } let columns: string[] = []; let rows: any[][] = []; try { const res = db.exec(`SELECT * FROM "${testCases.view_name}"`); if (res.length > 0 && res[0]) { columns = res[0].columns; rows = res[0].values; } } catch (err: any) { db.close(); return { isCorrect: false, feedback: `خطأ في استعلام العرض: ${err.message}`, suggestions: '' }; } db.close(); if (testCases.expected_columns && !columnsMatch(columns, testCases.expected_columns)) { return { isCorrect: false, feedback: `الأعمدة غير صحيحة. المتوقع: ${testCases.expected_columns.join(', ')}`, suggestions: '' }; } if (testCases.expected_rows && !rowsMatch(rows, testCases.expected_rows)) { return { isCorrect: false, feedback: `النتائج غير صحيحة.\nالمتوقع:\n${formatRows(testCases.expected_rows)}\nحصلت:\n${formatRows(rows)}`, suggestions: 'تحقق من شروط التصفية والحسابات' }; } return { isCorrect: true, feedback: 'ممتاز! تم إنشاء العرض بشكل صحيح', suggestions: '' }; } // ═══ Standard challenges with expected_rows ═══ if (testCases.expected_rows) { const db = new SQL.Database(); try { if (testCases.schema) execStatements(db, testCases.schema); if (testCases.seed_data) execStatements(db, testCases.seed_data); execStatements(db, userQuery); } catch (err: any) { db.close(); return { isCorrect: false, feedback: `خطأ في SQL: ${err.message}`, suggestions: 'تأكد من صحة بناء الجملة SQL' }; } const isDML = /^(INSERT|UPDATE|DELETE|ALTER)\s/.test(trimmed); let verifyQuery = userQuery; if (isDML) { const tableName = findTableName(userQuery); if (tableName) { verifyQuery = `SELECT * FROM "${tableName}"`; } } let columns: string[] = []; let rows: any[][] = []; try { const res = db.exec(verifyQuery); if (res.length > 0 && res[0]) { columns = res[0].columns; rows = res[0].values; } } catch (err: any) { db.close(); return { isCorrect: false, feedback: `خطأ في استعلام التحقق: ${err.message}`, suggestions: '' }; } db.close(); if (testCases.expected_columns && !columnsMatch(columns, testCases.expected_columns)) { return { isCorrect: false, feedback: `الأعمدة غير صحيحة. المتوقع: ${testCases.expected_columns.join(', ')}`, suggestions: '' }; } if (!rowsMatch(rows, testCases.expected_rows)) { return { isCorrect: false, feedback: `النتائج لا تتطابق مع المطلوب.\nالمتوقع:\n${formatRows(testCases.expected_rows)}\nحصلت:\n${formatRows(rows)}`, suggestions: 'تحقق من منطق الاستعلام' }; } return { isCorrect: true, feedback: 'صحيح! النتائج مطابقة للمطلوب', suggestions: '' }; } // No test_cases — just check syntax const db = new SQL.Database(); try { execStatements(db, userQuery); db.close(); return { isCorrect: true, feedback: 'الاستعلام صحيح', suggestions: '' }; } catch (err: any) { db.close(); return { isCorrect: false, feedback: `خطأ في SQL: ${err.message}`, suggestions: '' }; } } catch (err: any) { return { isCorrect: false, feedback: `خطأ في التحقق: ${err.message}`, suggestions: '' }; } }