@undefineds.co/xpod 0.3.14 → 0.3.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/config/local.json +5 -5
  2. package/config/xpod.json +24 -10
  3. package/dist/cli/commands/auth.d.ts +1 -0
  4. package/dist/cli/commands/auth.js +117 -37
  5. package/dist/cli/commands/auth.js.map +1 -1
  6. package/dist/cli/commands/login.js +16 -23
  7. package/dist/cli/commands/login.js.map +1 -1
  8. package/dist/cli/commands/logs.d.ts +2 -0
  9. package/dist/cli/commands/logs.js +20 -5
  10. package/dist/cli/commands/logs.js.map +1 -1
  11. package/dist/cli/commands/obj.d.ts +44 -0
  12. package/dist/cli/commands/obj.js +1059 -0
  13. package/dist/cli/commands/obj.js.map +1 -0
  14. package/dist/cli/commands/rdf.d.ts +14 -0
  15. package/dist/cli/commands/rdf.js +235 -0
  16. package/dist/cli/commands/rdf.js.map +1 -0
  17. package/dist/cli/commands/resource.d.ts +31 -0
  18. package/dist/cli/commands/resource.js +191 -0
  19. package/dist/cli/commands/resource.js.map +1 -0
  20. package/dist/cli/commands/secret.d.ts +36 -0
  21. package/dist/cli/commands/secret.js +285 -0
  22. package/dist/cli/commands/secret.js.map +1 -0
  23. package/dist/cli/commands/server.d.ts +11 -0
  24. package/dist/cli/commands/server.js +168 -0
  25. package/dist/cli/commands/server.js.map +1 -0
  26. package/dist/cli/commands/start.d.ts +1 -0
  27. package/dist/cli/commands/start.js +5 -0
  28. package/dist/cli/commands/start.js.map +1 -1
  29. package/dist/cli/commands/status.d.ts +1 -0
  30. package/dist/cli/commands/status.js +21 -6
  31. package/dist/cli/commands/status.js.map +1 -1
  32. package/dist/cli/commands/stop.d.ts +3 -0
  33. package/dist/cli/commands/stop.js +40 -6
  34. package/dist/cli/commands/stop.js.map +1 -1
  35. package/dist/cli/index.js +23 -8
  36. package/dist/cli/index.js.map +1 -1
  37. package/dist/cli/lib/auth-context.d.ts +24 -0
  38. package/dist/cli/lib/auth-context.js +70 -0
  39. package/dist/cli/lib/auth-context.js.map +1 -0
  40. package/dist/cli/lib/output.d.ts +23 -0
  41. package/dist/cli/lib/output.js +63 -0
  42. package/dist/cli/lib/output.js.map +1 -0
  43. package/dist/cli/lib/resource.d.ts +29 -0
  44. package/dist/cli/lib/resource.js +114 -0
  45. package/dist/cli/lib/resource.js.map +1 -0
  46. package/dist/components/context.jsonld +6 -0
  47. package/dist/identity/oidc/AutoDetectIdentityProviderHandler.d.ts +11 -10
  48. package/dist/identity/oidc/AutoDetectIdentityProviderHandler.js +13 -24
  49. package/dist/identity/oidc/AutoDetectIdentityProviderHandler.js.map +1 -1
  50. package/dist/identity/oidc/AutoDetectIdentityProviderHandler.jsonld +4 -4
  51. package/dist/identity/oidc/AutoDetectOidcHandler.d.ts +8 -4
  52. package/dist/identity/oidc/AutoDetectOidcHandler.js +10 -6
  53. package/dist/identity/oidc/AutoDetectOidcHandler.js.map +1 -1
  54. package/dist/identity/oidc/AutoDetectOidcHandler.jsonld +3 -3
  55. package/dist/storage/accessors/MixDataAccessor.js +3 -0
  56. package/dist/storage/accessors/MixDataAccessor.js.map +1 -1
  57. package/dist/storage/quint/SqliteQuintStore.d.ts +26 -1
  58. package/dist/storage/quint/SqliteQuintStore.js +551 -318
  59. package/dist/storage/quint/SqliteQuintStore.js.map +1 -1
  60. package/dist/storage/quint/SqliteQuintStore.jsonld +102 -2
  61. package/dist/storage/quint/schema.d.ts +76 -0
  62. package/dist/storage/quint/schema.js +13 -7
  63. package/dist/storage/quint/schema.js.map +1 -1
  64. package/dist/storage/rdf/RdfLocalQueryEngine.d.ts +4 -1
  65. package/dist/storage/rdf/RdfLocalQueryEngine.js +77 -8
  66. package/dist/storage/rdf/RdfLocalQueryEngine.js.map +1 -1
  67. package/dist/storage/rdf/SolidRdfEngine.d.ts +5 -0
  68. package/dist/storage/rdf/SolidRdfEngine.js +31 -3
  69. package/dist/storage/rdf/SolidRdfEngine.js.map +1 -1
  70. package/dist/storage/rdf/SolidRdfEngine.jsonld +34 -0
  71. package/dist/storage/sparql/ComunicaQuintEngine.js +16 -3
  72. package/dist/storage/sparql/ComunicaQuintEngine.js.map +1 -1
  73. package/package.json +1 -1
  74. package/dist/cli/commands/config.d.ts +0 -42
  75. package/dist/cli/commands/config.js +0 -289
  76. package/dist/cli/commands/config.js.map +0 -1
@@ -4,6 +4,7 @@
4
4
  */
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.SqliteQuintStore = void 0;
7
+ const node_crypto_1 = require("node:crypto");
7
8
  const node_fs_1 = require("node:fs");
8
9
  const node_path_1 = require("node:path");
9
10
  const drizzle_orm_1 = require("drizzle-orm");
@@ -13,7 +14,11 @@ const schema_1 = require("./schema");
13
14
  const serialization_1 = require("./serialization");
14
15
  const SqliteRuntime_1 = require("../SqliteRuntime");
15
16
  const types_1 = require("./types");
17
+ const value_types_1 = require("./value-types");
16
18
  const SQLITE_UNBOUNDED_LIMIT = Number.MAX_SAFE_INTEGER;
19
+ function digestObject(value) {
20
+ return (0, node_crypto_1.createHash)('sha256').update(value).digest('hex');
21
+ }
17
22
  class SqliteQuintStore {
18
23
  constructor(options) {
19
24
  this.sqlite = null;
@@ -47,20 +52,34 @@ class SqliteQuintStore {
47
52
  // Create table and indexes
48
53
  this.sqlite.exec(`
49
54
  CREATE TABLE IF NOT EXISTS quints (
55
+ object_kind TEXT,
56
+ object_key TEXT,
57
+ object_text TEXT,
58
+ object_digest TEXT,
50
59
  graph TEXT NOT NULL,
51
60
  subject TEXT NOT NULL,
52
61
  predicate TEXT NOT NULL,
53
62
  object TEXT NOT NULL,
54
- vector TEXT,
55
- PRIMARY KEY (graph, subject, predicate, object)
63
+ vector TEXT
56
64
  );
57
-
58
- CREATE INDEX IF NOT EXISTS idx_spog ON quints (subject, predicate, object, graph);
59
- CREATE INDEX IF NOT EXISTS idx_ogsp ON quints (object, graph, subject, predicate);
60
- CREATE INDEX IF NOT EXISTS idx_gspo ON quints (graph, subject, predicate, object);
61
- CREATE INDEX IF NOT EXISTS idx_sopg ON quints (subject, object, predicate, graph);
62
- CREATE INDEX IF NOT EXISTS idx_pogs ON quints (predicate, object, graph, subject);
63
- CREATE INDEX IF NOT EXISTS idx_gpos ON quints (graph, predicate, object, subject);
65
+ `);
66
+ this.ensureTypedObjectSchema();
67
+ this.sqlite.exec(`
68
+ CREATE INDEX IF NOT EXISTS idx_quints_graph ON quints (graph);
69
+ CREATE INDEX IF NOT EXISTS idx_quints_subject ON quints (subject);
70
+ CREATE INDEX IF NOT EXISTS idx_quints_predicate ON quints (predicate);
71
+ CREATE INDEX IF NOT EXISTS idx_quints_object_key ON quints (object_kind, object_key);
72
+ CREATE INDEX IF NOT EXISTS idx_quints_predicate_object_key ON quints (predicate, object_kind, object_key);
73
+ CREATE INDEX IF NOT EXISTS idx_quints_predicate_object_digest ON quints (predicate, object_kind, object_digest);
74
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_quints_gspo_key
75
+ ON quints (graph, subject, predicate, object_kind, object_key)
76
+ WHERE object_key IS NOT NULL;
77
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_quints_gspo_digest
78
+ ON quints (graph, subject, predicate, object_kind, object_digest)
79
+ WHERE object_digest IS NOT NULL;
80
+ CREATE INDEX IF NOT EXISTS idx_quints_gsp ON quints (graph, subject, predicate);
81
+ CREATE INDEX IF NOT EXISTS idx_quints_sp ON quints (subject, predicate);
82
+ CREATE INDEX IF NOT EXISTS idx_quints_gp ON quints (graph, predicate);
64
83
  `);
65
84
  }
66
85
  async close() {
@@ -75,29 +94,8 @@ class SqliteQuintStore {
75
94
  // ============================================
76
95
  async get(pattern, options) {
77
96
  this.ensureOpen();
78
- const conditions = this.buildConditions(pattern);
79
- let query = this.db.select().from(schema_1.quints);
80
- if (conditions.length > 0) {
81
- query = query.where((0, drizzle_orm_1.and)(...conditions));
82
- }
83
- // ORDER BY 支持
84
- if (options?.order && options.order.length > 0) {
85
- // 使用 sql 模板构建 ORDER BY
86
- const orderCol = options.order[0]; // 目前只支持单列排序
87
- const direction = options.reverse ? 'DESC' : 'ASC';
88
- query = query.orderBy(drizzle_orm_1.sql.raw(`${orderCol} ${direction}`));
89
- }
90
- if (options?.limit !== undefined) {
91
- query = query.limit(options.limit);
92
- }
93
- if (options?.offset !== undefined) {
94
- // SQLite requires LIMIT when using OFFSET
95
- if (options?.limit === undefined) {
96
- query = query.limit(SQLITE_UNBOUNDED_LIMIT);
97
- }
98
- query = query.offset(options.offset);
99
- }
100
- const rows = await query;
97
+ const { sql: query, params } = this.buildSelectQuery(pattern, options);
98
+ const rows = this.sqlite.prepare(query).all(...params);
101
99
  return rows.map((row) => this.rowToQuint(row));
102
100
  }
103
101
  match(subject, predicate, object, graph) {
@@ -117,13 +115,9 @@ class SqliteQuintStore {
117
115
  }
118
116
  async count(pattern) {
119
117
  this.ensureOpen();
120
- const conditions = this.buildConditions(pattern);
121
- let query = this.db.select({ count: (0, drizzle_orm_1.sql) `count(*)` }).from(schema_1.quints);
122
- if (conditions.length > 0) {
123
- query = query.where((0, drizzle_orm_1.and)(...conditions));
124
- }
125
- const result = await query;
126
- return result[0]?.count ?? 0;
118
+ const { whereClause, params } = this.buildWhereClause(pattern);
119
+ const result = this.sqlite.prepare(`SELECT COUNT(*) AS count FROM quints${whereClause}`).get(...params);
120
+ return Number(result?.count ?? 0);
127
121
  }
128
122
  /**
129
123
  * Compound query - multiple patterns JOINed by a common field
@@ -284,137 +278,12 @@ class SqliteQuintStore {
284
278
  */
285
279
  buildConditionsForAlias(pattern, alias, params) {
286
280
  const conditions = [];
287
- /**
288
- * Serialize operator value for comparison
289
- * - Term: use serializeObject/termToId
290
- * - number: for exact match ($eq, $ne, $in, $notIn) use full serialization
291
- * for range comparison ($gt, $gte, $lt, $lte) use fpstring
292
- * - string: assume already serialized or use as-is
293
- */
294
- const serializeOpValue = (value, isObject, filterOp) => {
295
- if (typeof value === 'object' && 'termType' in value) {
296
- // It's a Term - use full serialization
297
- return isObject ? (0, serialization_1.serializeObject)(value) : (0, serialization_1.termToId)(value);
298
- }
299
- if (typeof value === 'number') {
300
- if (isObject) {
301
- // For exact match operations, use full serialization (includes datatype and original value)
302
- if (filterOp === '$eq' || filterOp === '$ne' || filterOp === '$in' || filterOp === '$notIn') {
303
- const lit = n3_1.DataFactory.literal(String(value), n3_1.DataFactory.namedNode('http://www.w3.org/2001/XMLSchema#integer'));
304
- return (0, serialization_1.serializeObject)(lit);
305
- }
306
- // For range comparisons, use fpstring
307
- const fpValue = `N${serialization_1.SEP}${(0, serialization_1.fpEncode)(value)}`;
308
- // $gt and $lte need max suffix to compare correctly
309
- if (filterOp === '$gt' || filterOp === '$lte') {
310
- return fpValue + serialization_1.SEP + '\uffff';
311
- }
312
- // $lt and $gte use prefix only
313
- return fpValue;
314
- }
315
- return value;
316
- }
317
- if (isObject) {
318
- if ((0, serialization_1.isSerializedObjectValue)(value)) {
319
- return value;
320
- }
321
- return `"${value}"`;
322
- }
323
- return value;
324
- };
325
- const addCondition = (column, match, isObject = false) => {
326
- if (!match)
327
- return;
328
- const fullColumn = `${alias}.${column}`;
329
- if ((0, types_1.isTerm)(match)) {
330
- conditions.push(`${fullColumn} = ?`);
331
- params.push(isObject ? (0, serialization_1.serializeObject)(match) : (0, serialization_1.termToId)(match));
332
- }
333
- else {
334
- const ops = match;
335
- if (ops.$eq !== undefined) {
336
- conditions.push(`${fullColumn} = ?`);
337
- params.push(serializeOpValue(ops.$eq, isObject, '$eq'));
338
- }
339
- if (ops.$ne !== undefined) {
340
- conditions.push(`${fullColumn} != ?`);
341
- params.push(serializeOpValue(ops.$ne, isObject, '$ne'));
342
- }
343
- if (ops.$gt !== undefined) {
344
- conditions.push(`${fullColumn} > ?`);
345
- params.push(serializeOpValue(ops.$gt, isObject, '$gt'));
346
- }
347
- if (ops.$gte !== undefined) {
348
- conditions.push(`${fullColumn} >= ?`);
349
- params.push(serializeOpValue(ops.$gte, isObject, '$gte'));
350
- }
351
- if (ops.$lt !== undefined) {
352
- conditions.push(`${fullColumn} < ?`);
353
- params.push(serializeOpValue(ops.$lt, isObject, '$lt'));
354
- }
355
- if (ops.$lte !== undefined) {
356
- conditions.push(`${fullColumn} <= ?`);
357
- params.push(serializeOpValue(ops.$lte, isObject, '$lte'));
358
- }
359
- if (ops.$in !== undefined && ops.$in.length > 0) {
360
- const placeholders = ops.$in.map(() => '?').join(', ');
361
- conditions.push(`${fullColumn} IN (${placeholders})`);
362
- params.push(...ops.$in.map(v => serializeOpValue(v, isObject, '$in')));
363
- }
364
- if (ops.$notIn !== undefined && ops.$notIn.length > 0) {
365
- const placeholders = ops.$notIn.map(() => '?').join(', ');
366
- conditions.push(`${fullColumn} NOT IN (${placeholders})`);
367
- params.push(...ops.$notIn.map(v => serializeOpValue(v, isObject, '$notIn')));
368
- }
369
- if (ops.$startsWith !== undefined) {
370
- conditions.push(`${fullColumn} >= ?`);
371
- conditions.push(`${fullColumn} < ?`);
372
- params.push(ops.$startsWith);
373
- params.push(ops.$startsWith + '\uffff');
374
- }
375
- if (ops.$endsWith !== undefined) {
376
- conditions.push(`${fullColumn} LIKE ?`);
377
- params.push(`%${ops.$endsWith}`);
378
- }
379
- if (ops.$contains !== undefined) {
380
- conditions.push(`${fullColumn} LIKE ?`);
381
- params.push(`%${ops.$contains}%`);
382
- }
383
- if (isObject && ops.$strStartsWith !== undefined) {
384
- const lexical = this.objectLexicalSql(fullColumn);
385
- conditions.push(`${lexical} >= ?`);
386
- conditions.push(`${lexical} < ?`);
387
- params.push(ops.$strStartsWith);
388
- params.push(ops.$strStartsWith + '\uffff');
389
- }
390
- if (isObject && ops.$strEndsWith !== undefined) {
391
- conditions.push(`${this.objectLexicalSql(fullColumn)} LIKE ?`);
392
- params.push(`%${ops.$strEndsWith}`);
393
- }
394
- if (isObject && ops.$strContains !== undefined) {
395
- conditions.push(`${this.objectLexicalSql(fullColumn)} LIKE ?`);
396
- params.push(`%${ops.$strContains}%`);
397
- }
398
- if (isObject && ops.$strRegex !== undefined) {
399
- conditions.push(`${this.objectLexicalSql(fullColumn)} GLOB ?`);
400
- params.push(ops.$strRegex.replace(/\.\*/g, '*').replace(/\./g, '?'));
401
- }
402
- if (isObject && ops.$language !== undefined) {
403
- conditions.push(`lower(${fullColumn}) LIKE ?`);
404
- params.push(ops.$language === '*' ? '%"@%' : `%"@${ops.$language.toLowerCase()}`);
405
- }
406
- if (ops.$isNull === true) {
407
- conditions.push(`${fullColumn} IS NULL`);
408
- }
409
- if (ops.$isNull === false) {
410
- conditions.push(`${fullColumn} IS NOT NULL`);
411
- }
412
- }
413
- };
414
- addCondition('graph', pattern.graph);
415
- addCondition('subject', pattern.subject);
416
- addCondition('predicate', pattern.predicate);
417
- addCondition('object', pattern.object, true);
281
+ this.addTermConditions(conditions, params, `${alias}.graph`, pattern.graph, false);
282
+ this.addTermConditions(conditions, params, `${alias}.subject`, pattern.subject, false);
283
+ this.addTermConditions(conditions, params, `${alias}.predicate`, pattern.predicate, false);
284
+ if (pattern.object) {
285
+ this.addObjectConditions(conditions, params, alias, pattern.object, this.extractExactPredicate(pattern.predicate));
286
+ }
418
287
  return conditions;
419
288
  }
420
289
  // ============================================
@@ -423,43 +292,26 @@ class SqliteQuintStore {
423
292
  async put(quint) {
424
293
  this.ensureOpen();
425
294
  const row = this.quintToRow(quint);
426
- await this.db.insert(schema_1.quints)
427
- .values(row)
428
- .onConflictDoUpdate({
429
- target: [schema_1.quints.graph, schema_1.quints.subject, schema_1.quints.predicate, schema_1.quints.object],
430
- set: { vector: row.vector },
431
- });
295
+ const statement = this.writeStatementForRow(row);
296
+ this.sqlite.prepare(statement.sql).run(...statement.params);
432
297
  }
433
298
  async multiPut(quintList) {
434
299
  this.ensureOpen();
435
300
  if (quintList.length === 0)
436
301
  return;
437
- const rows = quintList.map(q => this.quintToRow(q));
302
+ const rows = quintList.map(q => this.writeStatementForRow(this.quintToRow(q)));
438
303
  // Use transaction for batch insert
439
304
  this.sqlite.transaction(() => {
440
305
  for (const row of rows) {
441
- this.db.insert(schema_1.quints)
442
- .values(row)
443
- .onConflictDoUpdate({
444
- target: [schema_1.quints.graph, schema_1.quints.subject, schema_1.quints.predicate, schema_1.quints.object],
445
- set: { vector: row.vector },
446
- })
447
- .run();
306
+ this.sqlite.prepare(row.sql).run(...row.params);
448
307
  }
449
308
  })();
450
309
  }
451
310
  async updateEmbedding(pattern, embedding) {
452
311
  this.ensureOpen();
453
- const conditions = this.buildConditions(pattern);
312
+ const { whereClause, params } = this.buildWhereClause(pattern);
454
313
  const vectorJson = JSON.stringify(embedding);
455
- if (conditions.length === 0) {
456
- const result = await this.db.update(schema_1.quints)
457
- .set({ vector: vectorJson });
458
- return result.changes;
459
- }
460
- const result = await this.db.update(schema_1.quints)
461
- .set({ vector: vectorJson })
462
- .where((0, drizzle_orm_1.and)(...conditions));
314
+ const result = this.sqlite.prepare(`UPDATE quints SET vector = ?${whereClause}`).run(vectorJson, ...params);
463
315
  return result.changes;
464
316
  }
465
317
  // ============================================
@@ -467,13 +319,8 @@ class SqliteQuintStore {
467
319
  // ============================================
468
320
  async del(pattern) {
469
321
  this.ensureOpen();
470
- const conditions = this.buildConditions(pattern);
471
- if (conditions.length === 0) {
472
- // Delete all - dangerous!
473
- const result = await this.db.delete(schema_1.quints);
474
- return result.changes;
475
- }
476
- const result = await this.db.delete(schema_1.quints).where((0, drizzle_orm_1.and)(...conditions));
322
+ const { whereClause, params } = this.buildWhereClause(pattern);
323
+ const result = this.sqlite.prepare(`DELETE FROM quints${whereClause}`).run(...params);
477
324
  return result.changes;
478
325
  }
479
326
  async multiDel(quintList) {
@@ -486,9 +333,13 @@ class SqliteQuintStore {
486
333
  const s = (0, serialization_1.termToId)(quint.subject);
487
334
  const p = (0, serialization_1.termToId)(quint.predicate);
488
335
  const o = (0, serialization_1.serializeObject)(quint.object);
489
- this.db.delete(schema_1.quints)
490
- .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.quints.graph, g), (0, drizzle_orm_1.eq)(schema_1.quints.subject, s), (0, drizzle_orm_1.eq)(schema_1.quints.predicate, p), (0, drizzle_orm_1.eq)(schema_1.quints.object, o)))
491
- .run();
336
+ this.sqlite.prepare(`
337
+ DELETE FROM quints
338
+ WHERE graph = ?
339
+ AND subject = ?
340
+ AND predicate = ?
341
+ AND object = ?
342
+ `).run(g, s, p, o);
492
343
  }
493
344
  })();
494
345
  }
@@ -608,142 +459,524 @@ class SqliteQuintStore {
608
459
  throw new Error('Store not open. Call open() first.');
609
460
  }
610
461
  }
611
- buildConditions(pattern) {
612
- const conditions = [];
613
- /**
614
- * Serialize operator value for comparison
615
- * - Term: use serializeObject/termToId
616
- * - number: for exact match ($eq, $ne, $in, $notIn) use full serialization
617
- * for range comparison ($gt, $gte, $lt, $lte) use fpstring
618
- * - string: assume already serialized or use as-is
619
- */
620
- const serializeOpValue = (value, isObject, filterOp) => {
621
- if (typeof value === 'object' && 'termType' in value) {
622
- return isObject ? (0, serialization_1.serializeObject)(value) : (0, serialization_1.termToId)(value);
623
- }
624
- if (typeof value === 'number') {
625
- if (isObject) {
626
- // For exact match operations, use full serialization (includes datatype and original value)
627
- if (filterOp === '$eq' || filterOp === '$ne' || filterOp === '$in' || filterOp === '$notIn') {
628
- const lit = n3_1.DataFactory.literal(String(value), n3_1.DataFactory.namedNode('http://www.w3.org/2001/XMLSchema#integer'));
629
- return (0, serialization_1.serializeObject)(lit);
630
- }
631
- // For range comparisons, use fpstring
632
- const fpValue = `N${serialization_1.SEP}${(0, serialization_1.fpEncode)(value)}`;
633
- // $gt and $lte need max suffix
634
- if (filterOp === '$gt' || filterOp === '$lte') {
635
- return fpValue + serialization_1.SEP + '\uffff';
462
+ ensureTypedObjectSchema() {
463
+ this.addColumnIfMissing('object_kind', 'TEXT');
464
+ this.addColumnIfMissing('object_key', 'TEXT');
465
+ this.addColumnIfMissing('object_text', 'TEXT');
466
+ this.addColumnIfMissing('object_digest', 'TEXT');
467
+ for (const indexName of ['idx_spog', 'idx_ogsp', 'idx_gspo', 'idx_sopg', 'idx_pogs', 'idx_gpos']) {
468
+ this.sqlite.exec(`DROP INDEX IF EXISTS ${indexName}`);
469
+ }
470
+ this.backfillMissingObjectIndexFields();
471
+ this.sqlite.prepare(`
472
+ UPDATE quints
473
+ SET object_kind = 'text'
474
+ WHERE object_kind = 'shortText'
475
+ `).run();
476
+ }
477
+ addColumnIfMissing(name, definition) {
478
+ const columns = new Set(this.sqlite.prepare('PRAGMA table_info(quints)').all().map(row => row.name));
479
+ if (!columns.has(name)) {
480
+ this.sqlite.exec(`ALTER TABLE quints ADD COLUMN ${name} ${definition}`);
481
+ }
482
+ }
483
+ backfillMissingObjectIndexFields() {
484
+ const rows = this.sqlite.prepare(`
485
+ SELECT graph, subject, predicate, object
486
+ FROM quints
487
+ WHERE object_kind IS NULL
488
+ OR (object_key IS NULL AND object_digest IS NULL)
489
+ `).all();
490
+ const update = this.sqlite.prepare(`
491
+ UPDATE quints
492
+ SET object_kind = ?,
493
+ object_key = ?,
494
+ object_text = ?,
495
+ object_digest = ?
496
+ WHERE graph = ?
497
+ AND subject = ?
498
+ AND predicate = ?
499
+ AND object = ?
500
+ `);
501
+ for (const row of rows) {
502
+ const objectIndex = this.objectIndexForSerialized(row.predicate, row.object);
503
+ update.run(objectIndex.objectKind, objectIndex.objectKey, objectIndex.objectText, this.objectDigestForIndex(row.object, objectIndex), row.graph, row.subject, row.predicate, row.object);
504
+ }
505
+ }
506
+ buildSelectQuery(pattern, options) {
507
+ const { whereClause, params } = this.buildWhereClause(pattern);
508
+ let query = `SELECT * FROM quints${whereClause}`;
509
+ if (options?.order && options.order.length > 0) {
510
+ const orderCols = options.order.map(field => {
511
+ if (field === 'object') {
512
+ const objectType = this.resolveObjectDataTypeForPattern(pattern);
513
+ if (objectType === 'longText') {
514
+ throw new Error('ORDER BY object is not supported for longText predicates');
636
515
  }
637
- return fpValue;
516
+ return 'object_key';
638
517
  }
639
- return value;
518
+ return field;
519
+ }).join(', ');
520
+ query += ` ORDER BY ${orderCols}`;
521
+ if (options.reverse) {
522
+ query += ' DESC';
640
523
  }
641
- if (isObject && !(0, serialization_1.isSerializedObjectValue)(value)) {
642
- return `"${value}"`;
524
+ }
525
+ if (options?.limit !== undefined) {
526
+ query += ' LIMIT ?';
527
+ params.push(options.limit);
528
+ }
529
+ if (options?.offset !== undefined) {
530
+ if (options.limit === undefined) {
531
+ query += ' LIMIT ?';
532
+ params.push(SQLITE_UNBOUNDED_LIMIT);
643
533
  }
644
- return value;
534
+ query += ' OFFSET ?';
535
+ params.push(options.offset);
536
+ }
537
+ return { sql: query, params };
538
+ }
539
+ buildWhereClause(pattern) {
540
+ const conditions = [];
541
+ const params = [];
542
+ const predicate = this.extractExactPredicate(pattern.predicate);
543
+ this.addTermConditions(conditions, params, 'graph', pattern.graph, false);
544
+ this.addTermConditions(conditions, params, 'subject', pattern.subject, false);
545
+ this.addTermConditions(conditions, params, 'predicate', pattern.predicate, false);
546
+ if (pattern.object) {
547
+ this.addObjectConditions(conditions, params, undefined, pattern.object, predicate);
548
+ }
549
+ return {
550
+ whereClause: conditions.length > 0 ? ` WHERE ${conditions.join(' AND ')}` : '',
551
+ params,
645
552
  };
646
- const addTermConditions = (column, match, isObject = false) => {
647
- if (!match)
648
- return;
649
- if ((0, types_1.isTerm)(match)) {
650
- // Exact Term match
651
- conditions.push((0, drizzle_orm_1.eq)(column, isObject ? (0, serialization_1.serializeObject)(match) : (0, serialization_1.termToId)(match)));
652
- }
653
- else {
654
- // Operator match
655
- const ops = match;
656
- if (ops.$eq !== undefined) {
657
- conditions.push((0, drizzle_orm_1.eq)(column, serializeOpValue(ops.$eq, isObject, '$eq')));
658
- }
659
- if (ops.$ne !== undefined) {
660
- conditions.push((0, drizzle_orm_1.ne)(column, serializeOpValue(ops.$ne, isObject, '$ne')));
661
- }
662
- if (ops.$gt !== undefined) {
663
- conditions.push((0, drizzle_orm_1.gt)(column, serializeOpValue(ops.$gt, isObject, '$gt')));
664
- }
665
- if (ops.$gte !== undefined) {
666
- conditions.push((0, drizzle_orm_1.gte)(column, serializeOpValue(ops.$gte, isObject, '$gte')));
667
- }
668
- if (ops.$lt !== undefined) {
669
- conditions.push((0, drizzle_orm_1.lt)(column, serializeOpValue(ops.$lt, isObject, '$lt')));
670
- }
671
- if (ops.$lte !== undefined) {
672
- conditions.push((0, drizzle_orm_1.lte)(column, serializeOpValue(ops.$lte, isObject, '$lte')));
673
- }
674
- if (ops.$in !== undefined && ops.$in.length > 0) {
675
- const serializedValues = ops.$in.map(v => serializeOpValue(v, isObject, '$in'));
676
- conditions.push((0, drizzle_orm_1.inArray)(column, serializedValues));
677
- }
678
- if (ops.$notIn !== undefined && ops.$notIn.length > 0) {
679
- const serializedValues = ops.$notIn.map(v => serializeOpValue(v, isObject, '$notIn'));
680
- conditions.push((0, drizzle_orm_1.notInArray)(column, serializedValues));
681
- }
682
- if (ops.$startsWith !== undefined) {
683
- // Use range query for prefix matching (index-friendly)
684
- conditions.push((0, drizzle_orm_1.gte)(column, ops.$startsWith));
685
- conditions.push((0, drizzle_orm_1.lt)(column, ops.$startsWith + '\uffff'));
686
- }
687
- if (ops.$endsWith !== undefined) {
688
- conditions.push((0, drizzle_orm_1.like)(column, `%${ops.$endsWith}`));
689
- }
690
- if (ops.$contains !== undefined) {
691
- conditions.push((0, drizzle_orm_1.like)(column, `%${ops.$contains}%`));
692
- }
693
- if (ops.$regex !== undefined) {
694
- // SQLite uses GLOB as regex approximation
695
- conditions.push((0, drizzle_orm_1.sql) `${column} GLOB ${ops.$regex.replace(/\.\*/g, '*').replace(/\./g, '?')}`);
696
- }
697
- if (isObject && ops.$strStartsWith !== undefined) {
698
- const lexical = drizzle_orm_1.sql.raw(this.objectLexicalSql('object'));
699
- conditions.push((0, drizzle_orm_1.sql) `${lexical} >= ${ops.$strStartsWith}`);
700
- conditions.push((0, drizzle_orm_1.sql) `${lexical} < ${ops.$strStartsWith + '\uffff'}`);
701
- }
702
- if (isObject && ops.$strEndsWith !== undefined) {
703
- conditions.push((0, drizzle_orm_1.sql) `${drizzle_orm_1.sql.raw(this.objectLexicalSql('object'))} LIKE ${`%${ops.$strEndsWith}`}`);
704
- }
705
- if (isObject && ops.$strContains !== undefined) {
706
- conditions.push((0, drizzle_orm_1.sql) `${drizzle_orm_1.sql.raw(this.objectLexicalSql('object'))} LIKE ${`%${ops.$strContains}%`}`);
553
+ }
554
+ addTermConditions(conditions, params, column, match, isObject) {
555
+ if (!match)
556
+ return;
557
+ if ((0, types_1.isTerm)(match)) {
558
+ conditions.push(`${column} = ?`);
559
+ params.push(isObject ? (0, serialization_1.serializeObject)(match) : (0, serialization_1.termToId)(match));
560
+ return;
561
+ }
562
+ const ops = match;
563
+ if (ops.$eq !== undefined) {
564
+ conditions.push(`${column} = ?`);
565
+ params.push(this.serializeOpValue(ops.$eq, isObject, '$eq'));
566
+ }
567
+ if (ops.$ne !== undefined) {
568
+ conditions.push(`${column} != ?`);
569
+ params.push(this.serializeOpValue(ops.$ne, isObject, '$ne'));
570
+ }
571
+ if (ops.$gt !== undefined) {
572
+ conditions.push(`${column} > ?`);
573
+ params.push(this.serializeOpValue(ops.$gt, isObject, '$gt'));
574
+ }
575
+ if (ops.$gte !== undefined) {
576
+ conditions.push(`${column} >= ?`);
577
+ params.push(this.serializeOpValue(ops.$gte, isObject, '$gte'));
578
+ }
579
+ if (ops.$lt !== undefined) {
580
+ conditions.push(`${column} < ?`);
581
+ params.push(this.serializeOpValue(ops.$lt, isObject, '$lt'));
582
+ }
583
+ if (ops.$lte !== undefined) {
584
+ conditions.push(`${column} <= ?`);
585
+ params.push(this.serializeOpValue(ops.$lte, isObject, '$lte'));
586
+ }
587
+ if (ops.$in !== undefined && ops.$in.length > 0) {
588
+ const placeholders = ops.$in.map(() => '?').join(', ');
589
+ conditions.push(`${column} IN (${placeholders})`);
590
+ params.push(...ops.$in.map(v => this.serializeOpValue(v, isObject, '$in')));
591
+ }
592
+ if (ops.$notIn !== undefined && ops.$notIn.length > 0) {
593
+ const placeholders = ops.$notIn.map(() => '?').join(', ');
594
+ conditions.push(`${column} NOT IN (${placeholders})`);
595
+ params.push(...ops.$notIn.map(v => this.serializeOpValue(v, isObject, '$notIn')));
596
+ }
597
+ if (ops.$startsWith !== undefined) {
598
+ conditions.push(`${column} >= ? AND ${column} < ?`);
599
+ params.push(ops.$startsWith, ops.$startsWith + '\uffff');
600
+ }
601
+ if (ops.$endsWith !== undefined) {
602
+ conditions.push(`${column} LIKE ?`);
603
+ params.push(`%${ops.$endsWith}`);
604
+ }
605
+ if (ops.$contains !== undefined) {
606
+ conditions.push(`${column} LIKE ?`);
607
+ params.push(`%${ops.$contains}%`);
608
+ }
609
+ if (ops.$regex !== undefined) {
610
+ conditions.push(`${column} GLOB ?`);
611
+ params.push(ops.$regex.replace(/\.\*/g, '*').replace(/\./g, '?'));
612
+ }
613
+ if (ops.$isNull === true) {
614
+ conditions.push(`${column} IS NULL`);
615
+ }
616
+ if (ops.$isNull === false) {
617
+ conditions.push(`${column} IS NOT NULL`);
618
+ }
619
+ }
620
+ serializeOpValue(value, isObject, filterOp) {
621
+ if (typeof value === 'object' && 'termType' in value) {
622
+ return isObject ? (0, serialization_1.serializeObject)(value) : (0, serialization_1.termToId)(value);
623
+ }
624
+ if (typeof value === 'number') {
625
+ if (isObject) {
626
+ if (filterOp === '$eq' || filterOp === '$ne' || filterOp === '$in' || filterOp === '$notIn') {
627
+ const lit = n3_1.DataFactory.literal(String(value), n3_1.DataFactory.namedNode('http://www.w3.org/2001/XMLSchema#integer'));
628
+ return (0, serialization_1.serializeObject)(lit);
707
629
  }
708
- if (isObject && ops.$strRegex !== undefined) {
709
- conditions.push((0, drizzle_orm_1.sql) `${drizzle_orm_1.sql.raw(this.objectLexicalSql('object'))} GLOB ${ops.$strRegex.replace(/\.\*/g, '*').replace(/\./g, '?')}`);
630
+ const fpValue = `N${serialization_1.SEP}${(0, serialization_1.fpEncode)(value)}`;
631
+ if (filterOp === '$gt' || filterOp === '$lte') {
632
+ return fpValue + serialization_1.SEP + '\uffff';
710
633
  }
711
- if (isObject && ops.$language !== undefined) {
712
- conditions.push(ops.$language === '*'
713
- ? (0, drizzle_orm_1.sql) `lower(${column}) LIKE ${`%"@%`}`
714
- : (0, drizzle_orm_1.sql) `lower(${column}) LIKE ${`%"@${ops.$language.toLowerCase()}`}`);
634
+ return fpValue;
635
+ }
636
+ return value;
637
+ }
638
+ if (isObject && !(0, serialization_1.isSerializedObjectValue)(value)) {
639
+ return `"${value}"`;
640
+ }
641
+ return value;
642
+ }
643
+ addObjectConditions(conditions, params, alias, match, predicate) {
644
+ const column = (name) => alias ? `${alias}.${name}` : name;
645
+ if (typeof match === 'object' && 'termType' in match) {
646
+ this.addObjectExactCondition(conditions, params, column, match, predicate);
647
+ return;
648
+ }
649
+ const ops = match;
650
+ if (ops.$eq !== undefined) {
651
+ this.addObjectExactValueCondition(conditions, params, column, ops.$eq, '$eq', predicate);
652
+ }
653
+ if (ops.$ne !== undefined) {
654
+ this.addObjectExactValueCondition(conditions, params, column, ops.$ne, '$ne', predicate);
655
+ }
656
+ if (ops.$gt !== undefined) {
657
+ this.addObjectComparableCondition(conditions, params, column, '>', ops.$gt, '$gt', predicate);
658
+ }
659
+ if (ops.$gte !== undefined) {
660
+ this.addObjectComparableCondition(conditions, params, column, '>=', ops.$gte, '$gte', predicate);
661
+ }
662
+ if (ops.$lt !== undefined) {
663
+ this.addObjectComparableCondition(conditions, params, column, '<', ops.$lt, '$lt', predicate);
664
+ }
665
+ if (ops.$lte !== undefined) {
666
+ this.addObjectComparableCondition(conditions, params, column, '<=', ops.$lte, '$lte', predicate);
667
+ }
668
+ if (ops.$in !== undefined && ops.$in.length > 0) {
669
+ const predicates = ops.$in.map((value) => this.objectPredicateForOperatorValue(value, '$in', predicate));
670
+ const placeholders = predicates.map((item) => {
671
+ if (item.fields.objectKey === null) {
672
+ return `(${column('object_kind')} = ? AND ${column('object_digest')} = ? AND ${column('object')} = ?)`;
715
673
  }
716
- if (ops.$isNull === true) {
717
- conditions.push((0, drizzle_orm_1.isNull)(column));
674
+ return `(${column('object_kind')} = ? AND ${column('object_key')} = ?)`;
675
+ }).join(' OR ');
676
+ conditions.push(`(${placeholders})`);
677
+ for (const item of predicates) {
678
+ if (item.fields.objectKey === null) {
679
+ params.push(item.fields.objectKind, this.objectDigestForIndex(item.serialized, item.fields), item.serialized);
718
680
  }
719
- if (ops.$isNull === false) {
720
- conditions.push((0, drizzle_orm_1.isNotNull)(column));
681
+ else {
682
+ params.push(item.fields.objectKind, item.fields.objectKey);
721
683
  }
722
684
  }
723
- };
724
- addTermConditions(schema_1.quints.graph, pattern.graph);
725
- addTermConditions(schema_1.quints.subject, pattern.subject);
726
- addTermConditions(schema_1.quints.predicate, pattern.predicate);
727
- addTermConditions(schema_1.quints.object, pattern.object, true);
728
- return conditions;
685
+ }
686
+ if (ops.$notIn !== undefined && ops.$notIn.length > 0) {
687
+ for (const value of ops.$notIn) {
688
+ conditions.push(`${column('object')} != ?`);
689
+ params.push(this.objectPredicateForOperatorValue(value, '$notIn', predicate).serialized);
690
+ }
691
+ }
692
+ if (ops.$startsWith !== undefined) {
693
+ const fields = this.objectFieldsForPrefix(ops.$startsWith, predicate);
694
+ this.assertComparableObject(fields, '$startsWith');
695
+ conditions.push(`${column('object_kind')} = ?`);
696
+ params.push(fields.objectKind);
697
+ conditions.push(`${column('object_key')} >= ? AND ${column('object_key')} < ?`);
698
+ params.push(ops.$startsWith, ops.$startsWith + '\uffff');
699
+ }
700
+ if (ops.$endsWith !== undefined) {
701
+ this.addObjectTextCondition(conditions, params, column, 'LIKE', `%${ops.$endsWith}`, predicate);
702
+ }
703
+ if (ops.$contains !== undefined) {
704
+ this.addObjectTextCondition(conditions, params, column, 'LIKE', `%${ops.$contains}%`, predicate);
705
+ }
706
+ if (ops.$regex !== undefined) {
707
+ this.addObjectTextCondition(conditions, params, column, 'GLOB', ops.$regex.replace(/\.\*/g, '*').replace(/\./g, '?'), predicate);
708
+ }
709
+ if (ops.$strStartsWith !== undefined) {
710
+ this.addObjectLexicalStringCondition(conditions, params, column, 'startsWith', ops.$strStartsWith);
711
+ }
712
+ if (ops.$strEndsWith !== undefined) {
713
+ this.addObjectLexicalStringCondition(conditions, params, column, 'endsWith', ops.$strEndsWith);
714
+ }
715
+ if (ops.$strContains !== undefined) {
716
+ this.addObjectLexicalStringCondition(conditions, params, column, 'contains', ops.$strContains);
717
+ }
718
+ if (ops.$strRegex !== undefined) {
719
+ this.addObjectLexicalStringCondition(conditions, params, column, 'regex', ops.$strRegex);
720
+ }
721
+ if (ops.$language !== undefined) {
722
+ this.addObjectLanguageCondition(conditions, params, column, ops.$language);
723
+ }
724
+ if (ops.$isNull === true) {
725
+ conditions.push(`${column('object')} IS NULL`);
726
+ }
727
+ if (ops.$isNull === false) {
728
+ conditions.push(`${column('object')} IS NOT NULL`);
729
+ }
730
+ }
731
+ addObjectExactCondition(conditions, params, column, object, predicate) {
732
+ const serialized = (0, serialization_1.serializeObject)(object);
733
+ const fields = this.objectIndexForTerm(this.predicateForIndex(predicate), object);
734
+ this.addObjectExactSerializedCondition(conditions, params, column, serialized, fields);
735
+ }
736
+ addObjectExactValueCondition(conditions, params, column, value, op, predicate) {
737
+ const item = this.objectPredicateForOperatorValue(value, op, predicate);
738
+ if (op === '$ne') {
739
+ conditions.push(`${column('object')} != ?`);
740
+ params.push(item.serialized);
741
+ return;
742
+ }
743
+ this.addObjectExactSerializedCondition(conditions, params, column, item.serialized, item.fields);
744
+ }
745
+ addObjectExactSerializedCondition(conditions, params, column, serialized, fields) {
746
+ if (fields.objectKey !== null) {
747
+ conditions.push(`${column('object_kind')} = ?`);
748
+ params.push(fields.objectKind);
749
+ conditions.push(`${column('object_key')} = ?`);
750
+ params.push(fields.objectKey);
751
+ return;
752
+ }
753
+ conditions.push(`${column('object_kind')} = ?`);
754
+ params.push(fields.objectKind);
755
+ conditions.push(`${column('object_digest')} = ?`);
756
+ params.push(this.objectDigestForIndex(serialized, fields));
757
+ conditions.push(`${column('object')} = ?`);
758
+ params.push(serialized);
759
+ }
760
+ addObjectComparableCondition(conditions, params, column, sqlOperator, value, op, predicate) {
761
+ const item = this.objectPredicateForOperatorValue(value, op, predicate);
762
+ this.assertComparableObject(item.fields, op);
763
+ conditions.push(`${column('object_kind')} = ?`);
764
+ params.push(item.fields.objectKind);
765
+ conditions.push(`${column('object_key')} ${sqlOperator} ?`);
766
+ params.push(item.serialized);
767
+ }
768
+ addObjectTextCondition(conditions, params, column, sqlOperator, value, predicate) {
769
+ const declaredType = (0, value_types_1.getPredicateObjectDataType)(predicate, this.options.predicateObjectDataTypes);
770
+ if (declaredType) {
771
+ if (!['text', 'longText', 'literal'].includes(declaredType)) {
772
+ throw new Error(`Object text search is not supported for ${declaredType}`);
773
+ }
774
+ conditions.push(`${column('object_kind')} = ?`);
775
+ params.push(declaredType);
776
+ }
777
+ conditions.push(`${column('object_text')} ${sqlOperator} ?`);
778
+ params.push(value);
779
+ }
780
+ addObjectLexicalStringCondition(conditions, params, column, op, value) {
781
+ const lexical = this.objectLexicalSql(column);
782
+ if (op === 'startsWith') {
783
+ conditions.push(`(${lexical} >= ? AND ${lexical} < ?)`);
784
+ params.push(value, value + '\uffff');
785
+ return;
786
+ }
787
+ if (op === 'endsWith') {
788
+ conditions.push(`${lexical} LIKE ?`);
789
+ params.push(`%${value}`);
790
+ return;
791
+ }
792
+ if (op === 'contains') {
793
+ conditions.push(`${lexical} LIKE ?`);
794
+ params.push(`%${value}%`);
795
+ return;
796
+ }
797
+ conditions.push(`${lexical} GLOB ?`);
798
+ params.push(value.replace(/\.\*/g, '*').replace(/\./g, '?'));
799
+ }
800
+ addObjectLanguageCondition(conditions, params, column, lang) {
801
+ const languageLiteralKinds = `${column('object_kind')} IN ('text', 'longText', 'literal')`;
802
+ if (lang === '*') {
803
+ conditions.push(`(${languageLiteralKinds} AND lower(${column('object')}) LIKE ?)`);
804
+ params.push('%"@%');
805
+ return;
806
+ }
807
+ conditions.push(`(${languageLiteralKinds} AND (lower(${column('object')}) LIKE ? OR lower(${column('object')}) LIKE ?))`);
808
+ params.push(`%"@${lang.toLowerCase()}`, `%"@${lang.toLowerCase()}-%`);
729
809
  }
730
810
  objectLexicalSql(column) {
811
+ const object = column('object');
731
812
  return `CASE
732
- WHEN ${column} LIKE '"%"@%' THEN substr(${column}, 2, instr(substr(${column}, 2), '"') - 1)
733
- WHEN ${column} LIKE '"%"^^%' THEN substr(${column}, 2, instr(substr(${column}, 2), '"') - 1)
734
- WHEN ${column} LIKE '"%"' THEN substr(${column}, 2, length(${column}) - 2)
735
- WHEN ${column} LIKE 'N${serialization_1.SEP}%' THEN substr(${column}, length('N${serialization_1.SEP}') + instr(substr(${column}, length('N${serialization_1.SEP}') + 1), '${serialization_1.SEP}') + instr(substr(${column}, length('N${serialization_1.SEP}') + instr(substr(${column}, length('N${serialization_1.SEP}') + 1), '${serialization_1.SEP}') + 1), '${serialization_1.SEP}') + 1)
736
- WHEN ${column} LIKE 'D${serialization_1.SEP}%' THEN substr(${column}, length('D${serialization_1.SEP}') + instr(substr(${column}, length('D${serialization_1.SEP}') + 1), '${serialization_1.SEP}') + 1)
737
- ELSE ${column}
813
+ WHEN ${column('object_text')} IS NOT NULL THEN ${column('object_text')}
814
+ WHEN ${column('object_kind')} IN ('iri', 'blankNode') THEN ${column('object_key')}
815
+ WHEN ${column('object_kind')} = 'numeric' THEN substr(${object}, length('N${serialization_1.SEP}') + instr(substr(${object}, length('N${serialization_1.SEP}') + 1), '${serialization_1.SEP}') + instr(substr(${object}, length('N${serialization_1.SEP}') + instr(substr(${object}, length('N${serialization_1.SEP}') + 1), '${serialization_1.SEP}') + 1), '${serialization_1.SEP}') + 1)
816
+ WHEN ${column('object_kind')} = 'dateTime' THEN substr(${object}, length('D${serialization_1.SEP}') + instr(substr(${object}, length('D${serialization_1.SEP}') + 1), '${serialization_1.SEP}') + 1)
817
+ ELSE NULL
738
818
  END`;
739
819
  }
820
+ objectPredicateForOperatorValue(value, op, predicate) {
821
+ const serialized = this.serializeOpValue(value, true, op);
822
+ return {
823
+ serialized,
824
+ fields: this.objectFieldsForOperatorValue(value, serialized, op, predicate),
825
+ };
826
+ }
827
+ objectFieldsForOperatorValue(value, serialized, op, predicate) {
828
+ const declaredType = (0, value_types_1.getPredicateObjectDataType)(predicate, this.options.predicateObjectDataTypes);
829
+ if (declaredType) {
830
+ if (declaredType === 'longText') {
831
+ return { objectKind: 'longText', objectKey: null, objectText: String(value) };
832
+ }
833
+ return { objectKind: declaredType, objectKey: serialized, objectText: null };
834
+ }
835
+ if (typeof value === 'number' && !['$eq', '$ne', '$in', '$notIn'].includes(op)) {
836
+ return { objectKind: 'numeric', objectKey: serialized, objectText: null };
837
+ }
838
+ if (!['$eq', '$ne', '$in', '$notIn'].includes(op)) {
839
+ if ((0, serialization_1.isSerializedNumericLiteral)(serialized)) {
840
+ return { objectKind: 'numeric', objectKey: serialized, objectText: null };
841
+ }
842
+ if ((0, serialization_1.isSerializedDateTimeLiteral)(serialized)) {
843
+ return { objectKind: 'dateTime', objectKey: serialized, objectText: null };
844
+ }
845
+ }
846
+ return this.objectIndexForSerialized(predicate, serialized);
847
+ }
848
+ objectIndexForTerm(predicate, object) {
849
+ return (0, value_types_1.objectIndexFieldsFromTerm)(object, {
850
+ predicate,
851
+ predicateObjectDataTypes: this.options.predicateObjectDataTypes,
852
+ textMaxBytes: this.options.textMaxBytes,
853
+ });
854
+ }
855
+ objectIndexForSerialized(predicate, object) {
856
+ return (0, value_types_1.objectIndexFieldsFromSerialized)(object, {
857
+ predicate,
858
+ predicateObjectDataTypes: this.options.predicateObjectDataTypes,
859
+ textMaxBytes: this.options.textMaxBytes,
860
+ });
861
+ }
862
+ objectFieldsForPrefix(prefix, predicate) {
863
+ const declaredType = (0, value_types_1.getPredicateObjectDataType)(predicate, this.options.predicateObjectDataTypes);
864
+ if (declaredType) {
865
+ if (declaredType === 'longText') {
866
+ return { objectKind: 'longText', objectKey: null, objectText: prefix };
867
+ }
868
+ return { objectKind: declaredType, objectKey: prefix, objectText: null };
869
+ }
870
+ if ((0, serialization_1.isSerializedNumericLiteral)(prefix)) {
871
+ return { objectKind: 'numeric', objectKey: prefix, objectText: null };
872
+ }
873
+ if ((0, serialization_1.isSerializedDateTimeLiteral)(prefix)) {
874
+ return { objectKind: 'dateTime', objectKey: prefix, objectText: null };
875
+ }
876
+ if (prefix.startsWith('"')) {
877
+ return { objectKind: 'text', objectKey: prefix, objectText: null };
878
+ }
879
+ if (prefix.startsWith('_:')) {
880
+ return { objectKind: 'blankNode', objectKey: prefix, objectText: null };
881
+ }
882
+ return { objectKind: 'iri', objectKey: prefix, objectText: null };
883
+ }
884
+ objectDigestForIndex(serialized, fields) {
885
+ return fields.objectKey === null ? digestObject(serialized) : null;
886
+ }
887
+ assertComparableObject(fields, op) {
888
+ if (fields.objectKey !== null && fields.objectKind !== 'longText') {
889
+ return;
890
+ }
891
+ throw new Error(`Object ${op} is not supported for ${fields.objectKind}; declare/use a comparable data type instead of longText`);
892
+ }
893
+ extractExactPredicate(match) {
894
+ if (!match)
895
+ return undefined;
896
+ if (typeof match === 'object' && 'termType' in match) {
897
+ return (0, serialization_1.termToId)(match);
898
+ }
899
+ const ops = match;
900
+ if (ops.$eq !== undefined) {
901
+ return String(this.serializeOpValue(ops.$eq, false, '$eq'));
902
+ }
903
+ return undefined;
904
+ }
905
+ resolveObjectDataTypeForPattern(pattern) {
906
+ const predicate = this.extractExactPredicate(pattern.predicate);
907
+ if (predicate) {
908
+ return (0, value_types_1.getPredicateObjectDataType)(predicate, this.options.predicateObjectDataTypes);
909
+ }
910
+ if (pattern.object && typeof pattern.object === 'object' && 'termType' in pattern.object) {
911
+ return this.objectIndexForTerm(predicate, pattern.object).objectKind;
912
+ }
913
+ return undefined;
914
+ }
915
+ predicateForIndex(predicate) {
916
+ return predicate;
917
+ }
918
+ writeStatementForRow(row) {
919
+ const params = [
920
+ row.objectKind,
921
+ row.objectKey,
922
+ row.objectText,
923
+ row.objectDigest,
924
+ row.graph,
925
+ row.subject,
926
+ row.predicate,
927
+ row.object,
928
+ row.vector,
929
+ ];
930
+ if (row.objectKey !== null) {
931
+ return {
932
+ sql: `
933
+ INSERT INTO quints (
934
+ object_kind, object_key, object_text, object_digest,
935
+ graph, subject, predicate, object, vector
936
+ )
937
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
938
+ ON CONFLICT (graph, subject, predicate, object_kind, object_key)
939
+ WHERE object_key IS NOT NULL
940
+ DO UPDATE SET
941
+ vector = excluded.vector,
942
+ object_text = excluded.object_text,
943
+ object_digest = excluded.object_digest
944
+ WHERE quints.object = excluded.object
945
+ `,
946
+ params,
947
+ };
948
+ }
949
+ return {
950
+ sql: `
951
+ INSERT INTO quints (
952
+ object_kind, object_key, object_text, object_digest,
953
+ graph, subject, predicate, object, vector
954
+ )
955
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
956
+ ON CONFLICT (graph, subject, predicate, object_kind, object_digest)
957
+ WHERE object_digest IS NOT NULL
958
+ DO UPDATE SET
959
+ vector = excluded.vector,
960
+ object_text = excluded.object_text
961
+ WHERE quints.object = excluded.object
962
+ `,
963
+ params,
964
+ };
965
+ }
740
966
  quintToRow(quint) {
967
+ const predicate = (0, serialization_1.termToId)(quint.predicate);
968
+ const object = (0, serialization_1.serializeObject)(quint.object);
969
+ const objectIndex = this.objectIndexForTerm(predicate, quint.object);
741
970
  return {
742
971
  graph: (0, serialization_1.termToId)(quint.graph),
743
972
  subject: (0, serialization_1.termToId)(quint.subject),
744
- predicate: (0, serialization_1.termToId)(quint.predicate),
745
- object: (0, serialization_1.serializeObject)(quint.object),
973
+ predicate,
974
+ object,
746
975
  vector: quint.vector ? JSON.stringify(quint.vector) : null,
976
+ objectKind: objectIndex.objectKind,
977
+ objectKey: objectIndex.objectKey,
978
+ objectText: objectIndex.objectText,
979
+ objectDigest: this.objectDigestForIndex(object, objectIndex),
747
980
  };
748
981
  }
749
982
  rowToQuint(row) {