UNPKG

@gftdcojp/ksqldb-orm

Version:

ksqldb-orm - Server-Side TypeScript ORM for ksqlDB with enterprise security extensions

561 lines 21.8 kB
"use strict"; /** * サーバーサイド用Database Module */ Object.defineProperty(exports, "__esModule", { value: true }); exports.DatabaseQueryBuilder = exports.DatabaseClient = void 0; exports.createDatabaseClient = createDatabaseClient; exports.createResilientDatabaseClient = createResilientDatabaseClient; const ksqldb_client_1 = require("./ksqldb-client"); const resilient_client_1 = require("./resilient-client"); const column_name_transform_1 = require("./utils/column-name-transform"); /** * サーバー環境用Database クラス */ class DatabaseClient { constructor(config) { this.initialized = false; ksqldb_client_1.KsqlDbClient.initialize(config.ksql); this.ksqlClient = ksqldb_client_1.KsqlDbClient; } /** * データベースを初期化 */ async initialize() { if (this.initialized) return; // サーバーサイドでの初期化処理 // コンストラクタで実行済みのため、ここでは不要 this.initialized = true; } /** * テーブルからデータを取得(Supabaseライク) */ from(table) { return new DatabaseQueryBuilder(table, this.ksqlClient); } /** * SQL文を直接実行 */ async sql(query, _params) { return this.ksqlClient.executeQuery(query); } /** * ヘルスチェック */ async health() { try { const connected = await this.ksqlClient.isConnected(); return { status: connected ? 'ok' : 'error', details: { connected } }; } catch (error) { return { status: 'error', details: error }; } } } exports.DatabaseClient = DatabaseClient; /** * サーバー環境用クエリビルダー */ class DatabaseQueryBuilder { constructor(tableName, ksqlClient) { this.tableName = tableName; this.ksqlClient = ksqlClient; this.selectFields = ['*']; this.whereConditions = {}; this.orderByConditions = {}; // クライアント設定から列名変換設定を取得 const clientConfig = this.ksqlClient.getConfig(); if (clientConfig) { this.columnTransformConfig = clientConfig.columnNameTransform; } } /** * 列名をクエリ用に変換するヘルパーメソッド */ transformColumnNameForQuery(columnName) { if (!this.columnTransformConfig || columnName === '*') { return columnName; // 設定がない場合や'*'の場合はそのまま } return (0, column_name_transform_1.transformColumnNameForKsqlDb)(columnName, this.columnTransformConfig); } /** * 選択するフィールドを指定 */ select(fields = '*') { if (fields === '*') { this.selectFields = ['*']; } else { // フィールド名を変換してから保存 const fieldArray = fields.split(',').map(f => f.trim()); this.selectFields = fieldArray.map(field => this.transformColumnNameForQuery(field)); } return this; } /** * WHERE条件を追加 */ eq(column, value) { // 列名をKsqlDB用に変換 const transformedColumn = this.transformColumnNameForQuery(column); this.whereConditions[transformedColumn] = value; return this; } /** * 不等価条件を追加(NOT EQUAL) */ neq(column, value) { this.whereConditions[column] = { neq: value }; return this; } /** * NULL判定条件を追加 */ isNull(column) { this.whereConditions[column] = { isNull: true }; return this; } /** * NOT NULL条件を追加 */ isNotNull(column) { this.whereConditions[column] = { isNotNull: true }; return this; } /** * LIKE条件を追加 */ like(column, pattern) { this.whereConditions[column] = { like: pattern }; return this; } /** * NOT LIKE条件を追加 */ notLike(column, pattern) { this.whereConditions[column] = { notLike: pattern }; return this; } /** * 範囲条件を追加 */ gte(column, value) { this.whereConditions[column] = { gte: value }; return this; } lte(column, value) { this.whereConditions[column] = { lte: value }; return this; } gt(column, value) { this.whereConditions[column] = { gt: value }; return this; } lt(column, value) { this.whereConditions[column] = { lt: value }; return this; } /** * BETWEEN条件を追加 */ between(column, min, max) { this.whereConditions[column] = { between: [min, max] }; return this; } /** * NOT BETWEEN条件を追加 */ notBetween(column, min, max) { this.whereConditions[column] = { notBetween: [min, max] }; return this; } /** * IN条件を追加 */ in(column, values) { this.whereConditions[column] = { in: values }; return this; } /** * NOT IN条件を追加 */ notIn(column, values) { this.whereConditions[column] = { notIn: values }; return this; } /** * ORDER BY を追加 */ order(column, ascending = true) { this.orderByConditions[column] = ascending ? 'asc' : 'desc'; return this; } /** * LIMIT を設定 */ limit(count) { this.limitValue = count; return this; } /** * OFFSET を設定 */ offset(count) { this.offsetValue = count; return this; } /** * クエリを実行してデータを取得 */ async execute() { try { // データ取得はテーブルに対してプルクエリを実行 // テーブル名に_streamが含まれている場合は_tableに変換 const tableNameForQuery = this.tableName.endsWith('_stream') ? this.tableName.replace('_stream', '_table') : this.tableName.endsWith('_table') ? this.tableName : `${this.tableName}_table`; const query = this.buildSelectQuery(tableNameForQuery); console.log(`[DEBUG] DatabaseQueryBuilder.execute - Table: ${tableNameForQuery}`); console.log(`[DEBUG] DatabaseQueryBuilder.execute - Query: ${query}`); const result = await this.ksqlClient.executePullQuery(query); // 結果をパース(実際の実装では適切にパース) const data = result?.data || result?.rows || []; console.log(`[DEBUG] DatabaseQueryBuilder.execute - Result:`, result); console.log(`[DEBUG] DatabaseQueryBuilder.execute - Data count: ${data.length}`); return { data }; } catch (error) { console.error(`[ERROR] DatabaseQueryBuilder.execute failed:`, error); console.error(`[ERROR] Table name: ${this.tableName}`); console.error(`[ERROR] Query details:`, { selectFields: this.selectFields, whereConditions: this.whereConditions, orderByConditions: this.orderByConditions, limitValue: this.limitValue, offsetValue: this.offsetValue }); return { data: [], error: { message: error.message || 'Unknown error', details: error, tableName: this.tableName, queryType: 'client_pull_query' } }; } } /** * 単一レコードを取得 */ async single() { const result = await this.limit(1).execute(); return { data: result.data.length > 0 ? result.data[0] : null, error: result.error }; } /** * 拡張オプション付きでクエリを実行(リジリエンス機能対応) * * 将来的にリジリエンス機能が統合される際に使用 * 現時点では通常のexecute()と同じ動作 */ async executeEnhanced(options = {}) { // 拡張オプションをログ出力(将来的に実装) if (options.retries || options.backoffStrategy || options.fallbackToHttp) { console.debug('[DatabaseQueryBuilder] Enhanced options detected but not yet implemented', options); } return this.execute(); } /** * データを挿入(Supabaseライク) */ async insert(values) { try { let query; if (Array.isArray(values)) { // バッチ挿入 query = this.buildBatchInsertQuery(values); await this.ksqlClient.executeQuery(query); return { data: values }; } else { // 単一挿入 query = this.buildInsertQuery(values); await this.ksqlClient.executeQuery(query); return { data: values }; } } catch (error) { return { data: null, error }; } } /** * データを更新(Supabaseライク) */ async update(values) { try { const query = this.buildUpdateQuery(values); await this.ksqlClient.executeQuery(query); return { data: [values] }; } catch (error) { return { data: [], error }; } } /** * データを削除(Supabaseライク) */ async delete() { try { const query = this.buildDeleteQuery(); await this.ksqlClient.executeQuery(query); return { data: { deleted: true } }; } catch (error) { return { data: null, error }; } } buildSelectQuery(tableName) { const targetTable = tableName || this.tableName; let query = `SELECT ${this.selectFields.join(', ')} FROM ${targetTable}`; // WHERE条件を追加 const whereConditions = []; for (const [key, value] of Object.entries(this.whereConditions)) { if (typeof value === 'object' && value !== null) { // 複雑な条件の場合 for (const [operator, operatorValue] of Object.entries(value)) { if (operator === 'gte') { whereConditions.push(`${key} >= ${this.formatValue(operatorValue)}`); } else if (operator === 'lte') { whereConditions.push(`${key} <= ${this.formatValue(operatorValue)}`); } else if (operator === 'gt') { whereConditions.push(`${key} > ${this.formatValue(operatorValue)}`); } else if (operator === 'lt') { whereConditions.push(`${key} < ${this.formatValue(operatorValue)}`); } else if (operator === 'neq') { whereConditions.push(`${key} <> ${this.formatValue(operatorValue)}`); } else if (operator === 'isNull') { whereConditions.push(`${key} IS NULL`); } else if (operator === 'isNotNull') { whereConditions.push(`${key} IS NOT NULL`); } else if (operator === 'like') { whereConditions.push(`${key} LIKE ${this.formatValue(operatorValue)}`); } else if (operator === 'notLike') { whereConditions.push(`${key} NOT LIKE ${this.formatValue(operatorValue)}`); } else if (operator === 'between') { const [min, max] = Array.isArray(operatorValue) ? operatorValue : [operatorValue, operatorValue]; whereConditions.push(`${key} BETWEEN ${this.formatValue(min)} AND ${this.formatValue(max)}`); } else if (operator === 'notBetween') { const [min, max] = Array.isArray(operatorValue) ? operatorValue : [operatorValue, operatorValue]; whereConditions.push(`${key} NOT BETWEEN ${this.formatValue(min)} AND ${this.formatValue(max)}`); } else if (operator === 'in') { const values = Array.isArray(operatorValue) ? operatorValue : [operatorValue]; const formattedValues = values.map(v => this.formatValue(v)).join(', '); whereConditions.push(`${key} IN (${formattedValues})`); } else if (operator === 'notIn') { const values = Array.isArray(operatorValue) ? operatorValue : [operatorValue]; const formattedValues = values.map(v => this.formatValue(v)).join(', '); whereConditions.push(`${key} NOT IN (${formattedValues})`); } } } else { whereConditions.push(`${key} = ${this.formatValue(value)}`); } } if (whereConditions.length > 0) { query += ` WHERE ${whereConditions.join(' AND ')}`; } // ORDER BY条件を追加 const orderByConditions = []; for (const [key, direction] of Object.entries(this.orderByConditions)) { const dir = typeof direction === 'string' ? direction.toUpperCase() : 'ASC'; orderByConditions.push(`${key} ${dir}`); } if (orderByConditions.length > 0) { query += ` ORDER BY ${orderByConditions.join(', ')}`; } // LIMIT条件を追加 if (this.limitValue !== undefined) { query += ` LIMIT ${this.limitValue}`; } // OFFSET条件を追加 if (this.offsetValue !== undefined) { query += ` OFFSET ${this.offsetValue}`; } return query + ';'; } formatValue(value) { if (typeof value === 'string') { return `'${value.replace(/'/g, "''")}'`; } else if (typeof value === 'number' || typeof value === 'boolean') { return value.toString(); } else if (value === null) { return 'NULL'; } else { return `'${String(value).replace(/'/g, "''")}'`; } } buildInsertQuery(values) { const columns = Object.keys(values).join(', '); const placeholders = Object.values(values).map(v => this.formatValue(v)).join(', '); return `INSERT INTO ${this.tableName} (${columns}) VALUES (${placeholders});`; } buildBatchInsertQuery(valuesList) { if (valuesList.length === 0) { throw new Error('Cannot insert empty array'); } // 全てのレコードで共通のカラムを取得 const allColumns = new Set(); valuesList.forEach(values => { Object.keys(values).forEach(key => allColumns.add(key)); }); const columns = Array.from(allColumns).sort(); const columnNames = columns.join(', '); const valueRows = valuesList.map(values => { const row = columns.map(col => this.formatValue(values[col] || null)); return `(${row.join(', ')})`; }); return `INSERT INTO ${this.tableName} (${columnNames}) VALUES ${valueRows.join(', ')};`; } buildUpdateQuery(values) { const setClause = Object.entries(values) .map(([key, value]) => `${key} = ${this.formatValue(value)}`) .join(', '); let query = `UPDATE ${this.tableName} SET ${setClause}`; if (Object.keys(this.whereConditions).length > 0) { const whereConditions = []; for (const [key, value] of Object.entries(this.whereConditions)) { if (typeof value === 'object' && value !== null) { // 複雑な条件の場合は同じロジックを使用 for (const [operator, operatorValue] of Object.entries(value)) { if (operator === 'eq' || !operator) { whereConditions.push(`${key} = ${this.formatValue(operatorValue)}`); } else { // 他の演算子も同様に処理 whereConditions.push(this.buildCondition(key, operator, operatorValue)); } } } else { whereConditions.push(`${key} = ${this.formatValue(value)}`); } } query += ` WHERE ${whereConditions.join(' AND ')}`; } return query + ';'; } buildDeleteQuery() { let query = `DELETE FROM ${this.tableName}`; if (Object.keys(this.whereConditions).length > 0) { const whereConditions = []; for (const [key, value] of Object.entries(this.whereConditions)) { if (typeof value === 'object' && value !== null) { // 複雑な条件の場合は同じロジックを使用 for (const [operator, operatorValue] of Object.entries(value)) { whereConditions.push(this.buildCondition(key, operator, operatorValue)); } } else { whereConditions.push(`${key} = ${this.formatValue(value)}`); } } query += ` WHERE ${whereConditions.join(' AND ')}`; } return query + ';'; } buildCondition(key, operator, operatorValue) { switch (operator) { case 'gte': return `${key} >= ${this.formatValue(operatorValue)}`; case 'lte': return `${key} <= ${this.formatValue(operatorValue)}`; case 'gt': return `${key} > ${this.formatValue(operatorValue)}`; case 'lt': return `${key} < ${this.formatValue(operatorValue)}`; case 'neq': return `${key} <> ${this.formatValue(operatorValue)}`; case 'isNull': return `${key} IS NULL`; case 'isNotNull': return `${key} IS NOT NULL`; case 'like': return `${key} LIKE ${this.formatValue(operatorValue)}`; case 'notLike': return `${key} NOT LIKE ${this.formatValue(operatorValue)}`; case 'between': const [min, max] = Array.isArray(operatorValue) ? operatorValue : [operatorValue, operatorValue]; return `${key} BETWEEN ${this.formatValue(min)} AND ${this.formatValue(max)}`; case 'notBetween': const [minNot, maxNot] = Array.isArray(operatorValue) ? operatorValue : [operatorValue, operatorValue]; return `${key} NOT BETWEEN ${this.formatValue(minNot)} AND ${this.formatValue(maxNot)}`; case 'in': const values = Array.isArray(operatorValue) ? operatorValue : [operatorValue]; const formattedValues = values.map(v => this.formatValue(v)).join(', '); return `${key} IN (${formattedValues})`; case 'notIn': const valuesNot = Array.isArray(operatorValue) ? operatorValue : [operatorValue]; const formattedValuesNot = valuesNot.map(v => this.formatValue(v)).join(', '); return `${key} NOT IN (${formattedValuesNot})`; default: return `${key} = ${this.formatValue(operatorValue)}`; } } } exports.DatabaseQueryBuilder = DatabaseQueryBuilder; /** * サーバー環境用データベースインスタンスを作成 */ function createDatabaseClient(config) { return new DatabaseClient(config); } /** * リジリエント機能付きデータベースクライアントを作成 * * パーティション再バランシング対応の高可用性Database Client * 既存のDatabase Clientの代替として使用可能 * * @param config リジリエント設定を含むデータベース設定 * @returns ResilientKsqlDbClientを内包するラッパー */ function createResilientDatabaseClient(config) { const resilientClient = new resilient_client_1.ResilientKsqlDbClient(config.ksql); return { from: (table) => { // 簡単な Query Builder ラッパー return { select: (fields = '*') => ({ fields }), eq: (column, value) => ({ column, value }), execute: async () => { // 基本的なクエリ実行(将来的により高度な統合を実装) const sql = `SELECT * FROM ${table}_table;`; const result = await resilientClient.executePullQueryCompat(sql); return { data: result?.data || [] }; }, executeEnhanced: async (options = {}) => { // 拡張オプション付きクエリ実行 const sql = `SELECT * FROM ${table}_table;`; const result = await resilientClient.executePullQuery(sql, options); return { data: result.data, error: result.error }; } }; }, health: () => resilientClient.healthCheck(), getMetrics: () => resilientClient.getMetrics(), dispose: () => resilientClient.dispose() }; } //# sourceMappingURL=database-client.js.map