This commit is contained in:
@@ -31,11 +31,20 @@ export const Filter = z.object({
|
||||
value: FilterValue.optional().array(),
|
||||
operator: z.enum(['and', 'or']),
|
||||
});
|
||||
export const aggregateTypes = ['avg', 'sum', 'min', 'max'] as const;
|
||||
export const AggregateType = z.enum(aggregateTypes);
|
||||
|
||||
export const Aggregate = z.object({ column: z.string(), type: AggregateType });
|
||||
|
||||
export const AggregateValue = z.object({
|
||||
column: z.string(),
|
||||
value: z.number(),
|
||||
});
|
||||
|
||||
export const RowsResponse = z.object({
|
||||
// TODO: Can't this just be a string[]?
|
||||
rows: z.any(),
|
||||
totalRows: z.bigint().nonnegative(),
|
||||
aggregateValues: AggregateValue.array(),
|
||||
});
|
||||
|
||||
export type Column = z.infer<typeof Column>;
|
||||
@@ -43,6 +52,9 @@ export type SortColumn = z.infer<typeof SortColumn>;
|
||||
export type FilterValue = z.infer<typeof FilterValue>;
|
||||
export type FilterOperator = z.infer<typeof FilterOperator>;
|
||||
export type Filter = z.infer<typeof Filter>;
|
||||
export type AggregateType = z.infer<typeof AggregateType>;
|
||||
export type Aggregate = z.infer<typeof Aggregate>;
|
||||
export type AggregateValue = z.infer<typeof AggregateValue>;
|
||||
export type RowsResponse = z.infer<typeof RowsResponse>;
|
||||
|
||||
const sanitisedFileName = (file: File) =>
|
||||
@@ -168,7 +180,7 @@ export class DuckdbService {
|
||||
columns: Column[],
|
||||
sorts: SortColumn[],
|
||||
filters: Filter[],
|
||||
aggregations: unknown[],
|
||||
aggregates: Aggregate[],
|
||||
): Promise<RowsResponse> {
|
||||
const conn = await this.db.connect();
|
||||
try {
|
||||
@@ -181,10 +193,20 @@ export class DuckdbService {
|
||||
`${prefix(value?.matchType!)}${value?.value}${suffix(value?.matchType!)}`,
|
||||
),
|
||||
);
|
||||
const rowCountQuery = `SELECT COUNT(1) totalRows FROM ${sanitisedFileName(file)} ${whereClause}`;
|
||||
const totalRowStmt = await conn.prepare(rowCountQuery);
|
||||
let aggregatesQuery = 'SELECT COUNT(1) totalRows';
|
||||
if (aggregates.length > 0) {
|
||||
for (const aggregate of aggregates) {
|
||||
aggregatesQuery += `, ${aggregate.type}("${aggregate.column}") "${aggregate.column}"`;
|
||||
}
|
||||
}
|
||||
aggregatesQuery += ` FROM ${sanitisedFileName(file)} ${whereClause}`;
|
||||
const totalRowStmt = await conn.prepare(aggregatesQuery);
|
||||
const totalRowResponse = await totalRowStmt.query(...mappedFilterValues);
|
||||
const { totalRows } = totalRowResponse.get(0)?.toJSON()!;
|
||||
const aggregatesJson = totalRowResponse.get(0)?.toJSON()!;
|
||||
const totalRows = aggregatesJson['totalRows'];
|
||||
const aggregateValues: AggregateValue[] = Object.entries(aggregatesJson)
|
||||
.filter(([key]) => key !== 'totalRows')
|
||||
.map(([key, value]) => AggregateValue.parse({ column: key, value }));
|
||||
let query = `SELECT ${columns.map((column) => `"${column.name}"`).join(', ')} FROM ${sanitisedFileName(file)} ${whereClause}`;
|
||||
if (sorts.length > 0) {
|
||||
query += ` ORDER BY ${sorts.map((sort) => `"${sort.name}" ${sort.sortType}`).join(', ')}`;
|
||||
@@ -197,10 +219,10 @@ export class DuckdbService {
|
||||
rows.push(row.toJSON()!);
|
||||
}
|
||||
}
|
||||
return { rows, totalRows };
|
||||
return { rows, totalRows, aggregateValues };
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return { rows: [], totalRows: 0n };
|
||||
return { rows: [], totalRows: 0n, aggregateValues: [] };
|
||||
} finally {
|
||||
conn.close();
|
||||
}
|
||||
@@ -217,7 +239,7 @@ export class DuckdbService {
|
||||
let or = '';
|
||||
for (const value of filter.value) {
|
||||
if (value?.value) {
|
||||
query += ` ${or} ${filter.column} ${sqlOperator(value?.matchType!)} ? `;
|
||||
query += ` ${or} "${filter.column}" ${sqlOperator(value?.matchType!)} ? `;
|
||||
or = filter.operator;
|
||||
}
|
||||
}
|
||||
@@ -228,4 +250,29 @@ export class DuckdbService {
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
async getAggregateValue(file: File, filters: Filter[], aggregate: Aggregate) {
|
||||
const conn = await this.db.connect();
|
||||
try {
|
||||
const whereClause = this.getWhereClause(filters);
|
||||
const mappedFilterValues = filters.flatMap((filter) =>
|
||||
filter.value
|
||||
.filter((value) => value?.value)
|
||||
.map(
|
||||
(value) =>
|
||||
`${prefix(value?.matchType!)}${value?.value}${suffix(value?.matchType!)}`,
|
||||
),
|
||||
);
|
||||
const aggregatesQuery = `SELECT ${aggregate.type}("${aggregate.column}") "${aggregate.column}" FROM ${sanitisedFileName(file)} ${whereClause}`;
|
||||
const totalRowStmt = await conn.prepare(aggregatesQuery);
|
||||
const totalRowResponse = await totalRowStmt.query(...mappedFilterValues);
|
||||
const aggregatesJson = totalRowResponse.get(0)?.toJSON()!;
|
||||
return aggregatesJson[aggregate.column];
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return { rows: [], totalRows: 0n, aggregateValues: [] };
|
||||
} finally {
|
||||
conn.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user