@undefineds.co/xpod 0.3.29 → 0.3.31

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 (35) hide show
  1. package/dist/authorization/PodAuthorizationResources.d.ts +1 -0
  2. package/dist/authorization/PodAuthorizationResources.js +36 -4
  3. package/dist/authorization/PodAuthorizationResources.js.map +1 -1
  4. package/dist/provision/LocalPodProvisioningService.js +2 -0
  5. package/dist/provision/LocalPodProvisioningService.js.map +1 -1
  6. package/dist/provision/ProvisionPodCreator.js +16 -0
  7. package/dist/provision/ProvisionPodCreator.js.map +1 -1
  8. package/dist/storage/accessors/MixDataAccessor.js.map +1 -1
  9. package/dist/storage/rdf/PostgresRdfEngine.d.ts +12 -15
  10. package/dist/storage/rdf/PostgresRdfEngine.js +1040 -150
  11. package/dist/storage/rdf/PostgresRdfEngine.js.map +1 -1
  12. package/dist/storage/rdf/PostgresRdfEngine.jsonld +40 -52
  13. package/dist/storage/rdf/{RdfLocalQueryEngine.d.ts → RdfQueryExecutor.d.ts} +3 -3
  14. package/dist/storage/rdf/{RdfLocalQueryEngine.js → RdfQueryExecutor.js} +9 -9
  15. package/dist/storage/rdf/RdfQueryExecutor.js.map +1 -0
  16. package/dist/storage/rdf/RdfSparqlAdapter.d.ts +5 -5
  17. package/dist/storage/rdf/RdfSparqlAdapter.js +27 -27
  18. package/dist/storage/rdf/RdfSparqlAdapter.js.map +1 -1
  19. package/dist/storage/rdf/SolidRdfEngine.d.ts +2 -5
  20. package/dist/storage/rdf/SolidRdfEngine.js +6 -38
  21. package/dist/storage/rdf/SolidRdfEngine.js.map +1 -1
  22. package/dist/storage/rdf/SolidRdfEngine.jsonld +0 -12
  23. package/dist/storage/rdf/SolidRdfSparqlEngine.js.map +1 -1
  24. package/dist/storage/rdf/index.d.ts +3 -3
  25. package/dist/storage/rdf/index.js +6 -6
  26. package/dist/storage/rdf/index.js.map +1 -1
  27. package/dist/storage/rdf/models-benchmark.d.ts +9 -9
  28. package/dist/storage/rdf/models-benchmark.js +23 -23
  29. package/dist/storage/rdf/models-benchmark.js.map +1 -1
  30. package/dist/storage/rdf/types.d.ts +5 -5
  31. package/dist/storage/rdf/types.js.map +1 -1
  32. package/package.json +1 -1
  33. package/templates/pod/acp/profile/.acr +21 -0
  34. package/templates/pod/wac/profile/.acl.hbs +18 -0
  35. package/dist/storage/rdf/RdfLocalQueryEngine.js.map +0 -1
@@ -2,16 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PostgresRdfEngine = void 0;
4
4
  const node_crypto_1 = require("node:crypto");
5
- const node_fs_1 = require("node:fs");
6
- const promises_1 = require("node:fs/promises");
7
- const node_path_1 = require("node:path");
8
- const node_os_1 = require("node:os");
9
5
  const n3_1 = require("n3");
10
6
  const pglite_1 = require("@electric-sql/pglite");
11
7
  const PostgresPoolManager_1 = require("../database/PostgresPoolManager");
12
8
  const RdfTermDictionary_1 = require("./RdfTermDictionary");
13
9
  const RdfTermSemantics_1 = require("./RdfTermSemantics");
14
- const SolidRdfEngine_1 = require("./SolidRdfEngine");
15
10
  const Rdf3xSchema_1 = require("./Rdf3xSchema");
16
11
  const types_1 = require("../quint/types");
17
12
  const { namedNode, quad } = n3_1.DataFactory;
@@ -412,18 +407,11 @@ class PostgresRdfEngine {
412
407
  constructor(options) {
413
408
  this.executor = null;
414
409
  this.termDictionary = null;
415
- this.cacheEngine = null;
416
- this.cacheDir = null;
417
- this.cachePath = null;
418
- this.factsDataVersion = 0;
419
- this.cacheDirty = true;
420
410
  this.initialized = false;
421
411
  this.initializing = null;
422
412
  this.sharedPoolConfig = null;
423
413
  this.pglite = null;
424
414
  this.pgPool = null;
425
- this.rdf3xDataVersion = 0;
426
- this.rdf3xDirty = true;
427
415
  this.pgOptions = {
428
416
  ...options,
429
417
  driver: options.driver ?? (options.connectionString || options.pool ? 'pg' : 'pglite'),
@@ -442,9 +430,6 @@ class PostgresRdfEngine {
442
430
  this.termDictionary = new PostgresRdfTermDictionary(this.requireExecutor());
443
431
  await this.termDictionary.initialize();
444
432
  await this.initializeSchema();
445
- this.factsDataVersion = await this.readFactsDataVersion();
446
- this.rdf3xDataVersion = await this.readRdf3xFactsDataVersion();
447
- this.rdf3xDirty = this.rdf3xDataVersion !== this.factsDataVersion;
448
433
  this.initialized = true;
449
434
  })
450
435
  .finally(() => {
@@ -456,15 +441,6 @@ class PostgresRdfEngine {
456
441
  if (this.initializing) {
457
442
  await this.initializing.catch(() => { });
458
443
  }
459
- if (this.cacheEngine) {
460
- await this.cacheEngine.close();
461
- this.cacheEngine = null;
462
- }
463
- if (this.cacheDir) {
464
- await (0, promises_1.rm)(this.cacheDir, { recursive: true, force: true });
465
- this.cacheDir = null;
466
- this.cachePath = null;
467
- }
468
444
  this.executor = null;
469
445
  if (this.pglite) {
470
446
  await this.pglite.close();
@@ -482,10 +458,6 @@ class PostgresRdfEngine {
482
458
  }
483
459
  this.termDictionary = null;
484
460
  this.initialized = false;
485
- this.cacheDirty = true;
486
- this.factsDataVersion = 0;
487
- this.rdf3xDataVersion = 0;
488
- this.rdf3xDirty = true;
489
461
  }
490
462
  async put(quads, options) {
491
463
  await this.ensureReady();
@@ -499,11 +471,8 @@ class PostgresRdfEngine {
499
471
  await this.insertQuads(tx, scopedDictionary, quadList, sourceId, options?.sourceLineNo ?? null);
500
472
  await this.bumpFactsDataVersion(tx);
501
473
  });
502
- this.factsDataVersion = await this.readFactsDataVersion();
503
- this.markDerivedDirty();
504
474
  }
505
475
  catch (error) {
506
- this.cacheDirty = true;
507
476
  throw error;
508
477
  }
509
478
  }
@@ -519,11 +488,8 @@ class PostgresRdfEngine {
519
488
  await this.insertQuads(tx, scopedDictionary, quads, sourceId, null);
520
489
  await this.bumpFactsDataVersion(tx);
521
490
  });
522
- this.factsDataVersion = await this.readFactsDataVersion();
523
- this.markDerivedDirty();
524
491
  }
525
492
  catch (error) {
526
- this.cacheDirty = true;
527
493
  throw error;
528
494
  }
529
495
  }
@@ -541,12 +507,9 @@ class PostgresRdfEngine {
541
507
  await this.bumpFactsDataVersion(tx);
542
508
  return deleteResult.length;
543
509
  });
544
- this.factsDataVersion = await this.readFactsDataVersion();
545
- this.markDerivedDirty();
546
510
  return result;
547
511
  }
548
512
  catch (error) {
549
- this.cacheDirty = true;
550
513
  throw error;
551
514
  }
552
515
  }
@@ -566,12 +529,9 @@ class PostgresRdfEngine {
566
529
  }
567
530
  await this.bumpFactsDataVersion(tx);
568
531
  });
569
- this.factsDataVersion = await this.readFactsDataVersion();
570
- this.markDerivedDirty();
571
532
  return scan.quads.length;
572
533
  }
573
534
  catch (error) {
574
- this.cacheDirty = true;
575
535
  throw error;
576
536
  }
577
537
  }
@@ -603,44 +563,31 @@ class PostgresRdfEngine {
603
563
  }
604
564
  return deletedRows;
605
565
  });
606
- this.factsDataVersion = await this.readFactsDataVersion();
607
- this.markDerivedDirty();
608
566
  return {
609
567
  deletedRows,
610
568
  insertedRows: inserts.length,
611
569
  };
612
570
  }
613
571
  catch (error) {
614
- this.cacheDirty = true;
615
572
  throw error;
616
573
  }
617
574
  }
618
575
  async scan(query) {
619
576
  await this.ensureReady();
620
- if (!isPgRdf3xCompatiblePattern(query.pattern)) {
621
- await this.ensureCacheReady();
622
- const fallback = await this.requireCache().scan(query);
623
- fallback.metrics.queryPlan = ['PostgresRdf3xScanFallback', ...(fallback.metrics.queryPlan ?? [])];
624
- return fallback;
625
- }
626
- return this.scanNative(query.pattern, query.options);
577
+ return isPgSqlScanCompatiblePattern(query.pattern)
578
+ ? this.scanNative(query.pattern, query.options)
579
+ : this.scanPostFilter(query.pattern, query.options);
627
580
  }
628
581
  async query(query) {
629
582
  await this.ensureReady();
630
583
  const native = await this.queryNative(query);
631
- if (native.result) {
632
- return native.result;
633
- }
634
- await this.ensureCacheReady();
635
- const fallback = await this.requireCache().query(query);
636
- fallback.metrics.plan.unshift('PostgresRdf3xFallback');
637
- return fallback;
584
+ return native ?? this.queryFacts(query);
638
585
  }
639
586
  async refreshDerivedIndexes() {
640
587
  await this.ensureReady();
641
588
  const factsDataVersion = await this.readFactsDataVersion();
642
589
  const previousFactsDataVersion = await this.readRdf3xFactsDataVersion();
643
- if (previousFactsDataVersion === factsDataVersion && !this.rdf3xDirty) {
590
+ if (previousFactsDataVersion === factsDataVersion) {
644
591
  return {
645
592
  derivedIndexProfile: 'rdf3x',
646
593
  factsDataVersion,
@@ -653,8 +600,6 @@ class PostgresRdfEngine {
653
600
  };
654
601
  }
655
602
  const rebuild = await this.rebuildRdf3xDerivedIndexes(factsDataVersion);
656
- this.rdf3xDataVersion = factsDataVersion;
657
- this.rdf3xDirty = false;
658
603
  return {
659
604
  derivedIndexProfile: 'rdf3x',
660
605
  factsDataVersion,
@@ -952,34 +897,59 @@ class PostgresRdfEngine {
952
897
  ]),
953
898
  };
954
899
  }
900
+ async scanPostFilter(pattern, options) {
901
+ const start = Date.now();
902
+ const rows = await this.requireExecutor().query(`
903
+ SELECT graph_id, subject_id, predicate_id, object_id
904
+ FROM ${RDF_FACTS_TABLE}
905
+ ORDER BY graph_id, subject_id, predicate_id, object_id
906
+ `);
907
+ const quads = await this.rowsToQuads(rows);
908
+ const matched = quads.filter((value) => matchesQuadPattern(value, pattern));
909
+ const ordered = orderQuads(matched, options);
910
+ const startOffset = Math.max(0, options?.offset ?? 0);
911
+ const endOffset = options?.limit === undefined
912
+ ? undefined
913
+ : startOffset + Math.max(0, options.limit);
914
+ const page = ordered.slice(startOffset, endOffset);
915
+ return {
916
+ quads: page,
917
+ metrics: this.indexMetrics('facts-post-filter', matched.length, page.length, start, [
918
+ 'PostgresFactsScan',
919
+ 'PostgresFactsPostFilter',
920
+ ...(options?.order?.length ? [`PostgresFactsScanOrder(${describeScanOrder(options)})`] : []),
921
+ ...(options?.limit !== undefined || options?.offset !== undefined ? ['PostgresFactsScanLimit'] : []),
922
+ ]),
923
+ };
924
+ }
955
925
  async queryNative(query) {
956
926
  if (!this.canTryNativeQuery(query)) {
957
- return { unsupported: true };
927
+ return undefined;
958
928
  }
959
929
  const start = Date.now();
960
930
  const aggregates = queryAggregates(query);
961
931
  const requiredPatterns = query.patterns.length > 0 ? query.patterns : [{}];
962
932
  const compiledPatterns = await this.compileNativeJoinPatterns(requiredPatterns, query.filters ?? []);
963
933
  if (!compiledPatterns) {
964
- return { unsupported: true };
934
+ return undefined;
965
935
  }
966
936
  if (!this.allFiltersPushed(query.filters ?? [], compiledPatterns)) {
967
- return { unsupported: true };
937
+ return undefined;
968
938
  }
969
939
  if (aggregates.length > 0) {
970
940
  if (!query.patterns.length) {
971
- return { unsupported: true };
941
+ return undefined;
972
942
  }
973
943
  const aggregateResult = await this.queryNativeAggregate(query, compiledPatterns, aggregates, start);
974
- return aggregateResult ? { result: aggregateResult } : { unsupported: true };
944
+ return aggregateResult;
975
945
  }
976
946
  const visibleVariables = uniqueStrings(requiredPatterns.flatMap((pattern) => variablesInPattern(pattern)));
977
947
  const project = query.select && query.select.length > 0 ? query.select : visibleVariables;
978
948
  if (project.some((variableName) => !visibleVariables.includes(variableName))) {
979
- return { unsupported: true };
949
+ return undefined;
980
950
  }
981
951
  if ((query.orderBy ?? []).some((entry) => !visibleVariables.includes(entry.variable))) {
982
- return { unsupported: true };
952
+ return undefined;
983
953
  }
984
954
  const compiled = await this.compileJoinSql(compiledPatterns, {
985
955
  project,
@@ -991,13 +961,11 @@ class PostgresRdfEngine {
991
961
  });
992
962
  if (compiled.unresolved) {
993
963
  return {
994
- result: {
995
- bindings: [],
996
- metrics: this.localMetrics(start, 0, 0, 0, [compiled.indexChoice], [
997
- ...compiled.queryPlan,
998
- `unresolved ${compiled.unresolved}`,
999
- ], query.filters?.length ?? 0),
1000
- },
964
+ bindings: [],
965
+ metrics: this.localMetrics(start, 0, 0, 0, [compiled.indexChoice], [
966
+ ...compiled.queryPlan,
967
+ `unresolved ${compiled.unresolved}`,
968
+ ], query.filters?.length ?? 0),
1001
969
  };
1002
970
  }
1003
971
  const rows = await this.requireExecutor().query(compiled.sql, compiled.params);
@@ -1012,12 +980,358 @@ class PostgresRdfEngine {
1012
980
  ...(query.limit !== undefined || query.offset !== undefined ? ['PostgresRdf3xJoinLimit'] : []),
1013
981
  ];
1014
982
  return {
1015
- result: {
1016
- bindings,
1017
- metrics: this.localMetrics(start, matchedRows, matchedRows, bindings.length, [compiled.indexChoice], plan, query.filters?.length ?? 0),
1018
- },
983
+ bindings,
984
+ metrics: this.localMetrics(start, matchedRows, matchedRows, bindings.length, [compiled.indexChoice], plan, query.filters?.length ?? 0),
985
+ };
986
+ }
987
+ async queryFacts(query) {
988
+ const start = Date.now();
989
+ const metrics = this.localMetrics(start, 0, 0, 0, ['facts-post-filter'], ['PostgresFactsQuery']);
990
+ if ((query.textSearch?.length ?? 0) > 0) {
991
+ throw new Error('RdfQuery textSearch requires a configured RdfTextIndex');
992
+ }
993
+ if ((query.vectorSearch?.length ?? 0) > 0) {
994
+ throw new Error('RdfQuery vectorSearch requires a configured RdfVectorIndex');
995
+ }
996
+ const hasNonPatternSource = (query.values?.length ?? 0) > 0
997
+ || (query.textSearch?.length ?? 0) > 0
998
+ || (query.vectorSearch?.length ?? 0) > 0;
999
+ const requiredPatterns = query.patterns.length > 0
1000
+ ? query.patterns
1001
+ : query.unions?.length || hasNonPatternSource
1002
+ ? []
1003
+ : [{}];
1004
+ const aggregates = queryAggregates(query);
1005
+ let bindings = [{}];
1006
+ for (const values of query.values ?? []) {
1007
+ bindings = joinValuesSource(bindings, values);
1008
+ metrics.scannedRows += values.rows.length;
1009
+ metrics.plan.push(`PostgresFactsValues(${values.variables.map((variableName) => `?${variableName}`).join(',')})`);
1010
+ if (bindings.length === 0) {
1011
+ break;
1012
+ }
1013
+ }
1014
+ if (bindings.length > 0) {
1015
+ for (const pattern of requiredPatterns) {
1016
+ bindings = await this.joinFactsPattern(bindings, pattern, query.filters ?? [], metrics, false);
1017
+ metrics.plan.push(`PostgresFactsScan(${describeQueryPattern(pattern)})`);
1018
+ if (bindings.length === 0) {
1019
+ break;
1020
+ }
1021
+ }
1022
+ }
1023
+ if ((query.binds?.length ?? 0) > 0 && bindings.length > 0) {
1024
+ bindings = this.applyFactsBinds(bindings, query.binds ?? []);
1025
+ metrics.plan.push(`PostgresFactsBind(${(query.binds ?? []).map(describeBind).join(',')})`);
1026
+ }
1027
+ for (const rawOptionalGroup of query.optional ?? []) {
1028
+ bindings = await this.joinFactsOptionalGroup(bindings, normalizeOptionalGroup(rawOptionalGroup), metrics);
1029
+ metrics.plan.push(`PostgresFactsOptionalJoin(${normalizeOptionalGroup(rawOptionalGroup).patterns.map(describeQueryPattern).join(',')})`);
1030
+ }
1031
+ for (const unionGroup of query.unions ?? []) {
1032
+ bindings = await this.joinFactsUnionGroup(bindings, unionGroup.branches, query.filters ?? [], metrics);
1033
+ metrics.plan.push(`PostgresFactsUnion(${unionGroup.branches.map((branch) => branch.patterns.map(describeQueryPattern).join(',')).join('|')})`);
1034
+ if (bindings.length === 0) {
1035
+ break;
1036
+ }
1037
+ }
1038
+ for (const minusGroup of query.minus ?? []) {
1039
+ bindings = await this.applyFactsMinusGroup(bindings, minusGroup, metrics);
1040
+ metrics.plan.push(`PostgresFactsMinus(${minusGroup.patterns.map(describeQueryPattern).join(',')})`);
1041
+ if (bindings.length === 0) {
1042
+ break;
1043
+ }
1044
+ }
1045
+ for (const existsGroup of query.exists ?? []) {
1046
+ bindings = await this.applyFactsExistsGroup(bindings, existsGroup, metrics);
1047
+ metrics.plan.push(`PostgresFactsExists(${existsGroup.patterns.map(describeQueryPattern).join(',')})`);
1048
+ if (bindings.length === 0) {
1049
+ break;
1050
+ }
1051
+ }
1052
+ if ((query.filters?.length ?? 0) > 0) {
1053
+ bindings = bindings.filter((binding) => matchesBindingFilters(binding, query.filters ?? []));
1054
+ metrics.filtersApplied += query.filters?.length ?? 0;
1055
+ metrics.plan.push(`PostgresFactsFilter(${(query.filters ?? []).map(describeFilter).join(',')})`);
1056
+ }
1057
+ if (aggregates.length > 0 && (query.groupBy?.length ?? 0) > 0) {
1058
+ const joinedRows = bindings.length;
1059
+ bindings = groupAggregateBindings(bindings, query.groupBy ?? [], aggregates);
1060
+ metrics.joinedRows = joinedRows;
1061
+ metrics.plan.push(aggregatePlan(aggregates, true));
1062
+ if ((query.having?.length ?? 0) > 0) {
1063
+ bindings = bindings.filter((binding) => matchesBindingFilters(binding, query.having ?? []));
1064
+ metrics.filtersApplied += query.having?.length ?? 0;
1065
+ metrics.plan.push(`PostgresFactsHaving(${(query.having ?? []).map(describeFilter).join(',')})`);
1066
+ }
1067
+ }
1068
+ else if (aggregates.length > 0) {
1069
+ const { binding, firstCount } = aggregateBindings(bindings, aggregates);
1070
+ const having = query.having ?? [];
1071
+ metrics.joinedRows = bindings.length;
1072
+ metrics.plan.push(aggregatePlan(aggregates, false));
1073
+ if (having.length > 0 && !matchesBindingFilters(binding, having)) {
1074
+ metrics.filtersApplied += having.length;
1075
+ metrics.plan.push(`PostgresFactsHaving(${having.map(describeFilter).join(',')})`);
1076
+ metrics.returnedRows = 0;
1077
+ metrics.durationMs = Date.now() - start;
1078
+ return {
1079
+ bindings: [],
1080
+ count: firstCount,
1081
+ metrics,
1082
+ };
1083
+ }
1084
+ if (having.length > 0) {
1085
+ metrics.filtersApplied += having.length;
1086
+ metrics.plan.push(`PostgresFactsHaving(${having.map(describeFilter).join(',')})`);
1087
+ }
1088
+ metrics.returnedRows = 1;
1089
+ metrics.durationMs = Date.now() - start;
1090
+ return {
1091
+ bindings: [binding],
1092
+ count: firstCount,
1093
+ metrics,
1094
+ };
1095
+ }
1096
+ const joinedRows = metrics.joinedRows > 0 ? metrics.joinedRows : bindings.length;
1097
+ if ((query.orderBy?.length ?? 0) > 0) {
1098
+ bindings = [...bindings].sort((left, right) => compareBindings(left, right, query.orderBy ?? []));
1099
+ metrics.plan.push(`PostgresFactsSort(${describeQueryOrder(query.orderBy ?? [])})`);
1100
+ }
1101
+ let projected = query.select && query.select.length > 0
1102
+ ? bindings.map((binding) => projectBinding(binding, query.select ?? []))
1103
+ : bindings;
1104
+ if (query.distinct) {
1105
+ projected = distinctBindings(projected);
1106
+ metrics.plan.push('PostgresFactsDistinct');
1107
+ }
1108
+ if (query.offset !== undefined || query.limit !== undefined) {
1109
+ const startOffset = Math.max(0, query.offset ?? 0);
1110
+ const endOffset = query.limit === undefined
1111
+ ? undefined
1112
+ : startOffset + Math.max(0, query.limit);
1113
+ projected = projected.slice(startOffset, endOffset);
1114
+ metrics.plan.push('PostgresFactsLimit');
1115
+ }
1116
+ metrics.joinedRows = joinedRows;
1117
+ metrics.returnedRows = projected.length;
1118
+ metrics.durationMs = Date.now() - start;
1119
+ return {
1120
+ bindings: projected,
1121
+ metrics,
1019
1122
  };
1020
1123
  }
1124
+ async joinFactsPattern(input, pattern, filters, metrics, optional) {
1125
+ const output = [];
1126
+ for (const binding of input) {
1127
+ const compiled = compilePatternForBinding(pattern, binding);
1128
+ if (!compiled) {
1129
+ if (optional) {
1130
+ output.push(binding);
1131
+ }
1132
+ continue;
1133
+ }
1134
+ const scan = isPgSqlScanCompatiblePattern(compiled)
1135
+ ? await this.scanNative(compiled)
1136
+ : await this.scanPostFilter(compiled);
1137
+ metrics.scannedRows += scan.metrics.matchedRows;
1138
+ metrics.indexChoices.push(scan.metrics.indexChoice);
1139
+ metrics.plan.push(...storagePlanMarkers(scan.metrics.queryPlan));
1140
+ const before = output.length;
1141
+ for (const value of scan.quads) {
1142
+ const next = bindQuadPattern(pattern, binding, value);
1143
+ if (next && matchesNewlyBoundFilters(next, binding, filters)) {
1144
+ output.push(next);
1145
+ }
1146
+ }
1147
+ if (optional && output.length === before) {
1148
+ output.push(binding);
1149
+ }
1150
+ }
1151
+ return output;
1152
+ }
1153
+ async joinFactsOptionalGroup(input, optionalGroup, metrics) {
1154
+ const filters = optionalGroup.filters ?? [];
1155
+ const output = [];
1156
+ for (const binding of input) {
1157
+ let matches = [binding];
1158
+ matches = this.applyFactsValues(matches, optionalGroup.values, metrics, 'Optional');
1159
+ if (matches.length > 0) {
1160
+ for (const pattern of optionalGroup.patterns) {
1161
+ matches = await this.joinFactsPattern(matches, pattern, filters, metrics, false);
1162
+ if (matches.length === 0) {
1163
+ break;
1164
+ }
1165
+ }
1166
+ }
1167
+ if (matches.length > 0) {
1168
+ for (const unionGroup of optionalGroup.unions ?? []) {
1169
+ matches = await this.joinFactsUnionGroup(matches, unionGroup.branches, filters, metrics);
1170
+ metrics.plan.push(`PostgresFactsOptionalUnion(${unionGroup.branches.map((branch) => branch.patterns.map(describeQueryPattern).join(',')).join('|')})`);
1171
+ if (matches.length === 0) {
1172
+ break;
1173
+ }
1174
+ }
1175
+ }
1176
+ if (matches.length > 0) {
1177
+ for (const rawNestedOptional of optionalGroup.optional ?? []) {
1178
+ matches = await this.joinFactsOptionalGroup(matches, normalizeOptionalGroup(rawNestedOptional), metrics);
1179
+ if (matches.length === 0) {
1180
+ break;
1181
+ }
1182
+ }
1183
+ }
1184
+ if (matches.length > 0) {
1185
+ for (const minusGroup of optionalGroup.minus ?? []) {
1186
+ matches = await this.applyFactsMinusGroup(matches, minusGroup, metrics);
1187
+ if (matches.length === 0) {
1188
+ break;
1189
+ }
1190
+ }
1191
+ }
1192
+ if (matches.length > 0) {
1193
+ for (const existsGroup of optionalGroup.exists ?? []) {
1194
+ matches = await this.applyFactsExistsGroup(matches, existsGroup, metrics);
1195
+ if (matches.length === 0) {
1196
+ break;
1197
+ }
1198
+ }
1199
+ }
1200
+ if ((optionalGroup.binds?.length ?? 0) > 0 && matches.length > 0) {
1201
+ matches = this.applyFactsBinds(matches, optionalGroup.binds ?? []);
1202
+ metrics.plan.push(`PostgresFactsOptionalBind(${(optionalGroup.binds ?? []).map(describeBind).join(',')})`);
1203
+ }
1204
+ if (filters.length > 0 && matches.length > 0) {
1205
+ matches = matches.filter((match) => matchesBindingFilters(match, filters));
1206
+ metrics.filtersApplied += filters.length;
1207
+ metrics.plan.push(`PostgresFactsOptionalFilter(${filters.map(describeFilter).join(',')})`);
1208
+ }
1209
+ output.push(...(matches.length > 0 ? matches : [binding]));
1210
+ }
1211
+ return output;
1212
+ }
1213
+ async joinFactsUnionGroup(input, branches, outerFilters, metrics) {
1214
+ const output = [];
1215
+ for (const binding of input) {
1216
+ for (const branch of branches) {
1217
+ const branchFilters = [...outerFilters, ...(branch.filters ?? [])];
1218
+ let matches = [binding];
1219
+ matches = this.applyFactsValues(matches, branch.values, metrics, 'Union');
1220
+ if (matches.length === 0) {
1221
+ continue;
1222
+ }
1223
+ for (const pattern of branch.patterns) {
1224
+ matches = await this.joinFactsPattern(matches, pattern, branchFilters, metrics, false);
1225
+ if (matches.length === 0) {
1226
+ break;
1227
+ }
1228
+ }
1229
+ if (matches.length === 0) {
1230
+ continue;
1231
+ }
1232
+ for (const unionGroup of branch.unions ?? []) {
1233
+ matches = await this.joinFactsUnionGroup(matches, unionGroup.branches, branchFilters, metrics);
1234
+ if (matches.length === 0) {
1235
+ break;
1236
+ }
1237
+ }
1238
+ if (matches.length === 0) {
1239
+ continue;
1240
+ }
1241
+ if ((branch.binds?.length ?? 0) > 0) {
1242
+ matches = this.applyFactsBinds(matches, branch.binds ?? []);
1243
+ }
1244
+ for (const rawOptionalGroup of branch.optional ?? []) {
1245
+ matches = await this.joinFactsOptionalGroup(matches, normalizeOptionalGroup(rawOptionalGroup), metrics);
1246
+ if (matches.length === 0) {
1247
+ break;
1248
+ }
1249
+ }
1250
+ if ((branch.filters?.length ?? 0) > 0) {
1251
+ matches = matches.filter((match) => matchesBindingFilters(match, branch.filters ?? []));
1252
+ metrics.filtersApplied += branch.filters?.length ?? 0;
1253
+ metrics.plan.push(`PostgresFactsUnionFilter(${(branch.filters ?? []).map(describeFilter).join(',')})`);
1254
+ }
1255
+ output.push(...matches);
1256
+ }
1257
+ }
1258
+ return output;
1259
+ }
1260
+ async applyFactsMinusGroup(input, minusGroup, metrics) {
1261
+ const output = [];
1262
+ for (const binding of input) {
1263
+ let matches = await this.evaluateFactsDependentGroup([binding], minusGroup, metrics, 'Minus');
1264
+ if ((minusGroup.filters?.length ?? 0) > 0) {
1265
+ matches = matches.filter((match) => matchesBindingFilters(match, minusGroup.filters ?? []));
1266
+ metrics.filtersApplied += minusGroup.filters?.length ?? 0;
1267
+ }
1268
+ if (matches.length === 0) {
1269
+ output.push(binding);
1270
+ }
1271
+ }
1272
+ return output;
1273
+ }
1274
+ async applyFactsExistsGroup(input, existsGroup, metrics) {
1275
+ const output = [];
1276
+ for (const binding of input) {
1277
+ let matches = await this.evaluateFactsDependentGroup([binding], existsGroup, metrics, 'Exists');
1278
+ if ((existsGroup.filters?.length ?? 0) > 0) {
1279
+ matches = matches.filter((match) => matchesBindingFilters(match, existsGroup.filters ?? []));
1280
+ metrics.filtersApplied += existsGroup.filters?.length ?? 0;
1281
+ }
1282
+ if (matches.length > 0) {
1283
+ output.push(binding);
1284
+ }
1285
+ }
1286
+ return output;
1287
+ }
1288
+ async evaluateFactsDependentGroup(input, group, metrics, label) {
1289
+ let matches = this.applyFactsValues(input, group.values, metrics, label);
1290
+ for (const pattern of group.patterns) {
1291
+ matches = await this.joinFactsPattern(matches, pattern, group.filters ?? [], metrics, false);
1292
+ if (matches.length === 0) {
1293
+ return [];
1294
+ }
1295
+ }
1296
+ for (const unionGroup of group.unions ?? []) {
1297
+ matches = await this.joinFactsUnionGroup(matches, unionGroup.branches, group.filters ?? [], metrics);
1298
+ if (matches.length === 0) {
1299
+ return [];
1300
+ }
1301
+ }
1302
+ for (const rawOptionalGroup of group.optional ?? []) {
1303
+ matches = await this.joinFactsOptionalGroup(matches, normalizeOptionalGroup(rawOptionalGroup), metrics);
1304
+ }
1305
+ if ((group.binds?.length ?? 0) > 0) {
1306
+ matches = this.applyFactsBinds(matches, group.binds ?? []);
1307
+ }
1308
+ return matches;
1309
+ }
1310
+ applyFactsValues(input, sources, metrics, label) {
1311
+ let output = input;
1312
+ for (const source of sources ?? []) {
1313
+ output = joinValuesSource(output, source);
1314
+ metrics.scannedRows += source.rows.length;
1315
+ metrics.plan.push(`PostgresFacts${label}Values(${source.variables.map((variableName) => `?${variableName}`).join(',')})`);
1316
+ if (output.length === 0) {
1317
+ break;
1318
+ }
1319
+ }
1320
+ return output;
1321
+ }
1322
+ applyFactsBinds(input, binds) {
1323
+ let output = input;
1324
+ for (const bind of binds) {
1325
+ output = output.flatMap((binding) => {
1326
+ if (binding[bind.variable]) {
1327
+ return [];
1328
+ }
1329
+ const value = evaluateBindExpression(bind.expression, binding);
1330
+ return value ? [{ ...binding, [bind.variable]: value }] : [binding];
1331
+ });
1332
+ }
1333
+ return output;
1334
+ }
1021
1335
  async queryNativeAggregate(query, patterns, aggregates, start) {
1022
1336
  if (!this.canNativeAggregate(query, aggregates)) {
1023
1337
  return undefined;
@@ -1985,47 +2299,6 @@ class PostgresRdfEngine {
1985
2299
  filtersPushedDown,
1986
2300
  };
1987
2301
  }
1988
- async rebuildCacheFromStore() {
1989
- if (this.cacheEngine) {
1990
- await this.cacheEngine.close();
1991
- this.cacheEngine = null;
1992
- }
1993
- if (this.cacheDir) {
1994
- await (0, promises_1.rm)(this.cacheDir, { recursive: true, force: true });
1995
- }
1996
- this.cacheDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'xpod-pg-rdf-cache-'));
1997
- this.cachePath = (0, node_path_1.join)(this.cacheDir, 'rdf-cache.sqlite');
1998
- this.cacheEngine = new SolidRdfEngine_1.SolidRdfEngine({
1999
- index: { path: this.cachePath },
2000
- autoOpen: true,
2001
- });
2002
- const sources = await this.requireExecutor().query('SELECT * FROM rdf_sources ORDER BY id');
2003
- const nullSourceRows = await this.requireExecutor().query('SELECT * FROM rdf_quads WHERE source_file_id IS NULL ORDER BY graph_id, subject_id, predicate_id, object_id');
2004
- await this.loadCacheQuads(nullSourceRows, undefined);
2005
- for (const source of sources) {
2006
- const quadRows = await this.requireExecutor().query('SELECT * FROM rdf_quads WHERE source_file_id = $1 ORDER BY graph_id, subject_id, predicate_id, object_id', [source.id]);
2007
- await this.loadCacheQuads(quadRows, source);
2008
- }
2009
- await this.requireCache().refreshDerivedIndexes();
2010
- this.factsDataVersion = await this.readFactsDataVersion();
2011
- this.cacheDirty = false;
2012
- }
2013
- async loadCacheQuads(rows, source) {
2014
- if (rows.length === 0) {
2015
- return;
2016
- }
2017
- const dictionary = this.requireDictionary();
2018
- const termIds = Array.from(new Set(rows.flatMap((row) => [row.graph_id, row.subject_id, row.predicate_id, row.object_id])));
2019
- const terms = await dictionary.rowsForIds(termIds);
2020
- const quads = rows.map((row) => quad(this.requiredTerm(terms, row.subject_id), this.requiredTerm(terms, row.predicate_id), this.requiredTerm(terms, row.object_id), this.requiredTerm(terms, row.graph_id)));
2021
- const sourceInput = source ? this.sourceRowToInput(source) : undefined;
2022
- if (sourceInput) {
2023
- this.requireCache().replaceSource(quads, sourceInput);
2024
- }
2025
- else {
2026
- this.requireCache().put(quads);
2027
- }
2028
- }
2029
2302
  async insertQuads(executor, dictionary, quads, sourceId, sourceLineNo) {
2030
2303
  for (const quadValue of quads) {
2031
2304
  const graphId = await dictionary.getOrCreate(quadValue.graph);
@@ -2067,15 +2340,6 @@ class PostgresRdfEngine {
2067
2340
  `, [graphId, subjectId, predicateId, objectId]);
2068
2341
  return result.length;
2069
2342
  }
2070
- sourceRowToInput(row) {
2071
- return {
2072
- source: row.source,
2073
- workspace: row.workspace,
2074
- localPath: row.local_path !== null ? row.local_path : undefined,
2075
- contentType: row.content_type !== null ? row.content_type : undefined,
2076
- sourceVersion: row.source_version !== null ? row.source_version : undefined,
2077
- };
2078
- }
2079
2343
  async upsertSource(source, executor = this.requireExecutor()) {
2080
2344
  const row = await executor.query(`
2081
2345
  INSERT INTO rdf_sources (
@@ -2134,21 +2398,6 @@ class PostgresRdfEngine {
2134
2398
  }
2135
2399
  async ensureReady() {
2136
2400
  await this.open();
2137
- const currentVersion = await this.readFactsDataVersion();
2138
- if (currentVersion !== this.factsDataVersion) {
2139
- this.factsDataVersion = currentVersion;
2140
- this.markDerivedDirty();
2141
- }
2142
- }
2143
- async ensureCacheReady() {
2144
- await this.ensureReady();
2145
- if (this.cacheDirty || !this.cacheEngine) {
2146
- await this.rebuildCacheFromStore();
2147
- }
2148
- }
2149
- markDerivedDirty() {
2150
- this.rdf3xDirty = true;
2151
- this.cacheDirty = true;
2152
2401
  }
2153
2402
  async scalarCount(sql, params = []) {
2154
2403
  const row = await this.requireExecutor().query(sql, params);
@@ -2268,12 +2517,6 @@ class PostgresRdfEngine {
2268
2517
  }
2269
2518
  return this.termDictionary;
2270
2519
  }
2271
- requireCache() {
2272
- if (!this.cacheEngine) {
2273
- throw new Error('PostgresRdfEngine cache is not initialized');
2274
- }
2275
- return this.cacheEngine;
2276
- }
2277
2520
  requiredTerm(termMap, id) {
2278
2521
  const term = termMap.get(id);
2279
2522
  if (!term) {
@@ -2351,6 +2594,12 @@ function variablesInPattern(pattern) {
2351
2594
  .filter(isVariable)
2352
2595
  .map((value) => value.variable));
2353
2596
  }
2597
+ function normalizeOptionalGroup(group) {
2598
+ return Array.isArray(group) ? { patterns: group } : group;
2599
+ }
2600
+ function isPgSqlScanCompatiblePattern(pattern) {
2601
+ return isPgRdf3xCompatiblePattern(pattern);
2602
+ }
2354
2603
  function isPgRdf3xCompatiblePattern(pattern) {
2355
2604
  return PATTERN_KEYS.every((key) => {
2356
2605
  const value = pattern[key];
@@ -2452,6 +2701,21 @@ function describeScanOrder(options) {
2452
2701
  : order.map(() => (options?.reverse ? 'desc' : 'asc'));
2453
2702
  return order.map((entry, index) => `${directions[index] ?? 'asc'}:${entry}`).join(',');
2454
2703
  }
2704
+ function describeQueryPattern(pattern) {
2705
+ return PATTERN_KEYS
2706
+ .filter((key) => pattern[key])
2707
+ .map((key) => `${key}:${describeQueryPatternValue(pattern[key])}`)
2708
+ .join(',');
2709
+ }
2710
+ function describeQueryPatternValue(value) {
2711
+ if (!value)
2712
+ return '*';
2713
+ if (isVariable(value))
2714
+ return `?${value.variable}`;
2715
+ if ((0, types_1.isTerm)(value))
2716
+ return (0, n3_1.termToId)(value);
2717
+ return 'op';
2718
+ }
2455
2719
  function describePatternSource(source) {
2456
2720
  return PATTERN_KEYS
2457
2721
  .map((key) => {
@@ -2480,7 +2744,7 @@ function aggregatePlan(aggregates, grouped) {
2480
2744
  return `Aggregate(${grouped ? 'group-' : ''}${aggregates.map((aggregate) => (`${aggregate.type}${aggregate.distinct ? ':DISTINCT' : ''}(${aggregate.variable ? `?${aggregate.variable}` : '*'})`)).join(',')})`;
2481
2745
  }
2482
2746
  function describeFilter(filter) {
2483
- return `?${filter.variable}${filter.operator}`;
2747
+ return `?${filter.variable}${filter.operand ? `:${filter.operand}` : ''}${filter.operator}`;
2484
2748
  }
2485
2749
  function describeQueryOrder(orderBy) {
2486
2750
  return orderBy.map((entry) => `${entry.direction ?? 'asc'}:${entry.variable}`).join(',');
@@ -2536,6 +2800,632 @@ function storagePlanMarkers(queryPlan) {
2536
2800
  || entry.startsWith('Language(')
2537
2801
  || entry.startsWith('Datatype(')));
2538
2802
  }
2803
+ function compilePatternForBinding(pattern, binding) {
2804
+ const compiled = {};
2805
+ for (const key of PATTERN_KEYS) {
2806
+ const value = pattern[key];
2807
+ if (!value) {
2808
+ continue;
2809
+ }
2810
+ if (isVariable(value)) {
2811
+ const bound = binding[value.variable];
2812
+ if (bound) {
2813
+ compiled[key] = bound;
2814
+ }
2815
+ continue;
2816
+ }
2817
+ compiled[key] = value;
2818
+ }
2819
+ return compiled;
2820
+ }
2821
+ function bindQuadPattern(pattern, binding, value) {
2822
+ const next = { ...binding };
2823
+ for (const key of PATTERN_KEYS) {
2824
+ const patternValue = pattern[key];
2825
+ if (!isVariable(patternValue)) {
2826
+ continue;
2827
+ }
2828
+ const term = value[key];
2829
+ const existing = next[patternValue.variable];
2830
+ if (existing && !sameTerm(existing, term)) {
2831
+ return null;
2832
+ }
2833
+ next[patternValue.variable] = term;
2834
+ }
2835
+ return next;
2836
+ }
2837
+ function matchesQuadPattern(value, pattern) {
2838
+ return PATTERN_KEYS.every((key) => {
2839
+ const match = pattern[key];
2840
+ return !match || matchesTermPattern(value[key], key, match);
2841
+ });
2842
+ }
2843
+ function matchesTermPattern(term, key, match) {
2844
+ if ((0, types_1.isTerm)(match)) {
2845
+ return sameTerm(term, match);
2846
+ }
2847
+ const operators = match;
2848
+ if (operators.$eq !== undefined && !sameTermOrLexical(term, operators.$eq))
2849
+ return false;
2850
+ if (operators.$ne !== undefined && sameTermOrLexical(term, operators.$ne))
2851
+ return false;
2852
+ if (operators.$in !== undefined && !operators.$in.some((candidate) => sameTermOrLexical(term, candidate)))
2853
+ return false;
2854
+ if (operators.$notIn !== undefined && operators.$notIn.some((candidate) => sameTermOrLexical(term, candidate)))
2855
+ return false;
2856
+ if (operators.$startsWith !== undefined && !term.value.startsWith(operators.$startsWith))
2857
+ return false;
2858
+ if (operators.$contains !== undefined && !term.value.includes(operators.$contains))
2859
+ return false;
2860
+ if (operators.$endsWith !== undefined && !term.value.endsWith(operators.$endsWith))
2861
+ return false;
2862
+ if (operators.$regex !== undefined && !new RegExp(operators.$regex).test(term.value))
2863
+ return false;
2864
+ if (operators.$strStartsWith !== undefined && !term.value.startsWith(operators.$strStartsWith))
2865
+ return false;
2866
+ if (operators.$strContains !== undefined && !term.value.includes(operators.$strContains))
2867
+ return false;
2868
+ if (operators.$strEndsWith !== undefined && !term.value.endsWith(operators.$strEndsWith))
2869
+ return false;
2870
+ if (operators.$strRegex !== undefined && !new RegExp(operators.$strRegex).test(term.value))
2871
+ return false;
2872
+ if (operators.$language !== undefined && !(term.termType === 'Literal' && term.language === operators.$language))
2873
+ return false;
2874
+ if (operators.$notLanguage !== undefined && !(term.termType === 'Literal' && term.language !== operators.$notLanguage))
2875
+ return false;
2876
+ if (operators.$langMatches !== undefined && !(term.termType === 'Literal' && langMatches(term.language, operators.$langMatches)))
2877
+ return false;
2878
+ if (operators.$datatype !== undefined && !(term.termType === 'Literal' && sameTerm(term.datatype, operators.$datatype)))
2879
+ return false;
2880
+ if (operators.$notDatatype !== undefined && !(term.termType === 'Literal' && !sameTerm(term.datatype, operators.$notDatatype)))
2881
+ return false;
2882
+ if (operators.$termType !== undefined && !matchesTermTypeForKey(term, key, operators.$termType))
2883
+ return false;
2884
+ if (operators.$isNull !== undefined)
2885
+ return !operators.$isNull;
2886
+ for (const operator of ['$gt', '$gte', '$lt', '$lte']) {
2887
+ const expected = operators[operator];
2888
+ if (expected === undefined)
2889
+ continue;
2890
+ const comparison = compareFilterValues(term, expected);
2891
+ if (operator === '$gt' && comparison <= 0)
2892
+ return false;
2893
+ if (operator === '$gte' && comparison < 0)
2894
+ return false;
2895
+ if (operator === '$lt' && comparison >= 0)
2896
+ return false;
2897
+ if (operator === '$lte' && comparison > 0)
2898
+ return false;
2899
+ }
2900
+ return true;
2901
+ }
2902
+ function orderQuads(values, options) {
2903
+ if (!options?.order?.length) {
2904
+ return values;
2905
+ }
2906
+ const directions = 'orderDirections' in options && Array.isArray(options.orderDirections)
2907
+ ? options.orderDirections
2908
+ : options.order.map(() => (options.reverse ? 'desc' : 'asc'));
2909
+ return [...values].sort((left, right) => {
2910
+ for (const [index, key] of (options.order ?? []).entries()) {
2911
+ const comparison = (0, n3_1.termToId)(left[key]).localeCompare((0, n3_1.termToId)(right[key]));
2912
+ if (comparison !== 0) {
2913
+ return directions[index] === 'desc' ? -comparison : comparison;
2914
+ }
2915
+ }
2916
+ return 0;
2917
+ });
2918
+ }
2919
+ function matchesNewlyBoundFilters(binding, previousBinding, filters) {
2920
+ const newlyBound = filters.filter((filter) => {
2921
+ const variables = filter.variable2 ? [filter.variable, filter.variable2] : [filter.variable];
2922
+ return variables.every((variableName) => binding[variableName])
2923
+ && variables.some((variableName) => !previousBinding[variableName]);
2924
+ });
2925
+ return matchesBindingFilters(binding, newlyBound);
2926
+ }
2927
+ function matchesBindingFilters(binding, filters) {
2928
+ return filters.every((filter) => matchesBindingFilter(binding, filter));
2929
+ }
2930
+ function matchesBindingFilter(binding, filter) {
2931
+ const value = binding[filter.variable];
2932
+ if (filter.operator === '$bound') {
2933
+ return Boolean(filter.value) ? Boolean(value) : !value;
2934
+ }
2935
+ if (!value) {
2936
+ return false;
2937
+ }
2938
+ const comparisonValue = filterOperandValue(value, filter.operand);
2939
+ switch (filter.operator) {
2940
+ case '$eq':
2941
+ if (filter.variable2)
2942
+ return compareVariableFilter(binding, comparisonValue, filter, (comparison) => comparison === 0);
2943
+ return filter.value !== undefined && sameTermOrLexical(comparisonValue, filter.value);
2944
+ case '$ne':
2945
+ if (filter.variable2)
2946
+ return compareVariableFilter(binding, comparisonValue, filter, (comparison) => comparison !== 0);
2947
+ return filter.value === undefined || !sameTermOrLexical(comparisonValue, filter.value);
2948
+ case '$gt':
2949
+ if (filter.variable2)
2950
+ return compareVariableFilter(binding, comparisonValue, filter, (comparison) => comparison > 0);
2951
+ return compareTermsForFilter(comparisonValue, filter.value) > 0;
2952
+ case '$gte':
2953
+ if (filter.variable2)
2954
+ return compareVariableFilter(binding, comparisonValue, filter, (comparison) => comparison >= 0);
2955
+ return compareTermsForFilter(comparisonValue, filter.value) >= 0;
2956
+ case '$lt':
2957
+ if (filter.variable2)
2958
+ return compareVariableFilter(binding, comparisonValue, filter, (comparison) => comparison < 0);
2959
+ return compareTermsForFilter(comparisonValue, filter.value) < 0;
2960
+ case '$lte':
2961
+ if (filter.variable2)
2962
+ return compareVariableFilter(binding, comparisonValue, filter, (comparison) => comparison <= 0);
2963
+ return compareTermsForFilter(comparisonValue, filter.value) <= 0;
2964
+ case '$in':
2965
+ return (filter.values ?? []).some((candidate) => sameTermOrLexical(comparisonValue, candidate));
2966
+ case '$notIn':
2967
+ return !(filter.values ?? []).some((candidate) => sameTermOrLexical(comparisonValue, candidate));
2968
+ case '$startsWith':
2969
+ return typeof filter.value === 'string' && filterStringValue(value, comparisonValue).startsWith(filter.value);
2970
+ case '$notStartsWith':
2971
+ return typeof filter.value === 'string' && !filterStringValue(value, comparisonValue).startsWith(filter.value);
2972
+ case '$contains':
2973
+ return typeof filter.value === 'string' && filterStringValue(value, comparisonValue).includes(filter.value);
2974
+ case '$notContains':
2975
+ return typeof filter.value === 'string' && !filterStringValue(value, comparisonValue).includes(filter.value);
2976
+ case '$endsWith':
2977
+ return typeof filter.value === 'string' && filterStringValue(value, comparisonValue).endsWith(filter.value);
2978
+ case '$notEndsWith':
2979
+ return typeof filter.value === 'string' && !filterStringValue(value, comparisonValue).endsWith(filter.value);
2980
+ case '$regex':
2981
+ return typeof filter.value === 'string' && new RegExp(filter.value, filter.flags).test(filterStringValue(value, comparisonValue));
2982
+ case '$notRegex':
2983
+ return typeof filter.value === 'string' && !new RegExp(filter.value, filter.flags).test(filterStringValue(value, comparisonValue));
2984
+ case '$termType':
2985
+ return typeof filter.value === 'string' && matchesTermType(value, filter.value);
2986
+ case '$notTermType':
2987
+ return typeof filter.value === 'string' && !matchesTermType(value, filter.value);
2988
+ case '$sameTerm': {
2989
+ const right = filter.variable2 ? binding[filter.variable2] : filter.value;
2990
+ return Boolean(right && (0, types_1.isTerm)(right) && sameTerm(value, right));
2991
+ }
2992
+ case '$notSameTerm': {
2993
+ const right = filter.variable2 ? binding[filter.variable2] : filter.value;
2994
+ return Boolean(right && (0, types_1.isTerm)(right) && !sameTerm(value, right));
2995
+ }
2996
+ case '$lang':
2997
+ return typeof filter.value === 'string' && value.termType === 'Literal' && value.language === filter.value;
2998
+ case '$notLang':
2999
+ return typeof filter.value === 'string' && value.termType === 'Literal' && value.language !== filter.value;
3000
+ case '$langIn':
3001
+ return value.termType === 'Literal' && (filter.values ?? []).some((candidate) => typeof candidate === 'string' && value.language === candidate);
3002
+ case '$notLangIn':
3003
+ return value.termType === 'Literal' && !(filter.values ?? []).some((candidate) => typeof candidate === 'string' && value.language === candidate);
3004
+ case '$langMatches':
3005
+ return typeof filter.value === 'string' && value.termType === 'Literal' && langMatches(value.language, filter.value);
3006
+ case '$notLangMatches':
3007
+ return typeof filter.value === 'string' && value.termType === 'Literal' && !langMatches(value.language, filter.value);
3008
+ case '$datatype':
3009
+ return filter.value !== undefined && value.termType === 'Literal' && sameTermOrLexical(value.datatype, filter.value);
3010
+ case '$notDatatype':
3011
+ return filter.value !== undefined && value.termType === 'Literal' && !sameTermOrLexical(value.datatype, filter.value);
3012
+ case '$datatypeIn':
3013
+ return value.termType === 'Literal' && (filter.values ?? []).some((candidate) => sameTermOrLexical(value.datatype, candidate));
3014
+ case '$notDatatypeIn':
3015
+ return value.termType === 'Literal' && !(filter.values ?? []).some((candidate) => sameTermOrLexical(value.datatype, candidate));
3016
+ default: {
3017
+ const exhaustive = filter.operator;
3018
+ throw new Error(`Unsupported RDF facts filter operator: ${exhaustive}`);
3019
+ }
3020
+ }
3021
+ }
3022
+ function compareVariableFilter(binding, comparisonValue, filter, predicate) {
3023
+ if (!filter.variable2) {
3024
+ return false;
3025
+ }
3026
+ const right = binding[filter.variable2];
3027
+ if (!right) {
3028
+ return false;
3029
+ }
3030
+ return predicate(compareFilterValues(comparisonValue, filterOperandValue(right, filter.operand)));
3031
+ }
3032
+ function filterOperandValue(value, operand) {
3033
+ switch (operand) {
3034
+ case 'stringLength':
3035
+ return value.value.length;
3036
+ case 'stringValue':
3037
+ return value.value;
3038
+ case 'lowerStringValue':
3039
+ return value.value.toLowerCase();
3040
+ case 'upperStringValue':
3041
+ return value.value.toUpperCase();
3042
+ default:
3043
+ return value;
3044
+ }
3045
+ }
3046
+ function filterStringValue(value, comparisonValue) {
3047
+ return typeof comparisonValue === 'string' ? comparisonValue : value.value;
3048
+ }
3049
+ function sameTerm(left, right) {
3050
+ return (0, n3_1.termToId)(left) === (0, n3_1.termToId)(right);
3051
+ }
3052
+ function sameTermOrLexical(left, right) {
3053
+ if (typeof left === 'number') {
3054
+ if (isNumericFilterValue(right)) {
3055
+ return left === (0, RdfTermSemantics_1.rdfNumericValue)((0, types_1.isTerm)(right) ? right.value : String(right));
3056
+ }
3057
+ return String(left) === String(right);
3058
+ }
3059
+ if (typeof left === 'string') {
3060
+ return left === ((0, types_1.isTerm)(right) ? right.value : String(right));
3061
+ }
3062
+ return (0, types_1.isTerm)(right) ? sameTerm(left, right) : left.value === String(right);
3063
+ }
3064
+ function compareTermsForFilter(left, right) {
3065
+ if (right === undefined) {
3066
+ return 1;
3067
+ }
3068
+ return compareFilterValues(left, right);
3069
+ }
3070
+ function compareFilterValues(left, right) {
3071
+ if (typeof left === 'number') {
3072
+ if (isNumericFilterValue(right)) {
3073
+ return left - (0, RdfTermSemantics_1.rdfNumericValue)((0, types_1.isTerm)(right) ? right.value : String(right));
3074
+ }
3075
+ return String(left).localeCompare(String(right));
3076
+ }
3077
+ if (typeof left === 'string') {
3078
+ return left.localeCompare((0, types_1.isTerm)(right) ? right.value : String(right));
3079
+ }
3080
+ if ((0, RdfTermSemantics_1.isRdfNumericTerm)(left) && isNumericFilterValue(right)) {
3081
+ return (0, RdfTermSemantics_1.rdfNumericValue)(left.value) - (0, RdfTermSemantics_1.rdfNumericValue)((0, types_1.isTerm)(right) ? right.value : String(right));
3082
+ }
3083
+ return left.value.localeCompare((0, types_1.isTerm)(right) ? right.value : String(right));
3084
+ }
3085
+ function matchesTermTypeForKey(term, key, expected) {
3086
+ return termKindsForPatternKey(key).includes(expected) && matchesTermType(term, expected);
3087
+ }
3088
+ function matchesTermType(term, expected) {
3089
+ switch (expected) {
3090
+ case 'iri':
3091
+ return term.termType === 'NamedNode';
3092
+ case 'blank':
3093
+ return term.termType === 'BlankNode';
3094
+ case 'literal':
3095
+ return term.termType === 'Literal';
3096
+ case 'numeric':
3097
+ return (0, RdfTermSemantics_1.isRdfNumericTerm)(term);
3098
+ default:
3099
+ return false;
3100
+ }
3101
+ }
3102
+ function langMatches(languageTag, languageRange) {
3103
+ if (!languageTag) {
3104
+ return false;
3105
+ }
3106
+ if (languageRange === '*') {
3107
+ return true;
3108
+ }
3109
+ const normalizedTag = languageTag.toLowerCase();
3110
+ const normalizedRange = languageRange.toLowerCase();
3111
+ return normalizedTag === normalizedRange || normalizedTag.startsWith(`${normalizedRange}-`);
3112
+ }
3113
+ function isNumericFilterValue(value) {
3114
+ return (0, types_1.isTerm)(value)
3115
+ ? (0, RdfTermSemantics_1.isRdfNumericTerm)(value)
3116
+ : (typeof value === 'number' || (typeof value === 'string' && (0, RdfTermSemantics_1.isFiniteNumericLexical)(value)));
3117
+ }
3118
+ function joinValuesSource(input, source) {
3119
+ const output = [];
3120
+ for (const binding of input) {
3121
+ for (const row of source.rows) {
3122
+ const next = mergeTupleValuesBinding(binding, source.variables, row);
3123
+ if (next) {
3124
+ output.push(next);
3125
+ }
3126
+ }
3127
+ }
3128
+ return output;
3129
+ }
3130
+ function mergeTupleValuesBinding(binding, variables, row) {
3131
+ const next = { ...binding };
3132
+ for (const variableName of variables) {
3133
+ const value = row[variableName];
3134
+ if (!value) {
3135
+ continue;
3136
+ }
3137
+ const existing = next[variableName];
3138
+ if (existing && !sameTerm(existing, value)) {
3139
+ return null;
3140
+ }
3141
+ next[variableName] = value;
3142
+ }
3143
+ return next;
3144
+ }
3145
+ function projectBinding(binding, select) {
3146
+ const projected = {};
3147
+ for (const variableName of select) {
3148
+ const value = binding[variableName];
3149
+ if (value) {
3150
+ projected[variableName] = value;
3151
+ }
3152
+ }
3153
+ return projected;
3154
+ }
3155
+ function compareBindings(left, right, orderBy) {
3156
+ for (const order of orderBy) {
3157
+ const leftValue = left[order.variable] ? (0, n3_1.termToId)(left[order.variable]) : '';
3158
+ const rightValue = right[order.variable] ? (0, n3_1.termToId)(right[order.variable]) : '';
3159
+ const comparison = leftValue.localeCompare(rightValue);
3160
+ if (comparison !== 0) {
3161
+ return order.direction === 'desc' ? -comparison : comparison;
3162
+ }
3163
+ }
3164
+ return 0;
3165
+ }
3166
+ function bindingKey(binding, variables) {
3167
+ return [...(variables ?? Object.keys(binding))]
3168
+ .sort()
3169
+ .map((key) => {
3170
+ const value = binding[key];
3171
+ return `${key}=${value ? (0, n3_1.termToId)(value) : '__UNBOUND__'}`;
3172
+ })
3173
+ .join('\u001f');
3174
+ }
3175
+ function distinctBindings(bindings) {
3176
+ const seen = new Set();
3177
+ const output = [];
3178
+ for (const binding of bindings) {
3179
+ const key = bindingKey(binding);
3180
+ if (seen.has(key)) {
3181
+ continue;
3182
+ }
3183
+ seen.add(key);
3184
+ output.push(binding);
3185
+ }
3186
+ return output;
3187
+ }
3188
+ function aggregateBindings(bindings, aggregates) {
3189
+ const binding = {};
3190
+ let firstCount = 0;
3191
+ aggregates.forEach((aggregate, index) => {
3192
+ const count = aggregate.type === 'count'
3193
+ ? countBindings(bindings, aggregate.variable, aggregate.distinct, aggregate.distinctVariables)
3194
+ : 0;
3195
+ if (index === 0) {
3196
+ firstCount = count;
3197
+ }
3198
+ const term = aggregateLiteral(bindings, aggregate);
3199
+ if (term) {
3200
+ binding[aggregate.as] = term;
3201
+ }
3202
+ });
3203
+ return { binding, firstCount };
3204
+ }
3205
+ function groupAggregateBindings(bindings, groupBy, aggregates) {
3206
+ const groups = new Map();
3207
+ for (const binding of bindings) {
3208
+ const key = groupBy.map((variableName) => {
3209
+ const value = binding[variableName];
3210
+ return value ? (0, n3_1.termToId)(value) : '__UNBOUND__';
3211
+ }).join('\u001f');
3212
+ groups.set(key, [...(groups.get(key) ?? []), binding]);
3213
+ }
3214
+ return [...groups.values()].map((groupBindings) => {
3215
+ const first = groupBindings[0];
3216
+ const grouped = {};
3217
+ for (const variableName of groupBy) {
3218
+ if (first[variableName]) {
3219
+ grouped[variableName] = first[variableName];
3220
+ }
3221
+ }
3222
+ for (const aggregate of aggregates) {
3223
+ const term = aggregateLiteral(groupBindings, aggregate);
3224
+ if (term) {
3225
+ grouped[aggregate.as] = term;
3226
+ }
3227
+ }
3228
+ return grouped;
3229
+ });
3230
+ }
3231
+ function countBindings(bindings, variable, distinct, distinctVariables) {
3232
+ if (!distinct) {
3233
+ return variable ? bindings.filter((binding) => binding[variable]).length : bindings.length;
3234
+ }
3235
+ if (!variable) {
3236
+ return new Set(bindings.map((binding) => bindingKey(binding, distinctVariables))).size;
3237
+ }
3238
+ return new Set(bindings
3239
+ .map((binding) => binding[variable])
3240
+ .filter((term) => Boolean(term))
3241
+ .map((term) => (0, n3_1.termToId)(term))).size;
3242
+ }
3243
+ function aggregateLiteral(bindings, aggregate) {
3244
+ if (aggregate.type === 'count') {
3245
+ return countLiteral(countBindings(bindings, aggregate.variable, aggregate.distinct, aggregate.distinctVariables));
3246
+ }
3247
+ const values = numericAggregateValues(bindings, aggregate.variable, aggregate.distinct);
3248
+ if (values.length === 0) {
3249
+ return aggregate.type === 'sum' ? decimalLiteral(0) : undefined;
3250
+ }
3251
+ switch (aggregate.type) {
3252
+ case 'sum':
3253
+ return decimalLiteral(values.reduce((sum, value) => sum + value, 0));
3254
+ case 'avg':
3255
+ return decimalLiteral(values.reduce((sum, value) => sum + value, 0) / values.length);
3256
+ case 'min':
3257
+ return decimalLiteral(Math.min(...values));
3258
+ case 'max':
3259
+ return decimalLiteral(Math.max(...values));
3260
+ default: {
3261
+ const exhaustive = aggregate.type;
3262
+ throw new Error(`Unsupported RDF facts aggregate type: ${exhaustive}`);
3263
+ }
3264
+ }
3265
+ }
3266
+ function numericAggregateValues(bindings, variable, distinct) {
3267
+ if (!variable) {
3268
+ return [];
3269
+ }
3270
+ const values = [];
3271
+ const seen = new Set();
3272
+ for (const binding of bindings) {
3273
+ const term = binding[variable];
3274
+ if (!term || !(0, RdfTermSemantics_1.isRdfNumericTerm)(term)) {
3275
+ continue;
3276
+ }
3277
+ if (distinct) {
3278
+ const key = (0, n3_1.termToId)(term);
3279
+ if (seen.has(key)) {
3280
+ continue;
3281
+ }
3282
+ seen.add(key);
3283
+ }
3284
+ values.push((0, RdfTermSemantics_1.rdfNumericValue)(term.value));
3285
+ }
3286
+ return values;
3287
+ }
3288
+ function countLiteral(count) {
3289
+ return n3_1.DataFactory.literal(String(count), n3_1.DataFactory.namedNode(XSD_INTEGER));
3290
+ }
3291
+ function decimalLiteral(value) {
3292
+ return n3_1.DataFactory.literal(String(value), n3_1.DataFactory.namedNode(XSD_DECIMAL));
3293
+ }
3294
+ function evaluateBindExpression(expression, binding) {
3295
+ switch (expression.type) {
3296
+ case 'term':
3297
+ return expression.term;
3298
+ case 'variable':
3299
+ return binding[expression.variable];
3300
+ case 'stringValue': {
3301
+ const value = binding[expression.variable];
3302
+ return value ? n3_1.DataFactory.literal(value.value) : undefined;
3303
+ }
3304
+ case 'stringLength': {
3305
+ const value = binding[expression.variable];
3306
+ return value ? countLiteral(value.value.length) : undefined;
3307
+ }
3308
+ case 'lowerCase': {
3309
+ const value = evaluateBindExpression(expression.expression, binding);
3310
+ return value ? n3_1.DataFactory.literal(value.value.toLocaleLowerCase('en-US')) : undefined;
3311
+ }
3312
+ case 'upperCase': {
3313
+ const value = evaluateBindExpression(expression.expression, binding);
3314
+ return value ? n3_1.DataFactory.literal(value.value.toLocaleUpperCase('en-US')) : undefined;
3315
+ }
3316
+ case 'coalesce':
3317
+ for (const item of expression.expressions) {
3318
+ const value = evaluateBindExpression(item, binding);
3319
+ if (value) {
3320
+ return value;
3321
+ }
3322
+ }
3323
+ return undefined;
3324
+ case 'if':
3325
+ return matchesBindingFilters(binding, expression.condition)
3326
+ ? evaluateBindExpression(expression.then, binding)
3327
+ : evaluateBindExpression(expression.else, binding);
3328
+ case 'substring': {
3329
+ const value = evaluateBindExpression(expression.expression, binding);
3330
+ const startTerm = evaluateBindExpression(expression.start, binding);
3331
+ const start = startTerm ? finiteBindNumber(startTerm) : undefined;
3332
+ const lengthTerm = expression.length ? evaluateBindExpression(expression.length, binding) : undefined;
3333
+ const length = lengthTerm ? finiteBindNumber(lengthTerm) : undefined;
3334
+ if (!value || start === undefined || (expression.length && length === undefined)) {
3335
+ return undefined;
3336
+ }
3337
+ const startIndex = Math.max(0, Math.round(start) - 1);
3338
+ const lengthValue = length === undefined ? undefined : Math.max(0, Math.round(length));
3339
+ return n3_1.DataFactory.literal(value.value.slice(startIndex, lengthValue === undefined ? undefined : startIndex + lengthValue));
3340
+ }
3341
+ case 'concat': {
3342
+ const values = expression.expressions.map((item) => evaluateBindExpression(item, binding));
3343
+ return values.every((value) => Boolean(value))
3344
+ ? n3_1.DataFactory.literal(values.map((value) => value.value).join(''))
3345
+ : undefined;
3346
+ }
3347
+ case 'iri': {
3348
+ const value = evaluateBindExpression(expression.expression, binding);
3349
+ if (!value) {
3350
+ return undefined;
3351
+ }
3352
+ try {
3353
+ return n3_1.DataFactory.namedNode(new URL(value.value, expression.base).href);
3354
+ }
3355
+ catch {
3356
+ return undefined;
3357
+ }
3358
+ }
3359
+ case 'strdt': {
3360
+ const lexical = evaluateBindExpression(expression.lexical, binding);
3361
+ const datatype = evaluateBindExpression(expression.datatype, binding);
3362
+ if (!lexical || !datatype || datatype.termType !== 'NamedNode') {
3363
+ return undefined;
3364
+ }
3365
+ return n3_1.DataFactory.literal(lexical.value, n3_1.DataFactory.namedNode(datatype.value));
3366
+ }
3367
+ case 'strlang': {
3368
+ const lexical = evaluateBindExpression(expression.lexical, binding);
3369
+ const language = evaluateBindExpression(expression.language, binding);
3370
+ if (!lexical || !language) {
3371
+ return undefined;
3372
+ }
3373
+ return n3_1.DataFactory.literal(lexical.value, language.value);
3374
+ }
3375
+ default: {
3376
+ const exhaustive = expression;
3377
+ throw new Error(`Unsupported RDF facts BIND expression: ${JSON.stringify(exhaustive)}`);
3378
+ }
3379
+ }
3380
+ }
3381
+ function finiteBindNumber(term) {
3382
+ if (term.termType !== 'Literal') {
3383
+ return undefined;
3384
+ }
3385
+ const value = Number(term.value);
3386
+ return Number.isFinite(value) ? value : undefined;
3387
+ }
3388
+ function describeBind(bind) {
3389
+ return `?${bind.variable}:=${describeBindExpression(bind.expression)}`;
3390
+ }
3391
+ function describeBindExpression(expression) {
3392
+ switch (expression.type) {
3393
+ case 'term':
3394
+ return (0, n3_1.termToId)(expression.term);
3395
+ case 'variable':
3396
+ return `?${expression.variable}`;
3397
+ case 'stringValue':
3398
+ return `STR(?${expression.variable})`;
3399
+ case 'stringLength':
3400
+ return `STRLEN(?${expression.variable})`;
3401
+ case 'lowerCase':
3402
+ return `LCASE(${describeBindExpression(expression.expression)})`;
3403
+ case 'upperCase':
3404
+ return `UCASE(${describeBindExpression(expression.expression)})`;
3405
+ case 'coalesce':
3406
+ return `COALESCE(${expression.expressions.map(describeBindExpression).join(',')})`;
3407
+ case 'if':
3408
+ return `IF(${expression.condition.map(describeFilter).join('&')},${describeBindExpression(expression.then)},${describeBindExpression(expression.else)})`;
3409
+ case 'substring':
3410
+ return `SUBSTR(${[
3411
+ describeBindExpression(expression.expression),
3412
+ describeBindExpression(expression.start),
3413
+ expression.length ? describeBindExpression(expression.length) : undefined,
3414
+ ].filter(Boolean).join(',')})`;
3415
+ case 'concat':
3416
+ return `CONCAT(${expression.expressions.map(describeBindExpression).join(',')})`;
3417
+ case 'iri':
3418
+ return `IRI(${describeBindExpression(expression.expression)})`;
3419
+ case 'strdt':
3420
+ return `STRDT(${describeBindExpression(expression.lexical)},${describeBindExpression(expression.datatype)})`;
3421
+ case 'strlang':
3422
+ return `STRLANG(${describeBindExpression(expression.lexical)},${describeBindExpression(expression.language)})`;
3423
+ default: {
3424
+ const exhaustive = expression;
3425
+ return JSON.stringify(exhaustive);
3426
+ }
3427
+ }
3428
+ }
2539
3429
  function uniqueStrings(values) {
2540
3430
  return [...new Set(values)];
2541
3431
  }