/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see .
*/
import type {ConditionalQueryResult, PreparedQuery} from '@fluxer/cassandra/src/CassandraTypes';
import type {ICassandraClient} from '@fluxer/cassandra/src/Client';
import {getDefaultCassandraClient, getLogger} from '@fluxer/cassandra/src/Client';
import {type Logger, NoopLogger} from '@fluxer/cassandra/src/Logger';
export interface ICassandraQueryExecutor {
fetchMany(query: PreparedQuery): Promise>;
fetchOne(query: PreparedQuery): Promise;
execute(query: PreparedQuery): Promise;
executeBatch(queries: Array): Promise;
executeConditional(query: PreparedQuery): Promise;
}
export class CassandraQueryExecutor implements ICassandraQueryExecutor {
private readonly client: ICassandraClient;
private readonly logger: Logger;
public constructor(client: ICassandraClient, logger: Logger = NoopLogger) {
this.client = client;
this.logger = logger;
}
public async fetchMany(query: PreparedQuery): Promise> {
const result = await this.runQuery(
'query',
query,
async () => {
return this.client.execute(query);
},
{},
);
return CassandraQueryExecutor.validateRows(result.rows);
}
public async fetchOne(query: PreparedQuery): Promise {
const rows = await this.fetchMany(query);
return rows[0] ?? null;
}
public async execute(query: PreparedQuery): Promise {
await this.runQuery(
'execute',
query,
async () => {
await this.client.execute(query);
},
{},
);
}
public async executeBatch(queries: Array): Promise {
if (queries.length === 0) {
return;
}
const start = Date.now();
try {
await this.client.batch(queries);
const durationMs = Date.now() - start;
this.logger.debug(
{
query_count: queries.length,
duration_ms: durationMs,
},
'batch execute',
);
} catch (error) {
const durationMs = Date.now() - start;
this.logger.error(
{
query_count: queries.length,
duration_ms: durationMs,
error,
},
'batch execute error',
);
throw error;
}
}
public async executeConditional(query: PreparedQuery): Promise {
const result = await this.runQuery(
'conditional',
query,
async () => {
return this.client.execute(query);
},
{},
);
const applied = result.rows[0]?.['[applied]'] !== false;
return {applied};
}
private async runQuery(
action: string,
query: PreparedQuery,
runner: () => Promise,
context: Record,
): Promise {
const start = Date.now();
try {
const result = await runner();
const durationMs = Date.now() - start;
this.logger.debug(
{
action,
cql: query.cql,
params: query.params,
duration_ms: durationMs,
...context,
},
'action success',
);
return result;
} catch (error) {
const durationMs = Date.now() - start;
this.logger.error(
{
action,
cql: query.cql,
params: query.params,
duration_ms: durationMs,
error,
},
'action error',
);
throw error;
}
}
private static validateRows(rows: unknown): Array {
if (!Array.isArray(rows)) {
throw new Error('Expected Cassandra row array result');
}
for (const row of rows) {
if (row === null || typeof row !== 'object') {
throw new Error('Expected Cassandra rows to contain objects');
}
}
return rows as Array;
}
}
function createDefaultQueryExecutor(): CassandraQueryExecutor {
return new CassandraQueryExecutor(getDefaultCassandraClient(), getLogger());
}
export async function fetchMany(query: PreparedQuery): Promise> {
return createDefaultQueryExecutor().fetchMany(query);
}
export async function fetchOne(query: PreparedQuery): Promise {
return createDefaultQueryExecutor().fetchOne(query);
}
export async function execute(query: PreparedQuery): Promise {
await createDefaultQueryExecutor().execute(query);
}
export async function executeBatch(queries: Array): Promise {
await createDefaultQueryExecutor().executeBatch(queries);
}
export async function executeConditional(query: PreparedQuery): Promise {
return createDefaultQueryExecutor().executeConditional(query);
}