@undefineds.co/xpod 0.2.43 → 0.2.45

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.
@@ -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
  */
@@ -157,6 +163,7 @@ class PgQuintStore extends BaseQuintStore_1.BaseQuintStore {
157
163
  super(options);
158
164
  this.pglite = null;
159
165
  this.pgPool = null; // pg.Pool
166
+ this.sharedPoolConfig = null;
160
167
  this.pgOptions = {
161
168
  driver: 'pglite', // 默认使用 PGLite
162
169
  ...options,
@@ -167,17 +174,19 @@ class PgQuintStore extends BaseQuintStore_1.BaseQuintStore {
167
174
  // 使用共享的连接池(如果提供),避免死锁
168
175
  if (this.pgOptions.pool) {
169
176
  this.pgPool = this.pgOptions.pool;
177
+ this.sharedPoolConfig = null;
170
178
  return new PgExecutor(this.pgPool);
171
179
  }
172
180
  // 使用共享连接池管理器,避免多个组件创建独立连接池
173
- this.pgPool = (0, PostgresPoolManager_1.getSharedPool)({
181
+ this.sharedPoolConfig = {
174
182
  connectionString: this.pgOptions.connectionString,
175
183
  host: this.pgOptions.host,
176
184
  port: this.pgOptions.port,
177
185
  database: this.pgOptions.database,
178
186
  user: this.pgOptions.user,
179
187
  password: this.pgOptions.password,
180
- });
188
+ };
189
+ this.pgPool = (0, PostgresPoolManager_1.getSharedPool)(this.sharedPoolConfig);
181
190
  return new PgExecutor(this.pgPool);
182
191
  }
183
192
  else {
@@ -193,8 +202,14 @@ class PgQuintStore extends BaseQuintStore_1.BaseQuintStore {
193
202
  this.pglite = null;
194
203
  }
195
204
  if (this.pgPool) {
196
- await this.pgPool.end();
205
+ if (this.sharedPoolConfig) {
206
+ (0, PostgresPoolManager_1.releaseSharedPool)(this.sharedPoolConfig);
207
+ }
208
+ else {
209
+ await this.pgPool.end();
210
+ }
197
211
  this.pgPool = null;
212
+ this.sharedPoolConfig = null;
198
213
  }
199
214
  }
200
215
  /**
@@ -208,43 +223,45 @@ class PgQuintStore extends BaseQuintStore_1.BaseQuintStore {
208
223
  // PostgreSQL 建表语法
209
224
  await this.executor.exec(`
210
225
  CREATE TABLE IF NOT EXISTS quints (
226
+ object_kind TEXT,
227
+ object_key TEXT,
228
+ object_text TEXT,
229
+ object_digest TEXT,
211
230
  graph TEXT NOT NULL,
212
231
  subject TEXT NOT NULL,
213
232
  predicate TEXT NOT NULL,
214
233
  object TEXT NOT NULL,
215
- vector TEXT,
216
- PRIMARY KEY (graph, subject, predicate, object)
234
+ vector TEXT
217
235
  )
218
236
  `);
237
+ await this.ensureTypedObjectSchema();
219
238
  const indexes = [
220
- 'CREATE INDEX IF NOT EXISTS idx_spog ON quints (subject, predicate, object, graph)',
221
- 'CREATE INDEX IF NOT EXISTS idx_ogsp ON quints (object, graph, subject, predicate)',
222
- 'CREATE INDEX IF NOT EXISTS idx_gspo ON quints (graph, subject, predicate, object)',
223
- 'CREATE INDEX IF NOT EXISTS idx_sopg ON quints (subject, object, predicate, graph)',
224
- 'CREATE INDEX IF NOT EXISTS idx_pogs ON quints (predicate, object, graph, subject)',
225
- 'CREATE INDEX IF NOT EXISTS idx_gpos ON quints (graph, predicate, object, subject)',
239
+ 'CREATE INDEX IF NOT EXISTS idx_quints_graph ON quints (graph)',
240
+ 'CREATE INDEX IF NOT EXISTS idx_quints_subject ON quints (subject)',
241
+ 'CREATE INDEX IF NOT EXISTS idx_quints_predicate ON quints (predicate)',
242
+ 'CREATE INDEX IF NOT EXISTS idx_quints_object_key ON quints (object_kind, object_key)',
243
+ 'CREATE INDEX IF NOT EXISTS idx_quints_predicate_object_key ON quints (predicate, object_kind, object_key)',
244
+ 'CREATE INDEX IF NOT EXISTS idx_quints_predicate_object_digest ON quints (predicate, object_kind, object_digest)',
245
+ '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',
246
+ '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',
247
+ 'CREATE INDEX IF NOT EXISTS idx_quints_gsp ON quints (graph, subject, predicate)',
248
+ 'CREATE INDEX IF NOT EXISTS idx_quints_sp ON quints (subject, predicate)',
249
+ 'CREATE INDEX IF NOT EXISTS idx_quints_gp ON quints (graph, predicate)',
226
250
  ];
227
251
  for (const indexSql of indexes) {
228
252
  await this.executor.exec(indexSql);
229
253
  }
230
254
  }
231
255
  /**
232
- * 重写 put 方法,使用 PostgreSQL ON CONFLICT
256
+ * 重写 put 方法,避免长文本对象进入 PostgreSQL btree 唯一键
233
257
  */
234
258
  async put(quint) {
235
259
  this.ensureOpen();
236
- const row = this.quintToRow(quint);
237
- // PostgreSQL UPSERT 语法
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]);
260
+ const row = this.quintToPgRow(quint);
261
+ await this.executor.executeInTransaction(this.writeStatementsForRow(row));
245
262
  }
246
263
  /**
247
- * 重写 multiPut 方法,使用 PostgreSQL 的 ON CONFLICT
264
+ * 重写 multiPut 方法,使用同一事务保持批量写入幂等
248
265
  */
249
266
  async multiPut(quintList) {
250
267
  console.log(`[PgQuintStore.multiPut] Starting: ${quintList.length} quints`);
@@ -253,22 +270,651 @@ class PgQuintStore extends BaseQuintStore_1.BaseQuintStore {
253
270
  console.log(`[PgQuintStore.multiPut] Empty list, skipping`);
254
271
  return;
255
272
  }
273
+ const statements = quintList.flatMap(quint => {
274
+ const row = this.quintToPgRow(quint);
275
+ return this.writeStatementsForRow(row);
276
+ });
277
+ console.log(`[PgQuintStore.multiPut] Executing ${statements.length} statements in transaction`);
278
+ const start = Date.now();
279
+ await this.executor.executeInTransaction(statements);
280
+ console.log(`[PgQuintStore.multiPut] Completed in ${Date.now() - start}ms`);
281
+ }
282
+ async multiDel(quintList) {
283
+ this.ensureOpen();
284
+ if (quintList.length === 0)
285
+ return;
256
286
  const statements = quintList.map(quint => {
257
- const row = this.quintToRow(quint);
287
+ const row = this.quintToPgRow(quint);
258
288
  return {
259
289
  sql: `
260
- INSERT INTO quints (graph, subject, predicate, object, vector)
261
- VALUES ($1, $2, $3, $4, $5)
262
- ON CONFLICT (graph, subject, predicate, object)
263
- DO UPDATE SET vector = EXCLUDED.vector
290
+ DELETE FROM quints
291
+ WHERE graph = $1
292
+ AND subject = $2
293
+ AND predicate = $3
294
+ AND object = $4
264
295
  `,
265
- params: [row.graph, row.subject, row.predicate, row.object, row.vector],
296
+ params: [row.graph, row.subject, row.predicate, row.object],
266
297
  };
267
298
  });
268
- console.log(`[PgQuintStore.multiPut] Executing ${statements.length} statements in transaction`);
269
- const start = Date.now();
270
299
  await this.executor.executeInTransaction(statements);
271
- console.log(`[PgQuintStore.multiPut] Completed in ${Date.now() - start}ms`);
300
+ }
301
+ async getAttributes(subjects, predicates, graph) {
302
+ this.ensureOpen();
303
+ if (subjects.length === 0 || predicates.length === 0) {
304
+ return new Map();
305
+ }
306
+ const subjectPlaceholders = subjects.map(() => '?').join(', ');
307
+ const predicatePlaceholders = predicates.map(() => '?').join(', ');
308
+ let sql = `
309
+ SELECT subject, predicate, object
310
+ FROM quints
311
+ WHERE subject IN (${subjectPlaceholders})
312
+ AND predicate IN (${predicatePlaceholders})
313
+ `;
314
+ const params = [...subjects, ...predicates];
315
+ if (graph && graph.termType !== 'DefaultGraph') {
316
+ const graphValue = (0, serialization_1.termToId)(graph);
317
+ sql += ` AND graph = ?`;
318
+ params.push(graphValue);
319
+ }
320
+ const rows = await this.executor.query(sql, params);
321
+ const result = new Map();
322
+ for (const row of rows) {
323
+ if (!result.has(row.subject)) {
324
+ result.set(row.subject, new Map());
325
+ }
326
+ const predicateMap = result.get(row.subject);
327
+ if (!predicateMap.has(row.predicate)) {
328
+ predicateMap.set(row.predicate, []);
329
+ }
330
+ predicateMap.get(row.predicate).push(this.deserializeObject(row.object));
331
+ }
332
+ return result;
333
+ }
334
+ buildWhereClause(pattern) {
335
+ const conditions = [];
336
+ const params = [];
337
+ const predicate = this.extractExactPredicate(pattern.predicate);
338
+ this.addPgCondition(conditions, params, 'graph', pattern.graph);
339
+ this.addPgCondition(conditions, params, 'subject', pattern.subject);
340
+ this.addPgCondition(conditions, params, 'predicate', pattern.predicate);
341
+ if (pattern.object) {
342
+ this.addObjectConditions(conditions, params, undefined, pattern.object, predicate);
343
+ }
344
+ const whereClause = conditions.length > 0 ? ` WHERE ${conditions.join(' AND ')}` : '';
345
+ return { whereClause, params };
346
+ }
347
+ addPgCondition(conditions, params, column, match) {
348
+ if (!match)
349
+ return;
350
+ if (typeof match === 'object' && 'termType' in match) {
351
+ const value = (0, serialization_1.termToId)(match);
352
+ conditions.push(`${column} = ?`);
353
+ params.push(value);
354
+ return;
355
+ }
356
+ const ops = match;
357
+ if (ops.$eq !== undefined) {
358
+ const value = this.serializeOpValue(ops.$eq, false, '$eq');
359
+ conditions.push(`${column} = ?`);
360
+ params.push(String(value));
361
+ return;
362
+ }
363
+ super.addConditions(conditions, params, column, match, false);
364
+ }
365
+ addAliasedConditions(conditions, params, alias, pattern) {
366
+ this.addAliasedPgCondition(conditions, params, alias, 'graph', pattern.graph, false);
367
+ this.addAliasedPgCondition(conditions, params, alias, 'subject', pattern.subject, false);
368
+ this.addAliasedPgCondition(conditions, params, alias, 'predicate', pattern.predicate, false);
369
+ this.addAliasedObjectConditions(conditions, params, alias, pattern);
370
+ }
371
+ buildCompoundQuery(compound, options) {
372
+ const { patterns, joinOn, select } = compound;
373
+ const params = [];
374
+ let selectClause = `q0.${joinOn} as join_value`;
375
+ if (select) {
376
+ for (const s of select) {
377
+ selectClause += `, q${s.pattern}.${s.field} as ${s.alias}`;
378
+ }
379
+ }
380
+ else {
381
+ for (let i = 0; i < patterns.length; i++) {
382
+ selectClause += `, q${i}.object as p${i}_object`;
383
+ selectClause += `, q${i}.predicate as p${i}_predicate`;
384
+ }
385
+ }
386
+ let fromClause = 'quints q0';
387
+ for (let i = 1; i < patterns.length; i++) {
388
+ fromClause += ` JOIN quints q${i} ON q0.${joinOn} = q${i}.${joinOn}`;
389
+ fromClause += ` AND q0.graph = q${i}.graph`;
390
+ }
391
+ const whereParts = [];
392
+ for (let i = 0; i < patterns.length; i++) {
393
+ const pattern = patterns[i];
394
+ const alias = `q${i}`;
395
+ this.addAliasedConditions(whereParts, params, alias, pattern);
396
+ }
397
+ let sql = `SELECT ${selectClause} FROM ${fromClause}`;
398
+ if (whereParts.length > 0) {
399
+ sql += ` WHERE ${whereParts.join(' AND ')}`;
400
+ }
401
+ if (options?.limit) {
402
+ sql += ` LIMIT ?`;
403
+ params.push(options.limit);
404
+ }
405
+ if (options?.offset) {
406
+ sql += ` OFFSET ?`;
407
+ params.push(options.offset);
408
+ }
409
+ return { sql, params };
410
+ }
411
+ buildSelectQuery(pattern, options) {
412
+ const { whereClause, params } = this.buildWhereClause(pattern);
413
+ let sql = `SELECT * FROM quints${whereClause}`;
414
+ if (options?.order && options.order.length > 0) {
415
+ const orderCols = options.order.map(field => {
416
+ if (field === 'object') {
417
+ const objectType = this.resolveObjectDataTypeForPattern(pattern);
418
+ if (objectType === 'longText') {
419
+ throw new Error('ORDER BY object is not supported for longText predicates');
420
+ }
421
+ return 'object_key';
422
+ }
423
+ return field;
424
+ }).join(', ');
425
+ sql += ` ORDER BY ${orderCols}`;
426
+ if (options.reverse) {
427
+ sql += ' DESC';
428
+ }
429
+ }
430
+ if (options?.limit) {
431
+ sql += ` LIMIT ?`;
432
+ params.push(options.limit);
433
+ }
434
+ if (options?.offset) {
435
+ sql += ` OFFSET ?`;
436
+ params.push(options.offset);
437
+ }
438
+ return { sql, params };
439
+ }
440
+ async count(pattern) {
441
+ const count = await super.count(pattern);
442
+ return Number(count);
443
+ }
444
+ async stats() {
445
+ const stats = await super.stats();
446
+ return {
447
+ totalCount: Number(stats.totalCount),
448
+ vectorCount: Number(stats.vectorCount),
449
+ graphCount: Number(stats.graphCount),
450
+ };
451
+ }
452
+ async ensureTypedObjectSchema() {
453
+ const statements = [
454
+ 'ALTER TABLE quints ADD COLUMN IF NOT EXISTS object_kind TEXT',
455
+ 'ALTER TABLE quints ADD COLUMN IF NOT EXISTS object_key TEXT',
456
+ 'ALTER TABLE quints ADD COLUMN IF NOT EXISTS object_text TEXT',
457
+ 'ALTER TABLE quints ADD COLUMN IF NOT EXISTS object_digest TEXT',
458
+ ];
459
+ for (const statement of statements) {
460
+ await this.executor.exec(statement);
461
+ }
462
+ // The old Postgres schema indexed complete RDF terms. Long literals can
463
+ // exceed the btree tuple size and surface as 500s while creating containers.
464
+ const obsoleteIndexes = [
465
+ 'idx_spog',
466
+ 'idx_ogsp',
467
+ 'idx_gspo',
468
+ 'idx_sopg',
469
+ 'idx_pogs',
470
+ 'idx_gpos',
471
+ 'idx_pg_spog',
472
+ 'idx_pg_ogsp',
473
+ 'idx_pg_gspo',
474
+ 'idx_pg_sopg',
475
+ 'idx_pg_pogs',
476
+ 'idx_pg_gpos',
477
+ 'idx_pg_graph_prefix',
478
+ 'idx_quints_predicate_object_text',
479
+ 'idx_quints_quint_hash',
480
+ 'idx_quints_graph_hash',
481
+ 'idx_quints_subject_hash',
482
+ 'idx_quints_predicate_hash',
483
+ 'idx_quints_object_hash',
484
+ 'idx_quints_gsp_hash',
485
+ 'idx_quints_sp_hash',
486
+ 'idx_quints_gp_hash',
487
+ ];
488
+ for (const indexName of obsoleteIndexes) {
489
+ await this.executor.exec(`DROP INDEX IF EXISTS ${indexName}`);
490
+ }
491
+ await this.executor.exec('ALTER TABLE quints DROP CONSTRAINT IF EXISTS quints_pkey');
492
+ const obsoleteColumns = [
493
+ 'quint_hash',
494
+ 'graph_hash',
495
+ 'subject_hash',
496
+ 'predicate_hash',
497
+ 'object_hash',
498
+ ];
499
+ for (const columnName of obsoleteColumns) {
500
+ await this.executor.exec(`ALTER TABLE quints DROP COLUMN IF EXISTS ${columnName}`);
501
+ }
502
+ await this.backfillMissingObjectIndexFields();
503
+ await this.executor.exec(`
504
+ UPDATE quints
505
+ SET object_kind = 'text'
506
+ WHERE object_kind = 'shortText'
507
+ `);
508
+ }
509
+ async backfillMissingObjectIndexFields() {
510
+ const rows = await this.executor.query(`
511
+ SELECT graph, subject, predicate, object
512
+ FROM quints
513
+ WHERE object_kind IS NULL
514
+ OR (object_key IS NULL AND object_digest IS NULL)
515
+ `);
516
+ for (const row of rows) {
517
+ const objectIndex = this.objectIndexForSerialized(row.predicate, row.object);
518
+ await this.executor.execute(`
519
+ UPDATE quints
520
+ SET object_kind = $1,
521
+ object_key = $2,
522
+ object_text = $3,
523
+ object_digest = $4
524
+ WHERE graph = $5
525
+ AND subject = $6
526
+ AND predicate = $7
527
+ AND object = $8
528
+ `, [
529
+ objectIndex.objectKind,
530
+ objectIndex.objectKey,
531
+ objectIndex.objectText,
532
+ this.objectDigestForIndex(row.object, objectIndex),
533
+ row.graph,
534
+ row.subject,
535
+ row.predicate,
536
+ row.object,
537
+ ]);
538
+ }
539
+ }
540
+ quintToPgRow(quint) {
541
+ const row = this.quintToRow(quint);
542
+ const objectIndex = this.objectIndexForTerm(row.predicate, quint.object);
543
+ return {
544
+ ...row,
545
+ objectKind: objectIndex.objectKind,
546
+ objectKey: objectIndex.objectKey,
547
+ objectText: objectIndex.objectText,
548
+ objectDigest: this.objectDigestForIndex(row.object, objectIndex),
549
+ };
550
+ }
551
+ objectIndexForTerm(predicate, object) {
552
+ return (0, value_types_1.objectIndexFieldsFromTerm)(object, {
553
+ predicate,
554
+ predicateObjectDataTypes: this.options.predicateObjectDataTypes,
555
+ textMaxBytes: this.options.textMaxBytes,
556
+ });
557
+ }
558
+ objectIndexForSerialized(predicate, object) {
559
+ return (0, value_types_1.objectIndexFieldsFromSerialized)(object, {
560
+ predicate,
561
+ predicateObjectDataTypes: this.options.predicateObjectDataTypes,
562
+ textMaxBytes: this.options.textMaxBytes,
563
+ });
564
+ }
565
+ objectDigestForIndex(serialized, fields) {
566
+ return fields.objectKey === null ? digestObject(serialized) : null;
567
+ }
568
+ writeStatementsForRow(row) {
569
+ const params = [
570
+ row.objectKind,
571
+ row.objectKey,
572
+ row.objectText,
573
+ row.objectDigest,
574
+ row.graph,
575
+ row.subject,
576
+ row.predicate,
577
+ row.object,
578
+ row.vector,
579
+ ];
580
+ if (row.objectKey !== null) {
581
+ return [{
582
+ sql: `
583
+ INSERT INTO quints (
584
+ object_kind, object_key, object_text, object_digest,
585
+ graph, subject, predicate, object, vector
586
+ )
587
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
588
+ ON CONFLICT (graph, subject, predicate, object_kind, object_key)
589
+ WHERE object_key IS NOT NULL
590
+ DO UPDATE SET
591
+ vector = EXCLUDED.vector,
592
+ object_text = EXCLUDED.object_text,
593
+ object_digest = EXCLUDED.object_digest
594
+ WHERE quints.object = EXCLUDED.object
595
+ `,
596
+ params,
597
+ }];
598
+ }
599
+ return [{
600
+ sql: `
601
+ INSERT INTO quints (
602
+ object_kind, object_key, object_text, object_digest,
603
+ graph, subject, predicate, object, vector
604
+ )
605
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
606
+ ON CONFLICT (graph, subject, predicate, object_kind, object_digest)
607
+ WHERE object_digest IS NOT NULL
608
+ DO UPDATE SET
609
+ object = CASE WHEN quints.object = EXCLUDED.object THEN EXCLUDED.object ELSE NULL END,
610
+ vector = EXCLUDED.vector,
611
+ object_text = EXCLUDED.object_text
612
+ `,
613
+ params,
614
+ }];
615
+ }
616
+ addObjectConditions(conditions, params, alias, match, predicate) {
617
+ const column = (name) => alias ? `${alias}.${name}` : name;
618
+ if (typeof match === 'object' && 'termType' in match) {
619
+ this.addObjectExactCondition(conditions, params, column, match, predicate);
620
+ return;
621
+ }
622
+ const ops = match;
623
+ if (ops.$eq !== undefined) {
624
+ this.addObjectExactValueCondition(conditions, params, column, ops.$eq, '$eq', predicate);
625
+ }
626
+ if (ops.$ne !== undefined) {
627
+ this.addObjectExactValueCondition(conditions, params, column, ops.$ne, '$ne', predicate);
628
+ }
629
+ if (ops.$gt !== undefined) {
630
+ this.addObjectComparableCondition(conditions, params, column, '>', ops.$gt, '$gt', predicate);
631
+ }
632
+ if (ops.$gte !== undefined) {
633
+ this.addObjectComparableCondition(conditions, params, column, '>=', ops.$gte, '$gte', predicate);
634
+ }
635
+ if (ops.$lt !== undefined) {
636
+ this.addObjectComparableCondition(conditions, params, column, '<', ops.$lt, '$lt', predicate);
637
+ }
638
+ if (ops.$lte !== undefined) {
639
+ this.addObjectComparableCondition(conditions, params, column, '<=', ops.$lte, '$lte', predicate);
640
+ }
641
+ if (ops.$in !== undefined && ops.$in.length > 0) {
642
+ const predicates = ops.$in.map((value) => this.objectPredicateForOperatorValue(value, '$in', predicate));
643
+ const placeholders = predicates.map((item) => {
644
+ if (item.fields.objectKey === null) {
645
+ return `(${column('object_kind')} = ? AND ${column('object_digest')} = ? AND ${column('object')} = ?)`;
646
+ }
647
+ return `(${column('object_kind')} = ? AND ${column('object_key')} = ?)`;
648
+ }).join(' OR ');
649
+ conditions.push(`(${placeholders})`);
650
+ for (const item of predicates) {
651
+ if (item.fields.objectKey === null) {
652
+ params.push(item.fields.objectKind, this.objectDigestForIndex(item.serialized, item.fields), item.serialized);
653
+ }
654
+ else {
655
+ params.push(item.fields.objectKind, item.fields.objectKey);
656
+ }
657
+ }
658
+ }
659
+ if (ops.$notIn !== undefined && ops.$notIn.length > 0) {
660
+ const values = ops.$notIn.map((value) => this.objectPredicateForOperatorValue(value, '$notIn', predicate).serialized);
661
+ for (const value of values) {
662
+ conditions.push(`${column('object')} != ?`);
663
+ params.push(value);
664
+ }
665
+ }
666
+ if (ops.$startsWith !== undefined) {
667
+ const fields = this.objectFieldsForPrefix(ops.$startsWith, predicate);
668
+ this.assertComparableObject(fields, '$startsWith');
669
+ conditions.push(`${column('object_kind')} = ?`);
670
+ params.push(fields.objectKind);
671
+ conditions.push(`${column('object_key')} >= ? AND ${column('object_key')} < ?`);
672
+ params.push(ops.$startsWith, ops.$startsWith + '\uffff');
673
+ }
674
+ if (ops.$endsWith !== undefined) {
675
+ this.addObjectTextCondition(conditions, params, column, 'LIKE', `%${ops.$endsWith}`, predicate);
676
+ }
677
+ if (ops.$contains !== undefined) {
678
+ this.addObjectTextCondition(conditions, params, column, 'LIKE', `%${ops.$contains}%`, predicate);
679
+ }
680
+ if (ops.$regex !== undefined) {
681
+ const pattern = ops.$regex.replace(/\.\*/g, '%').replace(/\./g, '_');
682
+ this.addObjectTextCondition(conditions, params, column, 'LIKE', pattern, predicate);
683
+ }
684
+ if (ops.$strStartsWith !== undefined) {
685
+ this.addObjectLexicalStringCondition(conditions, params, column, 'startsWith', ops.$strStartsWith);
686
+ }
687
+ if (ops.$strEndsWith !== undefined) {
688
+ this.addObjectLexicalStringCondition(conditions, params, column, 'endsWith', ops.$strEndsWith);
689
+ }
690
+ if (ops.$strContains !== undefined) {
691
+ this.addObjectLexicalStringCondition(conditions, params, column, 'contains', ops.$strContains);
692
+ }
693
+ if (ops.$strRegex !== undefined) {
694
+ this.addObjectLexicalStringCondition(conditions, params, column, 'regex', ops.$strRegex);
695
+ }
696
+ if (ops.$language !== undefined) {
697
+ this.addObjectLanguageCondition(conditions, params, column, ops.$language);
698
+ }
699
+ if (ops.$isNull === true) {
700
+ conditions.push(`${column('object')} IS NULL`);
701
+ }
702
+ if (ops.$isNull === false) {
703
+ conditions.push(`${column('object')} IS NOT NULL`);
704
+ }
705
+ }
706
+ addObjectExactCondition(conditions, params, column, object, predicate) {
707
+ const serialized = (0, serialization_1.serializeObject)(object);
708
+ const fields = this.objectFieldsForTerm(object, predicate);
709
+ this.addObjectExactSerializedCondition(conditions, params, column, serialized, fields);
710
+ }
711
+ addObjectExactValueCondition(conditions, params, column, value, op, predicate) {
712
+ const item = this.objectPredicateForOperatorValue(value, op, predicate);
713
+ if (op === '$ne') {
714
+ conditions.push(`${column('object')} != ?`);
715
+ params.push(item.serialized);
716
+ return;
717
+ }
718
+ this.addObjectExactSerializedCondition(conditions, params, column, item.serialized, item.fields);
719
+ }
720
+ addObjectExactSerializedCondition(conditions, params, column, serialized, fields) {
721
+ if (fields.objectKey !== null) {
722
+ conditions.push(`${column('object_kind')} = ?`);
723
+ params.push(fields.objectKind);
724
+ conditions.push(`${column('object_key')} = ?`);
725
+ params.push(fields.objectKey);
726
+ return;
727
+ }
728
+ conditions.push(`${column('object_kind')} = ?`);
729
+ params.push(fields.objectKind);
730
+ conditions.push(`${column('object_digest')} = ?`);
731
+ params.push(this.objectDigestForIndex(serialized, fields));
732
+ conditions.push(`${column('object')} = ?`);
733
+ params.push(serialized);
734
+ }
735
+ addObjectComparableCondition(conditions, params, column, sqlOperator, value, op, predicate) {
736
+ const item = this.objectPredicateForOperatorValue(value, op, predicate);
737
+ this.assertComparableObject(item.fields, op);
738
+ conditions.push(`${column('object_kind')} = ?`);
739
+ params.push(item.fields.objectKind);
740
+ conditions.push(`${column('object_key')} ${sqlOperator} ?`);
741
+ params.push(item.serialized);
742
+ }
743
+ addObjectTextCondition(conditions, params, column, sqlOperator, value, predicate) {
744
+ const declaredType = (0, value_types_1.getPredicateObjectDataType)(predicate, this.options.predicateObjectDataTypes);
745
+ if (declaredType) {
746
+ if (!['text', 'longText', 'literal'].includes(declaredType)) {
747
+ throw new Error(`Object text search is not supported for ${declaredType}`);
748
+ }
749
+ conditions.push(`${column('object_kind')} = ?`);
750
+ params.push(declaredType);
751
+ }
752
+ conditions.push(`${column('object_text')} ${sqlOperator} ?`);
753
+ params.push(value);
754
+ }
755
+ addObjectLexicalStringCondition(conditions, params, column, op, value) {
756
+ const lexical = this.objectLexicalSql(column);
757
+ if (op === 'startsWith') {
758
+ conditions.push(`(${lexical} >= ? AND ${lexical} < ?)`);
759
+ params.push(value, value + '\uffff');
760
+ return;
761
+ }
762
+ if (op === 'endsWith') {
763
+ conditions.push(`RIGHT(${lexical}, LENGTH(?)) = ?`);
764
+ params.push(value, value);
765
+ return;
766
+ }
767
+ if (op === 'contains') {
768
+ conditions.push(`POSITION(? IN ${lexical}) > 0`);
769
+ params.push(value);
770
+ return;
771
+ }
772
+ conditions.push(`${lexical} ~ ?`);
773
+ params.push(value);
774
+ }
775
+ objectLexicalSql(column) {
776
+ return `CASE
777
+ WHEN ${column('object_text')} IS NOT NULL THEN ${column('object_text')}
778
+ WHEN ${column('object_kind')} IN ('iri', 'blankNode') THEN ${column('object_key')}
779
+ WHEN ${column('object_kind')} = 'numeric' THEN split_part(${column('object')}, '${PG_SEP}', 4)
780
+ WHEN ${column('object_kind')} = 'dateTime' THEN split_part(${column('object')}, '${PG_SEP}', 3)
781
+ ELSE NULL
782
+ END`;
783
+ }
784
+ addObjectLanguageCondition(conditions, params, column, lang) {
785
+ const languageLiteralKinds = `${column('object_kind')} IN ('text', 'longText', 'literal')`;
786
+ if (lang === '*') {
787
+ conditions.push(`(${languageLiteralKinds} AND ${column('object')} ~ ?)`);
788
+ params.push('"@[A-Za-z]+(-[A-Za-z0-9]+)*$');
789
+ return;
790
+ }
791
+ const suffix = `"@${lang.toLowerCase()}`;
792
+ conditions.push(`(${languageLiteralKinds} AND (RIGHT(LOWER(${column('object')}), LENGTH(?)) = ? OR LOWER(${column('object')}) LIKE ?))`);
793
+ params.push(suffix, suffix, `%"@${lang.toLowerCase()}-%`);
794
+ }
795
+ objectPredicateForOperatorValue(value, op, predicate) {
796
+ const serialized = this.serializeOpValue(value, true, op);
797
+ return {
798
+ serialized,
799
+ fields: this.objectFieldsForSerialized(serialized, predicate),
800
+ };
801
+ }
802
+ objectFieldsForTerm(object, predicate) {
803
+ return (0, value_types_1.objectIndexFieldsFromTerm)(object, {
804
+ predicate,
805
+ predicateObjectDataTypes: this.options.predicateObjectDataTypes,
806
+ textMaxBytes: this.options.textMaxBytes,
807
+ });
808
+ }
809
+ objectFieldsForSerialized(serialized, predicate) {
810
+ return (0, value_types_1.objectIndexFieldsFromSerialized)(serialized, {
811
+ predicate,
812
+ predicateObjectDataTypes: this.options.predicateObjectDataTypes,
813
+ textMaxBytes: this.options.textMaxBytes,
814
+ });
815
+ }
816
+ objectFieldsForPrefix(prefix, predicate) {
817
+ const declaredType = (0, value_types_1.getPredicateObjectDataType)(predicate, this.options.predicateObjectDataTypes);
818
+ if (declaredType) {
819
+ if (declaredType === 'longText') {
820
+ return { objectKind: 'longText', objectKey: null, objectText: prefix };
821
+ }
822
+ return { objectKind: declaredType, objectKey: prefix, objectText: null };
823
+ }
824
+ if ((0, serialization_1.isSerializedNumericLiteral)(prefix)) {
825
+ return { objectKind: 'numeric', objectKey: prefix, objectText: null };
826
+ }
827
+ if ((0, serialization_1.isSerializedDateTimeLiteral)(prefix)) {
828
+ return { objectKind: 'dateTime', objectKey: prefix, objectText: null };
829
+ }
830
+ if (prefix.startsWith('"')) {
831
+ return { objectKind: 'text', objectKey: prefix, objectText: null };
832
+ }
833
+ if (prefix.startsWith('_:')) {
834
+ return { objectKind: 'blankNode', objectKey: prefix, objectText: null };
835
+ }
836
+ return { objectKind: 'iri', objectKey: prefix, objectText: null };
837
+ }
838
+ assertComparableObject(fields, op) {
839
+ if (fields.objectKey !== null && fields.objectKind !== 'longText') {
840
+ return;
841
+ }
842
+ throw new Error(`Object ${op} is not supported for ${fields.objectKind}; declare/use a comparable data type instead of longText`);
843
+ }
844
+ addAliasedObjectConditions(conditions, params, alias, pattern) {
845
+ if (!pattern.object)
846
+ return;
847
+ this.addObjectConditions(conditions, params, alias, pattern.object, this.extractExactPredicate(pattern.predicate));
848
+ }
849
+ extractExactPredicate(match) {
850
+ if (!match)
851
+ return undefined;
852
+ if (typeof match === 'object' && 'termType' in match) {
853
+ return (0, serialization_1.termToId)(match);
854
+ }
855
+ const ops = match;
856
+ if (ops.$eq !== undefined) {
857
+ return String(this.serializeOpValue(ops.$eq, false, '$eq'));
858
+ }
859
+ return undefined;
860
+ }
861
+ resolveObjectDataTypeForPattern(pattern) {
862
+ const predicate = this.extractExactPredicate(pattern.predicate);
863
+ if (predicate) {
864
+ return (0, value_types_1.getPredicateObjectDataType)(predicate, this.options.predicateObjectDataTypes);
865
+ }
866
+ if (pattern.object && typeof pattern.object === 'object' && 'termType' in pattern.object) {
867
+ return this.objectFieldsForTerm(pattern.object, predicate).objectKind;
868
+ }
869
+ return undefined;
870
+ }
871
+ addAliasedPgCondition(conditions, params, alias, column, match, isObject) {
872
+ if (!match)
873
+ return;
874
+ if (isObject) {
875
+ this.addObjectConditions(conditions, params, alias, match, undefined);
876
+ return;
877
+ }
878
+ if (typeof match === 'object' && 'termType' in match) {
879
+ const value = (0, serialization_1.termToId)(match);
880
+ conditions.push(`${alias}.${column} = ?`);
881
+ params.push(value);
882
+ return;
883
+ }
884
+ if (match.$eq !== undefined) {
885
+ const value = this.serializeOpValue(match.$eq, false, '$eq');
886
+ conditions.push(`${alias}.${column} = ?`);
887
+ params.push(value);
888
+ return;
889
+ }
890
+ this.addFallbackAliasedCondition(conditions, params, alias, column, match, isObject);
891
+ }
892
+ addFallbackAliasedCondition(conditions, params, alias, column, match, isObject) {
893
+ if (isObject) {
894
+ this.addObjectConditions(conditions, params, alias, match, undefined);
895
+ return;
896
+ }
897
+ if (match.$gt !== undefined) {
898
+ conditions.push(`${alias}.${column} > ?`);
899
+ params.push(this.serializeOpValue(match.$gt, false, '$gt'));
900
+ }
901
+ if (match.$gte !== undefined) {
902
+ conditions.push(`${alias}.${column} >= ?`);
903
+ params.push(this.serializeOpValue(match.$gte, false, '$gte'));
904
+ }
905
+ if (match.$lt !== undefined) {
906
+ conditions.push(`${alias}.${column} < ?`);
907
+ params.push(this.serializeOpValue(match.$lt, false, '$lt'));
908
+ }
909
+ if (match.$lte !== undefined) {
910
+ conditions.push(`${alias}.${column} <= ?`);
911
+ params.push(this.serializeOpValue(match.$lte, false, '$lte'));
912
+ }
913
+ if (match.$in !== undefined && match.$in.length > 0) {
914
+ const placeholders = match.$in.map(() => '?').join(', ');
915
+ conditions.push(`${alias}.${column} IN (${placeholders})`);
916
+ params.push(...match.$in.map((value) => this.serializeOpValue(value, false, '$in')));
917
+ }
272
918
  }
273
919
  }
274
920
  exports.PgQuintStore = PgQuintStore;