@gftdcojp/ksqldb-orm
Version:
ksqldb-orm - Server-Side TypeScript ORM for ksqlDB with enterprise security extensions
561 lines • 21.8 kB
JavaScript
;
/**
* サーバーサイド用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