UNPKG

@codai/cbd

Version:

Codai Better Database - High-Performance Vector Memory System with HPKV-inspired architecture and MCP server

637 lines 23.6 kB
/** * CBD Universal SQL Engine * Full SQL query processor with PostgreSQL compatibility * * Phase 1: Core SQL functionality with ACID guarantees and wire protocol compatibility */ import { EventEmitter } from 'events'; import { DataContainerFactory, UniversalRecordUtils } from './UniversalDataModel.js'; /** * Universal SQL Engine - PostgreSQL-compatible SQL processor */ export class UniversalSQLEngine extends EventEmitter { storageEngine; schemas; activeTransactions; lockManager; queryCache; statistics; constructor(storageEngine) { super(); this.storageEngine = storageEngine; this.schemas = new Map(); this.activeTransactions = new Map(); this.lockManager = new LockManager(); this.queryCache = new Map(); this.statistics = this.createInitialStats(); // Create default schema this.schemas.set('public', new Map()); } /** * Execute SQL query with full ACID compliance */ async executeSQL(sql, parameters = [], context) { const startTime = Date.now(); const queryHash = this.hashQuery(sql, parameters); try { // Parse SQL query const parsedQuery = await this.parseSQL(sql, parameters); // Check query cache for SELECT statements if (parsedQuery.type === 'SELECT' && this.queryCache.has(queryHash)) { return this.queryCache.get(queryHash); } // Validate query against schema await this.validateQuery(parsedQuery); // Handle transaction context const transaction = context?.transactionId ? this.activeTransactions.get(context.transactionId) : undefined; // Execute query based on type let result; switch (parsedQuery.type) { case 'SELECT': result = await this.executeSelect(parsedQuery, context, transaction); break; case 'INSERT': result = await this.executeInsert(parsedQuery, context, transaction); break; case 'UPDATE': result = await this.executeUpdate(parsedQuery, context, transaction); break; case 'DELETE': result = await this.executeDelete(parsedQuery, context, transaction); break; case 'CREATE': result = await this.executeCreate(parsedQuery, context); break; case 'DROP': result = await this.executeDrop(parsedQuery, context); break; case 'ALTER': result = await this.executeAlter(parsedQuery, context); break; default: throw new Error(`Unsupported query type: ${parsedQuery.type}`); } // Cache SELECT results if (parsedQuery.type === 'SELECT' && result.data.length > 0) { this.queryCache.set(queryHash, result); } // Update statistics this.updateStatistics(parsedQuery.type, Date.now() - startTime, result.rowCount); return result; } catch (error) { this.statistics.errorCount++; throw new Error(`SQL execution failed: ${error}`); } } /** * Begin transaction with specified isolation level */ async beginTransaction(isolationLevel = 'READ_COMMITTED') { const transactionId = `tx_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; const transaction = { id: transactionId, isolationLevel, status: 'ACTIVE', startTime: new Date(), readSet: new Set(), writeSet: new Set(), locks: new Map() }; this.activeTransactions.set(transactionId, transaction); console.log(`📘 Transaction ${transactionId} started with isolation level ${isolationLevel}`); return transactionId; } /** * Commit transaction */ async commitTransaction(transactionId) { const transaction = this.activeTransactions.get(transactionId); if (!transaction) { throw new Error(`Transaction ${transactionId} not found`); } if (transaction.status !== 'ACTIVE') { throw new Error(`Transaction ${transactionId} is not active`); } try { // Validate no conflicts (simplified implementation) await this.validateTransactionCommit(transaction); // Release all locks for (const [resource, lockType] of transaction.locks) { this.lockManager.releaseLock(resource, transactionId, lockType); } // Mark as committed transaction.status = 'COMMITTED'; // Clean up this.activeTransactions.delete(transactionId); console.log(`✅ Transaction ${transactionId} committed successfully`); } catch (error) { // Rollback on commit failure await this.rollbackTransaction(transactionId); throw new Error(`Transaction commit failed: ${error}`); } } /** * Rollback transaction */ async rollbackTransaction(transactionId) { const transaction = this.activeTransactions.get(transactionId); if (!transaction) { throw new Error(`Transaction ${transactionId} not found`); } try { // Undo all changes made in this transaction await this.undoTransactionChanges(transaction); // Release all locks for (const [resource, lockType] of transaction.locks) { this.lockManager.releaseLock(resource, transactionId, lockType); } // Mark as aborted transaction.status = 'ABORTED'; // Clean up this.activeTransactions.delete(transactionId); console.log(`🔄 Transaction ${transactionId} rolled back`); } catch (error) { console.error(`Error during rollback of transaction ${transactionId}:`, error); throw error; } } /** * Create table with full schema support */ async createTable(schemaName, tableName, columns, options = {}) { // Ensure schema exists if (!this.schemas.has(schemaName)) { this.schemas.set(schemaName, new Map()); } const schema = this.schemas.get(schemaName); // Check if table already exists if (schema.has(tableName)) { throw new Error(`Table ${schemaName}.${tableName} already exists`); } // Create table schema const tableSchema = { name: tableName, schema: schemaName, columns, primaryKey: options.primaryKey || [], foreignKeys: options.foreignKeys || [], indexes: options.indexes || [], constraints: options.constraints || [] }; // Validate schema this.validateTableSchema(tableSchema); // Store schema schema.set(tableName, tableSchema); // Create indexes for the table await this.createTableIndexes(schemaName, tableName, tableSchema); console.log(`📋 Created table ${schemaName}.${tableName}`); this.emit('tableCreated', { schema: schemaName, table: tableName }); } /** * Drop table */ async dropTable(schemaName, tableName, cascade = false) { const schema = this.schemas.get(schemaName); if (!schema || !schema.has(tableName)) { throw new Error(`Table ${schemaName}.${tableName} does not exist`); } if (!cascade) { // Check for foreign key references await this.checkForeignKeyReferences(schemaName, tableName); } // Remove all records for this table await this.deleteAllTableRecords(schemaName, tableName); // Remove table schema schema.delete(tableName); console.log(`🗑️ Dropped table ${schemaName}.${tableName}`); this.emit('tableDropped', { schema: schemaName, table: tableName }); } /** * Get table schema */ getTableSchema(schemaName, tableName) { const schema = this.schemas.get(schemaName); return schema?.get(tableName) || null; } /** * List all tables in schema */ getTables(schemaName = 'public') { const schema = this.schemas.get(schemaName); return schema ? Array.from(schema.keys()) : []; } /** * Get SQL engine statistics */ getStatistics() { return { ...this.statistics }; } // Private implementation methods async parseSQL(sql, parameters) { // Simple SQL parser - in production, this would be much more sophisticated const trimmedSQL = sql.trim().toUpperCase(); let type; if (trimmedSQL.startsWith('SELECT')) type = 'SELECT'; else if (trimmedSQL.startsWith('INSERT')) type = 'INSERT'; else if (trimmedSQL.startsWith('UPDATE')) type = 'UPDATE'; else if (trimmedSQL.startsWith('DELETE')) type = 'DELETE'; else if (trimmedSQL.startsWith('CREATE')) type = 'CREATE'; else if (trimmedSQL.startsWith('DROP')) type = 'DROP'; else if (trimmedSQL.startsWith('ALTER')) type = 'ALTER'; else throw new Error(`Unsupported SQL statement: ${sql}`); return { type, sql, parameters, tables: this.extractTables(sql), columns: this.extractColumns(sql), conditions: this.extractConditions(sql) }; } async validateQuery(query) { // Validate that referenced tables and columns exist for (const tableName of query.tables) { if (!tableName || typeof tableName !== 'string') continue; const [schema, table] = tableName.includes('.') ? tableName.split('.', 2) : ['public', tableName]; if (!schema || !table) continue; const tableSchema = this.getTableSchema(schema, table); if (!tableSchema) { throw new Error(`Table ${schema}.${table} does not exist`); } // Validate columns exist in table for (const columnName of query.columns) { if (!tableSchema.columns.some(col => col.name === columnName)) { throw new Error(`Column ${columnName} does not exist in table ${schema}.${table}`); } } } } async executeSelect(query, _context, transaction) { // Simplified SELECT implementation const tableName = query.tables[0]; if (!tableName) { throw new Error('No table specified in query'); } const [schemaName, table] = tableName.includes('.') ? tableName.split('.', 2) : ['public', tableName]; if (!schemaName || !table) { throw new Error(`Invalid table name: ${tableName}`); } const tableSchema = this.getTableSchema(schemaName, table); if (!tableSchema) { throw new Error(`Table ${schemaName}.${table} does not exist`); } // Acquire read locks if in transaction if (transaction) { await this.lockManager.acquireLock(tableName, transaction.id, 'SHARED'); transaction.locks.set(tableName, 'SHARED'); transaction.readSet.add(tableName); } // Query storage engine for relational records in this table const records = await this.queryTableRecords(schemaName, table, query.conditions); // Convert to SQL result format const columns = tableSchema.columns.map(col => ({ name: col.name, type: col.type, nullable: col.nullable })); const data = records.map(record => { if (record.data.type === 'relational') { return record.data.columns; } return {}; }); return { data, columns, rowCount: data.length, metadata: { executionTime: 0, recordsScanned: records.length, recordsReturned: data.length, indexesUsed: [], cacheHit: false } }; } async executeInsert(query, context, transaction) { const tableName = query.tables[0]; if (!tableName) { throw new Error('No table specified in INSERT query'); } const [schemaName, table] = tableName.includes('.') ? tableName.split('.', 2) : ['public', tableName]; if (!schemaName || !table) { throw new Error(`Invalid table name: ${tableName}`); } const tableSchema = this.getTableSchema(schemaName, table); if (!tableSchema) { throw new Error(`Table ${schemaName}.${table} does not exist`); } // Acquire write locks if in transaction if (transaction) { await this.lockManager.acquireLock(tableName, transaction.id, 'EXCLUSIVE'); transaction.locks.set(tableName, 'EXCLUSIVE'); transaction.writeSet.add(tableName); } // Extract values from INSERT statement (simplified) const values = this.extractInsertValues(query.sql, query.parameters); // Create relational record const recordId = UniversalRecordUtils.generateId('sql', `${schemaName}.${table}`); const metadata = UniversalRecordUtils.createMetadata(context?.user || 'system', context?.tenantId); const record = { id: recordId, data: DataContainerFactory.createRelational(schemaName, table, values), indexes: {}, metadata, version: 1 }; // Store in universal storage engine await this.storageEngine.store(record, context); return { data: [], columns: [], rowCount: 0, affectedRows: 1, metadata: { executionTime: 0, recordsScanned: 0, recordsReturned: 0, indexesUsed: [], cacheHit: false } }; } async executeUpdate(query, _context, transaction) { // Similar implementation for UPDATE const tableName = query.tables[0]; if (!tableName) { throw new Error('No table specified in UPDATE query'); } const [schemaName, table] = tableName.includes('.') ? tableName.split('.', 2) : ['public', tableName]; if (!schemaName || !table) { throw new Error(`Invalid table name: ${tableName}`); } if (transaction) { await this.lockManager.acquireLock(tableName, transaction.id, 'EXCLUSIVE'); transaction.locks.set(tableName, 'EXCLUSIVE'); transaction.writeSet.add(tableName); } // Implementation would query, modify, and update records return { data: [], columns: [], rowCount: 0, affectedRows: 0, metadata: { executionTime: 0, recordsScanned: 0, recordsReturned: 0, indexesUsed: [], cacheHit: false } }; } async executeDelete(query, _context, transaction) { // Similar implementation for DELETE const tableName = query.tables[0]; if (!tableName) { throw new Error('No table specified in DELETE query'); } const [schemaName, table] = tableName.includes('.') ? tableName.split('.', 2) : ['public', tableName]; if (!schemaName || !table) { throw new Error(`Invalid table name: ${tableName}`); } if (transaction) { await this.lockManager.acquireLock(tableName, transaction.id, 'EXCLUSIVE'); transaction.locks.set(tableName, 'EXCLUSIVE'); transaction.writeSet.add(tableName); } // Implementation would query and delete matching records return { data: [], columns: [], rowCount: 0, affectedRows: 0, metadata: { executionTime: 0, recordsScanned: 0, recordsReturned: 0, indexesUsed: [], cacheHit: false } }; } async executeCreate(_query, _context) { // Parse CREATE TABLE statement and execute // This would parse the full CREATE TABLE syntax return { data: [], columns: [], rowCount: 0, metadata: { executionTime: 0, recordsScanned: 0, recordsReturned: 0, indexesUsed: [], cacheHit: false } }; } async executeDrop(_query, _context) { // Parse and execute DROP statements return { data: [], columns: [], rowCount: 0, metadata: { executionTime: 0, recordsScanned: 0, recordsReturned: 0, indexesUsed: [], cacheHit: false } }; } async executeAlter(_query, _context) { // Parse and execute ALTER statements return { data: [], columns: [], rowCount: 0, metadata: { executionTime: 0, recordsScanned: 0, recordsReturned: 0, indexesUsed: [], cacheHit: false } }; } // Helper methods hashQuery(sql, parameters) { return `${sql}_${JSON.stringify(parameters)}`; } extractTables(sql) { // Simple table extraction - production would use proper SQL parsing const matches = sql.match(/FROM\s+(\w+(?:\.\w+)?)/gi) || []; return matches.map(match => match.replace(/FROM\s+/i, '')); } extractColumns(_sql) { // Simple column extraction return []; } extractConditions(_sql) { // Simple condition extraction return []; } extractInsertValues(_sql, _parameters) { // Extract values from INSERT statement return {}; } async queryTableRecords(_schema, _table, _conditions) { // Query the storage engine for records in this table // This would use the storage engine's query capabilities return []; } validateTableSchema(tableSchema) { // Validate table schema is well-formed if (!tableSchema.name || !tableSchema.columns.length) { throw new Error('Invalid table schema: name and columns are required'); } // Check for duplicate column names const columnNames = new Set(); for (const column of tableSchema.columns) { if (columnNames.has(column.name)) { throw new Error(`Duplicate column name: ${column.name}`); } columnNames.add(column.name); } } async createTableIndexes(_schema, _table, _tableSchema) { // Create indexes for the table // This would interact with the storage engine's index manager } async checkForeignKeyReferences(_schema, _table) { // Check if any other tables reference this table // Would prevent dropping if foreign keys exist } async deleteAllTableRecords(_schema, _table) { // Delete all records for a table // This would use the storage engine to remove all table records } async validateTransactionCommit(_transaction) { // Validate that the transaction can be committed without conflicts // This would implement proper MVCC or other concurrency control } async undoTransactionChanges(_transaction) { // Undo all changes made by this transaction // This would restore previous versions of modified records } createInitialStats() { return { queriesExecuted: 0, selectQueries: 0, insertQueries: 0, updateQueries: 0, deleteQueries: 0, ddlQueries: 0, averageQueryTime: 0, cacheHitRate: 0, activeTransactions: 0, committedTransactions: 0, rolledBackTransactions: 0, errorCount: 0, tablesCreated: 0, totalRows: 0 }; } updateStatistics(queryType, executionTime, rowCount) { this.statistics.queriesExecuted++; this.statistics.averageQueryTime = (this.statistics.averageQueryTime + executionTime) / 2; switch (queryType) { case 'SELECT': this.statistics.selectQueries++; break; case 'INSERT': this.statistics.insertQueries++; this.statistics.totalRows += rowCount; break; case 'UPDATE': this.statistics.updateQueries++; break; case 'DELETE': this.statistics.deleteQueries++; this.statistics.totalRows -= rowCount; break; case 'CREATE': case 'DROP': case 'ALTER': this.statistics.ddlQueries++; if (queryType === 'CREATE') { this.statistics.tablesCreated++; } break; } } } // Lock Manager for concurrency control class LockManager { locks; constructor() { this.locks = new Map(); } async acquireLock(resource, transactionId, lockType) { if (!this.locks.has(resource)) { this.locks.set(resource, new Map()); } const resourceLocks = this.locks.get(resource); // Simple lock compatibility check if (lockType === 'EXCLUSIVE' && resourceLocks.size > 0) { // Wait for exclusive lock await this.waitForLock(resource, transactionId, lockType); } else if (lockType === 'SHARED') { // Check for existing exclusive locks const hasExclusive = Array.from(resourceLocks.values()).includes('EXCLUSIVE'); if (hasExclusive) { await this.waitForLock(resource, transactionId, lockType); } } resourceLocks.set(transactionId, lockType); } releaseLock(resource, transactionId, _lockType) { const resourceLocks = this.locks.get(resource); if (resourceLocks) { resourceLocks.delete(transactionId); if (resourceLocks.size === 0) { this.locks.delete(resource); } } } async waitForLock(_resource, _transactionId, _lockType) { // Simple implementation - in production, this would be more sophisticated return new Promise((resolve) => { setTimeout(resolve, 10); // Short wait }); } } //# sourceMappingURL=UniversalSQLEngine.js.map