@evolu/common 5.4.8 → 6.0.0

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 (242) hide show
  1. package/README.md +30 -34
  2. package/dist/src/Array.d.ts +17 -0
  3. package/dist/src/Array.d.ts.map +1 -0
  4. package/dist/src/Array.js +12 -0
  5. package/dist/src/Assert.d.ts +68 -0
  6. package/dist/src/Assert.d.ts.map +1 -0
  7. package/dist/src/Assert.js +77 -0
  8. package/dist/src/BigInt.d.ts +20 -0
  9. package/dist/src/BigInt.d.ts.map +1 -0
  10. package/dist/src/BigInt.js +18 -0
  11. package/dist/src/Buffer.d.ts +92 -0
  12. package/dist/src/Buffer.d.ts.map +1 -0
  13. package/dist/src/Buffer.js +62 -0
  14. package/dist/src/Callbacks.d.ts +20 -0
  15. package/dist/src/Callbacks.d.ts.map +1 -0
  16. package/dist/src/Callbacks.js +18 -0
  17. package/dist/src/Console.d.ts +78 -0
  18. package/dist/src/Console.d.ts.map +1 -0
  19. package/dist/src/Console.js +103 -0
  20. package/dist/src/Crypto.d.ts +72 -39
  21. package/dist/src/Crypto.d.ts.map +1 -1
  22. package/dist/src/Crypto.js +89 -54
  23. package/dist/src/Eq.d.ts +97 -0
  24. package/dist/src/Eq.d.ts.map +1 -0
  25. package/dist/src/Eq.js +167 -0
  26. package/dist/src/Error.d.ts +14 -10
  27. package/dist/src/Error.d.ts.map +1 -1
  28. package/dist/src/Error.js +43 -11
  29. package/dist/src/Evolu/Config.d.ts +69 -0
  30. package/dist/src/Evolu/Config.d.ts.map +1 -0
  31. package/dist/src/Evolu/Config.js +9 -0
  32. package/dist/src/Evolu/Db.d.ts +126 -0
  33. package/dist/src/Evolu/Db.d.ts.map +1 -0
  34. package/dist/src/Evolu/Db.js +774 -0
  35. package/dist/src/Evolu/Diff.d.ts +43 -0
  36. package/dist/src/Evolu/Diff.d.ts.map +1 -0
  37. package/dist/src/Evolu/Diff.js +95 -0
  38. package/dist/src/Evolu/Evolu.d.ts +334 -0
  39. package/dist/src/Evolu/Evolu.d.ts.map +1 -0
  40. package/dist/src/Evolu/Evolu.js +434 -0
  41. package/dist/src/Evolu/Internal.d.ts +26 -0
  42. package/dist/src/Evolu/Internal.d.ts.map +1 -0
  43. package/dist/src/Evolu/Internal.js +25 -0
  44. package/dist/src/Evolu/Kysely.d.ts +6 -0
  45. package/dist/src/Evolu/Kysely.d.ts.map +1 -0
  46. package/dist/src/Evolu/Kysely.js +21 -0
  47. package/dist/src/Evolu/Owner.d.ts +155 -0
  48. package/dist/src/Evolu/Owner.d.ts.map +1 -0
  49. package/dist/src/Evolu/Owner.js +126 -0
  50. package/dist/src/Evolu/Platform.d.ts +23 -0
  51. package/dist/src/Evolu/Platform.d.ts.map +1 -0
  52. package/dist/src/Evolu/Platform.js +1 -0
  53. package/dist/src/Evolu/Protocol.d.ts +401 -0
  54. package/dist/src/Evolu/Protocol.d.ts.map +1 -0
  55. package/dist/src/Evolu/Protocol.js +1151 -0
  56. package/dist/src/Evolu/Public.d.ts +18 -0
  57. package/dist/src/Evolu/Public.d.ts.map +1 -0
  58. package/dist/src/Evolu/Public.js +11 -0
  59. package/dist/src/Evolu/PublicKysely.d.ts +148 -0
  60. package/dist/src/Evolu/PublicKysely.d.ts.map +1 -0
  61. package/dist/src/Evolu/PublicKysely.js +185 -0
  62. package/dist/src/Evolu/Query.d.ts +63 -0
  63. package/dist/src/Evolu/Query.d.ts.map +1 -0
  64. package/dist/src/Evolu/Query.js +61 -0
  65. package/dist/src/Evolu/Relay.d.ts +13 -0
  66. package/dist/src/Evolu/Relay.d.ts.map +1 -0
  67. package/dist/src/Evolu/Relay.js +109 -0
  68. package/dist/src/Evolu/Schema.d.ts +201 -0
  69. package/dist/src/Evolu/Schema.d.ts.map +1 -0
  70. package/dist/src/Evolu/Schema.js +150 -0
  71. package/dist/src/Evolu/Storage.d.ts +49 -0
  72. package/dist/src/Evolu/Storage.d.ts.map +1 -0
  73. package/dist/src/Evolu/Storage.js +1111 -0
  74. package/dist/src/Evolu/Sync.d.ts +59 -0
  75. package/dist/src/Evolu/Sync.d.ts.map +1 -0
  76. package/dist/src/Evolu/Sync.js +29 -0
  77. package/dist/src/Evolu/Timestamp.d.ts +106 -0
  78. package/dist/src/Evolu/Timestamp.d.ts.map +1 -0
  79. package/dist/src/Evolu/Timestamp.js +179 -0
  80. package/dist/src/Function.d.ts +54 -0
  81. package/dist/src/Function.d.ts.map +1 -0
  82. package/dist/src/Function.js +38 -0
  83. package/dist/src/ManyToManyMap.d.ts +26 -0
  84. package/dist/src/ManyToManyMap.d.ts.map +1 -0
  85. package/dist/src/ManyToManyMap.js +92 -0
  86. package/dist/src/NanoId.d.ts +27 -0
  87. package/dist/src/NanoId.d.ts.map +1 -0
  88. package/dist/src/NanoId.js +6 -0
  89. package/dist/src/Number.d.ts +42 -0
  90. package/dist/src/Number.d.ts.map +1 -0
  91. package/dist/src/Number.js +55 -0
  92. package/dist/src/Object.d.ts +35 -0
  93. package/dist/src/Object.d.ts.map +1 -0
  94. package/dist/src/Object.js +36 -0
  95. package/dist/src/Order.d.ts +90 -0
  96. package/dist/src/Order.d.ts.map +1 -0
  97. package/dist/src/Order.js +85 -0
  98. package/dist/src/Promise.d.ts +180 -0
  99. package/dist/src/Promise.d.ts.map +1 -0
  100. package/dist/src/Promise.js +176 -0
  101. package/dist/src/Random.d.ts +52 -0
  102. package/dist/src/Random.d.ts.map +1 -0
  103. package/dist/src/Random.js +29 -0
  104. package/dist/src/Ref.d.ts +40 -0
  105. package/dist/src/Ref.d.ts.map +1 -0
  106. package/dist/src/Ref.js +13 -0
  107. package/dist/src/Result.d.ts +421 -0
  108. package/dist/src/Result.d.ts.map +1 -0
  109. package/dist/src/Result.js +357 -0
  110. package/dist/src/Skiplist.d.ts +23 -0
  111. package/dist/src/Skiplist.d.ts.map +1 -0
  112. package/dist/src/Skiplist.js +58 -0
  113. package/dist/src/Sqlite.d.ts +116 -52
  114. package/dist/src/Sqlite.d.ts.map +1 -1
  115. package/dist/src/Sqlite.js +183 -67
  116. package/dist/src/Store.d.ts +45 -8
  117. package/dist/src/Store.d.ts.map +1 -1
  118. package/dist/src/Store.js +33 -17
  119. package/dist/src/String.d.ts +2 -0
  120. package/dist/src/String.d.ts.map +1 -0
  121. package/dist/src/String.js +14 -0
  122. package/dist/src/Time.d.ts +20 -0
  123. package/dist/src/Time.d.ts.map +1 -0
  124. package/dist/src/Time.js +25 -0
  125. package/dist/src/Type.d.ts +1937 -0
  126. package/dist/src/Type.d.ts.map +1 -0
  127. package/dist/src/Type.js +2002 -0
  128. package/dist/src/Types.d.ts +188 -0
  129. package/dist/src/Types.d.ts.map +1 -0
  130. package/dist/src/Types.js +6 -0
  131. package/dist/src/WebSocket.d.ts +112 -0
  132. package/dist/src/WebSocket.d.ts.map +1 -0
  133. package/dist/src/WebSocket.js +139 -0
  134. package/dist/src/Worker.d.ts +44 -0
  135. package/dist/src/Worker.d.ts.map +1 -0
  136. package/dist/src/Worker.js +66 -0
  137. package/dist/src/index.d.ts +24 -11
  138. package/dist/src/index.d.ts.map +1 -1
  139. package/dist/src/index.js +24 -11
  140. package/package.json +29 -38
  141. package/src/Array.ts +39 -0
  142. package/src/Assert.ts +116 -0
  143. package/src/BigInt.ts +29 -0
  144. package/src/Buffer.ts +175 -0
  145. package/src/Callbacks.ts +43 -0
  146. package/src/Console.ts +159 -0
  147. package/src/Crypto.ts +169 -115
  148. package/src/Eq.ts +204 -0
  149. package/src/Error.ts +57 -20
  150. package/src/Evolu/Config.ts +83 -0
  151. package/src/Evolu/Db.ts +1275 -0
  152. package/src/Evolu/Diff.ts +142 -0
  153. package/src/Evolu/Evolu.ts +947 -0
  154. package/src/Evolu/Internal.ts +26 -0
  155. package/src/Evolu/Kysely.ts +38 -0
  156. package/src/Evolu/Owner.ts +296 -0
  157. package/src/Evolu/Platform.ts +27 -0
  158. package/src/Evolu/Protocol.ts +1857 -0
  159. package/src/Evolu/Public.ts +43 -0
  160. package/src/Evolu/PublicKysely.ts +240 -0
  161. package/src/Evolu/Query.ts +167 -0
  162. package/src/Evolu/Relay.ts +142 -0
  163. package/src/Evolu/Schema.ts +417 -0
  164. package/src/Evolu/Storage.ts +1281 -0
  165. package/src/Evolu/Sync.ts +105 -0
  166. package/src/Evolu/Timestamp.ts +311 -0
  167. package/src/Function.ts +58 -0
  168. package/src/ManyToManyMap.ts +140 -0
  169. package/src/NanoId.ts +39 -0
  170. package/src/Number.ts +90 -0
  171. package/src/Object.ts +64 -0
  172. package/src/Order.ts +113 -0
  173. package/src/Promise.ts +295 -0
  174. package/src/Random.ts +68 -0
  175. package/src/Ref.ts +63 -0
  176. package/src/Result.ts +453 -0
  177. package/src/Skiplist.ts +102 -0
  178. package/src/Sqlite.ts +366 -153
  179. package/src/Store.ts +79 -36
  180. package/src/String.ts +10 -0
  181. package/src/Time.ts +36 -0
  182. package/src/Type.ts +3978 -0
  183. package/src/Types.ts +209 -0
  184. package/src/WebSocket.ts +273 -0
  185. package/src/Worker.ts +129 -0
  186. package/src/index.ts +24 -11
  187. package/dist/src/Config.d.ts +0 -56
  188. package/dist/src/Config.d.ts.map +0 -1
  189. package/dist/src/Config.js +0 -39
  190. package/dist/src/Crdt.d.ts +0 -89
  191. package/dist/src/Crdt.d.ts.map +0 -1
  192. package/dist/src/Crdt.js +0 -181
  193. package/dist/src/Db.d.ts +0 -107
  194. package/dist/src/Db.d.ts.map +0 -1
  195. package/dist/src/Db.js +0 -443
  196. package/dist/src/Diff.d.ts +0 -27
  197. package/dist/src/Diff.d.ts.map +0 -1
  198. package/dist/src/Diff.js +0 -84
  199. package/dist/src/Evolu.d.ts +0 -426
  200. package/dist/src/Evolu.d.ts.map +0 -1
  201. package/dist/src/Evolu.js +0 -333
  202. package/dist/src/Model.d.ts +0 -141
  203. package/dist/src/Model.d.ts.map +0 -1
  204. package/dist/src/Model.js +0 -125
  205. package/dist/src/Murmurhash.d.ts +0 -2
  206. package/dist/src/Murmurhash.d.ts.map +0 -1
  207. package/dist/src/Murmurhash.js +0 -60
  208. package/dist/src/Owner.d.ts +0 -33
  209. package/dist/src/Owner.d.ts.map +0 -1
  210. package/dist/src/Owner.js +0 -26
  211. package/dist/src/Platform.d.ts +0 -37
  212. package/dist/src/Platform.d.ts.map +0 -1
  213. package/dist/src/Platform.js +0 -11
  214. package/dist/src/Protobuf.d.ts +0 -81
  215. package/dist/src/Protobuf.d.ts.map +0 -1
  216. package/dist/src/Protobuf.js +0 -92
  217. package/dist/src/Public.d.ts +0 -13
  218. package/dist/src/Public.d.ts.map +0 -1
  219. package/dist/src/Public.js +0 -6
  220. package/dist/src/Socket.d.ts +0 -8
  221. package/dist/src/Socket.d.ts.map +0 -1
  222. package/dist/src/Socket.js +0 -51
  223. package/dist/src/Sql.d.ts +0 -12
  224. package/dist/src/Sql.d.ts.map +0 -1
  225. package/dist/src/Sql.js +0 -30
  226. package/dist/src/Sync.d.ts +0 -70
  227. package/dist/src/Sync.d.ts.map +0 -1
  228. package/dist/src/Sync.js +0 -127
  229. package/src/Config.ts +0 -119
  230. package/src/Crdt.ts +0 -361
  231. package/src/Db.ts +0 -955
  232. package/src/Diff.ts +0 -114
  233. package/src/Evolu.ts +0 -1016
  234. package/src/Model.ts +0 -233
  235. package/src/Murmurhash.ts +0 -70
  236. package/src/Owner.ts +0 -69
  237. package/src/Platform.ts +0 -47
  238. package/src/Protobuf.ts +0 -155
  239. package/src/Public.ts +0 -12
  240. package/src/Socket.ts +0 -83
  241. package/src/Sql.ts +0 -41
  242. package/src/Sync.ts +0 -315
@@ -0,0 +1,1111 @@
1
+ /**
2
+ * Evolu Storage for SQLite
3
+ *
4
+ * Evolu uses a Skiplist, which leverages SQLite indexes. The core logic is
5
+ * implemented in SQL, so it doesn't have to make roundtrips to the DB.
6
+ *
7
+ * The ideal storage for a Relay should use a similar architecture to
8
+ * [strfry](https://github.com/hoytech/strfry) (a KV storage), but with Skiplist
9
+ * to ensure that insertion order doesn't matter (local-first apps can often
10
+ * write in the past.)
11
+ *
12
+ * The ideal client implementation should probably use the SQLite extension
13
+ * instead of SQL or even a KV storage, when such a thing for browsers/native
14
+ * will exist and will be faster than SQLite.
15
+ *
16
+ * # Scaling
17
+ *
18
+ * The load can be distributed by deploying multiple relays, synchronized with
19
+ * each other, if necessary. One relay should handle hundreds of thousands of
20
+ * users, and when it goes down, nothing happens, because it will be
21
+ * synchronized later.
22
+ *
23
+ * @module
24
+ */
25
+ import { assert } from "../Assert.js";
26
+ import { decrement } from "../Number.js";
27
+ import { ok } from "../Result.js";
28
+ import { sql } from "../Sqlite.js";
29
+ import { binaryOwnerIdToOwnerId, binaryTimestampToFingerprint, InfiniteUpperBound, RangeType, zeroFingerprint, } from "./Protocol.js";
30
+ import { orderBinaryTimestamp } from "./Timestamp.js";
31
+ export const createSqliteStorageBase = (deps) => (options) => {
32
+ const createTablesResult = createTables(deps);
33
+ if (!createTablesResult.ok)
34
+ return createTablesResult;
35
+ const ownerStats = new Map();
36
+ return ok({
37
+ insertTimestamp: (ownerId, timestamp) => {
38
+ const ownerIdString = binaryOwnerIdToOwnerId(ownerId);
39
+ const level = randomSkiplistLevel(deps);
40
+ let stats = ownerStats.get(ownerIdString);
41
+ if (!stats) {
42
+ const result = deps.sqlite.exec(sql.prepared `
43
+ select min(t) as minT, max(t) as maxT
44
+ from evolu_timestamp
45
+ where ownerId = ${ownerId};
46
+ `);
47
+ if (!result.ok)
48
+ return result;
49
+ stats = {
50
+ minT: result.value.rows[0].minT ?? timestamp,
51
+ maxT: result.value.rows[0].maxT ?? timestamp,
52
+ };
53
+ ownerStats.set(ownerIdString, stats);
54
+ }
55
+ let strategy;
56
+ if (orderBinaryTimestamp(timestamp, stats.maxT) === 1) {
57
+ strategy = "append";
58
+ stats.maxT = timestamp;
59
+ }
60
+ else if (orderBinaryTimestamp(timestamp, stats.minT) === -1) {
61
+ strategy = "prepend";
62
+ stats.minT = timestamp;
63
+ }
64
+ else {
65
+ strategy = "insert";
66
+ }
67
+ return insertTimestamp(deps)(ownerId, timestamp, level, strategy);
68
+ },
69
+ getSize: (ownerId) => {
70
+ const size = getSize(deps)(ownerId);
71
+ if (!size.ok) {
72
+ options.onStorageError(size.error);
73
+ return null;
74
+ }
75
+ return size.value;
76
+ },
77
+ fingerprint: (ownerId, begin, end) => {
78
+ assertBeginEnd(begin, end);
79
+ const result = fingerprint(deps)(ownerId, begin, end);
80
+ if (!result.ok) {
81
+ options.onStorageError(result.error);
82
+ return null;
83
+ }
84
+ return result.value;
85
+ },
86
+ fingerprintRanges: (ownerId, buckets, upperBound) => {
87
+ const ranges = fingerprintRanges(deps)(ownerId, buckets, upperBound);
88
+ if (!ranges.ok) {
89
+ options.onStorageError(ranges.error);
90
+ return null;
91
+ }
92
+ return ranges.value;
93
+ },
94
+ findLowerBound: (ownerId, begin, end, upperBound) => {
95
+ const lowerBound = findLowerBound(deps)(ownerId, begin, end, upperBound);
96
+ if (!lowerBound.ok) {
97
+ options.onStorageError(lowerBound.error);
98
+ return null;
99
+ }
100
+ return lowerBound.value;
101
+ },
102
+ iterate: (ownerId, begin, end, callback) => {
103
+ assertBeginEnd(begin, end);
104
+ const length = end - begin;
105
+ if (length === 0)
106
+ return;
107
+ // This is much faster than SQL limit with offset.
108
+ const first = getTimestampByIndex(deps)(ownerId, begin);
109
+ if (!first.ok) {
110
+ options.onStorageError(first.error);
111
+ return;
112
+ }
113
+ if (!callback(first.value, begin))
114
+ return;
115
+ if (length === 1)
116
+ return;
117
+ /**
118
+ * TODO: In rare cases, we might overfetch a lot of rows here, but we
119
+ * don't have real usage numbers yet. Fetching one row at a time would
120
+ * probably be slower in almost all cases. In the future, we should
121
+ * fetch in chunks (e.g., 1,000 rows at a time). For now, consider
122
+ * logging unused rows to gather data and calculate an average, then use
123
+ * that information to determine an optimal chunk size. Before
124
+ * implementing chunking, be sure to run performance tests (including
125
+ * fetching one by one).
126
+ */
127
+ const result = deps.sqlite.exec(sql `
128
+ select t
129
+ from evolu_timestamp
130
+ where ownerId = ${ownerId} and t > ${first.value}
131
+ order by t
132
+ limit ${length - 1};
133
+ `);
134
+ if (!result.ok) {
135
+ options.onStorageError(result.error);
136
+ return;
137
+ }
138
+ for (let i = 0; i < result.value.rows.length; i++) {
139
+ const index = (begin + 1 + i);
140
+ if (!callback(result.value.rows[i].t, index))
141
+ return;
142
+ }
143
+ },
144
+ });
145
+ };
146
+ const assertBeginEnd = (begin, end) => {
147
+ assert(begin <= end, "invalid begin or end");
148
+ };
149
+ const createTables = (deps) => {
150
+ for (const query of [
151
+ /**
152
+ * Creates the `evolu_timestamp` table for storing timestamps of multiple
153
+ * owners.
154
+ *
155
+ * All timestamps are stored in a single table using `ownerId` as part of
156
+ * the primary key. The table implements a Skiplist structure via the `l`
157
+ * (level) column for fast, scalable queries without the need for tree
158
+ * balancing.
159
+ *
160
+ * Columns:
161
+ *
162
+ * - `t` – globally unique binary timestamp
163
+ * - `h1`/`h2` – 12-byte fingerprint split into two integers for fast XOR
164
+ * - `c` – incremental count
165
+ * - `l` – Skiplist level (1 to 32)
166
+ *
167
+ * For scaling or isolation, sharding is possible—each owner can have a
168
+ * separate SQLite database.
169
+ *
170
+ * Maybe we could use an integer surrogate key for ownerId, but it's fast
171
+ * enough even without it.
172
+ */
173
+ sql `
174
+ create table if not exists evolu_timestamp (
175
+ "ownerId" blob not null,
176
+ "t" blob not null,
177
+ "h1" integer,
178
+ "h2" integer,
179
+ "c" integer,
180
+ "l" integer not null,
181
+ primary key ("ownerId", "t")
182
+ )
183
+ strict;
184
+ `,
185
+ sql `
186
+ create index if not exists evolu_timestamp_index on evolu_timestamp (
187
+ "ownerId",
188
+ "l",
189
+ "t",
190
+ "h1",
191
+ "h2",
192
+ "c"
193
+ );
194
+ `,
195
+ ]) {
196
+ const result = deps.sqlite.exec(query);
197
+ if (!result.ok)
198
+ return result;
199
+ }
200
+ return ok();
201
+ };
202
+ // AFAIK, we can't do both insert and update in one query, and that's probably
203
+ // why append is 2x faster than insert. Prepend also has to update parents, but
204
+ // it's constantly fast. Insert degrades for reversed (yet LIMIT X magically
205
+ // makes it much faster) but it's OK for append. It's probably because it's the
206
+ // most complicated SQL, but I believe it can be simplified. If not, we can
207
+ // optimize prepending by reversing the incoming timestamps if we detect that
208
+ // they will prepend. They are always sorted in ascending order by the
209
+ // Protocol.
210
+ const insertTimestamp = (deps) => (ownerId, timestamp, level, strategy) => {
211
+ const [h1, h2] = fingerprintToSqliteFingerprint(binaryTimestampToFingerprint(timestamp));
212
+ let queries = [];
213
+ switch (strategy) {
214
+ case "append":
215
+ queries = [
216
+ level === 1
217
+ ? sql.prepared `
218
+ insert into evolu_timestamp
219
+ (ownerId, l, t, h1, h2, c)
220
+ values
221
+ (${ownerId}, 1, ${timestamp}, ${h1}, ${h2}, 1)
222
+ on conflict do nothing;
223
+ `
224
+ : sql.prepared `
225
+ with
226
+ fc(b, cl, pt, nt, ih1, ih2, ic) as (
227
+ select
228
+ 0,
229
+ (
230
+ select max(l)
231
+ from evolu_timestamp
232
+ where ownerId = ${ownerId}
233
+ ),
234
+ zeroblob(0),
235
+ null,
236
+ 0,
237
+ 0,
238
+ 0
239
+ union all
240
+ select
241
+ not b,
242
+ iif(b, iif(nt is null, cl - 1, cl), cl),
243
+ iif(b, ifnull(nt, pt), pt),
244
+ iif(
245
+ b,
246
+ null,
247
+ (
248
+ select t
249
+ from evolu_timestamp
250
+ where
251
+ ownerId = ${ownerId}
252
+ and l = cl
253
+ and t > pt
254
+ and t < ${timestamp}
255
+ order by t
256
+ limit 1
257
+ )
258
+ ),
259
+ iif(
260
+ b and cl < ${level} and nt is not null,
261
+ (
262
+ select (ih1 | h1) - (ih1 & h1)
263
+ from evolu_timestamp
264
+ where ownerId = ${ownerId} and t = nt
265
+ ),
266
+ ih1
267
+ ),
268
+ iif(
269
+ b and cl < ${level} and nt is not null,
270
+ (
271
+ select (ih2 | h2) - (ih2 & h2)
272
+ from evolu_timestamp
273
+ where ownerId = ${ownerId} and t = nt
274
+ ),
275
+ ih2
276
+ ),
277
+ iif(
278
+ b and cl < ${level} and nt is not null,
279
+ (
280
+ select ic + c
281
+ from evolu_timestamp
282
+ where ownerId = ${ownerId} and t = nt
283
+ ),
284
+ ic
285
+ )
286
+ from fc
287
+ where cl > 0
288
+ )
289
+ insert into evolu_timestamp (ownerId, t, l, h1, h2, c)
290
+ select
291
+ ${ownerId},
292
+ ${timestamp},
293
+ ${level},
294
+ (${h1} | ih1) - (${h1} & ih1),
295
+ (${h2} | ih2) - (${h2} & ih2),
296
+ ic + 1
297
+ from fc
298
+ order by cl asc
299
+ limit 1
300
+ on conflict do nothing;
301
+ `,
302
+ ];
303
+ break;
304
+ case "prepend":
305
+ queries = [
306
+ sql.prepared `
307
+ insert into evolu_timestamp
308
+ (ownerId, l, t, h1, h2, c)
309
+ values
310
+ (${ownerId}, ${level}, ${timestamp}, ${h1}, ${h2}, 1)
311
+ on conflict do nothing;
312
+ `,
313
+ sql.prepared `
314
+ with
315
+ ml(ml) as (
316
+ select max(l)
317
+ from evolu_timestamp
318
+ where ownerId = ${ownerId}
319
+ ),
320
+ fp(b, cl, pt, nt, h1, h2, c) as (
321
+ select
322
+ 0,
323
+ (select ml from ml),
324
+ null,
325
+ null,
326
+ null,
327
+ null,
328
+ null
329
+ union all
330
+ select
331
+ not b,
332
+ iif(b, cl - 1, cl),
333
+ iif(
334
+ b,
335
+ iif(nt is not null and (pt is null or nt < pt), nt, pt),
336
+ pt
337
+ ),
338
+ iif(
339
+ b,
340
+ null,
341
+ (
342
+ select t
343
+ from evolu_timestamp
344
+ where ownerId = ${ownerId} and l = cl and t > ${timestamp}
345
+ order by t
346
+ limit 1
347
+ )
348
+ ),
349
+ iif(
350
+ b and nt is not null and (pt is null or nt < pt),
351
+ (
352
+ select h1
353
+ from evolu_timestamp
354
+ where ownerId = ${ownerId} and t = nt
355
+ ),
356
+ null
357
+ ),
358
+ iif(
359
+ b and nt is not null and (pt is null or nt < pt),
360
+ (
361
+ select h2
362
+ from evolu_timestamp
363
+ where ownerId = ${ownerId} and t = nt
364
+ ),
365
+ null
366
+ ),
367
+ iif(
368
+ b and nt is not null and (pt is null or nt < pt),
369
+ (
370
+ select c
371
+ from evolu_timestamp
372
+ where ownerId = ${ownerId} and t = nt
373
+ ),
374
+ null
375
+ )
376
+ from fp
377
+ where cl > ${level}
378
+ ),
379
+ u(t, h1, h2) as (
380
+ select
381
+ pt,
382
+ (${h1} | h1) - (${h1} & h1),
383
+ (${h2} | h2) - (${h2} & h2)
384
+ from fp
385
+ where h1 is not null and pt is not null
386
+ order by pt
387
+ -- Check skiplistMaxLevel docs.
388
+ limit 10
389
+ )
390
+ update evolu_timestamp
391
+ set
392
+ h1 = u.h1,
393
+ h2 = u.h2,
394
+ c = c + 1
395
+ from u
396
+ where
397
+ changes() > 0
398
+ and ownerId = ${ownerId}
399
+ and evolu_timestamp.t = u.t;
400
+ `,
401
+ ];
402
+ break;
403
+ case "insert":
404
+ queries =
405
+ level === 1
406
+ ? [
407
+ sql.prepared `
408
+ insert into evolu_timestamp
409
+ (ownerId, l, t, h1, h2, c)
410
+ values
411
+ (${ownerId}, 1, ${timestamp}, ${h1}, ${h2}, 1)
412
+ on conflict do nothing;
413
+ `,
414
+ // DEV: Check whether a boolean flag is faster.
415
+ sql.prepared `
416
+ with
417
+ p(l, t, h1, h2) as (
418
+ select
419
+ (
420
+ select max(l) + 1
421
+ from evolu_timestamp
422
+ where ownerId = ${ownerId}
423
+ ),
424
+ null,
425
+ null,
426
+ null
427
+ union all
428
+ select
429
+ p.l - 1,
430
+ ifnull(
431
+ (
432
+ select t
433
+ from evolu_timestamp
434
+ where
435
+ ownerId = ${ownerId}
436
+ and l = p.l - 1
437
+ and t > ${timestamp}
438
+ and (p.t is null or p.t > t)
439
+ order by t
440
+ limit 1
441
+ ),
442
+ p.t
443
+ ),
444
+ (
445
+ select h1
446
+ from evolu_timestamp
447
+ where
448
+ ownerId = ${ownerId}
449
+ and l = p.l - 1
450
+ and t > ${timestamp}
451
+ and (p.t is null or p.t > t)
452
+ order by t
453
+ limit 1
454
+ ),
455
+ (
456
+ select h2
457
+ from evolu_timestamp
458
+ where
459
+ ownerId = ${ownerId}
460
+ and l = p.l - 1
461
+ and t > ${timestamp}
462
+ and (p.t is null or p.t > t)
463
+ order by t
464
+ limit 1
465
+ )
466
+ from p
467
+ where p.l > 2
468
+ -- Check skiplistMaxLevel docs.
469
+ limit 10
470
+ ),
471
+ u(t, h1, h2) as (
472
+ select
473
+ t,
474
+ (${h1} | h1) - (${h1} & h1),
475
+ (${h2} | h2) - (${h2} & h2)
476
+ from p
477
+ where h1 is not null
478
+ )
479
+ update evolu_timestamp
480
+ set
481
+ h1 = u.h1,
482
+ h2 = u.h2,
483
+ c = c + 1
484
+ from u
485
+ where
486
+ changes() > 0
487
+ and ownerId = ${ownerId}
488
+ and evolu_timestamp.t = u.t;
489
+ `,
490
+ ]
491
+ : [
492
+ sql.prepared `
493
+ insert into evolu_timestamp (ownerId, t, l)
494
+ values (${ownerId}, ${timestamp}, ${level})
495
+ on conflict do nothing;
496
+ `,
497
+ sql.prepared `
498
+ with
499
+ c0(b, cl, pt, nt, h1, h2, c) as (
500
+ select
501
+ 0,
502
+ (
503
+ select max(l)
504
+ from evolu_timestamp
505
+ where ownerId = ${ownerId}
506
+ ),
507
+ 0,
508
+ null,
509
+ null,
510
+ null,
511
+ null
512
+ union all
513
+ select
514
+ not b,
515
+ iif(b, iif(nt is null, cl - 1, cl), cl),
516
+ iif(b, ifnull(nt, pt), pt),
517
+ iif(
518
+ b,
519
+ null,
520
+ (
521
+ select t
522
+ from evolu_timestamp
523
+ where
524
+ ownerId = ${ownerId}
525
+ and l = cl
526
+ and t > pt
527
+ and t < ${timestamp}
528
+ order by t
529
+ limit 1
530
+ )
531
+ ),
532
+ iif(
533
+ b and cl < ${level} and nt is not null,
534
+ (
535
+ select h1
536
+ from evolu_timestamp
537
+ where ownerId = ${ownerId} and t = nt
538
+ ),
539
+ null
540
+ ),
541
+ iif(
542
+ b and cl < ${level} and nt is not null,
543
+ (
544
+ select h2
545
+ from evolu_timestamp
546
+ where ownerId = ${ownerId} and t = nt
547
+ ),
548
+ null
549
+ ),
550
+ iif(
551
+ b and cl < ${level} and nt is not null,
552
+ (
553
+ select c
554
+ from evolu_timestamp
555
+ where ownerId = ${ownerId} and t = nt
556
+ ),
557
+ null
558
+ )
559
+ from c0
560
+ where cl > 0
561
+ ),
562
+ c1(l, t, h1, h2, c) as (
563
+ select
564
+ ${level},
565
+ ${timestamp},
566
+ ${h1},
567
+ ${h2},
568
+ 1
569
+ union all
570
+ select cl, pt, h1, h2, c
571
+ from c0
572
+ where h1 is not null
573
+ ),
574
+ c2(rn, l, t, h1, h2, c) as (
575
+ select row_number() over (order by l), l, t, h1, h2, c
576
+ from c1
577
+ ),
578
+ c3(rn, l, t, h1, h2, c) as (
579
+ select rn, l, t, h1, h2, c from c2 where rn = 1
580
+ union all
581
+ select
582
+ c3.rn + 1,
583
+ c2.l,
584
+ c2.t,
585
+ (c2.h1 | c3.h1) - (c2.h1 & c3.h1),
586
+ (c2.h2 | c3.h2) - (c2.h2 & c3.h2),
587
+ c2.c + c3.c
588
+ from
589
+ c3
590
+ join c2 on c2.rn = c3.rn + 1
591
+ ),
592
+ c4(l, t, h1, h2, c, rn) as (
593
+ select l, t, h1, h2, c, max(rn)
594
+ from c3
595
+ group by l
596
+ ),
597
+ -- DEV: Check whether a boolean flag is faster.
598
+ n(l, t, h1, h2, c) as (
599
+ select
600
+ (
601
+ select max(l) + 1
602
+ from evolu_timestamp
603
+ where ownerId = ${ownerId}
604
+ ),
605
+ null,
606
+ null,
607
+ null,
608
+ null
609
+ union all
610
+ select
611
+ n.l - 1,
612
+ ifnull(
613
+ (
614
+ select t
615
+ from evolu_timestamp
616
+ where
617
+ ownerId = ${ownerId}
618
+ and l = n.l - 1
619
+ and t > ${timestamp}
620
+ and (n.t is null or t < n.t)
621
+ order by t
622
+ limit 1
623
+ ),
624
+ n.t
625
+ ),
626
+ (
627
+ select h1
628
+ from evolu_timestamp
629
+ where
630
+ ownerId = ${ownerId}
631
+ and l = n.l - 1
632
+ and t > ${timestamp}
633
+ and (n.t is null or t < n.t)
634
+ order by t
635
+ limit 1
636
+ ),
637
+ (
638
+ select h2
639
+ from evolu_timestamp
640
+ where
641
+ ownerId = ${ownerId}
642
+ and l = n.l - 1
643
+ and t > ${timestamp}
644
+ and (n.t is null or t < n.t)
645
+ order by t
646
+ limit 1
647
+ ),
648
+ (
649
+ select c
650
+ from evolu_timestamp
651
+ where
652
+ ownerId = ${ownerId}
653
+ and l = n.l - 1
654
+ and t > ${timestamp}
655
+ and (n.t is null or t < n.t)
656
+ order by t
657
+ limit 1
658
+ )
659
+ from n
660
+ where l - 1 > (select min(l) from c4)
661
+ ),
662
+ u(ut, uh1, uh2, uc) as (
663
+ select t, h1, h2, c from c4 where t = ${timestamp}
664
+ union all
665
+ select
666
+ max(t),
667
+ iif(
668
+ l > ${level},
669
+ (${h1} | h1) - (${h1} & h1),
670
+ (
671
+ select (c4.h1 | n.h1) - (c4.h1 & n.h1)
672
+ from c4
673
+ where
674
+ c4.l = (select max(l) from c4 where c4.l < n.l)
675
+ )
676
+ ),
677
+ iif(
678
+ l > ${level},
679
+ (${h2} | h2) - (${h2} & h2),
680
+ (
681
+ select (c4.h2 | n.h2) - (c4.h2 & n.h2)
682
+ from c4
683
+ where
684
+ c4.l = (select max(l) from c4 where c4.l < n.l)
685
+ )
686
+ ),
687
+ iif(
688
+ l > ${level},
689
+ c + 1,
690
+ (
691
+ select n.c - c4.c
692
+ from c4
693
+ where
694
+ c4.l = (select max(l) from c4 where c4.l < n.l)
695
+ )
696
+ )
697
+ from n
698
+ group by t
699
+ -- Check skiplistMaxLevel docs.
700
+ limit 10
701
+ )
702
+ update evolu_timestamp
703
+ set
704
+ h1 = uh1,
705
+ h2 = uh2,
706
+ c = uc
707
+ from u
708
+ where changes() > 0 and ownerId = ${ownerId} and t = ut;
709
+ `,
710
+ ];
711
+ break;
712
+ }
713
+ for (const query of queries) {
714
+ const result = deps.sqlite.exec(query);
715
+ if (!result.ok)
716
+ return result;
717
+ }
718
+ return ok();
719
+ };
720
+ /**
721
+ * Generates a random skiplist level in the range [1, skiplistMaxLevel].
722
+ * Probabilistic approach avoids the need for explicit tree balancing.
723
+ */
724
+ const randomSkiplistLevel = (deps) => {
725
+ let level = 1;
726
+ while (deps.random.next() <= skiplistProbability &&
727
+ level < skiplistMaxLevel) {
728
+ level += 1;
729
+ }
730
+ return level;
731
+ };
732
+ /**
733
+ * Probability used for generating Skiplist levels.
734
+ *
735
+ * 0.5 is little faster for the first 30k.
736
+ */
737
+ const skiplistProbability = 0.25;
738
+ /**
739
+ * The SQLite has weird behaviour when we have to limit CTEs even when we don't
740
+ * actually limit anything; otherwise, it would not work. But without it, SQLite
741
+ * insert and prepend is slow. The 10 is the maximum allowed number, which
742
+ * "fixes" SQLite.
743
+ *
744
+ * Because we are using {@link skiplistProbability} 0.25, it's OK even for
745
+ * millions of rows.
746
+ */
747
+ const skiplistMaxLevel = 10;
748
+ const fingerprintToSqliteFingerprint = (fingerprint) => {
749
+ let part1 = BigInt(0);
750
+ let part2 = BigInt(0);
751
+ for (let i = 0; i < 6; i++) {
752
+ part1 = (part1 << BigInt(8)) | BigInt(fingerprint[i]);
753
+ }
754
+ for (let i = 6; i < 12; i++) {
755
+ part2 = (part2 << BigInt(8)) | BigInt(fingerprint[i]);
756
+ }
757
+ return [part1.toString(), part2.toString()];
758
+ };
759
+ const sqliteFingerprintToFingerprint = ([part1String, part2String,]) => {
760
+ let part1 = BigInt(part1String);
761
+ let part2 = BigInt(part2String);
762
+ const hash = new Uint8Array(12);
763
+ for (let i = 5; i >= 0; i--) {
764
+ hash[i] = Number(part1 & BigInt(0xff));
765
+ part1 >>= BigInt(8);
766
+ }
767
+ for (let i = 11; i >= 6; i--) {
768
+ hash[i] = Number(part2 & BigInt(0xff));
769
+ part2 >>= BigInt(8);
770
+ }
771
+ return hash;
772
+ };
773
+ const getSize = (deps) => (ownerId) => {
774
+ const result = deps.sqlite.exec(sql.prepared `
775
+ with
776
+ ml(ml) as (
777
+ select max(l)
778
+ from evolu_timestamp
779
+ where ownerId = ${ownerId}
780
+ ),
781
+ sc(l, pt, c) as (
782
+ select (select ml + 1 from ml), zeroblob(0), 0
783
+ union all
784
+ select
785
+ sc.l - 1,
786
+ ifnull(
787
+ (
788
+ select max(t)
789
+ from evolu_timestamp as m
790
+ where ownerId = ${ownerId} and m.l = sc.l - 1 and m.t > sc.pt
791
+ ),
792
+ sc.pt
793
+ ),
794
+ ifnull(
795
+ (
796
+ select sum(m.c)
797
+ from evolu_timestamp as m
798
+ where ownerId = ${ownerId} and m.l = sc.l - 1 and m.t > sc.pt
799
+ ),
800
+ 0
801
+ )
802
+ from sc
803
+ where sc.l > 1
804
+ )
805
+ select sum(c) as size
806
+ from sc;
807
+ `);
808
+ if (!result.ok)
809
+ return result;
810
+ return ok(result.value.rows[0].size);
811
+ };
812
+ const findLowerBound = (deps) => (ownerId, begin, end, upperBound) => {
813
+ assertBeginEnd(begin, end);
814
+ if (end === 0 || begin === end || upperBound === InfiniteUpperBound) {
815
+ return ok(end);
816
+ }
817
+ const result = deps.sqlite.exec(sql.prepared `
818
+ select t
819
+ from evolu_timestamp
820
+ where ownerId = ${ownerId} and t >= ${upperBound}
821
+ order by t
822
+ limit 1;
823
+ `);
824
+ if (!result.ok)
825
+ return result;
826
+ if (result.value.rows.length === 0) {
827
+ return ok(end);
828
+ }
829
+ const count = getTimestampCount(deps)(ownerId, result.value.rows[0].t);
830
+ if (!count.ok)
831
+ return count;
832
+ // `decrement` converts a count to an index.
833
+ return ok(decrement(count.value));
834
+ };
835
+ const getTimestampCount = (deps) => (ownerId, timestamp) => {
836
+ const result = deps.sqlite.exec(sql.prepared `
837
+ with
838
+ ml(ml) as (
839
+ select max(l) from evolu_timestamp where ownerId = ${ownerId}
840
+ ),
841
+ sc(l, pt, tc) as (
842
+ select ml + 1, zeroblob(0), 0 from ml
843
+ union all
844
+ select
845
+ sc.l - 1,
846
+ ifnull(
847
+ (
848
+ select max(t)
849
+ from evolu_timestamp
850
+ where
851
+ ownerId = ${ownerId}
852
+ and l = sc.l - 1
853
+ and t <= ${timestamp}
854
+ and t > sc.pt
855
+ order by t
856
+ ),
857
+ sc.pt
858
+ ),
859
+ ifnull(
860
+ (
861
+ select sum(c)
862
+ from evolu_timestamp
863
+ where
864
+ ownerId = ${ownerId}
865
+ and l = sc.l - 1
866
+ and t <= ${timestamp}
867
+ and t > sc.pt
868
+ ),
869
+ 0
870
+ )
871
+ from sc
872
+ where sc.l > 1 and sc.pt != ${timestamp}
873
+ )
874
+ select sum(tc) as count
875
+ from sc;
876
+ `);
877
+ if (!result.ok)
878
+ return result;
879
+ return ok(result.value.rows[0].count);
880
+ };
881
+ /**
882
+ * TODO: We reuse {@link fingerprintRanges}, which returns upper bound, which we
883
+ * don't need, so fingerprintRanges should have a parameter for that.
884
+ */
885
+ const fingerprint = (deps) => (ownerId, begin, end) => {
886
+ // There is no need to fingerprint an empty range.
887
+ if (end - begin === 0) {
888
+ return ok(zeroFingerprint);
889
+ }
890
+ if (begin === 0) {
891
+ const result = fingerprintRanges(deps)(ownerId, [end]);
892
+ if (!result.ok)
893
+ return result;
894
+ return ok(result.value[0].fingerprint);
895
+ }
896
+ // We should have a param to skip the first result.
897
+ const result = fingerprintRanges(deps)(ownerId, [begin, end]);
898
+ if (!result.ok)
899
+ return result;
900
+ return ok(result.value[1].fingerprint);
901
+ };
902
+ /**
903
+ * First, check this: https://logperiodic.com/rbsr.html#tree-friendly-functions
904
+ *
905
+ * We are a little smarter. We leverage continuous ranges to have half of
906
+ * traversals. 16 instead of 32. And we compute all of them in a single SQL
907
+ * select. If only we could get rid of those subqueries. Then, it would be
908
+ * perfect. Btw, reading h1, h2, c in the second step would be slighly faster.
909
+ */
910
+ const fingerprintRanges = (deps) => (ownerId, buckets, upperBound = InfiniteUpperBound) => {
911
+ const bucketsJson = JSON.stringify(buckets);
912
+ const result = deps.sqlite.exec(sql.prepared `
913
+ with
914
+ ml(ml) as (
915
+ select max(l) from evolu_timestamp where ownerId = ${ownerId}
916
+ ),
917
+ c0(c) as (select value as c from json_each(${bucketsJson})),
918
+ c1(c, b, nt, nc, nh1, nh2, ft, tt, dl, ic, h1, h2) as (
919
+ select
920
+ c,
921
+ 1,
922
+ null,
923
+ null,
924
+ null,
925
+ null,
926
+ zeroblob(0),
927
+ X'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF',
928
+ ml,
929
+ 0,
930
+ 0,
931
+ 0
932
+ from
933
+ c0,
934
+ ml
935
+ union all
936
+ select
937
+ c,
938
+ not b,
939
+ iif(
940
+ b,
941
+ (
942
+ select t
943
+ from evolu_timestamp
944
+ where l = dl and t > ft and t < tt and ownerId = ${ownerId}
945
+ order by t
946
+ limit 1
947
+ ),
948
+ null
949
+ ),
950
+ iif(
951
+ b,
952
+ (
953
+ select c
954
+ from evolu_timestamp
955
+ where l = dl and t > ft and t < tt and ownerId = ${ownerId}
956
+ order by t
957
+ limit 1
958
+ ),
959
+ null
960
+ ),
961
+ iif(
962
+ b,
963
+ (
964
+ select h1
965
+ from evolu_timestamp
966
+ where l = dl and t > ft and t < tt and ownerId = ${ownerId}
967
+ order by t
968
+ limit 1
969
+ ),
970
+ null
971
+ ),
972
+ iif(
973
+ b,
974
+ (
975
+ select h2
976
+ from evolu_timestamp
977
+ where l = dl and t > ft and t < tt and ownerId = ${ownerId}
978
+ order by t
979
+ limit 1
980
+ ),
981
+ null
982
+ ),
983
+ iif(b, ft, iif(ic + nc <= c, nt, ft)),
984
+ iif(b, tt, iif(ic + nc <= c, tt, ifnull(nt, tt))),
985
+ iif(b, dl, iif(ic + nc <= c, dl, dl - 1)),
986
+ iif(b, ic, iif(ic + nc <= c, ic + nc, ic)),
987
+ iif(b, h1, iif(ic + nc <= c, ${x("h1", "nh1")}, h1)),
988
+ iif(b, h2, iif(ic + nc <= c, ${x("h2", "nh2")}, h2))
989
+ from c1
990
+ where iif(b, 1, ic != c)
991
+ ),
992
+ c2(h1, h2, t, rn) as (
993
+ select
994
+ h1,
995
+ h2,
996
+ (
997
+ select min(t)
998
+ from evolu_timestamp
999
+ where t > ft and ownerId = ${ownerId}
1000
+ ),
1001
+ row_number() over (order by c)
1002
+ from c1
1003
+ where c = ic and b = 1
1004
+ ),
1005
+ c3(oh1, oh2, b, rn, h1, h2) as (
1006
+ select h1, h2, t, rn, h1, h2 from c2 where rn = 1
1007
+ union all
1008
+ select
1009
+ c2.h1,
1010
+ c2.h2,
1011
+ t,
1012
+ c2.rn,
1013
+ ${x("c3.oh1", "c2.h1")},
1014
+ ${x("c3.oh2", "c2.h2")}
1015
+ from
1016
+ c2
1017
+ join c3 on c2.rn = c3.rn + 1
1018
+ )
1019
+ select b, cast(h1 as text) as h1, cast(h2 as text) as h2
1020
+ from c3;
1021
+ `);
1022
+ if (!result.ok)
1023
+ return result;
1024
+ const fingerprintRanges = result.value.rows.map((row, i, arr) => ({
1025
+ type: RangeType.Fingerprint,
1026
+ upperBound: i === arr.length - 1 ? upperBound : row.b,
1027
+ fingerprint: sqliteFingerprintToFingerprint([
1028
+ row.h1,
1029
+ row.h2,
1030
+ ]),
1031
+ }));
1032
+ return ok(fingerprintRanges);
1033
+ };
1034
+ // XOR in SQLite
1035
+ const x = (a, b) => sql.raw(`(${a} | ${b}) - (${a} & ${b})`);
1036
+ export const getTimestampByIndex = (deps) => (ownerId, index) => {
1037
+ const result = deps.sqlite.exec(sql.prepared `
1038
+ with
1039
+ fi(b, cl, ic, pt, mt, nt, nc) as (
1040
+ select
1041
+ 0,
1042
+ (
1043
+ select max(l)
1044
+ from evolu_timestamp
1045
+ where ownerId = ${ownerId}
1046
+ ),
1047
+ 0,
1048
+ zeroblob(0),
1049
+ X'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF',
1050
+ null,
1051
+ 0
1052
+ union all
1053
+ select
1054
+ not b,
1055
+ iif(
1056
+ b,
1057
+ iif(nt is null or nt > mt or ic + nc > ${index + 1}, cl - 1, cl),
1058
+ cl
1059
+ ),
1060
+ iif(
1061
+ b,
1062
+ iif(nt is null or nt > mt or ic + nc > ${index + 1}, ic, ic + nc),
1063
+ ic
1064
+ ),
1065
+ iif(
1066
+ b,
1067
+ iif(nt is null or nt > mt or ic + nc > ${index + 1}, pt, nt),
1068
+ pt
1069
+ ),
1070
+ iif(
1071
+ b,
1072
+ iif(
1073
+ nt is null or nt > mt or ic + nc > ${index + 1},
1074
+ iif(nt is not null and nt < mt, nt, mt),
1075
+ mt
1076
+ ),
1077
+ mt
1078
+ ),
1079
+ iif(
1080
+ b,
1081
+ null,
1082
+ (
1083
+ select t
1084
+ from evolu_timestamp
1085
+ where ownerId = ${ownerId} and l = cl and t > pt
1086
+ order by t
1087
+ limit 1
1088
+ )
1089
+ ),
1090
+ iif(
1091
+ b,
1092
+ null,
1093
+ (
1094
+ select c
1095
+ from evolu_timestamp
1096
+ where ownerId = ${ownerId} and l = cl and t > pt
1097
+ order by t
1098
+ limit 1
1099
+ )
1100
+ )
1101
+ from fi
1102
+ where ic != ${index + 1}
1103
+ )
1104
+ select pt
1105
+ from fi
1106
+ where ic == ${index + 1};
1107
+ `);
1108
+ if (!result.ok)
1109
+ return result;
1110
+ return ok(result.value.rows[0].pt);
1111
+ };