You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A non-dotted orderBy naming a formula field answers 200 in arbitrary order — the sort is silently dropped (measured on driver-sql + driver-memory) #6994
Found while measuring #6924's premise (the SORT-axis hint text). Recorded, not claimed. Routing suggestion: domain:engine-core — this is an engine/driver refusal question, not hint text, and #6924's dispatch explicitly scoped it out rather than absorbing it.
The defect
assertSortFieldsExist (packages/metadata-protocol/src/protocol.ts) refuses a dottedorderBy (?sort=account.company_name, #4256) and an unknown field (#4226). It does not refuse a known, non-dotted field that happens to be formula-typed — a formula field is in schema.fields, so it is in gate.known and passes.
It then reaches a driver that has no column for it, and the sort is silently dropped. Query succeeds, all rows present, order arbitrary — the exact failure mode #4226/#4256 exist to stop, on a shape no gate covers.
Measurement
Real SqlDriver (better-sqlite3, on-disk) and real InMemoryDriver, both wired to a real ObjectQL engine, with ObjectStackProtocolImplementation on top for the ingress half. Object carries a formula field sort_key (expression record.title). Five rows inserted C A E B D so "sorted" and "insertion order" are distinguishable.
driver-sql (better-sqlite3):
repro_contact physical columns: ["id","created_at","updated_at","title","seq","account_id","child_total"]
formula col `sort_key` present: false
CONTROL orderBy title asc -> ["A","B","C","D","E"] a real column really sorts
CONTROL orderBy title desc -> ["E","D","C","B","A"]
BASELINE no sort -> ["C","A","E","B","D"] insertion order
FORMULA orderBy sort_key asc -> ["C","A","E","B","D"] 5 rows, 200
sort_key values -> ["C","A","E","B","D"]
FORMULA orderBy sort_key desc -> ["C","A","E","B","D"] direction-blind
RAW SQL order by sort_key -> sqlite: "no such column: sort_key"
PROTOCOL ?sort=sort_key -> 200, 5 records, ["C","A","E","B","D"]
PROTOCOL ?orderBy=["sort_key"] -> 200, 5 records, ["C","A","E","B","D"]
driver-memory:
CONTROL orderBy title asc -> ["A","B","C","D","E"]
BASELINE no sort -> ["C","A","E","B","D"]
FORMULA orderBy sort_key asc -> ["C","A","E","B","D"]
FORMULA orderBy sort_key desc -> ["C","A","E","B","D"]
asc and desc returning the same order is what makes this a dropped sort rather than a coincidence.
Mechanism
SqlDriver.createColumn — case 'formula': return; // Virtual — no column. schema-drift.ts's fieldHasColumn() agrees: formula is the only type with no physical column.
engine.find rewrites ast.fields to drop virtual formula names and inject their dependencies (planFormulaProjection), but leaves ast.sort / ast.orderByuntouched; formulas are evaluated post-query by applyFormulaPlan, after driver.find has already returned.
The response even carries the sort_key values in plain view — C, A, E, B, D — in a column the caller asked to be sorted ascending, so the answer visibly contradicts the request and still reports success.
Worth noting for scoping: summary/rollup is not affected. It gets a real, maintained column (case 'summary': col = table.float(name)) and sorts correctly — measured orderBy child_total desc -> E D C B A over values 5 4 3 2 1. Only formula is virtual.
#6924's scope is the refusal hint text (domain:metadata) — it stopped the hint prescribing a formula field, since following that advice landed the author in this defect one step after being refused for the dotted form. The remaining question is a different decision on a different seat:
Refuse at ingress — assertSortFieldsExist rejects an orderBy naming a field whose type materializes no column (400 INVALID_SORT), consistent with how it already treats dotted paths. Cheap, and it keeps the whole REST 列表:sort / select / expand 指向不存在的字段时被静默丢弃(filter 轴已收口,这三条轴还没有) #4226 family on one door.
Refuse at the driver — the driver reports that it cannot order by the column instead of letting the fix(sharing): 共享规则新建页 — 自定义 widget 未国际化,且「接收方」永远无可选项 #3821 backstop swallow it. Catches internal callers reaching engine.find() directly, which the ingress gate deliberately does not cover.
Materialize — actually give formula fields a stored column. Much larger, and probably contradicts the "formula is virtual" contract the codebase states in several places.
Option 1 has a wrinkle worth flagging before anyone implements it: the gate reads gate.fields[...], so it can see the type — but the refusal would need to name the same remedy the corrected #6924 hint gives ("a stored field"), or the two doors will disagree again.
Refs: #6924 (the hint text, in flight), #6673 (same correction, search axis), #4226, #4256, #3821 (the backstop that swallows it).
Found while measuring #6924's premise (the SORT-axis hint text). Recorded, not claimed. Routing suggestion:
domain:engine-core— this is an engine/driver refusal question, not hint text, and #6924's dispatch explicitly scoped it out rather than absorbing it.The defect
assertSortFieldsExist(packages/metadata-protocol/src/protocol.ts) refuses a dottedorderBy(?sort=account.company_name, #4256) and an unknown field (#4226). It does not refuse a known, non-dotted field that happens to beformula-typed — aformulafield is inschema.fields, so it is ingate.knownand passes.It then reaches a driver that has no column for it, and the sort is silently dropped. Query succeeds, all rows present, order arbitrary — the exact failure mode #4226/#4256 exist to stop, on a shape no gate covers.
Measurement
Real
SqlDriver(better-sqlite3, on-disk) and realInMemoryDriver, both wired to a realObjectQLengine, withObjectStackProtocolImplementationon top for the ingress half. Object carries aformulafieldsort_key(expressionrecord.title). Five rows insertedC A E B Dso "sorted" and "insertion order" are distinguishable.driver-sql (better-sqlite3):
driver-memory:
ascanddescreturning the same order is what makes this a dropped sort rather than a coincidence.Mechanism
SqlDriver.createColumn—case 'formula': return; // Virtual — no column.schema-drift.ts'sfieldHasColumn()agrees:formulais the only type with no physical column.engine.findrewritesast.fieldsto drop virtual formula names and inject their dependencies (planFormulaProjection), but leavesast.sort/ast.orderByuntouched; formulas are evaluated post-query byapplyFormulaPlan, afterdriver.findhas already returned.ORDER BY; the fix(sharing): 共享规则新建页 — 自定义 widget 未国际化,且「接收方」永远无可选项 #3821 unknown-column backstop retries without the sort.The response even carries the
sort_keyvalues in plain view —C, A, E, B, D— in a column the caller asked to be sorted ascending, so the answer visibly contradicts the request and still reports success.Worth noting for scoping:
summary/rollupis not affected. It gets a real, maintained column (case 'summary': col = table.float(name)) and sorts correctly — measuredorderBy child_total desc -> E D C B Aover values5 4 3 2 1. Onlyformulais virtual.Why it is not fixed in #6924
#6924's scope is the refusal hint text (
domain:metadata) — it stopped the hint prescribing aformulafield, since following that advice landed the author in this defect one step after being refused for the dotted form. The remaining question is a different decision on a different seat:assertSortFieldsExistrejects anorderBynaming a field whose type materializes no column (400 INVALID_SORT), consistent with how it already treats dotted paths. Cheap, and it keeps the whole REST 列表:sort/select/expand指向不存在的字段时被静默丢弃(filter 轴已收口,这三条轴还没有) #4226 family on one door.engine.find()directly, which the ingress gate deliberately does not cover.Option 1 has a wrinkle worth flagging before anyone implements it: the gate reads
gate.fields[...], so it can see the type — but the refusal would need to name the same remedy the corrected #6924 hint gives ("a stored field"), or the two doors will disagree again.Refs: #6924 (the hint text, in flight), #6673 (same correction, search axis), #4226, #4256, #3821 (the backstop that swallows it).