696 lines
22 KiB
JavaScript
696 lines
22 KiB
JavaScript
const Database = require('better-sqlite3');
|
|
const path = require('path');
|
|
const logger = require('../utils/logger');
|
|
const { withDatabaseRetry, CircuitBreaker } = require('../utils/retry');
|
|
const { DatabaseError } = require('../middleware/errorHandler');
|
|
|
|
class DatabaseManager {
|
|
constructor() {
|
|
this.db = null;
|
|
this.dbPath = path.join(__dirname, '..', 'inventory.db');
|
|
this.isInitialized = false;
|
|
this.circuitBreaker = new CircuitBreaker({
|
|
failureThreshold: 5,
|
|
resetTimeout: 30000
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Initialize database connection and create tables
|
|
*/
|
|
async initialize() {
|
|
if (this.isInitialized) {
|
|
logger.debug('Database already initialized');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await withDatabaseRetry(async () => {
|
|
logger.info('Initializing database connection', { dbPath: this.dbPath });
|
|
|
|
this.db = new Database(this.dbPath);
|
|
|
|
// Configure database settings
|
|
this.db.pragma('journal_mode = WAL');
|
|
this.db.pragma('synchronous = NORMAL');
|
|
this.db.pragma('cache_size = 1000');
|
|
this.db.pragma('temp_store = memory');
|
|
this.db.pragma('mmap_size = 268435456'); // 256MB
|
|
|
|
// Set busy timeout
|
|
this.db.pragma('busy_timeout = 5000');
|
|
|
|
await this.createTables();
|
|
|
|
this.isInitialized = true;
|
|
|
|
logger.info('Database initialized successfully', {
|
|
dbPath: this.dbPath,
|
|
journalMode: this.db.pragma('journal_mode', { simple: true }),
|
|
cacheSize: this.db.pragma('cache_size', { simple: true })
|
|
});
|
|
});
|
|
} catch (error) {
|
|
logger.error('Database initialization failed', {
|
|
error: error.message,
|
|
dbPath: this.dbPath,
|
|
stack: error.stack
|
|
});
|
|
throw new DatabaseError('Failed to initialize database', {
|
|
originalError: error.message,
|
|
dbPath: this.dbPath
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create all required tables
|
|
*/
|
|
async createTables() {
|
|
// Products table as per design document
|
|
const createProductsTable = `
|
|
CREATE TABLE IF NOT EXISTS products (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
product_code VARCHAR(50) UNIQUE NOT NULL,
|
|
description TEXT,
|
|
category VARCHAR(100),
|
|
unit_of_measure VARCHAR(20),
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
`;
|
|
|
|
// Inventory table as per design document
|
|
const createInventoryTable = `
|
|
CREATE TABLE IF NOT EXISTS inventory (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
product_id INTEGER NOT NULL,
|
|
current_level INTEGER NOT NULL DEFAULT 0,
|
|
minimum_level INTEGER DEFAULT 0,
|
|
maximum_level INTEGER,
|
|
last_updated DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_by VARCHAR(100),
|
|
version INTEGER DEFAULT 1,
|
|
FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE
|
|
)
|
|
`;
|
|
|
|
// Inventory history table as per design document
|
|
const createInventoryHistoryTable = `
|
|
CREATE TABLE IF NOT EXISTS inventory_history (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
product_id INTEGER NOT NULL,
|
|
old_level INTEGER,
|
|
new_level INTEGER NOT NULL,
|
|
change_reason VARCHAR(200),
|
|
updated_by VARCHAR(100),
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE
|
|
)
|
|
`;
|
|
|
|
// Import sessions table as per design document
|
|
const createImportSessionsTable = `
|
|
CREATE TABLE IF NOT EXISTS import_sessions (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
filename VARCHAR(255),
|
|
total_records INTEGER,
|
|
successful_imports INTEGER,
|
|
failed_imports INTEGER,
|
|
import_date DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
status VARCHAR(20) DEFAULT 'completed'
|
|
)
|
|
`;
|
|
|
|
// Legacy tables for backward compatibility (will be migrated)
|
|
const createItemsTable = `
|
|
CREATE TABLE IF NOT EXISTS items (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
name TEXT NOT NULL,
|
|
description TEXT,
|
|
category TEXT,
|
|
quantity INTEGER NOT NULL DEFAULT 0,
|
|
unit TEXT,
|
|
barcode TEXT UNIQUE,
|
|
qr_code TEXT,
|
|
location TEXT,
|
|
min_stock_level INTEGER DEFAULT 0,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
`;
|
|
|
|
const createTransactionsTable = `
|
|
CREATE TABLE IF NOT EXISTS transactions (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
item_id INTEGER NOT NULL,
|
|
type TEXT NOT NULL CHECK (type IN ('in', 'out', 'adjustment')),
|
|
quantity INTEGER NOT NULL,
|
|
reason TEXT,
|
|
user_name TEXT,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (item_id) REFERENCES items (id) ON DELETE CASCADE
|
|
)
|
|
`;
|
|
|
|
const createIndexes = [
|
|
// New schema indexes - optimized for performance
|
|
'CREATE UNIQUE INDEX IF NOT EXISTS idx_products_product_code ON products(product_code)',
|
|
'CREATE INDEX IF NOT EXISTS idx_products_category ON products(category)',
|
|
'CREATE INDEX IF NOT EXISTS idx_products_description ON products(description)',
|
|
'CREATE INDEX IF NOT EXISTS idx_products_created_at ON products(created_at)',
|
|
|
|
// Inventory indexes - optimized for common queries
|
|
'CREATE UNIQUE INDEX IF NOT EXISTS idx_inventory_product_id ON inventory(product_id)',
|
|
'CREATE INDEX IF NOT EXISTS idx_inventory_current_level ON inventory(current_level)',
|
|
'CREATE INDEX IF NOT EXISTS idx_inventory_low_stock ON inventory(current_level, minimum_level) WHERE current_level <= minimum_level',
|
|
'CREATE INDEX IF NOT EXISTS idx_inventory_last_updated ON inventory(last_updated)',
|
|
'CREATE INDEX IF NOT EXISTS idx_inventory_updated_by ON inventory(updated_by)',
|
|
'CREATE INDEX IF NOT EXISTS idx_inventory_version ON inventory(version)', // For optimistic locking
|
|
|
|
// Inventory history indexes - optimized for audit queries
|
|
'CREATE INDEX IF NOT EXISTS idx_inventory_history_product_id ON inventory_history(product_id)',
|
|
'CREATE INDEX IF NOT EXISTS idx_inventory_history_updated_at ON inventory_history(updated_at)',
|
|
'CREATE INDEX IF NOT EXISTS idx_inventory_history_updated_by ON inventory_history(updated_by)',
|
|
'CREATE INDEX IF NOT EXISTS idx_inventory_history_product_date ON inventory_history(product_id, updated_at)',
|
|
|
|
// Import sessions indexes
|
|
'CREATE INDEX IF NOT EXISTS idx_import_sessions_date ON import_sessions(import_date)',
|
|
'CREATE INDEX IF NOT EXISTS idx_import_sessions_status ON import_sessions(status)',
|
|
|
|
// Composite indexes for complex queries
|
|
'CREATE INDEX IF NOT EXISTS idx_products_category_code ON products(category, product_code)',
|
|
'CREATE INDEX IF NOT EXISTS idx_inventory_product_level ON inventory(product_id, current_level)',
|
|
'CREATE INDEX IF NOT EXISTS idx_inventory_levels_range ON inventory(current_level, minimum_level, maximum_level)',
|
|
|
|
// Legacy schema indexes
|
|
'CREATE INDEX IF NOT EXISTS idx_items_barcode ON items(barcode)',
|
|
'CREATE INDEX IF NOT EXISTS idx_items_name ON items(name)',
|
|
'CREATE INDEX IF NOT EXISTS idx_items_category ON items(category)',
|
|
'CREATE INDEX IF NOT EXISTS idx_items_quantity ON items(quantity)',
|
|
'CREATE INDEX IF NOT EXISTS idx_transactions_item_id ON transactions(item_id)',
|
|
'CREATE INDEX IF NOT EXISTS idx_transactions_created_at ON transactions(created_at)',
|
|
'CREATE INDEX IF NOT EXISTS idx_transactions_type ON transactions(type)',
|
|
'CREATE INDEX IF NOT EXISTS idx_transactions_item_date ON transactions(item_id, created_at)'
|
|
];
|
|
|
|
try {
|
|
logger.info('Creating database tables and indexes');
|
|
|
|
const startTime = Date.now();
|
|
|
|
// Create new schema tables
|
|
this.db.exec(createProductsTable);
|
|
this.db.exec(createInventoryTable);
|
|
this.db.exec(createInventoryHistoryTable);
|
|
this.db.exec(createImportSessionsTable);
|
|
|
|
// Create legacy tables for backward compatibility
|
|
this.db.exec(createItemsTable);
|
|
this.db.exec(createTransactionsTable);
|
|
|
|
// Create indexes
|
|
createIndexes.forEach((indexQuery, index) => {
|
|
try {
|
|
this.db.exec(indexQuery);
|
|
logger.debug('Index created', { index: index + 1, total: createIndexes.length });
|
|
} catch (indexError) {
|
|
logger.warn('Index creation failed', {
|
|
query: indexQuery,
|
|
error: indexError.message
|
|
});
|
|
}
|
|
});
|
|
|
|
const duration = Date.now() - startTime;
|
|
logger.info('Database tables created successfully', { duration: `${duration}ms` });
|
|
} catch (error) {
|
|
logger.error('Error creating database tables', {
|
|
error: error.message,
|
|
stack: error.stack
|
|
});
|
|
throw new DatabaseError('Failed to create database tables', {
|
|
originalError: error.message
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get database instance with circuit breaker protection
|
|
*/
|
|
getDatabase() {
|
|
if (!this.db || !this.isInitialized) {
|
|
throw new DatabaseError('Database not initialized. Call initialize() first.');
|
|
}
|
|
return this.db;
|
|
}
|
|
|
|
/**
|
|
* Execute database operation with circuit breaker and retry logic
|
|
*/
|
|
async executeWithProtection(operation, operationName = 'database_operation') {
|
|
return await this.circuitBreaker.execute(async () => {
|
|
return await withDatabaseRetry(async () => {
|
|
const startTime = Date.now();
|
|
|
|
try {
|
|
const result = await operation();
|
|
const duration = Date.now() - startTime;
|
|
|
|
logger.logDbOperation(operationName, 'unknown', {}, duration);
|
|
|
|
return result;
|
|
} catch (error) {
|
|
const duration = Date.now() - startTime;
|
|
|
|
logger.error('Database operation failed', {
|
|
operation: operationName,
|
|
duration: `${duration}ms`,
|
|
error: error.message,
|
|
code: error.code
|
|
});
|
|
|
|
throw error;
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Close database connection
|
|
*/
|
|
close() {
|
|
if (this.db) {
|
|
try {
|
|
logger.info('Closing database connection');
|
|
this.db.close();
|
|
this.db = null;
|
|
this.isInitialized = false;
|
|
logger.info('Database connection closed successfully');
|
|
} catch (error) {
|
|
logger.error('Error closing database connection', {
|
|
error: error.message
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Execute a transaction with rollback support and error handling
|
|
*/
|
|
async executeTransaction(callback, transactionName = 'transaction') {
|
|
return await this.executeWithProtection(async () => {
|
|
const startTime = Date.now();
|
|
|
|
try {
|
|
logger.debug('Starting database transaction', { name: transactionName });
|
|
|
|
const transaction = this.db.transaction(callback);
|
|
const result = transaction();
|
|
|
|
const duration = Date.now() - startTime;
|
|
logger.debug('Database transaction completed', {
|
|
name: transactionName,
|
|
duration: `${duration}ms`
|
|
});
|
|
|
|
return result;
|
|
} catch (error) {
|
|
const duration = Date.now() - startTime;
|
|
logger.error('Database transaction failed', {
|
|
name: transactionName,
|
|
duration: `${duration}ms`,
|
|
error: error.message
|
|
});
|
|
|
|
throw new DatabaseError(`Transaction failed: ${transactionName}`, {
|
|
originalError: error.message,
|
|
transactionName
|
|
});
|
|
}
|
|
}, `transaction_${transactionName}`);
|
|
}
|
|
|
|
/**
|
|
* Health check for database connection
|
|
*/
|
|
async healthCheck() {
|
|
try {
|
|
const result = this.db.prepare('SELECT 1 as health').get();
|
|
return {
|
|
status: 'healthy',
|
|
connected: true,
|
|
result: result.health === 1
|
|
};
|
|
} catch (error) {
|
|
logger.error('Database health check failed', {
|
|
error: error.message
|
|
});
|
|
return {
|
|
status: 'unhealthy',
|
|
connected: false,
|
|
error: error.message
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get database statistics
|
|
*/
|
|
getStats() {
|
|
try {
|
|
const stats = {
|
|
isInitialized: this.isInitialized,
|
|
dbPath: this.dbPath,
|
|
circuitBreakerState: this.circuitBreaker.getState()
|
|
};
|
|
|
|
if (this.db) {
|
|
stats.pragmas = {
|
|
journalMode: this.db.pragma('journal_mode', { simple: true }),
|
|
synchronous: this.db.pragma('synchronous', { simple: true }),
|
|
cacheSize: this.db.pragma('cache_size', { simple: true }),
|
|
busyTimeout: this.db.pragma('busy_timeout', { simple: true })
|
|
};
|
|
|
|
// Get table statistics
|
|
stats.tables = this.getTableStats();
|
|
|
|
// Get index usage statistics
|
|
stats.indexes = this.getIndexStats();
|
|
}
|
|
|
|
return stats;
|
|
} catch (error) {
|
|
logger.error('Error getting database stats', {
|
|
error: error.message
|
|
});
|
|
return {
|
|
isInitialized: this.isInitialized,
|
|
error: error.message
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get table statistics for performance monitoring
|
|
*/
|
|
getTableStats() {
|
|
try {
|
|
const tables = ['products', 'inventory', 'inventory_history', 'import_sessions'];
|
|
const stats = {};
|
|
|
|
tables.forEach(table => {
|
|
try {
|
|
const countResult = this.db.prepare(`SELECT COUNT(*) as count FROM ${table}`).get();
|
|
stats[table] = {
|
|
rowCount: countResult.count
|
|
};
|
|
} catch (error) {
|
|
stats[table] = { error: error.message };
|
|
}
|
|
});
|
|
|
|
return stats;
|
|
} catch (error) {
|
|
logger.error('Error getting table stats', { error: error.message });
|
|
return {};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get index statistics for query optimization
|
|
*/
|
|
getIndexStats() {
|
|
try {
|
|
const indexes = this.db.prepare(`
|
|
SELECT name, tbl_name, sql
|
|
FROM sqlite_master
|
|
WHERE type = 'index' AND name NOT LIKE 'sqlite_%'
|
|
ORDER BY tbl_name, name
|
|
`).all();
|
|
|
|
return indexes.map(index => ({
|
|
name: index.name,
|
|
table: index.tbl_name,
|
|
definition: index.sql
|
|
}));
|
|
} catch (error) {
|
|
logger.error('Error getting index stats', { error: error.message });
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Analyze database performance and suggest optimizations
|
|
*/
|
|
analyzePerformance() {
|
|
try {
|
|
const analysis = {
|
|
timestamp: new Date().toISOString(),
|
|
recommendations: []
|
|
};
|
|
|
|
// Check for missing indexes on frequently queried columns
|
|
const tableStats = this.getTableStats();
|
|
|
|
// Analyze query patterns (this would be enhanced with actual query logging)
|
|
if (tableStats.products && tableStats.products.rowCount > 1000) {
|
|
analysis.recommendations.push({
|
|
type: 'INDEX_OPTIMIZATION',
|
|
message: 'Consider adding composite indexes for frequently filtered product queries',
|
|
priority: 'MEDIUM'
|
|
});
|
|
}
|
|
|
|
if (tableStats.inventory_history && tableStats.inventory_history.rowCount > 10000) {
|
|
analysis.recommendations.push({
|
|
type: 'ARCHIVAL',
|
|
message: 'Consider archiving old inventory history records to improve query performance',
|
|
priority: 'LOW'
|
|
});
|
|
}
|
|
|
|
// Check cache hit ratio (simplified)
|
|
const cacheSize = this.db.pragma('cache_size', { simple: true });
|
|
if (cacheSize < 2000) {
|
|
analysis.recommendations.push({
|
|
type: 'CACHE_OPTIMIZATION',
|
|
message: 'Consider increasing cache size for better performance with large datasets',
|
|
priority: 'HIGH'
|
|
});
|
|
}
|
|
|
|
return analysis;
|
|
} catch (error) {
|
|
logger.error('Error analyzing database performance', { error: error.message });
|
|
return {
|
|
timestamp: new Date().toISOString(),
|
|
error: error.message,
|
|
recommendations: []
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Optimize database for better performance
|
|
*/
|
|
async optimize() {
|
|
return await this.executeWithProtection(async () => {
|
|
logger.info('Starting database optimization');
|
|
|
|
const startTime = Date.now();
|
|
const results = {
|
|
vacuum: false,
|
|
analyze: false,
|
|
reindex: false,
|
|
pragmaUpdates: []
|
|
};
|
|
|
|
try {
|
|
// Run VACUUM to reclaim space and defragment
|
|
this.db.exec('VACUUM');
|
|
results.vacuum = true;
|
|
logger.debug('Database VACUUM completed');
|
|
|
|
// Run ANALYZE to update query planner statistics
|
|
this.db.exec('ANALYZE');
|
|
results.analyze = true;
|
|
logger.debug('Database ANALYZE completed');
|
|
|
|
// Reindex all indexes
|
|
this.db.exec('REINDEX');
|
|
results.reindex = true;
|
|
logger.debug('Database REINDEX completed');
|
|
|
|
// Optimize pragma settings for performance
|
|
const optimizations = [
|
|
{ pragma: 'optimize', value: null }, // SQLite auto-optimization
|
|
{ pragma: 'cache_size', value: 2000 },
|
|
{ pragma: 'temp_store', value: 'memory' },
|
|
{ pragma: 'mmap_size', value: 268435456 } // 256MB
|
|
];
|
|
|
|
optimizations.forEach(opt => {
|
|
try {
|
|
if (opt.value !== null) {
|
|
this.db.pragma(`${opt.pragma} = ${opt.value}`);
|
|
} else {
|
|
this.db.pragma(opt.pragma);
|
|
}
|
|
results.pragmaUpdates.push(opt.pragma);
|
|
} catch (error) {
|
|
logger.warn(`Failed to update pragma ${opt.pragma}`, { error: error.message });
|
|
}
|
|
});
|
|
|
|
const duration = Date.now() - startTime;
|
|
logger.info('Database optimization completed', {
|
|
duration: `${duration}ms`,
|
|
results
|
|
});
|
|
|
|
return {
|
|
success: true,
|
|
duration,
|
|
results
|
|
};
|
|
} catch (error) {
|
|
const duration = Date.now() - startTime;
|
|
logger.error('Database optimization failed', {
|
|
duration: `${duration}ms`,
|
|
error: error.message,
|
|
partialResults: results
|
|
});
|
|
|
|
throw new DatabaseError('Database optimization failed', {
|
|
originalError: error.message,
|
|
partialResults: results
|
|
});
|
|
}
|
|
}, 'database_optimization');
|
|
}
|
|
|
|
/**
|
|
* Prepare optimized statements for common queries
|
|
*/
|
|
prepareOptimizedStatements() {
|
|
if (!this.db) {
|
|
throw new DatabaseError('Database not initialized');
|
|
}
|
|
|
|
try {
|
|
// Cache frequently used prepared statements
|
|
this.preparedStatements = {
|
|
// Product queries
|
|
findProductByCode: this.db.prepare(`
|
|
SELECT p.*, i.current_level, i.minimum_level, i.maximum_level
|
|
FROM products p
|
|
LEFT JOIN inventory i ON p.id = i.product_id
|
|
WHERE p.product_code = ?
|
|
`),
|
|
|
|
findProductsByCategory: this.db.prepare(`
|
|
SELECT p.*, i.current_level, i.minimum_level, i.maximum_level
|
|
FROM products p
|
|
LEFT JOIN inventory i ON p.id = i.product_id
|
|
WHERE p.category = ?
|
|
ORDER BY p.product_code
|
|
LIMIT ? OFFSET ?
|
|
`),
|
|
|
|
// Inventory queries
|
|
getInventorySummary: this.db.prepare(`
|
|
SELECT
|
|
p.id,
|
|
p.product_code,
|
|
p.description,
|
|
p.category,
|
|
i.current_level,
|
|
i.minimum_level,
|
|
i.maximum_level,
|
|
i.last_updated,
|
|
i.updated_by,
|
|
CASE
|
|
WHEN i.current_level <= i.minimum_level THEN 'low'
|
|
WHEN i.current_level >= i.maximum_level THEN 'high'
|
|
ELSE 'normal'
|
|
END as stock_status
|
|
FROM products p
|
|
INNER JOIN inventory i ON p.id = i.product_id
|
|
ORDER BY p.product_code
|
|
LIMIT ? OFFSET ?
|
|
`),
|
|
|
|
getLowStockItems: this.db.prepare(`
|
|
SELECT
|
|
p.id,
|
|
p.product_code,
|
|
p.description,
|
|
p.category,
|
|
i.current_level,
|
|
i.minimum_level,
|
|
i.last_updated
|
|
FROM products p
|
|
INNER JOIN inventory i ON p.id = i.product_id
|
|
WHERE i.current_level <= i.minimum_level
|
|
ORDER BY (i.current_level - i.minimum_level), p.product_code
|
|
`),
|
|
|
|
// History queries
|
|
getInventoryHistory: this.db.prepare(`
|
|
SELECT
|
|
ih.*,
|
|
p.product_code,
|
|
p.description
|
|
FROM inventory_history ih
|
|
INNER JOIN products p ON ih.product_id = p.id
|
|
WHERE ih.product_id = ?
|
|
ORDER BY ih.updated_at DESC
|
|
LIMIT ? OFFSET ?
|
|
`),
|
|
|
|
// Update queries with optimistic locking
|
|
updateInventoryLevel: this.db.prepare(`
|
|
UPDATE inventory
|
|
SET current_level = ?,
|
|
last_updated = datetime('now'),
|
|
updated_by = ?,
|
|
version = version + 1
|
|
WHERE product_id = ? AND version = ?
|
|
`),
|
|
|
|
insertInventoryHistory: this.db.prepare(`
|
|
INSERT INTO inventory_history (product_id, old_level, new_level, change_reason, updated_by)
|
|
VALUES (?, ?, ?, ?, ?)
|
|
`)
|
|
};
|
|
|
|
logger.info('Optimized prepared statements created');
|
|
return this.preparedStatements;
|
|
} catch (error) {
|
|
logger.error('Failed to prepare optimized statements', { error: error.message });
|
|
throw new DatabaseError('Failed to prepare optimized statements', {
|
|
originalError: error.message
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get prepared statement by name
|
|
*/
|
|
getPreparedStatement(name) {
|
|
if (!this.preparedStatements) {
|
|
this.prepareOptimizedStatements();
|
|
}
|
|
|
|
const statement = this.preparedStatements[name];
|
|
if (!statement) {
|
|
throw new DatabaseError(`Prepared statement '${name}' not found`);
|
|
}
|
|
|
|
return statement;
|
|
}
|
|
}
|
|
|
|
module.exports = new DatabaseManager(); |