@reventlessdev/reventless-postgres 3.0.0-alpha.124 → 3.0.0-alpha.125
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/CHANGELOG.md +7 -0
- package/lib/bs/.compiler.log +2 -2
- package/lib/bs/compiler-info.json +1 -1
- package/lib/bs/src/DcbEventLogStorage_Postgres-ReventlessPostgres.cmi +0 -0
- package/lib/bs/src/DcbEventLogStorage_Postgres-ReventlessPostgres.cmt +0 -0
- package/lib/bs/src/DcbEventLogStorage_Postgres_Ops-ReventlessPostgres.cmi +0 -0
- package/lib/bs/src/DcbEventLogStorage_Postgres_Ops-ReventlessPostgres.cmt +0 -0
- package/lib/bs/src/DcbEventLogStorage_Postgres_Ops.ast +0 -0
- package/lib/bs/src/DcbEventLogStorage_Postgres_Ops.res +41 -40
- package/lib/bs/src/EventLogChangeFeed-ReventlessPostgres.cmi +0 -0
- package/lib/bs/src/EventLogChangeFeed-ReventlessPostgres.cmt +0 -0
- package/lib/bs/src/EventLogChangeFeed.ast +0 -0
- package/lib/bs/src/EventLogChangeFeed.res +10 -4
- package/lib/bs/src/EventLogStorage_Postgres-ReventlessPostgres.cmi +0 -0
- package/lib/bs/src/EventLogStorage_Postgres-ReventlessPostgres.cmt +0 -0
- package/lib/bs/src/EventLogStorage_Postgres_Ops-ReventlessPostgres.cmi +0 -0
- package/lib/bs/src/EventLogStorage_Postgres_Ops-ReventlessPostgres.cmt +0 -0
- package/lib/bs/src/EventLogStorage_Postgres_Ops.ast +0 -0
- package/lib/bs/src/EventLogStorage_Postgres_Ops.res +23 -13
- package/lib/bs/src/PgChangeFeed-ReventlessPostgres.cmi +0 -0
- package/lib/bs/src/PgChangeFeed-ReventlessPostgres.cmt +0 -0
- package/lib/bs/src/PgChangeFeed.ast +0 -0
- package/lib/bs/src/PgChangeFeed.res +15 -8
- package/lib/bs/src/PgDriver-ReventlessPostgres.cmt +0 -0
- package/lib/bs/src/PgDriver.ast +0 -0
- package/lib/bs/src/PgDriver.res +1 -2
- package/lib/bs/src/QueryDbStorage_Postgres-ReventlessPostgres.cmi +0 -0
- package/lib/bs/src/QueryDbStorage_Postgres-ReventlessPostgres.cmt +0 -0
- package/lib/bs/src/QueryDbStorage_Postgres.ast +0 -0
- package/lib/bs/src/QueryDbStorage_Postgres.res +7 -2
- package/lib/bs/src/QueryDbStorage_Postgres_Ops-ReventlessPostgres.cmi +0 -0
- package/lib/bs/src/QueryDbStorage_Postgres_Ops-ReventlessPostgres.cmt +0 -0
- package/lib/bs/src/QueryDbStorage_Postgres_Ops.ast +0 -0
- package/lib/bs/src/QueryDbStorage_Postgres_Ops.res +13 -13
- package/lib/bs/src/QueryEnginePostgres-ReventlessPostgres.cmi +0 -0
- package/lib/bs/src/QueryEnginePostgres-ReventlessPostgres.cmt +0 -0
- package/lib/bs/src/QueryEnginePostgres.ast +0 -0
- package/lib/bs/src/QueryEnginePostgres.res +72 -30
- package/lib/bs/src/QueryEnginePostgres.res.mjs +4 -4
- package/lib/bs/tests/PostgresIntegrationTest-ReventlessPostgres.cmi +0 -0
- package/lib/bs/tests/PostgresIntegrationTest-ReventlessPostgres.cmt +0 -0
- package/lib/bs/tests/PostgresIntegrationTest.ast +0 -0
- package/lib/bs/tests/PostgresIntegrationTest.res +157 -104
- package/lib/bs/tests/QueryEnginePostgresListPageTest-ReventlessPostgres.cmi +0 -0
- package/lib/bs/tests/QueryEnginePostgresListPageTest-ReventlessPostgres.cmt +0 -0
- package/lib/bs/tests/QueryEnginePostgresListPageTest.ast +0 -0
- package/lib/bs/tests/QueryEnginePostgresListPageTest.res +29 -6
- package/lib/ocaml/.compiler.log +2 -2
- package/lib/ocaml/DcbEventLogStorage_Postgres-ReventlessPostgres.cmi +0 -0
- package/lib/ocaml/DcbEventLogStorage_Postgres-ReventlessPostgres.cmt +0 -0
- package/lib/ocaml/DcbEventLogStorage_Postgres_Ops-ReventlessPostgres.cmi +0 -0
- package/lib/ocaml/DcbEventLogStorage_Postgres_Ops-ReventlessPostgres.cmt +0 -0
- package/lib/ocaml/DcbEventLogStorage_Postgres_Ops.ast +0 -0
- package/lib/ocaml/DcbEventLogStorage_Postgres_Ops.res +41 -40
- package/lib/ocaml/EventLogChangeFeed-ReventlessPostgres.cmi +0 -0
- package/lib/ocaml/EventLogChangeFeed-ReventlessPostgres.cmt +0 -0
- package/lib/ocaml/EventLogChangeFeed.ast +0 -0
- package/lib/ocaml/EventLogChangeFeed.res +10 -4
- package/lib/ocaml/EventLogStorage_Postgres-ReventlessPostgres.cmi +0 -0
- package/lib/ocaml/EventLogStorage_Postgres-ReventlessPostgres.cmt +0 -0
- package/lib/ocaml/EventLogStorage_Postgres_Ops-ReventlessPostgres.cmi +0 -0
- package/lib/ocaml/EventLogStorage_Postgres_Ops-ReventlessPostgres.cmt +0 -0
- package/lib/ocaml/EventLogStorage_Postgres_Ops.ast +0 -0
- package/lib/ocaml/EventLogStorage_Postgres_Ops.res +23 -13
- package/lib/ocaml/PgChangeFeed-ReventlessPostgres.cmi +0 -0
- package/lib/ocaml/PgChangeFeed-ReventlessPostgres.cmt +0 -0
- package/lib/ocaml/PgChangeFeed.ast +0 -0
- package/lib/ocaml/PgChangeFeed.res +15 -8
- package/lib/ocaml/PgDriver-ReventlessPostgres.cmt +0 -0
- package/lib/ocaml/PgDriver.ast +0 -0
- package/lib/ocaml/PgDriver.res +1 -2
- package/lib/ocaml/PostgresIntegrationTest.res +157 -104
- package/lib/ocaml/QueryDbStorage_Postgres-ReventlessPostgres.cmi +0 -0
- package/lib/ocaml/QueryDbStorage_Postgres-ReventlessPostgres.cmt +0 -0
- package/lib/ocaml/QueryDbStorage_Postgres.ast +0 -0
- package/lib/ocaml/QueryDbStorage_Postgres.res +7 -2
- package/lib/ocaml/QueryDbStorage_Postgres_Ops-ReventlessPostgres.cmi +0 -0
- package/lib/ocaml/QueryDbStorage_Postgres_Ops-ReventlessPostgres.cmt +0 -0
- package/lib/ocaml/QueryDbStorage_Postgres_Ops.ast +0 -0
- package/lib/ocaml/QueryDbStorage_Postgres_Ops.res +13 -13
- package/lib/ocaml/QueryEnginePostgres-ReventlessPostgres.cmi +0 -0
- package/lib/ocaml/QueryEnginePostgres-ReventlessPostgres.cmt +0 -0
- package/lib/ocaml/QueryEnginePostgres.ast +0 -0
- package/lib/ocaml/QueryEnginePostgres.res +72 -30
- package/lib/ocaml/QueryEnginePostgresListPageTest.res +29 -6
- package/package.json +6 -6
- package/src/DcbEventLogStorage_Postgres_Ops.res +41 -40
- package/src/EventLogChangeFeed.res +10 -4
- package/src/EventLogStorage_Postgres_Ops.res +23 -13
- package/src/PgChangeFeed.res +15 -8
- package/src/PgDriver.res +1 -2
- package/src/QueryDbStorage_Postgres.res +7 -2
- package/src/QueryDbStorage_Postgres_Ops.res +13 -13
- package/src/QueryEnginePostgres.res +72 -30
- package/src/QueryEnginePostgres.res.mjs +4 -4
- package/tests/PostgresIntegrationTest.res +157 -104
- package/tests/QueryEnginePostgresListPageTest.res +29 -6
package/src/PgChangeFeed.res
CHANGED
|
@@ -49,7 +49,9 @@ let readBatch = async (
|
|
|
49
49
|
~after=after->Option.flatMap(Dcb.decodeCursor),
|
|
50
50
|
~applyFence=true,
|
|
51
51
|
)
|
|
52
|
-
let sql = `SELECT ${Dcb.selectColumns} FROM dcb_event WHERE ${where} ORDER BY transaction_id ASC, position ASC LIMIT ${Int.toString(
|
|
52
|
+
let sql = `SELECT ${Dcb.selectColumns} FROM dcb_event WHERE ${where} ORDER BY transaction_id ASC, position ASC LIMIT ${Int.toString(
|
|
53
|
+
limit,
|
|
54
|
+
)}`
|
|
53
55
|
let rows = await pool->PgDriver.query(sql, b.params)
|
|
54
56
|
let events = rows->Array.map(Dcb.rowToEvent)
|
|
55
57
|
let cursor = switch events->Array.get(events->Array.length - 1) {
|
|
@@ -63,10 +65,9 @@ let readBatch = async (
|
|
|
63
65
|
|
|
64
66
|
// The persisted checkpoint for `subscriber`, encoded as a cursor, or None if the
|
|
65
67
|
// subscriber has never committed (→ replay from the beginning).
|
|
66
|
-
let loadCheckpoint = async (
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
): option<DcbTag.sequencePosition> =>
|
|
68
|
+
let loadCheckpoint = async (pool: PgDriver.pool, ~subscriber: string): option<
|
|
69
|
+
DcbTag.sequencePosition,
|
|
70
|
+
> =>
|
|
70
71
|
switch await pool->PgDriver.queryOne(
|
|
71
72
|
"SELECT lpad(last_tx::text, 20, '0') || ':' || lpad(last_position::text, 20, '0') AS cursor
|
|
72
73
|
FROM dcb_subscription WHERE subscriber = $1 AND last_position > 0",
|
|
@@ -105,8 +106,9 @@ let saveCheckpoint = async (
|
|
|
105
106
|
// dedicated client (caller owns it — release via PgDriver.unlisten). Pair with a
|
|
106
107
|
// low-frequency fallback tick in the consumer loop so a missed NOTIFY only adds
|
|
107
108
|
// latency, never a permanent stall.
|
|
108
|
-
let listen = (pool: PgDriver.pool, ~logName: string, ~onWake: unit => unit): promise<
|
|
109
|
-
|
|
109
|
+
let listen = (pool: PgDriver.pool, ~logName: string, ~onWake: unit => unit): promise<
|
|
110
|
+
PgDriver.client,
|
|
111
|
+
> => pool->PgDriver.listen(~channel="dcb_" ++ logName, ~onNotify=_ => onWake())
|
|
110
112
|
|
|
111
113
|
let unlisten = (client: PgDriver.client, ~logName: string): promise<unit> =>
|
|
112
114
|
client->PgDriver.unlisten(~channel="dcb_" ++ logName)
|
|
@@ -129,7 +131,12 @@ let drain = async (
|
|
|
129
131
|
let cursor = ref(await loadCheckpoint(pool, ~subscriber))
|
|
130
132
|
let continue = ref(true)
|
|
131
133
|
while continue.contents {
|
|
132
|
-
let {events, cursor: newCursor} = await readBatch(
|
|
134
|
+
let {events, cursor: newCursor} = await readBatch(
|
|
135
|
+
pool,
|
|
136
|
+
~logName,
|
|
137
|
+
~after=?cursor.contents,
|
|
138
|
+
~limit,
|
|
139
|
+
)
|
|
133
140
|
if events->Array.length == 0 {
|
|
134
141
|
continue := false
|
|
135
142
|
} else {
|
package/src/PgDriver.res
CHANGED
|
@@ -47,8 +47,7 @@ type queryResult = {rows: array<dict<JSON.t>>}
|
|
|
47
47
|
|
|
48
48
|
let query = async (pool, sql, params) => (await _query(pool, sql, params)).rows
|
|
49
49
|
|
|
50
|
-
let queryOne = async (pool, sql, params) =>
|
|
51
|
-
(await _query(pool, sql, params)).rows->Array.get(0)
|
|
50
|
+
let queryOne = async (pool, sql, params) => (await _query(pool, sql, params)).rows->Array.get(0)
|
|
52
51
|
|
|
53
52
|
let exec = async (pool, sql) => {
|
|
54
53
|
let _ = await _queryText(pool, sql)
|
|
@@ -26,7 +26,11 @@ let makeStorage = (
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
// Standalone/deploy storage: inject the pool via a provider module.
|
|
29
|
-
module Make = (
|
|
29
|
+
module Make = (
|
|
30
|
+
P: {
|
|
31
|
+
let pool: PgDriver.pool
|
|
32
|
+
},
|
|
33
|
+
) => {
|
|
30
34
|
type api = unit
|
|
31
35
|
type role = unit
|
|
32
36
|
|
|
@@ -37,6 +41,7 @@ module Make = (P: {let pool: PgDriver.pool}) => {
|
|
|
37
41
|
~ttl as _=?,
|
|
38
42
|
~api as _,
|
|
39
43
|
~apiRole as _,
|
|
40
|
-
~owner as _,
|
|
44
|
+
~owner as _,
|
|
45
|
+
~opts as _,
|
|
41
46
|
) => makeStorage(~pool=P.pool, ~name, ~indexes, ~subIdField)
|
|
42
47
|
}
|
|
@@ -14,14 +14,12 @@ open Reventless.ReadModel
|
|
|
14
14
|
let tableName = (name: string): string => "qdb_" ++ name->String.replaceAll("-", "_")
|
|
15
15
|
|
|
16
16
|
// Collapse anything outside [A-Za-z0-9_] to _ — predictable, injection-safe idents.
|
|
17
|
-
let sanitizeIdent = (s: string): string =>
|
|
18
|
-
s->String.replaceRegExp(%re("/[^A-Za-z0-9_]/g"), "_")
|
|
17
|
+
let sanitizeIdent = (s: string): string => s->String.replaceRegExp(/[^A-Za-z0-9_]/g, "_")
|
|
19
18
|
|
|
20
19
|
let notExpiredClause = "(expires_at IS NULL OR expires_at > extract(epoch from now()))"
|
|
21
20
|
|
|
22
21
|
// JSONB text extraction for a single top-level field.
|
|
23
|
-
let jsonField = (field: string): string =>
|
|
24
|
-
`(item->>'${field->String.replaceAll("'", "''")}')`
|
|
22
|
+
let jsonField = (field: string): string => `(item->>'${field->String.replaceAll("'", "''")}')`
|
|
25
23
|
|
|
26
24
|
let compositeExpr = (fields: array<string>, sep: string): string => {
|
|
27
25
|
let escapedSep = sep->String.replaceAll("'", "''")
|
|
@@ -120,10 +118,13 @@ let makeOperations = (
|
|
|
120
118
|
|
|
121
119
|
let rowsFor = async (id: string): array<JSON.t> => {
|
|
122
120
|
await ready()
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
121
|
+
|
|
122
|
+
(
|
|
123
|
+
await pool->PgDriver.query(
|
|
124
|
+
`SELECT item FROM ${table} WHERE partition_key = $1 AND ${notExpiredClause} ORDER BY sub_key ASC`,
|
|
125
|
+
[JSON.Encode.string(id)],
|
|
126
|
+
)
|
|
127
|
+
)->Array.map(decodeItem)
|
|
127
128
|
}
|
|
128
129
|
|
|
129
130
|
let saveOne = async (id: string, state: JSON.t, ttl: option<int>) => {
|
|
@@ -149,11 +150,10 @@ let makeOperations = (
|
|
|
149
150
|
|
|
150
151
|
let load: QueryDb.load<string, JSON.t> = async id => Ok(await rowsFor(id))
|
|
151
152
|
|
|
152
|
-
let loadStream: QueryDb.loadStream<string, JSON.t> =
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
->Stream.flatMap(arr => Stream.fromIterable(arr))
|
|
153
|
+
let loadStream: QueryDb.loadStream<string, JSON.t> = id =>
|
|
154
|
+
Effect.promise(() => rowsFor(id))
|
|
155
|
+
->Stream.fromEffect
|
|
156
|
+
->Stream.flatMap(arr => Stream.fromIterable(arr))
|
|
157
157
|
|
|
158
158
|
let save: QueryDb.save<string, JSON.t> = async (id, state, _saveMode, ttl) => {
|
|
159
159
|
await saveOne(id, state, ttl)
|
|
@@ -28,7 +28,12 @@ let param = (b: paramBuilder, v: JSON.t): string => {
|
|
|
28
28
|
let col = (field: string): string => `(item->>'${field->String.replaceAll("'", "''")}')`
|
|
29
29
|
|
|
30
30
|
// One Filter.config → SQL predicate, appending any bound params.
|
|
31
|
-
let filterSql = (
|
|
31
|
+
let filterSql = (
|
|
32
|
+
b: paramBuilder,
|
|
33
|
+
field: string,
|
|
34
|
+
cmp: QueryEngine.Filter.comparator,
|
|
35
|
+
v: QueryEngine.value,
|
|
36
|
+
): string => {
|
|
32
37
|
let c = col(field)
|
|
33
38
|
let bind = () => b->param(JSON.Encode.string(valueToText(v)))
|
|
34
39
|
let cmpSql = op =>
|
|
@@ -48,10 +53,16 @@ let filterSql = (b: paramBuilder, field: string, cmp: QueryEngine.Filter.compara
|
|
|
48
53
|
}
|
|
49
54
|
}
|
|
50
55
|
|
|
51
|
-
let subIdSql = (
|
|
56
|
+
let subIdSql = (
|
|
57
|
+
b: paramBuilder,
|
|
58
|
+
field: string,
|
|
59
|
+
cmp: QueryEngine.SubId.comparator,
|
|
60
|
+
v: QueryEngine.value,
|
|
61
|
+
): string => {
|
|
52
62
|
let c = col(field)
|
|
53
63
|
let bind = () => b->param(JSON.Encode.string(valueToText(v)))
|
|
54
|
-
let cmpSql = op =>
|
|
64
|
+
let cmpSql = op =>
|
|
65
|
+
isNumeric(v) ? `${c}::numeric ${op} ${bind()}::numeric` : `${c} ${op} ${bind()}`
|
|
55
66
|
switch cmp {
|
|
56
67
|
| QueryEngine.SubId.Equal => cmpSql("=")
|
|
57
68
|
| Unequal => cmpSql("<>")
|
|
@@ -80,7 +91,11 @@ let decodeRow = (~withId, row: dict<JSON.t>): JSON.t => {
|
|
|
80
91
|
}
|
|
81
92
|
}
|
|
82
93
|
|
|
83
|
-
module Make = (
|
|
94
|
+
module Make = (
|
|
95
|
+
P: {
|
|
96
|
+
let pool: PgDriver.pool
|
|
97
|
+
},
|
|
98
|
+
) => {
|
|
84
99
|
let query = async (
|
|
85
100
|
~readModelName,
|
|
86
101
|
~key=?,
|
|
@@ -109,7 +124,9 @@ module Make = (P: {let pool: PgDriver.pool}) => {
|
|
|
109
124
|
| Some(n) => ` LIMIT ${b->param(JSON.Encode.int(n))}`
|
|
110
125
|
| None => ""
|
|
111
126
|
}
|
|
112
|
-
let sql = `SELECT partition_key, item FROM ${table} WHERE ${where->Array.join(
|
|
127
|
+
let sql = `SELECT partition_key, item FROM ${table} WHERE ${where->Array.join(
|
|
128
|
+
" AND ",
|
|
129
|
+
)} ORDER BY sub_key ${dir}${limitClause}`
|
|
113
130
|
(await P.pool->PgDriver.query(sql, b.params))->Array.map(decodeRow(~withId=false, ...))
|
|
114
131
|
}
|
|
115
132
|
|
|
@@ -117,8 +134,12 @@ module Make = (P: {let pool: PgDriver.pool}) => {
|
|
|
117
134
|
let table = QueryDbStorage_Postgres_Ops.tableName(readModelName)
|
|
118
135
|
let b = {params: []}
|
|
119
136
|
let where = [notExpired]
|
|
120
|
-
filterConfigs->Array.forEach(((field, cmp, v)) =>
|
|
121
|
-
|
|
137
|
+
filterConfigs->Array.forEach(((field, cmp, v)) =>
|
|
138
|
+
where->Array.push(filterSql(b, field, cmp, v))
|
|
139
|
+
)
|
|
140
|
+
let sql = `SELECT partition_key, item FROM ${table} WHERE ${where->Array.join(
|
|
141
|
+
" AND ",
|
|
142
|
+
)} ORDER BY partition_key, sub_key LIMIT ${b->param(JSON.Encode.int(limit))}`
|
|
122
143
|
(await P.pool->PgDriver.query(sql, b.params))->Array.map(decodeRow(~withId=true, ...))
|
|
123
144
|
}
|
|
124
145
|
|
|
@@ -265,15 +286,17 @@ module Make = (P: {let pool: PgDriver.pool}) => {
|
|
|
265
286
|
}
|
|
266
287
|
capability.filterFields->Array.forEach(f => {
|
|
267
288
|
switch filterDict->Dict.get(f.name ++ "Eq") {
|
|
268
|
-
| Some(v)
|
|
289
|
+
| Some(v) if v != JSON.Encode.null =>
|
|
269
290
|
whereParts->Array.push(
|
|
270
|
-
`${jsonTextC(f.name)} = ${b->param(
|
|
291
|
+
`${jsonTextC(f.name)} = ${b->param(
|
|
292
|
+
JSON.Encode.string(valString(v)->Option.getOr("")),
|
|
293
|
+
)}`,
|
|
271
294
|
)
|
|
272
295
|
| _ => ()
|
|
273
296
|
}
|
|
274
297
|
if f.range {
|
|
275
298
|
switch filterDict->Dict.get(f.name ++ "From") {
|
|
276
|
-
| Some(v)
|
|
299
|
+
| Some(v) if v != JSON.Encode.null =>
|
|
277
300
|
whereParts->Array.push(
|
|
278
301
|
`${jsonTextC(f.name)} >= ${b->param(
|
|
279
302
|
JSON.Encode.string(valString(v)->Option.getOr("")),
|
|
@@ -282,7 +305,7 @@ module Make = (P: {let pool: PgDriver.pool}) => {
|
|
|
282
305
|
| _ => ()
|
|
283
306
|
}
|
|
284
307
|
switch filterDict->Dict.get(f.name ++ "To") {
|
|
285
|
-
| Some(v)
|
|
308
|
+
| Some(v) if v != JSON.Encode.null =>
|
|
286
309
|
whereParts->Array.push(
|
|
287
310
|
`${jsonTextC(f.name)} <= ${b->param(
|
|
288
311
|
JSON.Encode.string(valString(v)->Option.getOr("")),
|
|
@@ -304,7 +327,8 @@ module Make = (P: {let pool: PgDriver.pool}) => {
|
|
|
304
327
|
| Some(f) => jsonTextC(f)
|
|
305
328
|
| None => idExprC
|
|
306
329
|
}
|
|
307
|
-
|
|
330
|
+
let afterCursor = argsDict->Dict.get("after")->Option.flatMap(JSON.Decode.string)
|
|
331
|
+
switch afterCursor {
|
|
308
332
|
| Some(c) =>
|
|
309
333
|
whereParts->Array.push(
|
|
310
334
|
`${cursorExpr} ${isDesc ? "<" : ">"} ${b->param(
|
|
@@ -326,7 +350,8 @@ module Make = (P: {let pool: PgDriver.pool}) => {
|
|
|
326
350
|
let sql = `SELECT partition_key, item FROM ${table} WHERE ${whereParts->Array.join(
|
|
327
351
|
" AND ",
|
|
328
352
|
)} ORDER BY ${orderClause} LIMIT ${b->param(JSON.Encode.int(pageSize + 1))}`
|
|
329
|
-
let rows =
|
|
353
|
+
let rows =
|
|
354
|
+
(await P.pool->PgDriver.query(sql, b.params))->Array.map(decodeRow(~withId=true, ...))
|
|
330
355
|
let hasMore = rows->Array.length > pageSize
|
|
331
356
|
let pageItems = rows->Array.slice(~start=0, ~end=pageSize)
|
|
332
357
|
let cursorField = orderByField->Option.getOr("id")
|
|
@@ -338,7 +363,10 @@ module Make = (P: {let pool: PgDriver.pool}) => {
|
|
|
338
363
|
ReventlessCore.QueryDbListQuery.buildConnection(
|
|
339
364
|
~pageItems,
|
|
340
365
|
~hasNextPage=hasMore,
|
|
341
|
-
|
|
366
|
+
// Same rule the spec uses, because the parity suite compares them: a
|
|
367
|
+
// forward page reached by a cursor has one behind it. Hardcoding false
|
|
368
|
+
// made Prev disappear on exactly the pages that have a previous one.
|
|
369
|
+
~hasPreviousPage=afterCursor->Option.isSome,
|
|
342
370
|
~cursorValueOf,
|
|
343
371
|
),
|
|
344
372
|
)
|
|
@@ -365,8 +393,10 @@ module Make = (P: {let pool: PgDriver.pool}) => {
|
|
|
365
393
|
argsDict->Dict.get("filter")->Option.flatMap(JSON.Decode.object)->Option.getOr(Dict.make())
|
|
366
394
|
let fstr = k => filterDict->Dict.get(k)->Option.flatMap(JSON.Decode.string)
|
|
367
395
|
let orderDesc = fstr("order")->Option.mapOr(false, o => o == "DESC")
|
|
368
|
-
let last =
|
|
369
|
-
|
|
396
|
+
let last =
|
|
397
|
+
argsDict->Dict.get("last")->Option.flatMap(JSON.Decode.float)->Option.map(Float.toInt)
|
|
398
|
+
let first =
|
|
399
|
+
argsDict->Dict.get("first")->Option.flatMap(JSON.Decode.float)->Option.map(Float.toInt)
|
|
370
400
|
let after = argsDict->Dict.get("after")->Option.flatMap(JSON.Decode.string)
|
|
371
401
|
let before = argsDict->Dict.get("before")->Option.flatMap(JSON.Decode.string)
|
|
372
402
|
let isBackward = last->Option.isSome
|
|
@@ -387,25 +417,36 @@ module Make = (P: {let pool: PgDriver.pool}) => {
|
|
|
387
417
|
fstr("prefix")->Option.forEach(p =>
|
|
388
418
|
where->Array.push(`starts_with(sub_key, ${b->param(JSON.Encode.string(p))})`)
|
|
389
419
|
)
|
|
390
|
-
fstr("from")->Option.forEach(f =>
|
|
391
|
-
|
|
420
|
+
fstr("from")->Option.forEach(f =>
|
|
421
|
+
where->Array.push(`${sk} >= ${b->param(JSON.Encode.string(f))}`)
|
|
422
|
+
)
|
|
423
|
+
fstr("to")->Option.forEach(t =>
|
|
424
|
+
where->Array.push(`${sk} <= ${b->param(JSON.Encode.string(t))}`)
|
|
425
|
+
)
|
|
392
426
|
fstr("eq")->Option.forEach(e => where->Array.push(`${sk} = ${b->param(JSON.Encode.string(e))}`))
|
|
393
427
|
// Keyset: forward excludes ≤ after; backward excludes ≥ before.
|
|
394
428
|
switch (isBackward, after, before) {
|
|
395
429
|
| (false, Some(c), _) =>
|
|
396
|
-
where->Array.push(
|
|
430
|
+
where->Array.push(
|
|
431
|
+
`${sk} > ${b->param(JSON.Encode.string(ReventlessCore.QueryDbListQuery.decodeCursor(c)))}`,
|
|
432
|
+
)
|
|
397
433
|
| (true, _, Some(c)) =>
|
|
398
|
-
where->Array.push(
|
|
434
|
+
where->Array.push(
|
|
435
|
+
`${sk} < ${b->param(JSON.Encode.string(ReventlessCore.QueryDbListQuery.decodeCursor(c)))}`,
|
|
436
|
+
)
|
|
399
437
|
| _ => ()
|
|
400
438
|
}
|
|
401
439
|
// Page-fetch direction is the LOGICAL order forward, and its OPPOSITE
|
|
402
440
|
// backward (take the N nearest `before`, then reverse to logical) — mirrors
|
|
403
441
|
// the resolver's `reverse = isBackward ? !orderDesc : orderDesc`.
|
|
404
442
|
let fetchDesc = isBackward ? !orderDesc : orderDesc
|
|
405
|
-
let pageSize =
|
|
443
|
+
let pageSize =
|
|
444
|
+
(isBackward ? last : first)->Option.getOr(ReventlessCore.QueryDbListQuery.defaultListPageSize)
|
|
406
445
|
let sql = `SELECT partition_key, sub_key, item FROM ${table} WHERE ${where->Array.join(
|
|
407
446
|
" AND ",
|
|
408
|
-
)} ORDER BY ${sk} ${fetchDesc ? "DESC" : "ASC"} LIMIT ${b->param(
|
|
447
|
+
)} ORDER BY ${sk} ${fetchDesc ? "DESC" : "ASC"} LIMIT ${b->param(
|
|
448
|
+
JSON.Encode.int(pageSize + 1),
|
|
449
|
+
)}`
|
|
409
450
|
let rows = await P.pool->PgDriver.query(sql, b.params)
|
|
410
451
|
let hasMore = rows->Array.length > pageSize
|
|
411
452
|
let taken = rows->Array.slice(~start=0, ~end=pageSize)
|
|
@@ -419,17 +460,18 @@ module Make = (P: {let pool: PgDriver.pool}) => {
|
|
|
419
460
|
| _ => ""
|
|
420
461
|
}
|
|
421
462
|
let cursors = logical->Array.map(subKeyOf)
|
|
422
|
-
let edges =
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
)
|
|
463
|
+
let edges = pageItems->Array.mapWithIndex((item, i) =>
|
|
464
|
+
Obj.magic({
|
|
465
|
+
"node": item,
|
|
466
|
+
"cursor": ReventlessCore.QueryDbListQuery.encodeCursor(cursors->Array.getUnsafe(i)),
|
|
467
|
+
})
|
|
468
|
+
)
|
|
429
469
|
let startCursor =
|
|
430
470
|
cursors->Array.get(0)->Option.map(ReventlessCore.QueryDbListQuery.encodeCursor)
|
|
431
471
|
let endCursor =
|
|
432
|
-
cursors
|
|
472
|
+
cursors
|
|
473
|
+
->Array.get(cursors->Array.length - 1)
|
|
474
|
+
->Option.map(ReventlessCore.QueryDbListQuery.encodeCursor)
|
|
433
475
|
Obj.magic({
|
|
434
476
|
"edges": edges,
|
|
435
477
|
"pageInfo": {
|
|
@@ -235,11 +235,11 @@ function Make(P) {
|
|
|
235
235
|
let orderByField = Stdlib_Option.flatMap(Stdlib_Option.flatMap(orderByDict, ob => ob["field"]), Stdlib_JSON.Decode.string);
|
|
236
236
|
let isDesc = Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_Option.flatMap(orderByDict, ob => ob["direction"]), Stdlib_JSON.Decode.string), "ASC") === "DESC";
|
|
237
237
|
let cursorExpr = orderByField !== undefined ? jsonTextC(orderByField) : idExprC;
|
|
238
|
-
let
|
|
239
|
-
if (
|
|
238
|
+
let afterCursor = Stdlib_Option.flatMap(argsDict["after"], Stdlib_JSON.Decode.string);
|
|
239
|
+
if (afterCursor !== undefined) {
|
|
240
240
|
whereParts.push(cursorExpr + ` ` + (
|
|
241
241
|
isDesc ? "<" : ">"
|
|
242
|
-
) + ` ` + param(b, QueryDbListQuery$ReventlessCore.decodeCursor(
|
|
242
|
+
) + ` ` + param(b, QueryDbListQuery$ReventlessCore.decodeCursor(afterCursor)));
|
|
243
243
|
}
|
|
244
244
|
let orderClause = orderByField !== undefined ? jsonTextC(orderByField) + ` ` + (
|
|
245
245
|
isDesc ? "DESC" : "ASC"
|
|
@@ -251,7 +251,7 @@ function Make(P) {
|
|
|
251
251
|
let pageItems = rows.slice(0, pageSize);
|
|
252
252
|
let cursorField = Stdlib_Option.getOr(orderByField, "id");
|
|
253
253
|
let cursorValueOf = item => Stdlib_Option.getOr(QueryDbListQuery$ReventlessCore.getFieldString(item, cursorField), QueryDbListQuery$ReventlessCore.getId(item));
|
|
254
|
-
return QueryDbListQuery$ReventlessCore.buildConnection(pageItems, hasMore,
|
|
254
|
+
return QueryDbListQuery$ReventlessCore.buildConnection(pageItems, hasMore, Stdlib_Option.isSome(afterCursor), cursorValueOf);
|
|
255
255
|
};
|
|
256
256
|
let itemsPage = async (readModelName, param$1, id, argsDict, ownerScope, retiredScope) => {
|
|
257
257
|
let table = QueryDbStorage_Postgres_Ops$ReventlessPostgres.tableName(readModelName);
|