@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.
- package/dist/authorization/PodAuthorizationResources.d.ts +1 -0
- package/dist/authorization/PodAuthorizationResources.js +36 -4
- package/dist/authorization/PodAuthorizationResources.js.map +1 -1
- package/dist/provision/LocalPodProvisioningService.js +2 -0
- package/dist/provision/LocalPodProvisioningService.js.map +1 -1
- package/dist/provision/ProvisionPodCreator.js +16 -0
- package/dist/provision/ProvisionPodCreator.js.map +1 -1
- package/dist/storage/accessors/MixDataAccessor.js.map +1 -1
- package/dist/storage/rdf/PostgresRdfEngine.d.ts +12 -15
- package/dist/storage/rdf/PostgresRdfEngine.js +1040 -150
- package/dist/storage/rdf/PostgresRdfEngine.js.map +1 -1
- package/dist/storage/rdf/PostgresRdfEngine.jsonld +40 -52
- package/dist/storage/rdf/{RdfLocalQueryEngine.d.ts → RdfQueryExecutor.d.ts} +3 -3
- package/dist/storage/rdf/{RdfLocalQueryEngine.js → RdfQueryExecutor.js} +9 -9
- package/dist/storage/rdf/RdfQueryExecutor.js.map +1 -0
- package/dist/storage/rdf/RdfSparqlAdapter.d.ts +5 -5
- package/dist/storage/rdf/RdfSparqlAdapter.js +27 -27
- package/dist/storage/rdf/RdfSparqlAdapter.js.map +1 -1
- package/dist/storage/rdf/SolidRdfEngine.d.ts +2 -5
- package/dist/storage/rdf/SolidRdfEngine.js +6 -38
- package/dist/storage/rdf/SolidRdfEngine.js.map +1 -1
- package/dist/storage/rdf/SolidRdfEngine.jsonld +0 -12
- package/dist/storage/rdf/SolidRdfSparqlEngine.js.map +1 -1
- package/dist/storage/rdf/index.d.ts +3 -3
- package/dist/storage/rdf/index.js +6 -6
- package/dist/storage/rdf/index.js.map +1 -1
- package/dist/storage/rdf/models-benchmark.d.ts +9 -9
- package/dist/storage/rdf/models-benchmark.js +23 -23
- package/dist/storage/rdf/models-benchmark.js.map +1 -1
- package/dist/storage/rdf/types.d.ts +5 -5
- package/dist/storage/rdf/types.js.map +1 -1
- package/package.json +1 -1
- package/templates/pod/acp/profile/.acr +21 -0
- package/templates/pod/wac/profile/.acl.hbs +18 -0
- 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
|
-
|
|
621
|
-
|
|
622
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
934
|
+
return undefined;
|
|
965
935
|
}
|
|
966
936
|
if (!this.allFiltersPushed(query.filters ?? [], compiledPatterns)) {
|
|
967
|
-
return
|
|
937
|
+
return undefined;
|
|
968
938
|
}
|
|
969
939
|
if (aggregates.length > 0) {
|
|
970
940
|
if (!query.patterns.length) {
|
|
971
|
-
return
|
|
941
|
+
return undefined;
|
|
972
942
|
}
|
|
973
943
|
const aggregateResult = await this.queryNativeAggregate(query, compiledPatterns, aggregates, start);
|
|
974
|
-
return aggregateResult
|
|
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
|
|
949
|
+
return undefined;
|
|
980
950
|
}
|
|
981
951
|
if ((query.orderBy ?? []).some((entry) => !visibleVariables.includes(entry.variable))) {
|
|
982
|
-
return
|
|
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
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
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
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
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
|
}
|