Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 34 additions & 27 deletions src/core/query-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,9 @@ export function buildSelectQuery(table: string, options: TableQueryOptions): { s
const whereClauses: string[] = [];
const params: any[] = [];

// Column filters
for (const filter of filters) {
if (filter.value) {
whereClauses.push(`${escapeIdentifier(filter.column)} LIKE ?`);
params.push(`%${filter.value}%`);
}
}

// Global filter
if (globalFilter) {
const globalConditions = columns.map(col => `${escapeIdentifier(col)} LIKE ?`).join(' OR ');
whereClauses.push(`(${globalConditions})`);
// Add param for each column in the OR clause
for (let i = 0; i < columns.length; i++) {
params.push(`%${globalFilter}%`);
}
}
const { conditions, params: filterParams } = buildFilterConditions(filters, globalFilter, columns);
whereClauses.push(...conditions);
params.push(...filterParams);

if (whereClauses.length > 0) {
sql += ` WHERE ${whereClauses.join(' AND ')}`;
Expand Down Expand Up @@ -79,26 +65,47 @@ export function buildCountQuery(table: string, options: TableCountOptions): { sq
const whereClauses: string[] = [];
const params: any[] = [];

const { conditions, params: filterParams } = buildFilterConditions(filters, globalFilter, columns);
whereClauses.push(...conditions);
params.push(...filterParams);

if (whereClauses.length > 0) {
sql += ` WHERE ${whereClauses.join(' AND ')}`;
}

return { sql, params };
}

/**
* Helper to build WHERE conditions for column filters and global search.
*/
function buildFilterConditions(
filters: { column: string; value: string }[] = [],
globalFilter: string | undefined,
searchColumns: string[]
): { conditions: string[]; params: any[] } {
const conditions: string[] = [];
const params: any[] = [];

// Column filters
for (const filter of filters) {
if (filter.value) {
whereClauses.push(`${escapeIdentifier(filter.column)} LIKE ?`);
conditions.push(`${escapeIdentifier(filter.column)} LIKE ?`);
params.push(`%${filter.value}%`);
}
}

// Global filter
if (globalFilter && columns.length > 0) {
const globalConditions = columns.map(col => `${escapeIdentifier(col)} LIKE ?`).join(' OR ');
whereClauses.push(`(${globalConditions})`);
for (let i = 0; i < columns.length; i++) {
if (globalFilter && searchColumns.length > 0) {
const globalConditions = searchColumns
.map(col => `${escapeIdentifier(col)} LIKE ?`)
.join(' OR ');

conditions.push(`(${globalConditions})`);
for (let i = 0; i < searchColumns.length; i++) {
params.push(`%${globalFilter}%`);
}
}

if (whereClauses.length > 0) {
sql += ` WHERE ${whereClauses.join(' AND ')}`;
}

return { sql, params };
return { conditions, params };
}
59 changes: 59 additions & 0 deletions tests/unit/query-builder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,64 @@ describe('Query Builder', () => {
const { sql } = buildSelectQuery('logs', options);
assert.strictEqual(sql, 'SELECT * FROM "logs" ORDER BY "created_at" DESC LIMIT 10 OFFSET 20');
});

it('should handle global filter with explicit columns', () => {
const options = {
columns: ['name', 'description'],
globalFilter: 'test'
};
const { sql, params } = buildSelectQuery('products', options);
assert.strictEqual(sql, 'SELECT "name", "description" FROM "products" WHERE ("name" LIKE ? OR "description" LIKE ?)');
assert.deepStrictEqual(params, ['%test%', '%test%']);
});

it('should handle global filter with default columns (edge case)', () => {
// This documents current behavior where default ['*'] results in " * " LIKE ?
const options = {
globalFilter: 'test'
};
const { sql, params } = buildSelectQuery('products', options);
assert.strictEqual(sql, 'SELECT * FROM "products" WHERE ("*" LIKE ?)');
assert.deepStrictEqual(params, ['%test%']);
});

it('should handle global filter with empty columns (safe behavior)', () => {
const options = {
columns: [],
globalFilter: 'test'
};
const { sql, params } = buildSelectQuery('products', options);
// Implementation should avoid generating WHERE ()
assert.strictEqual(sql, 'SELECT FROM "products"');
assert.deepStrictEqual(params, []);
});
});

describe('buildCountQuery', () => {
it('should build simple count', () => {
const { sql, params } = buildCountQuery('my_table', {});
assert.strictEqual(sql, 'SELECT COUNT(*) as count FROM "my_table"');
assert.deepStrictEqual(params, []);
});

it('should handle global filter with explicit columns', () => {
const options = {
columns: ['name', 'description'],
globalFilter: 'test'
};
const { sql, params } = buildCountQuery('products', options);
assert.strictEqual(sql, 'SELECT COUNT(*) as count FROM "products" WHERE ("name" LIKE ? OR "description" LIKE ?)');
assert.deepStrictEqual(params, ['%test%', '%test%']);
});

it('should handle global filter with empty columns (safe behavior)', () => {
const options = {
columns: [],
globalFilter: 'test'
};
const { sql, params } = buildCountQuery('products', options);
assert.strictEqual(sql, 'SELECT COUNT(*) as count FROM "products"');
assert.deepStrictEqual(params, []);
});
});
});