UNPKG

oracle-mcp-v1

Version:

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

360 lines (298 loc) 10.7 kB
import Joi from 'joi'; import { Logger } from './logger.js'; export class MigrationValidator { constructor() { this.logger = new Logger(); this.dangerousOperations = [ 'DROP TABLE', 'DROP INDEX', 'DROP SEQUENCE', 'DROP PROCEDURE', 'DROP FUNCTION', 'DROP PACKAGE', 'DROP VIEW', 'TRUNCATE TABLE', 'DELETE FROM', 'ALTER TABLE DROP COLUMN', 'ALTER TABLE MODIFY COLUMN' ]; this.safeOperations = [ 'CREATE TABLE', 'CREATE INDEX', 'CREATE SEQUENCE', 'CREATE PROCEDURE', 'CREATE FUNCTION', 'CREATE PACKAGE', 'CREATE VIEW', 'ALTER TABLE ADD COLUMN', 'ALTER TABLE ADD CONSTRAINT', 'INSERT INTO', 'UPDATE SET' ]; this.validationSchema = Joi.object({ script: Joi.string().required().min(10), targetSchema: Joi.string().required().min(1) }); } async validateScript(script, targetSchema) { try { // Validar entrada const { error: validationError } = this.validationSchema.validate({ script, targetSchema }); if (validationError) { return `❌ **Erro de Validação:** ${validationError.details[0].message}`; } const results = []; // 1. Verificar se contém operações perigosas const dangerousOps = this.checkDangerousOperations(script); if (dangerousOps.length > 0) { results.push(`⚠️ **Operações Perigosas Detectadas:**\n${dangerousOps.map(op => `- ${op}`).join('\n')}`); } // 2. Verificar se tem backup/rollback const hasBackup = this.checkBackupStrategy(script); if (!hasBackup && dangerousOps.length > 0) { results.push(`❌ **Falta Estratégia de Backup:** Script contém operações perigosas mas não possui estratégia de backup/rollback`); } // 3. Verificar sintaxe básica const syntaxIssues = this.checkSyntax(script); if (syntaxIssues.length > 0) { results.push(`❌ **Problemas de Sintaxe:**\n${syntaxIssues.map(issue => `- ${issue}`).join('\n')}`); } // 4. Verificar se está no esquema correto const schemaIssues = this.checkSchemaUsage(script, targetSchema); if (schemaIssues.length > 0) { results.push(`⚠️ **Problemas de Esquema:**\n${schemaIssues.map(issue => `- ${issue}`).join('\n')}`); } // 5. Verificar se tem comentários explicativos const hasComments = this.checkComments(script); if (!hasComments) { results.push(`⚠️ **Falta Documentação:** Script não possui comentários explicativos`); } // 6. Verificar se tem validações const hasValidations = this.checkValidations(script); if (!hasValidations && dangerousOps.length > 0) { results.push(`⚠️ **Falta Validações:** Script não possui validações antes de operações críticas`); } // 7. Verificar tamanho e complexidade const complexity = this.checkComplexity(script); if (complexity.isComplex) { results.push(`⚠️ **Script Complexo:** ${complexity.reasons.join(', ')}`); } // Resultado final if (results.length === 0) { return `✅ **Script Aprovado:** O script de migração está adequado para execução em produção.`; } const status = dangerousOps.length > 0 ? '❌' : '⚠️'; return `${status} **Script Requer Revisão:**\n\n${results.join('\n\n')}`; } catch (error) { this.logger.error('Erro ao validar script:', error); return `❌ **Erro na Validação:** ${error.message}`; } } checkDangerousOperations(script) { const upperScript = script.toUpperCase(); const found = []; for (const operation of this.dangerousOperations) { if (upperScript.includes(operation)) { found.push(operation); } } return found; } checkBackupStrategy(script) { const backupKeywords = [ 'BACKUP', 'CREATE TABLE.*_BACKUP', 'CREATE TABLE.*_OLD', '-- BACKUP', '-- ROLLBACK', 'SAVEPOINT', 'COMMIT', 'ROLLBACK' ]; return backupKeywords.some(keyword => { const regex = new RegExp(keyword, 'i'); return regex.test(script); }); } checkSyntax(script) { const issues = []; const lines = script.split('\n'); // Verificar parênteses balanceados let parenCount = 0; for (const line of lines) { for (const char of line) { if (char === '(') parenCount++; if (char === ')') parenCount--; } } if (parenCount !== 0) { issues.push('Parênteses não balanceados'); } // Verificar aspas balanceadas let inString = false; for (const line of lines) { for (const char of line) { if (char === "'" && !inString) { inString = true; } else if (char === "'" && inString) { inString = false; } } } if (inString) { issues.push('Aspas simples não balanceadas'); } // Verificar ponto e vírgula no final const lastLine = lines[lines.length - 1].trim(); if (lastLine && !lastLine.endsWith(';') && !lastLine.startsWith('--')) { issues.push('Script não termina com ponto e vírgula'); } return issues; } checkSchemaUsage(script, targetSchema) { const issues = []; const upperScript = script.toUpperCase(); const upperTargetSchema = targetSchema.toUpperCase(); // Verificar se usa o esquema correto const schemaPattern = new RegExp(`\\b${upperTargetSchema}\\.`, 'g'); const schemaMatches = upperScript.match(schemaPattern); if (!schemaMatches || schemaMatches.length === 0) { issues.push(`Script não referencia o esquema de destino '${targetSchema}'`); } // Verificar se não usa outros esquemas const otherSchemaPattern = /\b[A-Z_][A-Z0-9_]*\./g; const allSchemaMatches = upperScript.match(otherSchemaPattern) || []; const otherSchemas = allSchemaMatches .map(match => match.replace('.', '')) .filter(schema => schema !== upperTargetSchema && !['SYS', 'SYSTEM', 'DBA'].includes(schema)); if (otherSchemas.length > 0) { issues.push(`Script referencia outros esquemas: ${otherSchemas.join(', ')}`); } return issues; } checkComments(script) { const lines = script.split('\n'); const commentLines = lines.filter(line => line.trim().startsWith('--') || line.trim().startsWith('/*') || line.trim().startsWith('*') ); const commentRatio = commentLines.length / lines.length; return commentRatio > 0.1; // Pelo menos 10% do script deve ser comentários } checkValidations(script) { const upperScript = script.toUpperCase(); const validationKeywords = [ 'IF EXISTS', 'IF NOT EXISTS', 'SELECT COUNT', 'WHERE EXISTS', 'CASE WHEN', 'DECODE', 'NVL', 'COALESCE' ]; return validationKeywords.some(keyword => upperScript.includes(keyword)); } checkComplexity(script) { const lines = script.split('\n'); const reasons = []; if (lines.length > 100) { reasons.push('Muitas linhas'); } const upperScript = script.toUpperCase(); const complexKeywords = ['CURSOR', 'LOOP', 'WHILE', 'FOR', 'EXCEPTION', 'RAISE']; const complexCount = complexKeywords.filter(keyword => upperScript.includes(keyword)).length; if (complexCount > 3) { reasons.push('Muitas estruturas complexas'); } const semicolonCount = (script.match(/;/g) || []).length; if (semicolonCount > 20) { reasons.push('Muitas declarações'); } return { isComplex: reasons.length > 0, reasons }; } generateMigrationTemplate(targetSchema, operation) { const templates = { 'CREATE_TABLE': ` -- Migração: Criar tabela em ${targetSchema} -- Data: ${new Date().toISOString()} -- Autor: [SEU_NOME] -- Verificar se a tabela já existe SELECT COUNT(*) INTO v_count FROM user_tables WHERE table_name = UPPER('${targetSchema}_NEW_TABLE'); IF v_count = 0 THEN -- Criar tabela CREATE TABLE ${targetSchema}.new_table ( id NUMBER PRIMARY KEY, name VARCHAR2(100) NOT NULL, created_date DATE DEFAULT SYSDATE ); -- Criar índice CREATE INDEX idx_${targetSchema}_new_table_name ON ${targetSchema}.new_table(name); -- Comentários COMMENT ON TABLE ${targetSchema}.new_table IS 'Nova tabela criada via migração'; COMMIT; DBMS_OUTPUT.PUT_LINE('Tabela criada com sucesso'); ELSE DBMS_OUTPUT.PUT_LINE('Tabela já existe'); END IF; `, 'ALTER_TABLE': ` -- Migração: Alterar tabela em ${targetSchema} -- Data: ${new Date().toISOString()} -- Autor: [SEU_NOME] -- Backup da tabela original CREATE TABLE ${targetSchema}.table_backup AS SELECT * FROM ${targetSchema}.original_table; -- Verificar se a coluna já existe SELECT COUNT(*) INTO v_count FROM user_tab_columns WHERE table_name = UPPER('ORIGINAL_TABLE') AND column_name = UPPER('NEW_COLUMN'); IF v_count = 0 THEN -- Adicionar nova coluna ALTER TABLE ${targetSchema}.original_table ADD new_column VARCHAR2(50); -- Atualizar dados se necessário UPDATE ${targetSchema}.original_table SET new_column = 'DEFAULT_VALUE' WHERE new_column IS NULL; COMMIT; DBMS_OUTPUT.PUT_LINE('Coluna adicionada com sucesso'); ELSE DBMS_OUTPUT.PUT_LINE('Coluna já existe'); END IF; `, 'DATA_MIGRATION': ` -- Migração: Migração de dados em ${targetSchema} -- Data: ${new Date().toISOString()} -- Autor: [SEU_NOME] -- Verificar dados antes da migração SELECT COUNT(*) as total_records FROM ${targetSchema}.source_table; -- Backup dos dados CREATE TABLE ${targetSchema}.source_table_backup AS SELECT * FROM ${targetSchema}.source_table; -- Migrar dados INSERT INTO ${targetSchema}.target_table (col1, col2, col3) SELECT col1, col2, col3 FROM ${targetSchema}.source_table WHERE condition = 'value'; -- Verificar integridade SELECT COUNT(*) as migrated_records FROM ${targetSchema}.target_table; -- Commit se tudo estiver correto COMMIT; DBMS_OUTPUT.PUT_LINE('Migração de dados concluída'); ` }; return templates[operation] || 'Template não encontrado'; } }