|
1 | 1 | import { DurableObject } from "cloudflare:workers"; |
2 | 2 | import type { ColumnMeta, DataType, Env, ExplainResult, FilterOp, Footer, Row, TableMeta, DatasetMeta, IcebergDatasetMeta, QueryResult } from "./types.js"; |
3 | | -import { queryReferencedColumns, NULL_SENTINEL } from "./types.js"; |
| 3 | +import { queryReferencedColumns, queryCacheKey, NULL_SENTINEL } from "./types.js"; |
4 | 4 | import type { QueryDescriptor } from "./client.js"; |
5 | 5 | import { parseFooter, parseColumnMetaFromProtobuf } from "./footer.js"; |
6 | 6 | import { parseManifest, logicalTypeToDataType } from "./manifest.js"; |
@@ -652,45 +652,6 @@ export class QueryDO extends DurableObject<Env> { |
652 | 652 | await this.ctx.storage.put(`table:${body.table}`, meta); |
653 | 653 | } |
654 | 654 |
|
655 | | - private queryKey(query: QueryDescriptor): string { |
656 | | - // FNV-1a hash over query components — no JSON serialization |
657 | | - let h = 0x811c9dc5; |
658 | | - const feed = (s: string) => { for (let i = 0; i < s.length; i++) { h ^= s.charCodeAt(i); h = Math.imul(h, 0x01000193); } }; |
659 | | - feed(query.table); feed("\0"); |
660 | | - if (query.version !== undefined) { feed(`v${query.version}`); feed("\0"); } |
661 | | - for (const f of [...query.filters].sort((a, b) => a.column.localeCompare(b.column) || a.op.localeCompare(b.op))) { |
662 | | - feed(f.column); feed("\0"); feed(f.op); feed("\0"); feed(String(f.value)); feed("\0"); |
663 | | - } |
664 | | - if (query.filterGroups) { |
665 | | - for (const group of query.filterGroups) { |
666 | | - feed("|"); |
667 | | - for (const f of [...group].sort((a, b) => a.column.localeCompare(b.column) || a.op.localeCompare(b.op))) { |
668 | | - feed(f.column); feed("\0"); feed(f.op); feed("\0"); feed(String(f.value)); feed("\0"); |
669 | | - } |
670 | | - } |
671 | | - } |
672 | | - for (const p of [...query.projections].sort()) { feed(p); feed("\0"); } |
673 | | - if (query.sortColumn) { feed(query.sortColumn); feed("\0"); feed(query.sortDirection ?? "asc"); feed("\0"); } |
674 | | - if (query.limit !== undefined) { feed(String(query.limit)); feed("\0"); } |
675 | | - if (query.offset !== undefined) { feed(String(query.offset)); feed("\0"); } |
676 | | - if (query.aggregates) for (const a of query.aggregates) { feed(a.fn); feed("\0"); feed(a.column); feed("\0"); if (a.alias) feed(a.alias); feed("\0"); } |
677 | | - if (query.groupBy) for (const g of query.groupBy) { feed(g); feed("\0"); } |
678 | | - if (query.distinct) for (const d of query.distinct) { feed(d); feed("\0"); } |
679 | | - if (query.windows) for (const w of query.windows) { |
680 | | - feed(w.fn); feed("\0"); feed(w.alias); feed("\0"); feed(w.column ?? NULL_SENTINEL); feed("\0"); |
681 | | - if (w.partitionBy) for (const p of w.partitionBy) { feed(p); feed("\0"); } |
682 | | - if (w.orderBy) for (const o of w.orderBy) { feed(o.column); feed(o.direction); feed("\0"); } |
683 | | - if (w.frame) { feed(w.frame.type); feed(String(w.frame.start)); feed(String(w.frame.end)); feed("\0"); } |
684 | | - if (w.args?.offset !== undefined) { feed(String(w.args.offset)); feed("\0"); } |
685 | | - if (w.args?.default_ !== undefined) { feed(String(w.args.default_)); feed("\0"); } |
686 | | - } |
687 | | - if (query.computedColumns) for (const cc of query.computedColumns) { feed(cc.alias); feed("\0"); if (cc.fn) { feed(cc.fn.toString()); feed("\0"); } } |
688 | | - if (query.setOperation) { feed(query.setOperation.mode); feed("\0"); feed(this.queryKey(query.setOperation.right)); feed("\0"); } |
689 | | - if (query.subqueryIn) for (const sq of query.subqueryIn) { feed(sq.column); feed("\0"); for (const v of sq.valueSet) { feed(v); feed("\0"); } } |
690 | | - if (query.join) { feed(query.join.type ?? "inner"); feed("\0"); feed(query.join.leftKey); feed("\0"); feed(query.join.rightKey); feed("\0"); feed(this.queryKey(query.join.right)); feed("\0"); } |
691 | | - return `qr:${query.table}:${(h >>> 0).toString(36)}`; |
692 | | - } |
693 | | - |
694 | 655 | private parseQuery(body: unknown): QueryDescriptor { |
695 | 656 | return parseAndValidateQuery(body) as QueryDescriptor; |
696 | 657 | } |
@@ -818,15 +779,15 @@ export class QueryDO extends DurableObject<Env> { |
818 | 779 | // Result cache check (skip for vector search) |
819 | 780 | const cacheable = !!(query.cacheTTL && !query.vectorSearch); |
820 | 781 | if (cacheable) { |
821 | | - const cached = this.resultCache.get(this.queryKey(query)); |
| 782 | + const cached = this.resultCache.get(queryCacheKey(query)); |
822 | 783 | if (cached) return { ...cached, durationMs: 0 }; |
823 | 784 | } |
824 | 785 |
|
825 | 786 | // Join path: use operator pipeline with R2 spill |
826 | 787 | if (query.join) { |
827 | 788 | const result = await this.executeJoin(query, t0); |
828 | 789 | if (cacheable) { |
829 | | - this.resultCache.setWithTTL(this.queryKey(query), result, query.cacheTTL!); |
| 790 | + this.resultCache.setWithTTL(queryCacheKey(query), result, query.cacheTTL!); |
830 | 791 | } |
831 | 792 | return result; |
832 | 793 | } |
@@ -874,7 +835,7 @@ export class QueryDO extends DurableObject<Env> { |
874 | 835 | } |
875 | 836 |
|
876 | 837 | if (cacheable) { |
877 | | - this.resultCache.setWithTTL(this.queryKey(query), result, query.cacheTTL!); |
| 838 | + this.resultCache.setWithTTL(queryCacheKey(query), result, query.cacheTTL!); |
878 | 839 | } |
879 | 840 | return result; |
880 | 841 | } |
|
0 commit comments