@undefineds.co/xpod 0.2.43 → 0.2.44
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/dist/components/context.jsonld +45 -0
- package/dist/storage/quint/BaseQuintStore.jsonld +82 -0
- package/dist/storage/quint/PgQuintStore.d.ts +43 -3
- package/dist/storage/quint/PgQuintStore.js +596 -28
- package/dist/storage/quint/PgQuintStore.js.map +1 -1
- package/dist/storage/quint/PgQuintStore.jsonld +202 -0
- package/dist/storage/quint/SqliteQuintStore.jsonld +82 -0
- package/dist/storage/quint/index.d.ts +1 -0
- package/dist/storage/quint/index.js +1 -0
- package/dist/storage/quint/index.js.map +1 -1
- package/dist/storage/quint/types.d.ts +13 -0
- package/dist/storage/quint/types.js.map +1 -1
- package/dist/storage/quint/value-types.d.ts +32 -0
- package/dist/storage/quint/value-types.js +100 -0
- package/dist/storage/quint/value-types.js.map +1 -0
- package/package.json +1 -1
|
@@ -12,7 +12,10 @@
|
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
13
|
exports.PgQuintStore = void 0;
|
|
14
14
|
const pglite_1 = require("@electric-sql/pglite");
|
|
15
|
+
const node_crypto_1 = require("node:crypto");
|
|
15
16
|
const BaseQuintStore_1 = require("./BaseQuintStore");
|
|
17
|
+
const serialization_1 = require("./serialization");
|
|
18
|
+
const value_types_1 = require("./value-types");
|
|
16
19
|
const PostgresPoolManager_1 = require("../database/PostgresPoolManager");
|
|
17
20
|
/**
|
|
18
21
|
* PostgreSQL 兼容的分隔符
|
|
@@ -31,6 +34,9 @@ function toPgSafe(str) {
|
|
|
31
34
|
function fromPgSafe(str) {
|
|
32
35
|
return str.replace(new RegExp(PG_SEP, 'g'), '\u0000');
|
|
33
36
|
}
|
|
37
|
+
function digestObject(value) {
|
|
38
|
+
return (0, node_crypto_1.createHash)('sha256').update(toPgSafe(value)).digest('hex');
|
|
39
|
+
}
|
|
34
40
|
/**
|
|
35
41
|
* PGLite 执行器
|
|
36
42
|
*/
|
|
@@ -208,43 +214,45 @@ class PgQuintStore extends BaseQuintStore_1.BaseQuintStore {
|
|
|
208
214
|
// PostgreSQL 建表语法
|
|
209
215
|
await this.executor.exec(`
|
|
210
216
|
CREATE TABLE IF NOT EXISTS quints (
|
|
217
|
+
object_kind TEXT,
|
|
218
|
+
object_key TEXT,
|
|
219
|
+
object_text TEXT,
|
|
220
|
+
object_digest TEXT,
|
|
211
221
|
graph TEXT NOT NULL,
|
|
212
222
|
subject TEXT NOT NULL,
|
|
213
223
|
predicate TEXT NOT NULL,
|
|
214
224
|
object TEXT NOT NULL,
|
|
215
|
-
vector TEXT
|
|
216
|
-
PRIMARY KEY (graph, subject, predicate, object)
|
|
225
|
+
vector TEXT
|
|
217
226
|
)
|
|
218
227
|
`);
|
|
228
|
+
await this.ensureTypedObjectSchema();
|
|
219
229
|
const indexes = [
|
|
220
|
-
'CREATE INDEX IF NOT EXISTS
|
|
221
|
-
'CREATE INDEX IF NOT EXISTS
|
|
222
|
-
'CREATE INDEX IF NOT EXISTS
|
|
223
|
-
'CREATE INDEX IF NOT EXISTS
|
|
224
|
-
'CREATE INDEX IF NOT EXISTS
|
|
225
|
-
'CREATE INDEX IF NOT EXISTS
|
|
230
|
+
'CREATE INDEX IF NOT EXISTS idx_quints_graph ON quints (graph)',
|
|
231
|
+
'CREATE INDEX IF NOT EXISTS idx_quints_subject ON quints (subject)',
|
|
232
|
+
'CREATE INDEX IF NOT EXISTS idx_quints_predicate ON quints (predicate)',
|
|
233
|
+
'CREATE INDEX IF NOT EXISTS idx_quints_object_key ON quints (object_kind, object_key)',
|
|
234
|
+
'CREATE INDEX IF NOT EXISTS idx_quints_predicate_object_key ON quints (predicate, object_kind, object_key)',
|
|
235
|
+
'CREATE INDEX IF NOT EXISTS idx_quints_predicate_object_digest ON quints (predicate, object_kind, object_digest)',
|
|
236
|
+
'CREATE UNIQUE INDEX IF NOT EXISTS idx_quints_gspo_key ON quints (graph, subject, predicate, object_kind, object_key) WHERE object_key IS NOT NULL',
|
|
237
|
+
'CREATE UNIQUE INDEX IF NOT EXISTS idx_quints_gspo_digest ON quints (graph, subject, predicate, object_kind, object_digest) WHERE object_digest IS NOT NULL',
|
|
238
|
+
'CREATE INDEX IF NOT EXISTS idx_quints_gsp ON quints (graph, subject, predicate)',
|
|
239
|
+
'CREATE INDEX IF NOT EXISTS idx_quints_sp ON quints (subject, predicate)',
|
|
240
|
+
'CREATE INDEX IF NOT EXISTS idx_quints_gp ON quints (graph, predicate)',
|
|
226
241
|
];
|
|
227
242
|
for (const indexSql of indexes) {
|
|
228
243
|
await this.executor.exec(indexSql);
|
|
229
244
|
}
|
|
230
245
|
}
|
|
231
246
|
/**
|
|
232
|
-
* 重写 put
|
|
247
|
+
* 重写 put 方法,避免长文本对象进入 PostgreSQL btree 唯一键
|
|
233
248
|
*/
|
|
234
249
|
async put(quint) {
|
|
235
250
|
this.ensureOpen();
|
|
236
|
-
const row = this.
|
|
237
|
-
|
|
238
|
-
const sql = `
|
|
239
|
-
INSERT INTO quints (graph, subject, predicate, object, vector)
|
|
240
|
-
VALUES ($1, $2, $3, $4, $5)
|
|
241
|
-
ON CONFLICT (graph, subject, predicate, object)
|
|
242
|
-
DO UPDATE SET vector = EXCLUDED.vector
|
|
243
|
-
`;
|
|
244
|
-
await this.executor.execute(sql, [row.graph, row.subject, row.predicate, row.object, row.vector]);
|
|
251
|
+
const row = this.quintToPgRow(quint);
|
|
252
|
+
await this.executor.executeInTransaction(this.writeStatementsForRow(row));
|
|
245
253
|
}
|
|
246
254
|
/**
|
|
247
|
-
* 重写 multiPut
|
|
255
|
+
* 重写 multiPut 方法,使用同一事务保持批量写入幂等
|
|
248
256
|
*/
|
|
249
257
|
async multiPut(quintList) {
|
|
250
258
|
console.log(`[PgQuintStore.multiPut] Starting: ${quintList.length} quints`);
|
|
@@ -253,22 +261,582 @@ class PgQuintStore extends BaseQuintStore_1.BaseQuintStore {
|
|
|
253
261
|
console.log(`[PgQuintStore.multiPut] Empty list, skipping`);
|
|
254
262
|
return;
|
|
255
263
|
}
|
|
264
|
+
const statements = quintList.flatMap(quint => {
|
|
265
|
+
const row = this.quintToPgRow(quint);
|
|
266
|
+
return this.writeStatementsForRow(row);
|
|
267
|
+
});
|
|
268
|
+
console.log(`[PgQuintStore.multiPut] Executing ${statements.length} statements in transaction`);
|
|
269
|
+
const start = Date.now();
|
|
270
|
+
await this.executor.executeInTransaction(statements);
|
|
271
|
+
console.log(`[PgQuintStore.multiPut] Completed in ${Date.now() - start}ms`);
|
|
272
|
+
}
|
|
273
|
+
async multiDel(quintList) {
|
|
274
|
+
this.ensureOpen();
|
|
275
|
+
if (quintList.length === 0)
|
|
276
|
+
return;
|
|
256
277
|
const statements = quintList.map(quint => {
|
|
257
|
-
const row = this.
|
|
278
|
+
const row = this.quintToPgRow(quint);
|
|
258
279
|
return {
|
|
259
280
|
sql: `
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
281
|
+
DELETE FROM quints
|
|
282
|
+
WHERE graph = $1
|
|
283
|
+
AND subject = $2
|
|
284
|
+
AND predicate = $3
|
|
285
|
+
AND object = $4
|
|
264
286
|
`,
|
|
265
|
-
params: [row.graph, row.subject, row.predicate, row.object
|
|
287
|
+
params: [row.graph, row.subject, row.predicate, row.object],
|
|
266
288
|
};
|
|
267
289
|
});
|
|
268
|
-
console.log(`[PgQuintStore.multiPut] Executing ${statements.length} statements in transaction`);
|
|
269
|
-
const start = Date.now();
|
|
270
290
|
await this.executor.executeInTransaction(statements);
|
|
271
|
-
|
|
291
|
+
}
|
|
292
|
+
async getAttributes(subjects, predicates, graph) {
|
|
293
|
+
this.ensureOpen();
|
|
294
|
+
if (subjects.length === 0 || predicates.length === 0) {
|
|
295
|
+
return new Map();
|
|
296
|
+
}
|
|
297
|
+
const subjectPlaceholders = subjects.map(() => '?').join(', ');
|
|
298
|
+
const predicatePlaceholders = predicates.map(() => '?').join(', ');
|
|
299
|
+
let sql = `
|
|
300
|
+
SELECT subject, predicate, object
|
|
301
|
+
FROM quints
|
|
302
|
+
WHERE subject IN (${subjectPlaceholders})
|
|
303
|
+
AND predicate IN (${predicatePlaceholders})
|
|
304
|
+
`;
|
|
305
|
+
const params = [...subjects, ...predicates];
|
|
306
|
+
if (graph && graph.termType !== 'DefaultGraph') {
|
|
307
|
+
const graphValue = (0, serialization_1.termToId)(graph);
|
|
308
|
+
sql += ` AND graph = ?`;
|
|
309
|
+
params.push(graphValue);
|
|
310
|
+
}
|
|
311
|
+
const rows = await this.executor.query(sql, params);
|
|
312
|
+
const result = new Map();
|
|
313
|
+
for (const row of rows) {
|
|
314
|
+
if (!result.has(row.subject)) {
|
|
315
|
+
result.set(row.subject, new Map());
|
|
316
|
+
}
|
|
317
|
+
const predicateMap = result.get(row.subject);
|
|
318
|
+
if (!predicateMap.has(row.predicate)) {
|
|
319
|
+
predicateMap.set(row.predicate, []);
|
|
320
|
+
}
|
|
321
|
+
predicateMap.get(row.predicate).push(this.deserializeObject(row.object));
|
|
322
|
+
}
|
|
323
|
+
return result;
|
|
324
|
+
}
|
|
325
|
+
buildWhereClause(pattern) {
|
|
326
|
+
const conditions = [];
|
|
327
|
+
const params = [];
|
|
328
|
+
const predicate = this.extractExactPredicate(pattern.predicate);
|
|
329
|
+
this.addPgCondition(conditions, params, 'graph', pattern.graph);
|
|
330
|
+
this.addPgCondition(conditions, params, 'subject', pattern.subject);
|
|
331
|
+
this.addPgCondition(conditions, params, 'predicate', pattern.predicate);
|
|
332
|
+
if (pattern.object) {
|
|
333
|
+
this.addObjectConditions(conditions, params, undefined, pattern.object, predicate);
|
|
334
|
+
}
|
|
335
|
+
const whereClause = conditions.length > 0 ? ` WHERE ${conditions.join(' AND ')}` : '';
|
|
336
|
+
return { whereClause, params };
|
|
337
|
+
}
|
|
338
|
+
addPgCondition(conditions, params, column, match) {
|
|
339
|
+
if (!match)
|
|
340
|
+
return;
|
|
341
|
+
if (typeof match === 'object' && 'termType' in match) {
|
|
342
|
+
const value = (0, serialization_1.termToId)(match);
|
|
343
|
+
conditions.push(`${column} = ?`);
|
|
344
|
+
params.push(value);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
const ops = match;
|
|
348
|
+
if (ops.$eq !== undefined) {
|
|
349
|
+
const value = this.serializeOpValue(ops.$eq, false, '$eq');
|
|
350
|
+
conditions.push(`${column} = ?`);
|
|
351
|
+
params.push(String(value));
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
super.addConditions(conditions, params, column, match, false);
|
|
355
|
+
}
|
|
356
|
+
addAliasedConditions(conditions, params, alias, pattern) {
|
|
357
|
+
this.addAliasedPgCondition(conditions, params, alias, 'graph', pattern.graph, false);
|
|
358
|
+
this.addAliasedPgCondition(conditions, params, alias, 'subject', pattern.subject, false);
|
|
359
|
+
this.addAliasedPgCondition(conditions, params, alias, 'predicate', pattern.predicate, false);
|
|
360
|
+
this.addAliasedObjectConditions(conditions, params, alias, pattern);
|
|
361
|
+
}
|
|
362
|
+
buildCompoundQuery(compound, options) {
|
|
363
|
+
const { patterns, joinOn, select } = compound;
|
|
364
|
+
const params = [];
|
|
365
|
+
let selectClause = `q0.${joinOn} as join_value`;
|
|
366
|
+
if (select) {
|
|
367
|
+
for (const s of select) {
|
|
368
|
+
selectClause += `, q${s.pattern}.${s.field} as ${s.alias}`;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
else {
|
|
372
|
+
for (let i = 0; i < patterns.length; i++) {
|
|
373
|
+
selectClause += `, q${i}.object as p${i}_object`;
|
|
374
|
+
selectClause += `, q${i}.predicate as p${i}_predicate`;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
let fromClause = 'quints q0';
|
|
378
|
+
for (let i = 1; i < patterns.length; i++) {
|
|
379
|
+
fromClause += ` JOIN quints q${i} ON q0.${joinOn} = q${i}.${joinOn}`;
|
|
380
|
+
fromClause += ` AND q0.graph = q${i}.graph`;
|
|
381
|
+
}
|
|
382
|
+
const whereParts = [];
|
|
383
|
+
for (let i = 0; i < patterns.length; i++) {
|
|
384
|
+
const pattern = patterns[i];
|
|
385
|
+
const alias = `q${i}`;
|
|
386
|
+
this.addAliasedConditions(whereParts, params, alias, pattern);
|
|
387
|
+
}
|
|
388
|
+
let sql = `SELECT ${selectClause} FROM ${fromClause}`;
|
|
389
|
+
if (whereParts.length > 0) {
|
|
390
|
+
sql += ` WHERE ${whereParts.join(' AND ')}`;
|
|
391
|
+
}
|
|
392
|
+
if (options?.limit) {
|
|
393
|
+
sql += ` LIMIT ?`;
|
|
394
|
+
params.push(options.limit);
|
|
395
|
+
}
|
|
396
|
+
if (options?.offset) {
|
|
397
|
+
sql += ` OFFSET ?`;
|
|
398
|
+
params.push(options.offset);
|
|
399
|
+
}
|
|
400
|
+
return { sql, params };
|
|
401
|
+
}
|
|
402
|
+
buildSelectQuery(pattern, options) {
|
|
403
|
+
const { whereClause, params } = this.buildWhereClause(pattern);
|
|
404
|
+
let sql = `SELECT * FROM quints${whereClause}`;
|
|
405
|
+
if (options?.order && options.order.length > 0) {
|
|
406
|
+
const orderCols = options.order.map(field => {
|
|
407
|
+
if (field === 'object') {
|
|
408
|
+
const objectType = this.resolveObjectDataTypeForPattern(pattern);
|
|
409
|
+
if (objectType === 'longText') {
|
|
410
|
+
throw new Error('ORDER BY object is not supported for longText predicates');
|
|
411
|
+
}
|
|
412
|
+
return 'object_key';
|
|
413
|
+
}
|
|
414
|
+
return field;
|
|
415
|
+
}).join(', ');
|
|
416
|
+
sql += ` ORDER BY ${orderCols}`;
|
|
417
|
+
if (options.reverse) {
|
|
418
|
+
sql += ' DESC';
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
if (options?.limit) {
|
|
422
|
+
sql += ` LIMIT ?`;
|
|
423
|
+
params.push(options.limit);
|
|
424
|
+
}
|
|
425
|
+
if (options?.offset) {
|
|
426
|
+
sql += ` OFFSET ?`;
|
|
427
|
+
params.push(options.offset);
|
|
428
|
+
}
|
|
429
|
+
return { sql, params };
|
|
430
|
+
}
|
|
431
|
+
async ensureTypedObjectSchema() {
|
|
432
|
+
const statements = [
|
|
433
|
+
'ALTER TABLE quints ADD COLUMN IF NOT EXISTS object_kind TEXT',
|
|
434
|
+
'ALTER TABLE quints ADD COLUMN IF NOT EXISTS object_key TEXT',
|
|
435
|
+
'ALTER TABLE quints ADD COLUMN IF NOT EXISTS object_text TEXT',
|
|
436
|
+
'ALTER TABLE quints ADD COLUMN IF NOT EXISTS object_digest TEXT',
|
|
437
|
+
];
|
|
438
|
+
for (const statement of statements) {
|
|
439
|
+
await this.executor.exec(statement);
|
|
440
|
+
}
|
|
441
|
+
// The old Postgres schema indexed complete RDF terms. Long literals can
|
|
442
|
+
// exceed the btree tuple size and surface as 500s while creating containers.
|
|
443
|
+
const obsoleteIndexes = [
|
|
444
|
+
'idx_spog',
|
|
445
|
+
'idx_ogsp',
|
|
446
|
+
'idx_gspo',
|
|
447
|
+
'idx_sopg',
|
|
448
|
+
'idx_pogs',
|
|
449
|
+
'idx_gpos',
|
|
450
|
+
'idx_pg_spog',
|
|
451
|
+
'idx_pg_ogsp',
|
|
452
|
+
'idx_pg_gspo',
|
|
453
|
+
'idx_pg_sopg',
|
|
454
|
+
'idx_pg_pogs',
|
|
455
|
+
'idx_pg_gpos',
|
|
456
|
+
'idx_pg_graph_prefix',
|
|
457
|
+
'idx_quints_predicate_object_text',
|
|
458
|
+
'idx_quints_quint_hash',
|
|
459
|
+
'idx_quints_graph_hash',
|
|
460
|
+
'idx_quints_subject_hash',
|
|
461
|
+
'idx_quints_predicate_hash',
|
|
462
|
+
'idx_quints_object_hash',
|
|
463
|
+
'idx_quints_gsp_hash',
|
|
464
|
+
'idx_quints_sp_hash',
|
|
465
|
+
'idx_quints_gp_hash',
|
|
466
|
+
];
|
|
467
|
+
for (const indexName of obsoleteIndexes) {
|
|
468
|
+
await this.executor.exec(`DROP INDEX IF EXISTS ${indexName}`);
|
|
469
|
+
}
|
|
470
|
+
await this.executor.exec('ALTER TABLE quints DROP CONSTRAINT IF EXISTS quints_pkey');
|
|
471
|
+
const obsoleteColumns = [
|
|
472
|
+
'quint_hash',
|
|
473
|
+
'graph_hash',
|
|
474
|
+
'subject_hash',
|
|
475
|
+
'predicate_hash',
|
|
476
|
+
'object_hash',
|
|
477
|
+
];
|
|
478
|
+
for (const columnName of obsoleteColumns) {
|
|
479
|
+
await this.executor.exec(`ALTER TABLE quints DROP COLUMN IF EXISTS ${columnName}`);
|
|
480
|
+
}
|
|
481
|
+
await this.backfillMissingObjectIndexFields();
|
|
482
|
+
await this.executor.exec(`
|
|
483
|
+
UPDATE quints
|
|
484
|
+
SET object_kind = 'text'
|
|
485
|
+
WHERE object_kind = 'shortText'
|
|
486
|
+
`);
|
|
487
|
+
}
|
|
488
|
+
async backfillMissingObjectIndexFields() {
|
|
489
|
+
const rows = await this.executor.query(`
|
|
490
|
+
SELECT graph, subject, predicate, object
|
|
491
|
+
FROM quints
|
|
492
|
+
WHERE object_kind IS NULL
|
|
493
|
+
OR (object_key IS NULL AND object_digest IS NULL)
|
|
494
|
+
`);
|
|
495
|
+
for (const row of rows) {
|
|
496
|
+
const objectIndex = this.objectIndexForSerialized(row.predicate, row.object);
|
|
497
|
+
await this.executor.execute(`
|
|
498
|
+
UPDATE quints
|
|
499
|
+
SET object_kind = $1,
|
|
500
|
+
object_key = $2,
|
|
501
|
+
object_text = $3,
|
|
502
|
+
object_digest = $4
|
|
503
|
+
WHERE graph = $5
|
|
504
|
+
AND subject = $6
|
|
505
|
+
AND predicate = $7
|
|
506
|
+
AND object = $8
|
|
507
|
+
`, [
|
|
508
|
+
objectIndex.objectKind,
|
|
509
|
+
objectIndex.objectKey,
|
|
510
|
+
objectIndex.objectText,
|
|
511
|
+
this.objectDigestForIndex(row.object, objectIndex),
|
|
512
|
+
row.graph,
|
|
513
|
+
row.subject,
|
|
514
|
+
row.predicate,
|
|
515
|
+
row.object,
|
|
516
|
+
]);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
quintToPgRow(quint) {
|
|
520
|
+
const row = this.quintToRow(quint);
|
|
521
|
+
const objectIndex = this.objectIndexForTerm(row.predicate, quint.object);
|
|
522
|
+
return {
|
|
523
|
+
...row,
|
|
524
|
+
objectKind: objectIndex.objectKind,
|
|
525
|
+
objectKey: objectIndex.objectKey,
|
|
526
|
+
objectText: objectIndex.objectText,
|
|
527
|
+
objectDigest: this.objectDigestForIndex(row.object, objectIndex),
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
objectIndexForTerm(predicate, object) {
|
|
531
|
+
return (0, value_types_1.objectIndexFieldsFromTerm)(object, {
|
|
532
|
+
predicate,
|
|
533
|
+
predicateObjectDataTypes: this.options.predicateObjectDataTypes,
|
|
534
|
+
textMaxBytes: this.options.textMaxBytes,
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
objectIndexForSerialized(predicate, object) {
|
|
538
|
+
return (0, value_types_1.objectIndexFieldsFromSerialized)(object, {
|
|
539
|
+
predicate,
|
|
540
|
+
predicateObjectDataTypes: this.options.predicateObjectDataTypes,
|
|
541
|
+
textMaxBytes: this.options.textMaxBytes,
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
objectDigestForIndex(serialized, fields) {
|
|
545
|
+
return fields.objectKey === null ? digestObject(serialized) : null;
|
|
546
|
+
}
|
|
547
|
+
writeStatementsForRow(row) {
|
|
548
|
+
const params = [
|
|
549
|
+
row.objectKind,
|
|
550
|
+
row.objectKey,
|
|
551
|
+
row.objectText,
|
|
552
|
+
row.objectDigest,
|
|
553
|
+
row.graph,
|
|
554
|
+
row.subject,
|
|
555
|
+
row.predicate,
|
|
556
|
+
row.object,
|
|
557
|
+
row.vector,
|
|
558
|
+
];
|
|
559
|
+
if (row.objectKey !== null) {
|
|
560
|
+
return [{
|
|
561
|
+
sql: `
|
|
562
|
+
INSERT INTO quints (
|
|
563
|
+
object_kind, object_key, object_text, object_digest,
|
|
564
|
+
graph, subject, predicate, object, vector
|
|
565
|
+
)
|
|
566
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
|
567
|
+
ON CONFLICT (graph, subject, predicate, object_kind, object_key)
|
|
568
|
+
WHERE object_key IS NOT NULL
|
|
569
|
+
DO UPDATE SET
|
|
570
|
+
vector = EXCLUDED.vector,
|
|
571
|
+
object_text = EXCLUDED.object_text,
|
|
572
|
+
object_digest = EXCLUDED.object_digest
|
|
573
|
+
WHERE quints.object = EXCLUDED.object
|
|
574
|
+
`,
|
|
575
|
+
params,
|
|
576
|
+
}];
|
|
577
|
+
}
|
|
578
|
+
return [{
|
|
579
|
+
sql: `
|
|
580
|
+
INSERT INTO quints (
|
|
581
|
+
object_kind, object_key, object_text, object_digest,
|
|
582
|
+
graph, subject, predicate, object, vector
|
|
583
|
+
)
|
|
584
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
|
585
|
+
ON CONFLICT (graph, subject, predicate, object_kind, object_digest)
|
|
586
|
+
WHERE object_digest IS NOT NULL
|
|
587
|
+
DO UPDATE SET
|
|
588
|
+
object = CASE WHEN quints.object = EXCLUDED.object THEN EXCLUDED.object ELSE NULL END,
|
|
589
|
+
vector = EXCLUDED.vector,
|
|
590
|
+
object_text = EXCLUDED.object_text
|
|
591
|
+
`,
|
|
592
|
+
params,
|
|
593
|
+
}];
|
|
594
|
+
}
|
|
595
|
+
addObjectConditions(conditions, params, alias, match, predicate) {
|
|
596
|
+
const column = (name) => alias ? `${alias}.${name}` : name;
|
|
597
|
+
if (typeof match === 'object' && 'termType' in match) {
|
|
598
|
+
this.addObjectExactCondition(conditions, params, column, match, predicate);
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
const ops = match;
|
|
602
|
+
if (ops.$eq !== undefined) {
|
|
603
|
+
this.addObjectExactValueCondition(conditions, params, column, ops.$eq, '$eq', predicate);
|
|
604
|
+
}
|
|
605
|
+
if (ops.$ne !== undefined) {
|
|
606
|
+
this.addObjectExactValueCondition(conditions, params, column, ops.$ne, '$ne', predicate);
|
|
607
|
+
}
|
|
608
|
+
if (ops.$gt !== undefined) {
|
|
609
|
+
this.addObjectComparableCondition(conditions, params, column, '>', ops.$gt, '$gt', predicate);
|
|
610
|
+
}
|
|
611
|
+
if (ops.$gte !== undefined) {
|
|
612
|
+
this.addObjectComparableCondition(conditions, params, column, '>=', ops.$gte, '$gte', predicate);
|
|
613
|
+
}
|
|
614
|
+
if (ops.$lt !== undefined) {
|
|
615
|
+
this.addObjectComparableCondition(conditions, params, column, '<', ops.$lt, '$lt', predicate);
|
|
616
|
+
}
|
|
617
|
+
if (ops.$lte !== undefined) {
|
|
618
|
+
this.addObjectComparableCondition(conditions, params, column, '<=', ops.$lte, '$lte', predicate);
|
|
619
|
+
}
|
|
620
|
+
if (ops.$in !== undefined && ops.$in.length > 0) {
|
|
621
|
+
const predicates = ops.$in.map((value) => this.objectPredicateForOperatorValue(value, '$in', predicate));
|
|
622
|
+
const placeholders = predicates.map((item) => {
|
|
623
|
+
if (item.fields.objectKey === null) {
|
|
624
|
+
return `(${column('object_digest')} = ? AND ${column('object')} = ?)`;
|
|
625
|
+
}
|
|
626
|
+
return `(${column('object_kind')} = ? AND ${column('object_key')} = ?)`;
|
|
627
|
+
}).join(' OR ');
|
|
628
|
+
conditions.push(`(${placeholders})`);
|
|
629
|
+
for (const item of predicates) {
|
|
630
|
+
if (item.fields.objectKey === null) {
|
|
631
|
+
params.push(this.objectDigestForIndex(item.serialized, item.fields), item.serialized);
|
|
632
|
+
}
|
|
633
|
+
else {
|
|
634
|
+
params.push(item.fields.objectKind, item.fields.objectKey);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
if (ops.$notIn !== undefined && ops.$notIn.length > 0) {
|
|
639
|
+
const values = ops.$notIn.map((value) => this.objectPredicateForOperatorValue(value, '$notIn', predicate).serialized);
|
|
640
|
+
for (const value of values) {
|
|
641
|
+
conditions.push(`${column('object')} != ?`);
|
|
642
|
+
params.push(value);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
if (ops.$startsWith !== undefined) {
|
|
646
|
+
const fields = this.objectFieldsForPrefix(ops.$startsWith, predicate);
|
|
647
|
+
this.assertComparableObject(fields, '$startsWith');
|
|
648
|
+
conditions.push(`${column('object_kind')} = ?`);
|
|
649
|
+
params.push(fields.objectKind);
|
|
650
|
+
conditions.push(`${column('object_key')} >= ? AND ${column('object_key')} < ?`);
|
|
651
|
+
params.push(ops.$startsWith, ops.$startsWith + '\uffff');
|
|
652
|
+
}
|
|
653
|
+
if (ops.$endsWith !== undefined) {
|
|
654
|
+
this.addObjectTextCondition(conditions, params, column, 'LIKE', `%${ops.$endsWith}`, predicate);
|
|
655
|
+
}
|
|
656
|
+
if (ops.$contains !== undefined) {
|
|
657
|
+
this.addObjectTextCondition(conditions, params, column, 'LIKE', `%${ops.$contains}%`, predicate);
|
|
658
|
+
}
|
|
659
|
+
if (ops.$regex !== undefined) {
|
|
660
|
+
const pattern = ops.$regex.replace(/\.\*/g, '%').replace(/\./g, '_');
|
|
661
|
+
this.addObjectTextCondition(conditions, params, column, 'LIKE', pattern, predicate);
|
|
662
|
+
}
|
|
663
|
+
if (ops.$isNull === true) {
|
|
664
|
+
conditions.push(`${column('object')} IS NULL`);
|
|
665
|
+
}
|
|
666
|
+
if (ops.$isNull === false) {
|
|
667
|
+
conditions.push(`${column('object')} IS NOT NULL`);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
addObjectExactCondition(conditions, params, column, object, predicate) {
|
|
671
|
+
const serialized = (0, serialization_1.serializeObject)(object);
|
|
672
|
+
const fields = this.objectFieldsForTerm(object, predicate);
|
|
673
|
+
this.addObjectExactSerializedCondition(conditions, params, column, serialized, fields);
|
|
674
|
+
}
|
|
675
|
+
addObjectExactValueCondition(conditions, params, column, value, op, predicate) {
|
|
676
|
+
const item = this.objectPredicateForOperatorValue(value, op, predicate);
|
|
677
|
+
if (op === '$ne') {
|
|
678
|
+
conditions.push(`${column('object')} != ?`);
|
|
679
|
+
params.push(item.serialized);
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
this.addObjectExactSerializedCondition(conditions, params, column, item.serialized, item.fields);
|
|
683
|
+
}
|
|
684
|
+
addObjectExactSerializedCondition(conditions, params, column, serialized, fields) {
|
|
685
|
+
if (fields.objectKey !== null) {
|
|
686
|
+
conditions.push(`${column('object_kind')} = ?`);
|
|
687
|
+
params.push(fields.objectKind);
|
|
688
|
+
conditions.push(`${column('object_key')} = ?`);
|
|
689
|
+
params.push(fields.objectKey);
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
conditions.push(`${column('object_digest')} = ?`);
|
|
693
|
+
params.push(this.objectDigestForIndex(serialized, fields));
|
|
694
|
+
conditions.push(`${column('object')} = ?`);
|
|
695
|
+
params.push(serialized);
|
|
696
|
+
}
|
|
697
|
+
addObjectComparableCondition(conditions, params, column, sqlOperator, value, op, predicate) {
|
|
698
|
+
const item = this.objectPredicateForOperatorValue(value, op, predicate);
|
|
699
|
+
this.assertComparableObject(item.fields, op);
|
|
700
|
+
conditions.push(`${column('object_kind')} = ?`);
|
|
701
|
+
params.push(item.fields.objectKind);
|
|
702
|
+
conditions.push(`${column('object_key')} ${sqlOperator} ?`);
|
|
703
|
+
params.push(item.serialized);
|
|
704
|
+
}
|
|
705
|
+
addObjectTextCondition(conditions, params, column, sqlOperator, value, predicate) {
|
|
706
|
+
const declaredType = (0, value_types_1.getPredicateObjectDataType)(predicate, this.options.predicateObjectDataTypes);
|
|
707
|
+
if (declaredType) {
|
|
708
|
+
if (!['text', 'longText', 'literal'].includes(declaredType)) {
|
|
709
|
+
throw new Error(`Object text search is not supported for ${declaredType}`);
|
|
710
|
+
}
|
|
711
|
+
conditions.push(`${column('object_kind')} = ?`);
|
|
712
|
+
params.push(declaredType);
|
|
713
|
+
}
|
|
714
|
+
conditions.push(`${column('object_text')} ${sqlOperator} ?`);
|
|
715
|
+
params.push(value);
|
|
716
|
+
}
|
|
717
|
+
objectPredicateForOperatorValue(value, op, predicate) {
|
|
718
|
+
const serialized = this.serializeOpValue(value, true, op);
|
|
719
|
+
return {
|
|
720
|
+
serialized,
|
|
721
|
+
fields: this.objectFieldsForSerialized(serialized, predicate),
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
objectFieldsForTerm(object, predicate) {
|
|
725
|
+
return (0, value_types_1.objectIndexFieldsFromTerm)(object, {
|
|
726
|
+
predicate,
|
|
727
|
+
predicateObjectDataTypes: this.options.predicateObjectDataTypes,
|
|
728
|
+
textMaxBytes: this.options.textMaxBytes,
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
objectFieldsForSerialized(serialized, predicate) {
|
|
732
|
+
return (0, value_types_1.objectIndexFieldsFromSerialized)(serialized, {
|
|
733
|
+
predicate,
|
|
734
|
+
predicateObjectDataTypes: this.options.predicateObjectDataTypes,
|
|
735
|
+
textMaxBytes: this.options.textMaxBytes,
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
objectFieldsForPrefix(prefix, predicate) {
|
|
739
|
+
const declaredType = (0, value_types_1.getPredicateObjectDataType)(predicate, this.options.predicateObjectDataTypes);
|
|
740
|
+
if (declaredType) {
|
|
741
|
+
if (declaredType === 'longText') {
|
|
742
|
+
return { objectKind: 'longText', objectKey: null, objectText: prefix };
|
|
743
|
+
}
|
|
744
|
+
return { objectKind: declaredType, objectKey: prefix, objectText: null };
|
|
745
|
+
}
|
|
746
|
+
if ((0, serialization_1.isSerializedNumericLiteral)(prefix)) {
|
|
747
|
+
return { objectKind: 'numeric', objectKey: prefix, objectText: null };
|
|
748
|
+
}
|
|
749
|
+
if ((0, serialization_1.isSerializedDateTimeLiteral)(prefix)) {
|
|
750
|
+
return { objectKind: 'dateTime', objectKey: prefix, objectText: null };
|
|
751
|
+
}
|
|
752
|
+
if (prefix.startsWith('"')) {
|
|
753
|
+
return { objectKind: 'text', objectKey: prefix, objectText: null };
|
|
754
|
+
}
|
|
755
|
+
if (prefix.startsWith('_:')) {
|
|
756
|
+
return { objectKind: 'blankNode', objectKey: prefix, objectText: null };
|
|
757
|
+
}
|
|
758
|
+
return { objectKind: 'iri', objectKey: prefix, objectText: null };
|
|
759
|
+
}
|
|
760
|
+
assertComparableObject(fields, op) {
|
|
761
|
+
if (fields.objectKey !== null && fields.objectKind !== 'longText') {
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
throw new Error(`Object ${op} is not supported for ${fields.objectKind}; declare/use a comparable data type instead of longText`);
|
|
765
|
+
}
|
|
766
|
+
addAliasedObjectConditions(conditions, params, alias, pattern) {
|
|
767
|
+
if (!pattern.object)
|
|
768
|
+
return;
|
|
769
|
+
this.addObjectConditions(conditions, params, alias, pattern.object, this.extractExactPredicate(pattern.predicate));
|
|
770
|
+
}
|
|
771
|
+
extractExactPredicate(match) {
|
|
772
|
+
if (!match)
|
|
773
|
+
return undefined;
|
|
774
|
+
if (typeof match === 'object' && 'termType' in match) {
|
|
775
|
+
return (0, serialization_1.termToId)(match);
|
|
776
|
+
}
|
|
777
|
+
const ops = match;
|
|
778
|
+
if (ops.$eq !== undefined) {
|
|
779
|
+
return String(this.serializeOpValue(ops.$eq, false, '$eq'));
|
|
780
|
+
}
|
|
781
|
+
return undefined;
|
|
782
|
+
}
|
|
783
|
+
resolveObjectDataTypeForPattern(pattern) {
|
|
784
|
+
const predicate = this.extractExactPredicate(pattern.predicate);
|
|
785
|
+
if (predicate) {
|
|
786
|
+
return (0, value_types_1.getPredicateObjectDataType)(predicate, this.options.predicateObjectDataTypes);
|
|
787
|
+
}
|
|
788
|
+
if (pattern.object && typeof pattern.object === 'object' && 'termType' in pattern.object) {
|
|
789
|
+
return this.objectFieldsForTerm(pattern.object, predicate).objectKind;
|
|
790
|
+
}
|
|
791
|
+
return undefined;
|
|
792
|
+
}
|
|
793
|
+
addAliasedPgCondition(conditions, params, alias, column, match, isObject) {
|
|
794
|
+
if (!match)
|
|
795
|
+
return;
|
|
796
|
+
if (isObject) {
|
|
797
|
+
this.addObjectConditions(conditions, params, alias, match, undefined);
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
if (typeof match === 'object' && 'termType' in match) {
|
|
801
|
+
const value = (0, serialization_1.termToId)(match);
|
|
802
|
+
conditions.push(`${alias}.${column} = ?`);
|
|
803
|
+
params.push(value);
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
if (match.$eq !== undefined) {
|
|
807
|
+
const value = this.serializeOpValue(match.$eq, false, '$eq');
|
|
808
|
+
conditions.push(`${alias}.${column} = ?`);
|
|
809
|
+
params.push(value);
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
this.addFallbackAliasedCondition(conditions, params, alias, column, match, isObject);
|
|
813
|
+
}
|
|
814
|
+
addFallbackAliasedCondition(conditions, params, alias, column, match, isObject) {
|
|
815
|
+
if (isObject) {
|
|
816
|
+
this.addObjectConditions(conditions, params, alias, match, undefined);
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
if (match.$gt !== undefined) {
|
|
820
|
+
conditions.push(`${alias}.${column} > ?`);
|
|
821
|
+
params.push(this.serializeOpValue(match.$gt, false, '$gt'));
|
|
822
|
+
}
|
|
823
|
+
if (match.$gte !== undefined) {
|
|
824
|
+
conditions.push(`${alias}.${column} >= ?`);
|
|
825
|
+
params.push(this.serializeOpValue(match.$gte, false, '$gte'));
|
|
826
|
+
}
|
|
827
|
+
if (match.$lt !== undefined) {
|
|
828
|
+
conditions.push(`${alias}.${column} < ?`);
|
|
829
|
+
params.push(this.serializeOpValue(match.$lt, false, '$lt'));
|
|
830
|
+
}
|
|
831
|
+
if (match.$lte !== undefined) {
|
|
832
|
+
conditions.push(`${alias}.${column} <= ?`);
|
|
833
|
+
params.push(this.serializeOpValue(match.$lte, false, '$lte'));
|
|
834
|
+
}
|
|
835
|
+
if (match.$in !== undefined && match.$in.length > 0) {
|
|
836
|
+
const placeholders = match.$in.map(() => '?').join(', ');
|
|
837
|
+
conditions.push(`${alias}.${column} IN (${placeholders})`);
|
|
838
|
+
params.push(...match.$in.map((value) => this.serializeOpValue(value, false, '$in')));
|
|
839
|
+
}
|
|
272
840
|
}
|
|
273
841
|
}
|
|
274
842
|
exports.PgQuintStore = PgQuintStore;
|