UNPKG

oracle-mcp-v1

Version:

Servidor MCP completo para Oracle Database com operações DDL, DML, DCL, monitoramento e auditoria

557 lines (472 loc) 17.3 kB
import { Logger } from './logger.js'; import oracledb from 'oracledb'; import { ConnectionManager } from './connection-manager.js'; export class DDLOperations { constructor(connectionConfig = null, connectionManager = null) { this.logger = new Logger(); this.connectionConfig = connectionConfig; this.connectionManager = connectionManager || new ConnectionManager(); } async getConnection(connectionName = null) { try { // Se temos um ConnectionManager, usar ele if (this.connectionManager) { return await this.connectionManager.getConnection(connectionName); } // Fallback para configuração antiga if (this.connectionConfig) { const connection = await oracledb.getConnection(this.connectionConfig); return connection; } throw new Error('Nenhuma configuração de conexão disponível'); } catch (error) { this.logger.error('Erro ao conectar com Oracle:', error); throw new Error(`Falha na conexão: ${error.message}`); } } // ===== OPERAÇÕES DE TABELA ===== async createTable(options = {}) { const { tableName, schema = 'HR', columns = [], constraints = [], tablespace = 'USERS', ifNotExists = true, connectionName = null } = options; if (!tableName || columns.length === 0) { throw new Error('Nome da tabela e colunas são obrigatórios'); } let connection; try { connection = await this.getConnection(connectionName); // Verificar se a tabela já existe if (ifNotExists) { const exists = await this.tableExists(connection, tableName, schema); if (exists) { return `⚠️ Tabela ${schema}.${tableName} já existe. Operação ignorada.`; } } // Construir query CREATE TABLE const columnDefinitions = columns.map(col => { let def = `${col.name} ${col.type}`; if (col.length) def += `(${col.length}${col.precision ? ',' + col.precision : ''})`; if (col.notNull) def += ' NOT NULL'; if (col.defaultValue) def += ` DEFAULT ${col.defaultValue}`; return def; }).join(',\n '); const constraintDefinitions = constraints.map(constraint => { let def = `CONSTRAINT ${constraint.name} `; switch (constraint.type) { case 'PRIMARY KEY': def += `PRIMARY KEY (${constraint.columns.join(', ')})`; break; case 'UNIQUE': def += `UNIQUE (${constraint.columns.join(', ')})`; break; case 'CHECK': def += `CHECK (${constraint.condition})`; break; case 'FOREIGN KEY': def += `FOREIGN KEY (${constraint.columns.join(', ')}) REFERENCES ${constraint.referencedTable}(${constraint.referencedColumns.join(', ')})`; break; } return def; }).join(',\n '); const createQuery = ` CREATE TABLE ${schema}.${tableName} ( ${columnDefinitions}${constraints.length > 0 ? ',\n ' + constraintDefinitions : ''} ) TABLESPACE ${tablespace} `; await connection.execute(createQuery); await connection.commit(); this.logger.info(`Tabela ${schema}.${tableName} criada com sucesso`); return `✅ Tabela ${schema}.${tableName} criada com sucesso!`; } catch (error) { this.logger.error('Erro ao criar tabela:', error); throw new Error(`Erro ao criar tabela: ${error.message}`); } finally { if (connection) { await connection.close(); } } } async alterTable(options = {}) { const { tableName, schema = 'HR', operation, columnName, columnType, columnLength, notNull, defaultValue, constraintName, constraintType, constraintColumns, constraintCondition, referencedTable, referencedColumns, connectionName = null } = options; if (!tableName || !operation) { throw new Error('Nome da tabela e operação são obrigatórios'); } let connection; try { connection = await this.getConnection(connectionName); let alterQuery = `ALTER TABLE ${schema}.${tableName} `; switch (operation) { case 'ADD_COLUMN': { if (!columnName || !columnType) { throw new Error('Nome e tipo da coluna são obrigatórios para ADD_COLUMN'); } let columnDef = `${columnName} ${columnType}`; if (columnLength) columnDef += `(${columnLength})`; if (notNull) columnDef += ' NOT NULL'; if (defaultValue) columnDef += ` DEFAULT ${defaultValue}`; alterQuery += `ADD ${columnDef}`; break; } case 'MODIFY_COLUMN': { if (!columnName || !columnType) { throw new Error('Nome e tipo da coluna são obrigatórios para MODIFY_COLUMN'); } let modifyDef = `${columnName} ${columnType}`; if (columnLength) modifyDef += `(${columnLength})`; if (notNull !== undefined) modifyDef += notNull ? ' NOT NULL' : ' NULL'; if (defaultValue) modifyDef += ` DEFAULT ${defaultValue}`; alterQuery += `MODIFY ${modifyDef}`; break; } case 'DROP_COLUMN': if (!columnName) { throw new Error('Nome da coluna é obrigatório para DROP_COLUMN'); } alterQuery += `DROP COLUMN ${columnName}`; break; case 'ADD_CONSTRAINT': { if (!constraintName || !constraintType) { throw new Error('Nome e tipo da constraint são obrigatórios para ADD_CONSTRAINT'); } let constraintDef = `CONSTRAINT ${constraintName} `; switch (constraintType) { case 'PRIMARY KEY': constraintDef += `PRIMARY KEY (${constraintColumns.join(', ')})`; break; case 'UNIQUE': constraintDef += `UNIQUE (${constraintColumns.join(', ')})`; break; case 'CHECK': constraintDef += `CHECK (${constraintCondition})`; break; case 'FOREIGN KEY': constraintDef += `FOREIGN KEY (${constraintColumns.join(', ')}) REFERENCES ${referencedTable}(${referencedColumns.join(', ')})`; break; } alterQuery += `ADD ${constraintDef}`; break; } case 'DROP_CONSTRAINT': if (!constraintName) { throw new Error('Nome da constraint é obrigatório para DROP_CONSTRAINT'); } alterQuery += `DROP CONSTRAINT ${constraintName}`; break; case 'RENAME_COLUMN': if (!columnName || !options.newColumnName) { throw new Error('Nome atual e novo nome da coluna são obrigatórios para RENAME_COLUMN'); } alterQuery += `RENAME COLUMN ${columnName} TO ${options.newColumnName}`; break; default: throw new Error(`Operação não suportada: ${operation}`); } await connection.execute(alterQuery); await connection.commit(); this.logger.info(`Tabela ${schema}.${tableName} alterada com sucesso`); return `✅ Tabela ${schema}.${tableName} alterada com sucesso!`; } catch (error) { this.logger.error('Erro ao alterar tabela:', error); throw new Error(`Erro ao alterar tabela: ${error.message}`); } finally { if (connection) { await connection.close(); } } } async dropTable(options = {}) { const { tableName, schema = 'HR', ifExists = true, cascadeConstraints = false, connectionName = null } = options; if (!tableName) { throw new Error('Nome da tabela é obrigatório'); } let connection; try { connection = await this.getConnection(connectionName); // Verificar se a tabela existe if (ifExists) { const exists = await this.tableExists(connection, tableName, schema); if (!exists) { return `⚠️ Tabela ${schema}.${tableName} não existe. Operação ignorada.`; } } let dropQuery = `DROP TABLE ${schema}.${tableName}`; if (cascadeConstraints) { dropQuery += ' CASCADE CONSTRAINTS'; } await connection.execute(dropQuery); await connection.commit(); this.logger.info(`Tabela ${schema}.${tableName} removida com sucesso`); return `✅ Tabela ${schema}.${tableName} removida com sucesso!`; } catch (error) { this.logger.error('Erro ao remover tabela:', error); throw new Error(`Erro ao remover tabela: ${error.message}`); } finally { if (connection) { await connection.close(); } } } // ===== OPERAÇÕES DE ÍNDICE ===== async createIndex(options = {}) { const { indexName, tableName, schema = 'HR', columns = [], unique = false, tablespace = 'USERS', ifNotExists = true } = options; if (!indexName || !tableName || columns.length === 0) { throw new Error('Nome do índice, tabela e colunas são obrigatórios'); } let connection; try { connection = await this.getConnection(); // Verificar se o índice já existe if (ifNotExists) { const exists = await this.indexExists(connection, indexName, schema); if (exists) { return `⚠️ Índice ${schema}.${indexName} já existe. Operação ignorada.`; } } const uniqueClause = unique ? 'UNIQUE ' : ''; const createQuery = ` CREATE ${uniqueClause}INDEX ${schema}.${indexName} ON ${schema}.${tableName} (${columns.join(', ')}) TABLESPACE ${tablespace} `; await connection.execute(createQuery); await connection.commit(); this.logger.info(`Índice ${schema}.${indexName} criado com sucesso`); return `✅ Índice ${schema}.${indexName} criado com sucesso!`; } catch (error) { this.logger.error('Erro ao criar índice:', error); throw new Error(`Erro ao criar índice: ${error.message}`); } finally { if (connection) { await connection.close(); } } } async dropIndex(options = {}) { const { indexName, schema = 'HR', ifExists = true } = options; if (!indexName) { throw new Error('Nome do índice é obrigatório'); } let connection; try { connection = await this.getConnection(); // Verificar se o índice existe if (ifExists) { const exists = await this.indexExists(connection, indexName, schema); if (!exists) { return `⚠️ Índice ${schema}.${indexName} não existe. Operação ignorada.`; } } const dropQuery = `DROP INDEX ${schema}.${indexName}`; await connection.execute(dropQuery); await connection.commit(); this.logger.info(`Índice ${schema}.${indexName} removido com sucesso`); return `✅ Índice ${schema}.${indexName} removido com sucesso!`; } catch (error) { this.logger.error('Erro ao remover índice:', error); throw new Error(`Erro ao remover índice: ${error.message}`); } finally { if (connection) { await connection.close(); } } } // ===== OPERAÇÕES DE SEQUENCE ===== async createSequence(options = {}) { const { sequenceName, schema = 'HR', startWith = 1, incrementBy = 1, minValue = 1, maxValue = null, cache = 20, cycle = false, ifNotExists = true } = options; if (!sequenceName) { throw new Error('Nome da sequence é obrigatório'); } let connection; try { connection = await this.getConnection(); // Verificar se a sequence já existe if (ifNotExists) { const exists = await this.sequenceExists(connection, sequenceName, schema); if (exists) { return `⚠️ Sequence ${schema}.${sequenceName} já existe. Operação ignorada.`; } } let createQuery = ` CREATE SEQUENCE ${schema}.${sequenceName} START WITH ${startWith} INCREMENT BY ${incrementBy} MINVALUE ${minValue} ${maxValue ? `MAXVALUE ${maxValue}` : 'NOMAXVALUE'} CACHE ${cache} ${cycle ? 'CYCLE' : 'NOCYCLE'} `; await connection.execute(createQuery); await connection.commit(); this.logger.info(`Sequence ${schema}.${sequenceName} criada com sucesso`); return `✅ Sequence ${schema}.${sequenceName} criada com sucesso!`; } catch (error) { this.logger.error('Erro ao criar sequence:', error); throw new Error(`Erro ao criar sequence: ${error.message}`); } finally { if (connection) { await connection.close(); } } } async dropSequence(options = {}) { const { sequenceName, schema = 'HR', ifExists = true } = options; if (!sequenceName) { throw new Error('Nome da sequence é obrigatório'); } let connection; try { connection = await this.getConnection(); // Verificar se a sequence existe if (ifExists) { const exists = await this.sequenceExists(connection, sequenceName, schema); if (!exists) { return `⚠️ Sequence ${schema}.${sequenceName} não existe. Operação ignorada.`; } } const dropQuery = `DROP SEQUENCE ${schema}.${sequenceName}`; await connection.execute(dropQuery); await connection.commit(); this.logger.info(`Sequence ${schema}.${sequenceName} removida com sucesso`); return `✅ Sequence ${schema}.${sequenceName} removida com sucesso!`; } catch (error) { this.logger.error('Erro ao remover sequence:', error); throw new Error(`Erro ao remover sequence: ${error.message}`); } finally { if (connection) { await connection.close(); } } } // ===== MÉTODOS AUXILIARES ===== async tableExists(connection, tableName, schema) { try { const query = ` SELECT COUNT(*) FROM dba_tables WHERE table_name = UPPER(:tableName) AND owner = UPPER(:schema) `; const result = await connection.execute(query, { tableName, schema }); return result.rows[0][0] > 0; } catch (error) { return false; } } async indexExists(connection, indexName, schema) { try { const query = ` SELECT COUNT(*) FROM dba_indexes WHERE index_name = UPPER(:indexName) AND owner = UPPER(:schema) `; const result = await connection.execute(query, { indexName, schema }); return result.rows[0][0] > 0; } catch (error) { return false; } } async sequenceExists(connection, sequenceName, schema) { try { const query = ` SELECT COUNT(*) FROM dba_sequences WHERE sequence_name = UPPER(:sequenceName) AND sequence_owner = UPPER(:schema) `; const result = await connection.execute(query, { sequenceName, schema }); return result.rows[0][0] > 0; } catch (error) { return false; } } // ===== VALIDAÇÕES DE SEGURANÇA ===== validateTableName(tableName) { if (!tableName || typeof tableName !== 'string') { throw new Error('Nome da tabela deve ser uma string válida'); } if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(tableName)) { throw new Error('Nome da tabela deve conter apenas letras, números e underscore, começando com letra'); } if (tableName.length > 30) { throw new Error('Nome da tabela não pode exceder 30 caracteres'); } } validateColumnDefinition(column) { if (!column.name || !column.type) { throw new Error('Nome e tipo da coluna são obrigatórios'); } this.validateTableName(column.name); const validTypes = [ 'VARCHAR2', 'CHAR', 'NUMBER', 'DATE', 'TIMESTAMP', 'CLOB', 'BLOB', 'RAW', 'LONG', 'LONG RAW', 'BFILE', 'ROWID', 'UROWID' ]; if (!validTypes.includes(column.type.toUpperCase())) { throw new Error(`Tipo de coluna inválido: ${column.type}`); } } validateConstraintDefinition(constraint) { if (!constraint.name || !constraint.type) { throw new Error('Nome e tipo da constraint são obrigatórios'); } this.validateTableName(constraint.name); const validTypes = ['PRIMARY KEY', 'UNIQUE', 'CHECK', 'FOREIGN KEY']; if (!validTypes.includes(constraint.type)) { throw new Error(`Tipo de constraint inválido: ${constraint.type}`); } } }