Skip to content
Merged
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
21 changes: 21 additions & 0 deletions packages/plugins/driver-sql/src/sql-driver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,27 @@ describe('SqlDriver (SQLite Integration)', () => {
expect(results.map((r: any) => r.name)).toEqual(['Alice', 'Charlie']);
});

it('returns rows when $select names an unknown column (retries with SELECT *)', async () => {
// A generic list view auto-requests view-binding fields (status, due_date,
// image, ...) the object may not have. The unknown column must NOT zero
// the whole result — the real rows still come back, minus the phantom field.
const results = await driver.find('users', {
fields: ['id', 'name', 'status', 'due_date', 'image'],
orderBy: [{ field: 'name', order: 'asc' }],
});

expect(results.length).toBe(4);
expect(results.map((r: any) => r.name)).toEqual(['Alice', 'Bob', 'Charlie', 'Dave']);
// The phantom columns are simply absent (the table never had them).
expect((results[0] as any).status).toBeUndefined();
});

it('still surfaces non-column errors (e.g. unknown table) instead of empty', async () => {
await expect(
driver.find('no_such_table', { fields: ['id'] }),
).rejects.toThrow();
});

it('should apply simple AND/OR logic', async () => {
const results = await driver.find('users', {
where: {
Expand Down
79 changes: 54 additions & 25 deletions packages/plugins/driver-sql/src/sql-driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -362,8 +362,34 @@ export class SqlDriver implements IDataDriver {
// ===================================

async find(object: string, query: QueryAST, options?: DriverOptions): Promise<any[]> {
const builder = this.getBuilder(object, options);
this.applyTenantScope(builder, object, options);
// Build everything EXCEPT the SELECT list, so the unknown-column retry
// below can rebuild without re-deriving where/order/pagination.
const buildBase = () => {
const b = this.getBuilder(object, options);
this.applyTenantScope(b, object, options);

// WHERE
if (query.where) {
this.applyFilters(b, query.where);
}

// ORDER BY
if (query.orderBy && Array.isArray(query.orderBy)) {
for (const item of query.orderBy) {
if (item.field) {
b.orderBy(this.mapSortField(item.field), item.order || 'asc');
}
}
}

// PAGINATION
if (query.offset !== undefined) b.offset(query.offset);
if (query.limit !== undefined) b.limit(query.limit);

return b;
};

const builder = buildBase();

// SELECT
if (query.fields) {
Expand All @@ -372,36 +398,39 @@ export class SqlDriver implements IDataDriver {
builder.select('*');
}

// WHERE
if (query.where) {
this.applyFilters(builder, query.where);
}

// ORDER BY
if (query.orderBy && Array.isArray(query.orderBy)) {
for (const item of query.orderBy) {
if (item.field) {
builder.orderBy(this.mapSortField(item.field), item.order || 'asc');
}
}
}

// PAGINATION
if (query.offset !== undefined) builder.offset(query.offset);
if (query.limit !== undefined) builder.limit(query.limit);

let results: any[];
try {
results = await builder;
} catch (error: any) {
if (
const isUnknownColumn =
error.message &&
(error.message.includes('no such column') ||
(error.message.includes('column') && error.message.includes('does not exist')))
) {
return [];
(error.message.includes('column') && error.message.includes('does not exist')));
if (isUnknownColumn) {
// A `$select` projection naming a column the table lacks (e.g. a
// generic list view auto-requesting `status`/`due_date`/`image` on an
// object without them) makes the WHOLE query fail. Swallowing that
// into an empty result — the old behavior — reads to the UI as "no
// records exist" even though rows are there: a silent data-loss
// footgun. When the failure came from the projection, retry once
// selecting all columns so the real rows still come back; the unknown
// field is simply absent from each row (it never existed). The
// engine's unknown-field filter is the first line of defense, but it
// only fires when the object's schema is populated in the registry —
// this driver backstop holds even when it isn't (notably the cloud
// multi-tenant runtime, where the projection otherwise zeroes the list).
if (query.fields) {
try {
results = await buildBase().select('*');
} catch {
return [];
}
} else {
return [];
}
} else {
throw error;
}
throw error;
}

if (!Array.isArray(results)) {
Expand Down