@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.
- package/README.md +30 -34
- package/dist/src/Array.d.ts +17 -0
- package/dist/src/Array.d.ts.map +1 -0
- package/dist/src/Array.js +12 -0
- package/dist/src/Assert.d.ts +68 -0
- package/dist/src/Assert.d.ts.map +1 -0
- package/dist/src/Assert.js +77 -0
- package/dist/src/BigInt.d.ts +20 -0
- package/dist/src/BigInt.d.ts.map +1 -0
- package/dist/src/BigInt.js +18 -0
- package/dist/src/Buffer.d.ts +92 -0
- package/dist/src/Buffer.d.ts.map +1 -0
- package/dist/src/Buffer.js +62 -0
- package/dist/src/Callbacks.d.ts +20 -0
- package/dist/src/Callbacks.d.ts.map +1 -0
- package/dist/src/Callbacks.js +18 -0
- package/dist/src/Console.d.ts +78 -0
- package/dist/src/Console.d.ts.map +1 -0
- package/dist/src/Console.js +103 -0
- package/dist/src/Crypto.d.ts +72 -39
- package/dist/src/Crypto.d.ts.map +1 -1
- package/dist/src/Crypto.js +89 -54
- package/dist/src/Eq.d.ts +97 -0
- package/dist/src/Eq.d.ts.map +1 -0
- package/dist/src/Eq.js +167 -0
- package/dist/src/Error.d.ts +14 -10
- package/dist/src/Error.d.ts.map +1 -1
- package/dist/src/Error.js +43 -11
- package/dist/src/Evolu/Config.d.ts +69 -0
- package/dist/src/Evolu/Config.d.ts.map +1 -0
- package/dist/src/Evolu/Config.js +9 -0
- package/dist/src/Evolu/Db.d.ts +126 -0
- package/dist/src/Evolu/Db.d.ts.map +1 -0
- package/dist/src/Evolu/Db.js +774 -0
- package/dist/src/Evolu/Diff.d.ts +43 -0
- package/dist/src/Evolu/Diff.d.ts.map +1 -0
- package/dist/src/Evolu/Diff.js +95 -0
- package/dist/src/Evolu/Evolu.d.ts +334 -0
- package/dist/src/Evolu/Evolu.d.ts.map +1 -0
- package/dist/src/Evolu/Evolu.js +434 -0
- package/dist/src/Evolu/Internal.d.ts +26 -0
- package/dist/src/Evolu/Internal.d.ts.map +1 -0
- package/dist/src/Evolu/Internal.js +25 -0
- package/dist/src/Evolu/Kysely.d.ts +6 -0
- package/dist/src/Evolu/Kysely.d.ts.map +1 -0
- package/dist/src/Evolu/Kysely.js +21 -0
- package/dist/src/Evolu/Owner.d.ts +155 -0
- package/dist/src/Evolu/Owner.d.ts.map +1 -0
- package/dist/src/Evolu/Owner.js +126 -0
- package/dist/src/Evolu/Platform.d.ts +23 -0
- package/dist/src/Evolu/Platform.d.ts.map +1 -0
- package/dist/src/Evolu/Platform.js +1 -0
- package/dist/src/Evolu/Protocol.d.ts +401 -0
- package/dist/src/Evolu/Protocol.d.ts.map +1 -0
- package/dist/src/Evolu/Protocol.js +1151 -0
- package/dist/src/Evolu/Public.d.ts +18 -0
- package/dist/src/Evolu/Public.d.ts.map +1 -0
- package/dist/src/Evolu/Public.js +11 -0
- package/dist/src/Evolu/PublicKysely.d.ts +148 -0
- package/dist/src/Evolu/PublicKysely.d.ts.map +1 -0
- package/dist/src/Evolu/PublicKysely.js +185 -0
- package/dist/src/Evolu/Query.d.ts +63 -0
- package/dist/src/Evolu/Query.d.ts.map +1 -0
- package/dist/src/Evolu/Query.js +61 -0
- package/dist/src/Evolu/Relay.d.ts +13 -0
- package/dist/src/Evolu/Relay.d.ts.map +1 -0
- package/dist/src/Evolu/Relay.js +109 -0
- package/dist/src/Evolu/Schema.d.ts +201 -0
- package/dist/src/Evolu/Schema.d.ts.map +1 -0
- package/dist/src/Evolu/Schema.js +150 -0
- package/dist/src/Evolu/Storage.d.ts +49 -0
- package/dist/src/Evolu/Storage.d.ts.map +1 -0
- package/dist/src/Evolu/Storage.js +1111 -0
- package/dist/src/Evolu/Sync.d.ts +59 -0
- package/dist/src/Evolu/Sync.d.ts.map +1 -0
- package/dist/src/Evolu/Sync.js +29 -0
- package/dist/src/Evolu/Timestamp.d.ts +106 -0
- package/dist/src/Evolu/Timestamp.d.ts.map +1 -0
- package/dist/src/Evolu/Timestamp.js +179 -0
- package/dist/src/Function.d.ts +54 -0
- package/dist/src/Function.d.ts.map +1 -0
- package/dist/src/Function.js +38 -0
- package/dist/src/ManyToManyMap.d.ts +26 -0
- package/dist/src/ManyToManyMap.d.ts.map +1 -0
- package/dist/src/ManyToManyMap.js +92 -0
- package/dist/src/NanoId.d.ts +27 -0
- package/dist/src/NanoId.d.ts.map +1 -0
- package/dist/src/NanoId.js +6 -0
- package/dist/src/Number.d.ts +42 -0
- package/dist/src/Number.d.ts.map +1 -0
- package/dist/src/Number.js +55 -0
- package/dist/src/Object.d.ts +35 -0
- package/dist/src/Object.d.ts.map +1 -0
- package/dist/src/Object.js +36 -0
- package/dist/src/Order.d.ts +90 -0
- package/dist/src/Order.d.ts.map +1 -0
- package/dist/src/Order.js +85 -0
- package/dist/src/Promise.d.ts +180 -0
- package/dist/src/Promise.d.ts.map +1 -0
- package/dist/src/Promise.js +176 -0
- package/dist/src/Random.d.ts +52 -0
- package/dist/src/Random.d.ts.map +1 -0
- package/dist/src/Random.js +29 -0
- package/dist/src/Ref.d.ts +40 -0
- package/dist/src/Ref.d.ts.map +1 -0
- package/dist/src/Ref.js +13 -0
- package/dist/src/Result.d.ts +421 -0
- package/dist/src/Result.d.ts.map +1 -0
- package/dist/src/Result.js +357 -0
- package/dist/src/Skiplist.d.ts +23 -0
- package/dist/src/Skiplist.d.ts.map +1 -0
- package/dist/src/Skiplist.js +58 -0
- package/dist/src/Sqlite.d.ts +116 -52
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +183 -67
- package/dist/src/Store.d.ts +45 -8
- package/dist/src/Store.d.ts.map +1 -1
- package/dist/src/Store.js +33 -17
- package/dist/src/String.d.ts +2 -0
- package/dist/src/String.d.ts.map +1 -0
- package/dist/src/String.js +14 -0
- package/dist/src/Time.d.ts +20 -0
- package/dist/src/Time.d.ts.map +1 -0
- package/dist/src/Time.js +25 -0
- package/dist/src/Type.d.ts +1937 -0
- package/dist/src/Type.d.ts.map +1 -0
- package/dist/src/Type.js +2002 -0
- package/dist/src/Types.d.ts +188 -0
- package/dist/src/Types.d.ts.map +1 -0
- package/dist/src/Types.js +6 -0
- package/dist/src/WebSocket.d.ts +112 -0
- package/dist/src/WebSocket.d.ts.map +1 -0
- package/dist/src/WebSocket.js +139 -0
- package/dist/src/Worker.d.ts +44 -0
- package/dist/src/Worker.d.ts.map +1 -0
- package/dist/src/Worker.js +66 -0
- package/dist/src/index.d.ts +24 -11
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +24 -11
- package/package.json +29 -38
- package/src/Array.ts +39 -0
- package/src/Assert.ts +116 -0
- package/src/BigInt.ts +29 -0
- package/src/Buffer.ts +175 -0
- package/src/Callbacks.ts +43 -0
- package/src/Console.ts +159 -0
- package/src/Crypto.ts +169 -115
- package/src/Eq.ts +204 -0
- package/src/Error.ts +57 -20
- package/src/Evolu/Config.ts +83 -0
- package/src/Evolu/Db.ts +1275 -0
- package/src/Evolu/Diff.ts +142 -0
- package/src/Evolu/Evolu.ts +947 -0
- package/src/Evolu/Internal.ts +26 -0
- package/src/Evolu/Kysely.ts +38 -0
- package/src/Evolu/Owner.ts +296 -0
- package/src/Evolu/Platform.ts +27 -0
- package/src/Evolu/Protocol.ts +1857 -0
- package/src/Evolu/Public.ts +43 -0
- package/src/Evolu/PublicKysely.ts +240 -0
- package/src/Evolu/Query.ts +167 -0
- package/src/Evolu/Relay.ts +142 -0
- package/src/Evolu/Schema.ts +417 -0
- package/src/Evolu/Storage.ts +1281 -0
- package/src/Evolu/Sync.ts +105 -0
- package/src/Evolu/Timestamp.ts +311 -0
- package/src/Function.ts +58 -0
- package/src/ManyToManyMap.ts +140 -0
- package/src/NanoId.ts +39 -0
- package/src/Number.ts +90 -0
- package/src/Object.ts +64 -0
- package/src/Order.ts +113 -0
- package/src/Promise.ts +295 -0
- package/src/Random.ts +68 -0
- package/src/Ref.ts +63 -0
- package/src/Result.ts +453 -0
- package/src/Skiplist.ts +102 -0
- package/src/Sqlite.ts +366 -153
- package/src/Store.ts +79 -36
- package/src/String.ts +10 -0
- package/src/Time.ts +36 -0
- package/src/Type.ts +3978 -0
- package/src/Types.ts +209 -0
- package/src/WebSocket.ts +273 -0
- package/src/Worker.ts +129 -0
- package/src/index.ts +24 -11
- package/dist/src/Config.d.ts +0 -56
- package/dist/src/Config.d.ts.map +0 -1
- package/dist/src/Config.js +0 -39
- package/dist/src/Crdt.d.ts +0 -89
- package/dist/src/Crdt.d.ts.map +0 -1
- package/dist/src/Crdt.js +0 -181
- package/dist/src/Db.d.ts +0 -107
- package/dist/src/Db.d.ts.map +0 -1
- package/dist/src/Db.js +0 -443
- package/dist/src/Diff.d.ts +0 -27
- package/dist/src/Diff.d.ts.map +0 -1
- package/dist/src/Diff.js +0 -84
- package/dist/src/Evolu.d.ts +0 -426
- package/dist/src/Evolu.d.ts.map +0 -1
- package/dist/src/Evolu.js +0 -333
- package/dist/src/Model.d.ts +0 -141
- package/dist/src/Model.d.ts.map +0 -1
- package/dist/src/Model.js +0 -125
- package/dist/src/Murmurhash.d.ts +0 -2
- package/dist/src/Murmurhash.d.ts.map +0 -1
- package/dist/src/Murmurhash.js +0 -60
- package/dist/src/Owner.d.ts +0 -33
- package/dist/src/Owner.d.ts.map +0 -1
- package/dist/src/Owner.js +0 -26
- package/dist/src/Platform.d.ts +0 -37
- package/dist/src/Platform.d.ts.map +0 -1
- package/dist/src/Platform.js +0 -11
- package/dist/src/Protobuf.d.ts +0 -81
- package/dist/src/Protobuf.d.ts.map +0 -1
- package/dist/src/Protobuf.js +0 -92
- package/dist/src/Public.d.ts +0 -13
- package/dist/src/Public.d.ts.map +0 -1
- package/dist/src/Public.js +0 -6
- package/dist/src/Socket.d.ts +0 -8
- package/dist/src/Socket.d.ts.map +0 -1
- package/dist/src/Socket.js +0 -51
- package/dist/src/Sql.d.ts +0 -12
- package/dist/src/Sql.d.ts.map +0 -1
- package/dist/src/Sql.js +0 -30
- package/dist/src/Sync.d.ts +0 -70
- package/dist/src/Sync.d.ts.map +0 -1
- package/dist/src/Sync.js +0 -127
- package/src/Config.ts +0 -119
- package/src/Crdt.ts +0 -361
- package/src/Db.ts +0 -955
- package/src/Diff.ts +0 -114
- package/src/Evolu.ts +0 -1016
- package/src/Model.ts +0 -233
- package/src/Murmurhash.ts +0 -70
- package/src/Owner.ts +0 -69
- package/src/Platform.ts +0 -47
- package/src/Protobuf.ts +0 -155
- package/src/Public.ts +0 -12
- package/src/Socket.ts +0 -83
- package/src/Sql.ts +0 -41
- 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
|
+
};
|