@msugiura/rawsql-prisma
Version:
Prisma integration for rawsql-ts - Dynamic SQL generation with type safety and hierarchical JSON serialization
306 lines • 12 kB
JavaScript
;
/**
* SQL Static Analysis Library
*
* Provides unified API for static analysis of SQL files, including:
* - SQL file discovery and loading
* - Schema validation using SqlSchemaValidator
* - JSON mapping compatibility validation
* - Comprehensive validation reports
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.SqlStaticAnalyzer = void 0;
exports.analyzeSqlFiles = analyzeSqlFiles;
exports.validateSqlFile = validateSqlFile;
const rawsql_ts_1 = require("rawsql-ts");
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
class SqlStaticAnalyzer {
constructor(options) {
this.options = options;
this.tableColumnResolver = options.schemaResolver.createTableColumnResolver();
}
/**
* Discover all SQL files in the configured directory
*/
discoverSqlFiles() {
const { sqlDirectory, debug } = this.options;
if (!fs.existsSync(sqlDirectory)) {
throw new Error(`SQL directory does not exist: ${sqlDirectory}`);
}
// Recursively find all SQL files
const sqlFiles = [];
const searchDirectory = (dir) => {
const entries = fs.readdirSync(dir);
for (const entry of entries) {
const fullPath = path.join(dir, entry);
const stat = fs.statSync(fullPath);
if (stat.isDirectory()) {
searchDirectory(fullPath);
}
else if (stat.isFile() && entry.endsWith('.sql')) {
const content = fs.readFileSync(fullPath, 'utf-8');
const baseName = path.basename(entry, '.sql');
const relativePath = path.relative(sqlDirectory, fullPath);
sqlFiles.push({
filename: entry,
fullPath,
baseName,
content,
relativePath
});
}
}
};
searchDirectory(sqlDirectory);
if (debug) {
console.log(`📄 Found ${sqlFiles.length} SQL files in ${sqlDirectory}:`, sqlFiles.map(f => f.relativePath || f.filename));
}
return sqlFiles;
}
/**
* Validate a single SQL file
*/
async validateSqlFile(sqlFile) {
const { debug } = this.options;
const result = {
filename: sqlFile.filename,
isValid: false,
errors: [],
hasJsonMapping: false
};
try {
if (debug) {
console.log(`🔍 Validating ${sqlFile.filename}...`);
}
// Parse SQL content
const parseResult = rawsql_ts_1.SelectQueryParser.parse(sqlFile.content);
if (!parseResult) {
result.errors.push('Failed to parse SQL content');
return result;
}
result.parseResult = parseResult;
// Validate schema
try {
rawsql_ts_1.SqlSchemaValidator.validate(parseResult, this.tableColumnResolver);
}
catch (schemaError) {
result.errors.push(`Schema validation failed: ${schemaError.message}`);
return result;
}
// Check for JSON mapping
const jsonPath = path.join(path.dirname(sqlFile.fullPath), `${sqlFile.baseName}.json`);
result.hasJsonMapping = fs.existsSync(jsonPath);
if (result.hasJsonMapping) {
const jsonValidationResult = await this.validateJsonMapping(sqlFile, jsonPath, parseResult);
result.jsonMappingValid = jsonValidationResult.isValid;
result.jsonMappingErrors = jsonValidationResult.errors;
}
// Mark as valid if SQL parsing and schema validation passed
result.isValid = result.errors.length === 0;
if (debug && result.isValid) {
console.log(`✅ ${sqlFile.filename}: Validation passed`);
}
}
catch (error) {
result.errors.push(`Unexpected error: ${error.message}`);
}
return result;
}
/**
* Validate JSON mapping for a SQL file
*/
async validateJsonMapping(sqlFile, jsonPath, parseResult) {
const { debug } = this.options;
const result = { isValid: false, errors: [] };
try {
if (debug) {
console.log(`✅ Found JSON mapping: ${sqlFile.baseName}.json`);
}
// Read and parse JSON mapping using unified processor
const jsonContent = fs.readFileSync(jsonPath, 'utf-8');
let jsonMapping;
try {
const rawMapping = JSON.parse(jsonContent);
// Check if this is a Model-driven mapping (has typeInfo and structure)
if (rawMapping.typeInfo && rawMapping.structure) {
// Use the convertModelDrivenMapping function
const conversionResult = (0, rawsql_ts_1.convertModelDrivenMapping)(rawMapping);
jsonMapping = conversionResult.jsonMapping;
}
else if (rawMapping.rootName && rawMapping.rootEntity) {
// Already in legacy format
jsonMapping = rawMapping;
}
else {
// Invalid format
result.errors.push('JSON mapping must be either Model-driven format (with typeInfo and structure) or Legacy format (with rootName and rootEntity)');
return result;
}
}
catch (parseError) {
result.errors.push(`Failed to process JSON mapping: ${parseError.message}`);
return result;
}
// Basic validation - the unified processor ensures correct structure
if (!jsonMapping || !jsonMapping.rootName || !jsonMapping.rootEntity) {
result.errors.push('Invalid JSON mapping structure after processing');
return result;
}
// Test JSON query building
try {
const jsonQueryBuilder = new rawsql_ts_1.PostgresJsonQueryBuilder();
const jsonQuery = jsonQueryBuilder.buildJsonQuery(parseResult, jsonMapping);
if (!jsonQuery) {
result.errors.push('Failed to build JSON query');
return result;
}
}
catch (buildError) {
result.errors.push(`JSON query building failed: ${buildError.message}`);
return result;
}
result.isValid = true;
if (debug) {
console.log(`🎯 ${sqlFile.filename}: SQL + JSON mapping validation passed`);
}
}
catch (error) {
result.errors.push(`JSON mapping validation error: ${error.message}`);
}
return result;
}
/**
* Validate all SQL files in the directory
*/
async validateAllSqlFiles() {
const sqlFiles = this.discoverSqlFiles();
const results = [];
for (const sqlFile of sqlFiles) {
const result = await this.validateSqlFile(sqlFile);
results.push(result);
}
return results;
}
/**
* Generate comprehensive analysis report
*/
async generateAnalysisReport() {
const results = await this.validateAllSqlFiles();
const totalFiles = results.length;
const validFiles = results.filter(r => r.isValid).length;
const invalidFiles = totalFiles - validFiles;
const filesWithMapping = results.filter(r => r.hasJsonMapping).length;
const validMappings = results.filter(r => r.hasJsonMapping && r.jsonMappingValid).length;
const invalidMappings = filesWithMapping - validMappings;
const summary = [
`📊 SQL Static Analysis Report`,
`Total SQL files: ${totalFiles}`,
`Valid SQL files: ${validFiles}`,
`Invalid SQL files: ${invalidFiles}`,
`Files with JSON mapping: ${filesWithMapping}`,
`Valid JSON mappings: ${validMappings}`,
`Invalid JSON mappings: ${invalidMappings}`,
validFiles === totalFiles && validMappings === filesWithMapping ?
'🎉 All validations passed!' :
'⚠️ Some validations failed - check details below'
].join('\n');
return {
totalFiles,
validFiles,
invalidFiles,
filesWithMapping,
validMappings,
invalidMappings,
results,
summary
};
}
/**
* Validate schema availability and table information
*/
validateSchemaInfo() {
const result = { isValid: false, tableNames: [], errors: [] };
try {
const tableNames = this.options.schemaResolver.getTableNames();
if (!tableNames || tableNames.length === 0) {
result.errors.push('No tables found in schema');
return result;
}
result.tableNames = tableNames;
result.isValid = true;
if (this.options.debug) {
console.log(`🏗️ Available tables: ${tableNames.join(', ')}`);
}
}
catch (error) {
result.errors.push(`Schema validation error: ${error.message}`);
}
return result;
}
}
exports.SqlStaticAnalyzer = SqlStaticAnalyzer;
/**
* Convenience function for quick SQL static analysis
*/
async function analyzeSqlFiles(options) {
const analyzer = new SqlStaticAnalyzer(options);
return await analyzer.generateAnalysisReport();
}
/**
* Convenience function for validating a single SQL file
*/
async function validateSqlFile(sqlFilePath, schemaResolver) {
const sqlDirectory = path.dirname(sqlFilePath);
const filename = path.basename(sqlFilePath);
const analyzer = new SqlStaticAnalyzer({
sqlDirectory,
schemaResolver,
debug: false
});
const sqlFiles = analyzer.discoverSqlFiles();
const targetFile = sqlFiles.find(f => f.filename === filename);
if (!targetFile) {
return {
filename,
isValid: false,
errors: [`File not found: ${sqlFilePath}`],
hasJsonMapping: false
};
}
return await analyzer.validateSqlFile(targetFile);
}
//# sourceMappingURL=SqlStaticAnalyzer.js.map