@codai/cbd
Version:
Codai Better Database - High-Performance Vector Memory System with HPKV-inspired architecture and MCP server
637 lines • 23.6 kB
JavaScript
/**
* 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