@rocicorp/zero 0.25.0-canary.4 → 0.25.0-canary.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/out/{chunk-HE4M4K7Q.js → chunk-FODUNUAD.js} +82 -86
  2. package/out/chunk-FODUNUAD.js.map +7 -0
  3. package/out/{chunk-ZZXMKAAG.js → chunk-HCZQVP5R.js} +2 -2
  4. package/out/{chunk-3KJ5OEIB.js → chunk-S633A55A.js} +2 -2
  5. package/out/{chunk-3KJ5OEIB.js.map → chunk-S633A55A.js.map} +1 -1
  6. package/out/{chunk-ECUMGQGC.js → chunk-WPAQ4EPM.js} +13 -3
  7. package/out/{chunk-ECUMGQGC.js.map → chunk-WPAQ4EPM.js.map} +2 -2
  8. package/out/expo-sqlite.js +2 -2
  9. package/out/op-sqlite.js +1 -1
  10. package/out/react-native.js +2 -2
  11. package/out/react.js +2 -1
  12. package/out/react.js.map +2 -2
  13. package/out/replicache/src/kv/sqlite-store.d.ts.map +1 -1
  14. package/out/shared/src/deep-merge.d.ts +6 -3
  15. package/out/shared/src/deep-merge.d.ts.map +1 -1
  16. package/out/shared/src/options.d.ts +2 -0
  17. package/out/shared/src/options.d.ts.map +1 -1
  18. package/out/shared/src/options.js +8 -4
  19. package/out/shared/src/options.js.map +1 -1
  20. package/out/solid.js +2 -2
  21. package/out/sqlite.js +1 -1
  22. package/out/zero/package.json +3 -3
  23. package/out/zero/src/zero-cache-dev.js +59 -40
  24. package/out/zero/src/zero-cache-dev.js.map +1 -1
  25. package/out/zero-cache/src/db/mode-enum.d.ts +2 -0
  26. package/out/zero-cache/src/db/mode-enum.d.ts.map +1 -1
  27. package/out/zero-cache/src/db/mode-enum.js +1 -0
  28. package/out/zero-cache/src/db/mode-enum.js.map +1 -1
  29. package/out/zero-cache/src/server/syncer.d.ts.map +1 -1
  30. package/out/zero-cache/src/server/syncer.js +1 -1
  31. package/out/zero-cache/src/server/syncer.js.map +1 -1
  32. package/out/zero-cache/src/services/change-streamer/backup-monitor.d.ts +1 -1
  33. package/out/zero-cache/src/services/change-streamer/backup-monitor.d.ts.map +1 -1
  34. package/out/zero-cache/src/services/change-streamer/backup-monitor.js +6 -2
  35. package/out/zero-cache/src/services/change-streamer/backup-monitor.js.map +1 -1
  36. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +2 -2
  37. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
  38. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +7 -0
  39. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
  40. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts +5 -1
  41. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts.map +1 -1
  42. package/out/zero-cache/src/services/change-streamer/change-streamer.js +5 -1
  43. package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
  44. package/out/zero-cache/src/services/change-streamer/snapshot.d.ts +58 -0
  45. package/out/zero-cache/src/services/change-streamer/snapshot.d.ts.map +1 -1
  46. package/out/zero-cache/src/services/change-streamer/snapshot.js +19 -0
  47. package/out/zero-cache/src/services/change-streamer/snapshot.js.map +1 -1
  48. package/out/zero-cache/src/services/change-streamer/storer.d.ts +1 -0
  49. package/out/zero-cache/src/services/change-streamer/storer.d.ts.map +1 -1
  50. package/out/zero-cache/src/services/change-streamer/storer.js +5 -0
  51. package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
  52. package/out/zero-cache/src/services/litestream/commands.d.ts.map +1 -1
  53. package/out/zero-cache/src/services/litestream/commands.js +43 -11
  54. package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
  55. package/out/zero-cache/src/services/view-syncer/cvr-purger.d.ts.map +1 -1
  56. package/out/zero-cache/src/services/view-syncer/cvr-purger.js +5 -6
  57. package/out/zero-cache/src/services/view-syncer/cvr-purger.js.map +1 -1
  58. package/out/zero-cache/src/services/view-syncer/cvr-store.d.ts.map +1 -1
  59. package/out/zero-cache/src/services/view-syncer/cvr-store.js +8 -4
  60. package/out/zero-cache/src/services/view-syncer/cvr-store.js.map +1 -1
  61. package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
  62. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +95 -102
  63. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
  64. package/out/zero-cache/src/services/view-syncer/row-record-cache.d.ts +1 -1
  65. package/out/zero-cache/src/services/view-syncer/row-record-cache.d.ts.map +1 -1
  66. package/out/zero-cache/src/services/view-syncer/row-record-cache.js +6 -3
  67. package/out/zero-cache/src/services/view-syncer/row-record-cache.js.map +1 -1
  68. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
  69. package/out/zero-cache/src/services/view-syncer/view-syncer.js +4 -29
  70. package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
  71. package/out/zero-cache/src/types/websocket-handoff.d.ts +2 -2
  72. package/out/zero-cache/src/types/websocket-handoff.d.ts.map +1 -1
  73. package/out/zero-cache/src/types/websocket-handoff.js +2 -2
  74. package/out/zero-cache/src/types/websocket-handoff.js.map +1 -1
  75. package/out/zero-client/src/client/connection.d.ts +2 -2
  76. package/out/zero-client/src/client/connection.d.ts.map +1 -1
  77. package/out/zero-client/src/client/context.d.ts +2 -2
  78. package/out/zero-client/src/client/context.d.ts.map +1 -1
  79. package/out/zero-client/src/client/custom.d.ts +5 -5
  80. package/out/zero-client/src/client/custom.d.ts.map +1 -1
  81. package/out/zero-client/src/client/delete-clients-manager.d.ts +2 -2
  82. package/out/zero-client/src/client/delete-clients-manager.d.ts.map +1 -1
  83. package/out/zero-client/src/client/metrics.d.ts +2 -2
  84. package/out/zero-client/src/client/metrics.d.ts.map +1 -1
  85. package/out/zero-client/src/client/mutation-tracker.d.ts +4 -4
  86. package/out/zero-client/src/client/mutation-tracker.d.ts.map +1 -1
  87. package/out/zero-client/src/client/mutator-proxy.d.ts.map +1 -1
  88. package/out/zero-client/src/client/query-manager.d.ts +2 -2
  89. package/out/zero-client/src/client/query-manager.d.ts.map +1 -1
  90. package/out/zero-client/src/client/reload-error-handler.d.ts +3 -3
  91. package/out/zero-client/src/client/reload-error-handler.d.ts.map +1 -1
  92. package/out/zero-client/src/client/zero-poke-handler.d.ts +2 -2
  93. package/out/zero-client/src/client/zero-poke-handler.d.ts.map +1 -1
  94. package/out/zero-client/src/client/zero.d.ts +2 -2
  95. package/out/zero-client/src/client/zero.d.ts.map +1 -1
  96. package/out/zero-client/src/mod.d.ts +1 -1
  97. package/out/zero-client/src/mod.d.ts.map +1 -1
  98. package/out/zero-react/src/mod.d.ts +1 -1
  99. package/out/zero-react/src/mod.d.ts.map +1 -1
  100. package/out/zero-react/src/zero-provider.d.ts +1 -0
  101. package/out/zero-react/src/zero-provider.d.ts.map +1 -1
  102. package/out/zero-server/src/adapters/drizzle.js +1 -1
  103. package/out/zero-server/src/adapters/drizzle.js.map +1 -1
  104. package/out/zero-server/src/adapters/pg.d.ts +1 -1
  105. package/out/zero-server/src/adapters/pg.d.ts.map +1 -1
  106. package/out/zero-server/src/adapters/pg.js +1 -1
  107. package/out/zero-server/src/adapters/pg.js.map +1 -1
  108. package/out/zero-server/src/adapters/postgresjs.d.ts +1 -1
  109. package/out/zero-server/src/adapters/postgresjs.d.ts.map +1 -1
  110. package/out/zero-server/src/adapters/postgresjs.js +1 -1
  111. package/out/zero-server/src/adapters/postgresjs.js.map +1 -1
  112. package/out/zero-server/src/custom.js +1 -1
  113. package/out/zero-server/src/custom.js.map +1 -1
  114. package/out/zero.js +2 -2
  115. package/out/zql/src/builder/builder.d.ts +1 -1
  116. package/out/zql/src/builder/builder.d.ts.map +1 -1
  117. package/out/zql/src/mutate/custom.d.ts +1 -1
  118. package/out/zql/src/mutate/custom.d.ts.map +1 -1
  119. package/out/zql/src/planner/planner-connection.d.ts +7 -0
  120. package/out/zql/src/planner/planner-connection.d.ts.map +1 -1
  121. package/out/zql/src/planner/planner-connection.js +2 -1
  122. package/out/zql/src/planner/planner-connection.js.map +1 -1
  123. package/out/zql/src/planner/planner-debug.d.ts +2 -1
  124. package/out/zql/src/planner/planner-debug.d.ts.map +1 -1
  125. package/out/zql/src/planner/planner-debug.js.map +1 -1
  126. package/out/zql/src/planner/planner-fan-in.d.ts.map +1 -1
  127. package/out/zql/src/planner/planner-fan-in.js +5 -0
  128. package/out/zql/src/planner/planner-fan-in.js.map +1 -1
  129. package/out/zql/src/planner/planner-graph.d.ts +1 -2
  130. package/out/zql/src/planner/planner-graph.d.ts.map +1 -1
  131. package/out/zql/src/planner/planner-graph.js +49 -68
  132. package/out/zql/src/planner/planner-graph.js.map +1 -1
  133. package/out/zql/src/planner/planner-join.d.ts +6 -2
  134. package/out/zql/src/planner/planner-join.d.ts.map +1 -1
  135. package/out/zql/src/planner/planner-join.js +27 -11
  136. package/out/zql/src/planner/planner-join.js.map +1 -1
  137. package/out/zql/src/planner/planner-node.d.ts +2 -1
  138. package/out/zql/src/planner/planner-node.d.ts.map +1 -1
  139. package/out/zql/src/query/define-query.d.ts +3 -3
  140. package/out/zql/src/query/define-query.d.ts.map +1 -1
  141. package/out/zqlite/src/sqlite-cost-model.d.ts.map +1 -1
  142. package/out/zqlite/src/sqlite-cost-model.js +10 -3
  143. package/out/zqlite/src/sqlite-cost-model.js.map +1 -1
  144. package/out/zqlite/src/sqlite-stat-fanout.d.ts +121 -0
  145. package/out/zqlite/src/sqlite-stat-fanout.d.ts.map +1 -0
  146. package/out/zqlite/src/sqlite-stat-fanout.js +377 -0
  147. package/out/zqlite/src/sqlite-stat-fanout.js.map +1 -0
  148. package/package.json +3 -3
  149. package/out/chunk-HE4M4K7Q.js.map +0 -7
  150. package/out/zero-client/src/client/zero-log-context.d.ts +0 -7
  151. package/out/zero-client/src/client/zero-log-context.d.ts.map +0 -1
  152. /package/out/{chunk-ZZXMKAAG.js.map → chunk-HCZQVP5R.js.map} +0 -0
@@ -0,0 +1,121 @@
1
+ import type { Database } from './db.ts';
2
+ /**
3
+ * Result of fanout calculation from SQLite statistics.
4
+ */
5
+ export interface FanoutResult {
6
+ /**
7
+ * The fanout value (average rows per distinct value of the join column).
8
+ * For non-NULL joins, this represents how many child rows exist per parent key.
9
+ */
10
+ fanout: number;
11
+ confidence: 'high' | 'med' | 'none';
12
+ /**
13
+ * Source of the fanout calculation.
14
+ * - 'stat4': From sqlite_stat4 histogram (most accurate, excludes NULLs)
15
+ * - 'stat1': From sqlite_stat1 average (includes NULLs, may overestimate)
16
+ * - 'default': Fallback constant when statistics unavailable
17
+ */
18
+ source: 'stat4' | 'stat1' | 'default';
19
+ }
20
+ /**
21
+ * Computes join fanout factors from SQLite statistics tables.
22
+ *
23
+ * Fanout is the average number of child rows per distinct parent key value,
24
+ * used to estimate join cardinality in query planning.
25
+ *
26
+ * ## Problem
27
+ *
28
+ * sqlite_stat1 includes NULL rows in its calculation, which can significantly
29
+ * overestimate fanout for sparse foreign keys:
30
+ *
31
+ * ```
32
+ * Example: 100 tasks, 20 with project_id, 80 with NULL
33
+ * - stat1 reports: "100 17" → fanout = 17 (WRONG - includes NULLs)
34
+ * - stat4 shows: NULL samples with fanout=80, non-NULL samples with fanout=4
35
+ * - True fanout: 4 (CORRECT)
36
+ * ```
37
+ *
38
+ * ## Solution
39
+ *
40
+ * This class uses sqlite_stat4 histogram to separate NULL and non-NULL samples,
41
+ * providing accurate fanout for non-NULL joins.
42
+ *
43
+ * ## Usage
44
+ *
45
+ * ```typescript
46
+ * const calculator = new SQLiteStatFanout(db);
47
+ *
48
+ * // Get fanout for posts.userId → users.id join
49
+ * const result = calculator.getFanout('posts', 'userId');
50
+ *
51
+ * if (result.source === 'stat4') {
52
+ * // Accurate: excludes NULLs, samples actual distribution
53
+ * console.log(`Fanout: ${result.fanout} (from stat4)`);
54
+ * } else if (result.source === 'stat1') {
55
+ * // Conservative: includes NULLs, may overestimate
56
+ * console.log(`Fanout: ${result.fanout} (from stat1, includes NULLs)`);
57
+ * } else {
58
+ * // Fallback: no statistics available
59
+ * console.log(`Fanout: ${result.fanout} (default estimate)`);
60
+ * }
61
+ * ```
62
+ *
63
+ * ## Requirements
64
+ *
65
+ * - SQLite compiled with ENABLE_STAT4 (most builds include this)
66
+ * - `ANALYZE` command run on the database
67
+ * - Index exists on the join column
68
+ *
69
+ * @see https://sqlite.org/fileformat2.html#stat4tab
70
+ * @see packages/zql/src/planner/SELECTIVITY_PLAN.md
71
+ */
72
+ export declare class SQLiteStatFanout {
73
+ #private;
74
+ /**
75
+ * Creates a new fanout calculator.
76
+ *
77
+ * @param db Database instance
78
+ * @param defaultFanout Default fanout when statistics unavailable (default: 3)
79
+ * - 1: Conservative (assumes FK relationships)
80
+ * - 3: Moderate (recommended, safe middle ground)
81
+ * - 10: SQLite's default (optimistic)
82
+ */
83
+ constructor(db: Database, defaultFanout?: number);
84
+ /**
85
+ * Gets the fanout factor for join column(s).
86
+ *
87
+ * Fanout = average number of child rows per distinct parent key value(s).
88
+ *
89
+ * ## Strategy
90
+ *
91
+ * 1. Try sqlite_stat4 (best): Histogram with separate NULL/non-NULL samples
92
+ * 2. Fallback to sqlite_stat1: Average across all rows (includes NULLs)
93
+ * 3. Fallback to default: When no statistics available
94
+ *
95
+ * ## Compound Indexes
96
+ *
97
+ * For multi-column joins, finds indexes where ALL columns appear as an
98
+ * exact prefix. Uses the appropriate depth in stat1/stat4.
99
+ *
100
+ * Example:
101
+ * - Columns: `['customerId', 'storeId']`
102
+ * - Matches index: `(customerId, storeId, date)` at depth 2
103
+ * - Uses stat1 parts[2] or stat4 neq[1] for accurate fanout
104
+ *
105
+ * ## Caching
106
+ *
107
+ * Results are cached per (table, columns) combination. Clear the cache if
108
+ * you run ANALYZE to update statistics.
109
+ *
110
+ * @param tableName Table containing the join column(s)
111
+ * @param columns Array of column names (one or more columns)
112
+ * @returns Fanout result with value and source
113
+ */
114
+ getFanout(tableName: string, columns: string[]): FanoutResult;
115
+ /**
116
+ * Clears the fanout cache.
117
+ * Call this after running ANALYZE to pick up updated statistics.
118
+ */
119
+ clearCache(): void;
120
+ }
121
+ //# sourceMappingURL=sqlite-stat-fanout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite-stat-fanout.d.ts","sourceRoot":"","sources":["../../../../zqlite/src/sqlite-stat-fanout.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,SAAS,CAAC;AAEtC;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,CAAC;IAEpC;;;;;OAKG;IACH,MAAM,EAAE,OAAO,GAAG,OAAO,GAAG,SAAS,CAAC;CACvC;AAgBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AACH,qBAAa,gBAAgB;;IAkB3B;;;;;;;;OAQG;gBACS,EAAE,EAAE,QAAQ,EAAE,aAAa,SAAI;IA0B3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACH,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,YAAY;IAkC7D;;;OAGG;IACH,UAAU,IAAI,IAAI;CAkQnB"}
@@ -0,0 +1,377 @@
1
+ /**
2
+ * Computes join fanout factors from SQLite statistics tables.
3
+ *
4
+ * Fanout is the average number of child rows per distinct parent key value,
5
+ * used to estimate join cardinality in query planning.
6
+ *
7
+ * ## Problem
8
+ *
9
+ * sqlite_stat1 includes NULL rows in its calculation, which can significantly
10
+ * overestimate fanout for sparse foreign keys:
11
+ *
12
+ * ```
13
+ * Example: 100 tasks, 20 with project_id, 80 with NULL
14
+ * - stat1 reports: "100 17" → fanout = 17 (WRONG - includes NULLs)
15
+ * - stat4 shows: NULL samples with fanout=80, non-NULL samples with fanout=4
16
+ * - True fanout: 4 (CORRECT)
17
+ * ```
18
+ *
19
+ * ## Solution
20
+ *
21
+ * This class uses sqlite_stat4 histogram to separate NULL and non-NULL samples,
22
+ * providing accurate fanout for non-NULL joins.
23
+ *
24
+ * ## Usage
25
+ *
26
+ * ```typescript
27
+ * const calculator = new SQLiteStatFanout(db);
28
+ *
29
+ * // Get fanout for posts.userId → users.id join
30
+ * const result = calculator.getFanout('posts', 'userId');
31
+ *
32
+ * if (result.source === 'stat4') {
33
+ * // Accurate: excludes NULLs, samples actual distribution
34
+ * console.log(`Fanout: ${result.fanout} (from stat4)`);
35
+ * } else if (result.source === 'stat1') {
36
+ * // Conservative: includes NULLs, may overestimate
37
+ * console.log(`Fanout: ${result.fanout} (from stat1, includes NULLs)`);
38
+ * } else {
39
+ * // Fallback: no statistics available
40
+ * console.log(`Fanout: ${result.fanout} (default estimate)`);
41
+ * }
42
+ * ```
43
+ *
44
+ * ## Requirements
45
+ *
46
+ * - SQLite compiled with ENABLE_STAT4 (most builds include this)
47
+ * - `ANALYZE` command run on the database
48
+ * - Index exists on the join column
49
+ *
50
+ * @see https://sqlite.org/fileformat2.html#stat4tab
51
+ * @see packages/zql/src/planner/SELECTIVITY_PLAN.md
52
+ */
53
+ export class SQLiteStatFanout {
54
+ #db;
55
+ #defaultFanout;
56
+ /**
57
+ * Cache of fanout results by table and columns.
58
+ * Key format: "tableName:col1,col2,col3" (sorted alphabetically)
59
+ */
60
+ #cache = new Map();
61
+ /**
62
+ * Prepared statements for querying SQLite statistics tables.
63
+ * Prepared once in constructor for performance.
64
+ */
65
+ #stat4Stmt;
66
+ #stat1Stmt;
67
+ #indexStmt;
68
+ /**
69
+ * Creates a new fanout calculator.
70
+ *
71
+ * @param db Database instance
72
+ * @param defaultFanout Default fanout when statistics unavailable (default: 3)
73
+ * - 1: Conservative (assumes FK relationships)
74
+ * - 3: Moderate (recommended, safe middle ground)
75
+ * - 10: SQLite's default (optimistic)
76
+ */
77
+ constructor(db, defaultFanout = 3) {
78
+ this.#db = db;
79
+ this.#defaultFanout = defaultFanout;
80
+ // Prepare SQL statements once for reuse across multiple getFanout() calls
81
+ this.#stat4Stmt = this.#db.prepare(`
82
+ SELECT neq, nlt, ndlt, sample
83
+ FROM sqlite_stat4
84
+ WHERE tbl = ? AND idx = ?
85
+ ORDER BY nlt
86
+ `);
87
+ this.#stat1Stmt = this.#db.prepare(`
88
+ SELECT stat
89
+ FROM sqlite_stat1
90
+ WHERE tbl = ? AND idx = ?
91
+ `);
92
+ this.#indexStmt = this.#db.prepare(`
93
+ SELECT il.name as index_name, ii.seqno, ii.name as column_name
94
+ FROM pragma_index_list(?) il
95
+ JOIN pragma_index_info(il.name) ii
96
+ ORDER BY il.seq, ii.seqno
97
+ `);
98
+ }
99
+ /**
100
+ * Gets the fanout factor for join column(s).
101
+ *
102
+ * Fanout = average number of child rows per distinct parent key value(s).
103
+ *
104
+ * ## Strategy
105
+ *
106
+ * 1. Try sqlite_stat4 (best): Histogram with separate NULL/non-NULL samples
107
+ * 2. Fallback to sqlite_stat1: Average across all rows (includes NULLs)
108
+ * 3. Fallback to default: When no statistics available
109
+ *
110
+ * ## Compound Indexes
111
+ *
112
+ * For multi-column joins, finds indexes where ALL columns appear as an
113
+ * exact prefix. Uses the appropriate depth in stat1/stat4.
114
+ *
115
+ * Example:
116
+ * - Columns: `['customerId', 'storeId']`
117
+ * - Matches index: `(customerId, storeId, date)` at depth 2
118
+ * - Uses stat1 parts[2] or stat4 neq[1] for accurate fanout
119
+ *
120
+ * ## Caching
121
+ *
122
+ * Results are cached per (table, columns) combination. Clear the cache if
123
+ * you run ANALYZE to update statistics.
124
+ *
125
+ * @param tableName Table containing the join column(s)
126
+ * @param columns Array of column names (one or more columns)
127
+ * @returns Fanout result with value and source
128
+ */
129
+ getFanout(tableName, columns) {
130
+ // Cache key uses sorted columns for consistency
131
+ const cacheKey = `${tableName}:${[...columns].sort().join(',')}`;
132
+ const cached = this.#cache.get(cacheKey);
133
+ if (cached) {
134
+ return cached;
135
+ }
136
+ // Strategy 1: Try stat4 first (most accurate)
137
+ // NOTE: columns are NOT sorted - preserves Object.keys() order from constraint
138
+ // Matching is order-independent (flexible), but we keep original order for consistency
139
+ const stat4Result = this.#getFanoutFromStat4(tableName, columns);
140
+ if (stat4Result) {
141
+ this.#cache.set(cacheKey, stat4Result);
142
+ return stat4Result;
143
+ }
144
+ // Strategy 2: Fallback to stat1 (includes NULLs)
145
+ const stat1Result = this.#getFanoutFromStat1(tableName, columns);
146
+ if (stat1Result) {
147
+ this.#cache.set(cacheKey, stat1Result);
148
+ return stat1Result;
149
+ }
150
+ // Strategy 3: Use default
151
+ const defaultResult = {
152
+ fanout: this.#defaultFanout,
153
+ confidence: 'none',
154
+ source: 'default',
155
+ };
156
+ this.#cache.set(cacheKey, defaultResult);
157
+ return defaultResult;
158
+ }
159
+ /**
160
+ * Clears the fanout cache.
161
+ * Call this after running ANALYZE to pick up updated statistics.
162
+ */
163
+ clearCache() {
164
+ this.#cache.clear();
165
+ }
166
+ /**
167
+ * Gets fanout from sqlite_stat4 histogram.
168
+ *
169
+ * Queries stat4 samples, decodes to identify NULLs, and returns
170
+ * the median fanout of non-NULL samples.
171
+ *
172
+ * For compound indexes, uses the neq value at the appropriate depth.
173
+ *
174
+ * @param columns Array of column names to get fanout for
175
+ * @returns Fanout result or undefined if stat4 unavailable
176
+ */
177
+ #getFanoutFromStat4(tableName, columns) {
178
+ try {
179
+ // Find index containing the columns as a prefix
180
+ const indexInfo = this.#findIndexForColumns(tableName, columns);
181
+ if (!indexInfo) {
182
+ return undefined;
183
+ }
184
+ // Query stat4 samples for this index (using prepared statement)
185
+ const samples = this.#stat4Stmt.all(tableName, indexInfo.indexName);
186
+ if (samples.length === 0) {
187
+ return undefined;
188
+ }
189
+ // Decode samples and separate NULL from non-NULL
190
+ // Use depth-1 for neq array index (depth is 1-based, array is 0-based)
191
+ const neqIndex = indexInfo.depth - 1;
192
+ const decodedSamples = samples.map(s => {
193
+ const neqParts = s.neq.split(' ');
194
+ return {
195
+ fanout: parseInt(neqParts[neqIndex] ?? neqParts[0], 10),
196
+ isNull: this.#decodeSampleIsNull(s.sample),
197
+ };
198
+ });
199
+ const nonNullSamples = decodedSamples.filter(s => !s.isNull);
200
+ if (nonNullSamples.length === 0) {
201
+ // All samples are NULL - return fanout of 0 since NULLs don't match in joins
202
+ return {
203
+ fanout: 0,
204
+ source: 'stat4',
205
+ confidence: 'high',
206
+ };
207
+ }
208
+ // Use median of non-NULL fanouts (more robust than average)
209
+ const fanouts = nonNullSamples.map(s => s.fanout).sort((a, b) => a - b);
210
+ const medianFanout = fanouts.length % 2 === 0
211
+ ? Math.floor((fanouts[fanouts.length / 2 - 1] + fanouts[fanouts.length / 2]) /
212
+ 2)
213
+ : fanouts[Math.floor(fanouts.length / 2)];
214
+ return {
215
+ fanout: medianFanout,
216
+ source: 'stat4',
217
+ confidence: 'high',
218
+ };
219
+ }
220
+ catch {
221
+ // stat4 table may not exist or query may fail
222
+ return undefined;
223
+ }
224
+ }
225
+ /**
226
+ * Gets fanout from sqlite_stat1 average.
227
+ *
228
+ * Note: This includes NULL rows in the calculation and may overestimate
229
+ * fanout for sparse foreign keys.
230
+ *
231
+ * For compound indexes, uses the stat value at the appropriate depth.
232
+ *
233
+ * @param columns Array of column names to get fanout for
234
+ * @returns Fanout result or undefined if stat1 unavailable
235
+ */
236
+ #getFanoutFromStat1(tableName, columns) {
237
+ try {
238
+ // Find index containing the columns as a prefix
239
+ const indexInfo = this.#findIndexForColumns(tableName, columns);
240
+ if (!indexInfo) {
241
+ return undefined;
242
+ }
243
+ // Query stat1 for this index (using prepared statement)
244
+ const result = this.#stat1Stmt.get(tableName, indexInfo.indexName);
245
+ if (!result) {
246
+ return undefined;
247
+ }
248
+ const parts = result.stat.split(' ');
249
+ // Check if we have enough parts for the requested depth
250
+ if (parts.length < indexInfo.depth + 1) {
251
+ return undefined;
252
+ }
253
+ const fanout = parseInt(parts[indexInfo.depth], 10);
254
+ if (isNaN(fanout)) {
255
+ return undefined;
256
+ }
257
+ return {
258
+ fanout,
259
+ source: 'stat1',
260
+ confidence: 'med',
261
+ };
262
+ }
263
+ catch {
264
+ return undefined;
265
+ }
266
+ }
267
+ /**
268
+ * Finds an index that can be used to get statistics for column(s).
269
+ *
270
+ * Uses pragma_index_list and pragma_index_info to reliably get index
271
+ * column names, avoiding brittle SQL parsing. Includes all indices:
272
+ * user-created (CREATE INDEX), PRIMARY KEY, and UNIQUE constraints.
273
+ *
274
+ * Uses flexible matching: Finds indexes where ALL columns appear in the
275
+ * first N positions, regardless of order. This works because SQLite statistics
276
+ * at depth N represent the fanout for the combination of the first N columns,
277
+ * and combinations are order-independent.
278
+ *
279
+ * Example:
280
+ * - columns: ['customerId', 'storeId']
281
+ * - Matches: (customerId, storeId, date) at depth 2 ✅
282
+ * - Matches: (storeId, customerId, date) at depth 2 ✅ (flexible order)
283
+ * - Does NOT match: (date, customerId, storeId) ❌ (columns not in first 2 positions)
284
+ * - Does NOT match: (customerId, date, storeId) ❌ (storeId not in first 2 positions)
285
+ *
286
+ * @param columns Array of column names (order-independent for matching)
287
+ * @returns Index info with name and depth, or undefined if no match
288
+ */
289
+ #findIndexForColumns(tableName, columns) {
290
+ try {
291
+ // Query returns all columns for all indexes (including PK/UNIQUE) in order
292
+ const rows = this.#indexStmt.all(tableName);
293
+ // Group by index name
294
+ const indexMap = new Map();
295
+ for (const row of rows) {
296
+ const cols = indexMap.get(row.index_name) ?? [];
297
+ cols.push(row.column_name);
298
+ indexMap.set(row.index_name, cols);
299
+ }
300
+ // Check each index for prefix match
301
+ for (const [indexName, indexColumns] of indexMap) {
302
+ if (this.#isPrefixMatch(columns, indexColumns)) {
303
+ return {
304
+ indexName,
305
+ depth: columns.length,
306
+ };
307
+ }
308
+ }
309
+ return undefined;
310
+ }
311
+ catch {
312
+ return undefined;
313
+ }
314
+ }
315
+ /**
316
+ * Checks if all queryColumns exist in the first N positions of indexColumns,
317
+ * regardless of order.
318
+ *
319
+ * This allows flexible matching: constraint {a, b} matches both index (a, b, c)
320
+ * and index (b, a, c) at depth 2, since both represent the fanout for the
321
+ * combination of columns a and b.
322
+ *
323
+ * Gaps are NOT allowed: constraint {a, c} does NOT match index (a, b, c)
324
+ * because no depth represents just (a, c) without b. Statistics are cumulative
325
+ * from position 0.
326
+ *
327
+ * @param queryColumns Columns we're looking for (from constraint)
328
+ * @param indexColumns Columns in the index (in order)
329
+ * @returns true if all queryColumns exist in indexColumns[0...queryColumns.length-1]
330
+ */
331
+ #isPrefixMatch(queryColumns, indexColumns) {
332
+ if (queryColumns.length > indexColumns.length) {
333
+ return false;
334
+ }
335
+ // Get the prefix of the index that we're checking against
336
+ const indexPrefix = indexColumns.slice(0, queryColumns.length);
337
+ // Normalize to lowercase for case-insensitive comparison
338
+ const indexPrefixLower = new Set(indexPrefix.map(col => col.toLowerCase()));
339
+ const queryColumnsLower = queryColumns.map(col => col.toLowerCase());
340
+ // Check if ALL query columns exist in the index prefix
341
+ return queryColumnsLower.every(queryCol => indexPrefixLower.has(queryCol));
342
+ }
343
+ /**
344
+ * Decodes a sqlite_stat4 sample value to check if it's NULL.
345
+ *
346
+ * SQLite record format (simplified):
347
+ * - Varint: header size
348
+ * - Serial types for each column (one byte each typically)
349
+ * - Actual data
350
+ *
351
+ * Serial type 0 = NULL
352
+ * Serial type 1 = 8-bit int
353
+ * Serial type 2 = 16-bit int
354
+ * Serial type 3 = 24-bit int
355
+ * etc.
356
+ *
357
+ * We only need to check the first column's serial type.
358
+ *
359
+ * @param sample Binary-encoded sample from stat4
360
+ * @returns true if the sample value is NULL
361
+ */
362
+ #decodeSampleIsNull(sample) {
363
+ if (sample.length === 0) {
364
+ return true;
365
+ }
366
+ // Read header size (varint - simplified: assume single byte)
367
+ const headerSize = sample[0];
368
+ if (headerSize === 0 || headerSize >= sample.length) {
369
+ return true;
370
+ }
371
+ // Read first serial type (at position 1)
372
+ const serialType = sample[1];
373
+ // Serial type 0 = NULL
374
+ return serialType === 0;
375
+ }
376
+ }
377
+ //# sourceMappingURL=sqlite-stat-fanout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite-stat-fanout.js","sourceRoot":"","sources":["../../../../zqlite/src/sqlite-stat-fanout.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rocicorp/zero",
3
- "version": "0.25.0-canary.4",
3
+ "version": "0.25.0-canary.7",
4
4
  "description": "Zero is a web framework for serverless web development.",
5
5
  "author": "Rocicorp, Inc.",
6
6
  "repository": {
@@ -72,6 +72,7 @@
72
72
  "postgres": "^3.4.4",
73
73
  "prettier": "^3.5.3",
74
74
  "semver": "^7.5.4",
75
+ "@standard-schema/spec": "^1.0.0",
75
76
  "tsx": "^4.19.1",
76
77
  "url-pattern": "^1.0.3",
77
78
  "urlpattern-polyfill": "^10.1.0",
@@ -80,7 +81,6 @@
80
81
  "devDependencies": {
81
82
  "@op-engineering/op-sqlite": ">=15",
82
83
  "@rocicorp/prettier-config": "^0.4.0",
83
- "@standard-schema/spec": "^1.0.0",
84
84
  "@vitest/runner": "3.2.4",
85
85
  "analyze-query": "0.0.0",
86
86
  "ast-to-zql": "0.0.0",
@@ -90,7 +90,7 @@
90
90
  "shared": "0.0.0",
91
91
  "typedoc": "^0.28.2",
92
92
  "typedoc-plugin-markdown": "^4.6.1",
93
- "typescript": "~5.8.2",
93
+ "typescript": "~5.9.3",
94
94
  "vitest": "3.2.4",
95
95
  "zero-cache": "0.0.0",
96
96
  "zero-client": "0.0.0",