Remove legacy query key fallbacks (filters/sort/skip/top) from driver-sql#961
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…river-sql Strictly enforce IDataDriver / QueryAST protocol in SqlDriver: - find(): remove fallback to (query as any).filters/.sort/.skip/.top - updateMany(): remove fallback to (query as any).filters - deleteMany(): remove fallback to (query as any).filters - count(): remove fallback to (query as any).filters - applyFilters(): clean up skip-list of legacy keys Update all tests to use standard QueryAST keys (where/orderBy/offset/limit). Add tests verifying legacy keys are ignored. Closes #960 Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> Agent-Logs-Url: https://github.com/objectstack-ai/spec/sessions/61332fa6-4cc7-491f-b72c-2c73c6357e02
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> Agent-Logs-Url: https://github.com/objectstack-ai/spec/sessions/61332fa6-4cc7-491f-b72c-2c73c6357e02
There was a problem hiding this comment.
Pull request overview
This PR removes driver-sql’s legacy query-key fallbacks that previously bypassed QueryAST typing via as any, aiming to enforce stricter adherence to the IDataDriver / QueryAST protocol.
Changes:
- Removed legacy fallbacks for
filters/sort/skip/topinfind(), plus legacy filter handling inupdateMany(),deleteMany(), andcount(). - Updated tests to use standard
QueryASTkeys (where/orderBy/offset/limit) and added tests asserting legacy keys are ignored. - Documented the removal in
CHANGELOG.md.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/plugins/driver-sql/src/sql-driver.ts | Removes legacy key fallbacks in query handling and adjusts applyFilters skip-list. |
| packages/plugins/driver-sql/src/sql-driver.test.ts | Updates count test input to use where. |
| packages/plugins/driver-sql/src/sql-driver-queryast.test.ts | Migrates tests to standard keys and adds “legacy keys ignored” assertions. |
| packages/plugins/driver-sql/src/sql-driver-advanced.test.ts | Migrates updateMany/deleteMany/count tests to standard where usage. |
| CHANGELOG.md | Adds an “Unreleased → Removed” entry describing legacy-key fallback removal. |
| it('should ignore legacy "top" key — only "limit" is recognized', async () => { | ||
| const results = await driver.find('products', { | ||
| top: 2, | ||
| orderBy: [{ field: 'name', order: 'asc' as const }], | ||
| } as any); | ||
|
|
||
| // "top" is not recognized — no limit applied, returns all 5 rows | ||
| expect(results.length).toBe(5); |
There was a problem hiding this comment.
This test asserts legacy top is ignored, but QueryAST in @objectstack/spec/data currently defines top as an alias for limit (OData compatibility). If the driver is meant to follow the spec, top should behave like limit and this test should be updated/removed. If top is truly being removed from the protocol, the spec/schema should be updated in the same change so the contract is consistent.
| it('should ignore legacy "top" key — only "limit" is recognized', async () => { | |
| const results = await driver.find('products', { | |
| top: 2, | |
| orderBy: [{ field: 'name', order: 'asc' as const }], | |
| } as any); | |
| // "top" is not recognized — no limit applied, returns all 5 rows | |
| expect(results.length).toBe(5); | |
| it('should treat legacy "top" key as alias for "limit"', async () => { | |
| const results = await driver.find('products', { | |
| top: 2, | |
| orderBy: [{ field: 'name', order: 'asc' as const }], | |
| } as any); | |
| // "top" is defined in QueryAST as an alias for "limit" (OData compatibility) — applies a limit of 2 | |
| expect(results.length).toBe(2); |
| query keys `filters` (use `where`), `sort` (use `orderBy`), `skip` (use `offset`), and `top` | ||
| (use `limit`) from `find`, `updateMany`, `deleteMany`, and `count` methods. The SQL driver now | ||
| strictly follows the `IDataDriver` / `QueryAST` protocol. All `as any` casts for legacy key | ||
| access have been eliminated. Tests updated to use only standard `QueryAST` keys. |
There was a problem hiding this comment.
The changelog entry claims top support was removed as a deprecated legacy key, but QueryAST in @objectstack/spec/data still defines top as an alias for limit. Either keep top supported in the driver (and adjust this entry), or update the spec/schema in the same release so the documented protocol and driver behavior don’t diverge.
| query keys `filters` (use `where`), `sort` (use `orderBy`), `skip` (use `offset`), and `top` | |
| (use `limit`) from `find`, `updateMany`, `deleteMany`, and `count` methods. The SQL driver now | |
| strictly follows the `IDataDriver` / `QueryAST` protocol. All `as any` casts for legacy key | |
| access have been eliminated. Tests updated to use only standard `QueryAST` keys. | |
| query keys `filters` (use `where`), `sort` (use `orderBy`), and `skip` (use `offset`) from | |
| `find`, `updateMany`, `deleteMany`, and `count` methods. The `top` key remains supported as an | |
| alias for `limit` via the standard `QueryAST` contract in `@objectstack/spec/data`. The SQL | |
| driver now strictly follows the `IDataDriver` / `QueryAST` protocol. All `as any` casts for | |
| legacy key access have been eliminated. Tests updated to use only standard `QueryAST` keys and | |
| no driver-specific legacy fallbacks. |
| if (limitValue !== undefined) builder.limit(limitValue); | ||
| // PAGINATION | ||
| if (query.offset !== undefined) builder.offset(query.offset); | ||
| if (query.limit !== undefined) builder.limit(query.limit); |
There was a problem hiding this comment.
find() no longer recognizes the top alias for pagination. However, QueryAST in @objectstack/spec/data still includes top (described as an alias for limit for OData compatibility), so ignoring it breaks the QueryAST contract for SqlDriver consumers. Either map query.top → limit here, or remove top from the QueryAST schema/protocol first so the driver and spec stay consistent.
| if (query.limit !== undefined) builder.limit(query.limit); | |
| const limit = query.limit ?? query.top; | |
| if (limit !== undefined) builder.limit(limit); |
| async updateMany(object: string, query: QueryAST, data: any, options?: DriverOptions): Promise<number> { | ||
| const builder = this.getBuilder(object, options); | ||
| const filters = query.where || (query as any).filters || query; | ||
| if (filters) this.applyFilters(builder, filters); | ||
| if (query.where) this.applyFilters(builder, query.where); | ||
| const count = await builder.update(data); | ||
| return count || 0; | ||
| } | ||
|
|
||
| async deleteMany(object: string, query: QueryAST, options?: DriverOptions): Promise<number> { | ||
| const builder = this.getBuilder(object, options); | ||
| const filters = query.where || (query as any).filters || query; | ||
| if (filters) this.applyFilters(builder, filters); | ||
| if (query.where) this.applyFilters(builder, query.where); | ||
| const count = await builder.delete(); |
There was a problem hiding this comment.
updateMany() / deleteMany() now apply filters only when query.where is present. If a caller still passes a legacy shape (e.g. { filters: ... } or the older { status: 'pending' } shorthand), the operation will run with no WHERE clause and can update/delete the entire table. To avoid destructive surprises, consider rejecting queries that lack where (or at least throw when legacy keys like filters/sort/skip/top are present without where), and require an explicit empty where to intentionally target all rows.
| for (const item of query.orderBy) { | ||
| const field = item.field || item[0]; | ||
| const dir = item.order || item[1] || 'asc'; | ||
| if (field) { |
There was a problem hiding this comment.
The ORDER BY loop still supports tuple/array forms (item[0] / item[1]) even though the QueryAST spec’s orderBy is defined as an array of { field, order } objects. If the goal is strict QueryAST enforcement, this should only accept the spec shape and drop the tuple fallback (and update tests accordingly), otherwise the driver continues to accept non-spec input and type safety remains weakened.
| for (const item of query.orderBy) { | |
| const field = item.field || item[0]; | |
| const dir = item.order || item[1] || 'asc'; | |
| if (field) { | |
| for (const { field, order } of query.orderBy as Array<{ field?: string; order?: 'asc' | 'desc' }>) { | |
| if (field) { | |
| const dir = order ?? 'asc'; |
SqlDriverbypassedQueryASTtype safety viaas anycasts to support deprecated keys (filters,sort,skip,top). This blocks downstream protocol enforcement and weakens theIDataDrivercontract.Driver changes (
sql-driver.ts)find()— Remove fallbacks to(query as any).filters,.sort,.skip,.top; use onlyquery.where,query.orderBy,query.offset,query.limitupdateMany()/deleteMany()— Remove(query as any).filters || querypattern; use onlyquery.wherecount()— Remove(query as any).filtersfallback; use onlyquery?.whereapplyFilters()— Removefilters,sort,skipfrom the object-key skip-listBefore:
After:
Test updates
QueryASTformat (where/orderBy/offset/limit)Original prompt
🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.