@open-mercato/core 0.6.6-develop.5834.1.dc9eb0615e → 0.6.6-develop.5900.1.b3fb52925e
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/modules/query_index/lib/engine.js +116 -62
- package/dist/modules/query_index/lib/engine.js.map +2 -2
- package/dist/modules/sync_excel/widgets/injection/upload-config/widget.client.js +17 -5
- package/dist/modules/sync_excel/widgets/injection/upload-config/widget.client.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/query_index/lib/engine.ts +124 -60
- package/src/modules/sync_excel/widgets/injection/upload-config/widget.client.tsx +26 -12
|
@@ -14,7 +14,9 @@ import {
|
|
|
14
14
|
import { resolveSearchConfig } from "@open-mercato/shared/lib/search/config";
|
|
15
15
|
import { tokenizeText } from "@open-mercato/shared/lib/search/tokenize";
|
|
16
16
|
import { runBeforeQueryPipeline, runAfterQueryPipeline } from "@open-mercato/shared/lib/query/query-extension-runner";
|
|
17
|
-
import { resolveEncryptedSortFields, sortRowsInMemory } from "@open-mercato/shared/lib/query/encrypted-sort";
|
|
17
|
+
import { resolveEncryptedSortFields, resolveEncryptedSortMaxRows, sortRowsInMemory } from "@open-mercato/shared/lib/query/encrypted-sort";
|
|
18
|
+
import { mapWithConcurrency } from "@open-mercato/shared/lib/query/bounded-decrypt";
|
|
19
|
+
const DECRYPT_CONCURRENCY = 8;
|
|
18
20
|
function buildFilterableCustomFieldJoins(sources) {
|
|
19
21
|
if (!sources || sources.length === 0) return [];
|
|
20
22
|
return sources.flatMap((source, index) => {
|
|
@@ -662,9 +664,10 @@ class HybridQueryEngine {
|
|
|
662
664
|
opts.includeCustomFields.map((key) => String(key)).forEach((key) => selectFieldSet.add(`cf:${key}`));
|
|
663
665
|
}
|
|
664
666
|
const selectFields = Array.from(selectFieldSet);
|
|
665
|
-
const applySelection = (q) => {
|
|
667
|
+
const applySelection = (q, fieldsOverride) => {
|
|
666
668
|
let next = q;
|
|
667
|
-
|
|
669
|
+
const fields = fieldsOverride ?? selectFields;
|
|
670
|
+
for (const field of fields) {
|
|
668
671
|
const fieldName = String(field);
|
|
669
672
|
if (fieldName.startsWith("cf:")) {
|
|
670
673
|
const alias = this.sanitize(fieldName);
|
|
@@ -751,71 +754,122 @@ class HybridQueryEngine {
|
|
|
751
754
|
);
|
|
752
755
|
total = this.parseCount(countRow);
|
|
753
756
|
}
|
|
754
|
-
const dataRoot = db.selectFrom(`${baseTable} as b`);
|
|
755
|
-
let dataBuilder = await applyQueryShape(dataRoot);
|
|
756
|
-
dataBuilder = applySelection(dataBuilder);
|
|
757
|
-
dataBuilder = applySort(dataBuilder);
|
|
758
|
-
if (!requiresPlaintextSort) {
|
|
759
|
-
dataBuilder = dataBuilder.limit(pageSize).offset((page - 1) * pageSize);
|
|
760
|
-
}
|
|
761
|
-
if (debugEnabled && sqlDebugEnabled) {
|
|
762
|
-
const compiled = dataBuilder.compile();
|
|
763
|
-
this.debug("query:sql:data", { entity, sql: compiled.sql, bindings: compiled.parameters, page, pageSize });
|
|
764
|
-
}
|
|
765
|
-
const itemsRaw = await this.captureSqlTiming(
|
|
766
|
-
"query:sql:data",
|
|
767
|
-
entity,
|
|
768
|
-
() => dataBuilder.execute(),
|
|
769
|
-
{ page, pageSize },
|
|
770
|
-
profiler
|
|
771
|
-
);
|
|
772
|
-
if (debugEnabled) this.debug("query:complete", { entity, total, items: Array.isArray(itemsRaw) ? itemsRaw.length : 0 });
|
|
773
|
-
let items = itemsRaw;
|
|
774
757
|
const dekKeyCache = /* @__PURE__ */ new Map();
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
758
|
+
const decryptRow = async (item) => {
|
|
759
|
+
let next = item;
|
|
760
|
+
if (encSvc?.decryptEntityPayload) {
|
|
761
|
+
const decrypt = encSvc.decryptEntityPayload.bind(encSvc);
|
|
762
|
+
try {
|
|
763
|
+
const decrypted = await decrypt(
|
|
764
|
+
entity,
|
|
765
|
+
next,
|
|
766
|
+
next?.tenant_id ?? next?.tenantId ?? opts.tenantId ?? null,
|
|
767
|
+
next?.organization_id ?? next?.organizationId ?? fallbackOrgId ?? null
|
|
768
|
+
);
|
|
769
|
+
next = { ...next, ...decrypted };
|
|
770
|
+
} catch (err) {
|
|
771
|
+
console.error("Error decrypting entity payload", err);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
if (encSvc) {
|
|
775
|
+
try {
|
|
776
|
+
next = await decryptIndexDocCustomFields(
|
|
777
|
+
next,
|
|
778
|
+
{
|
|
779
|
+
tenantId: next?.tenant_id ?? next?.tenantId ?? opts.tenantId ?? null,
|
|
780
|
+
organizationId: next?.organization_id ?? next?.organizationId ?? null
|
|
781
|
+
},
|
|
782
|
+
encSvc,
|
|
783
|
+
dekKeyCache
|
|
784
|
+
);
|
|
785
|
+
} catch {
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
return next;
|
|
789
|
+
};
|
|
790
|
+
let items;
|
|
791
|
+
let encryptedSortRowCapWarning;
|
|
792
|
+
if (requiresPlaintextSort) {
|
|
793
|
+
const cap = resolveEncryptedSortMaxRows();
|
|
794
|
+
const phase1Root = db.selectFrom(`${baseTable} as b`);
|
|
795
|
+
let phase1 = await applyQueryShape(phase1Root);
|
|
796
|
+
const sortFieldNames = Array.from(/* @__PURE__ */ new Set(["id", ...resolvedSorts.map((s) => String(s.field))]));
|
|
797
|
+
phase1 = applySelection(phase1, sortFieldNames);
|
|
798
|
+
if (cap !== null) {
|
|
799
|
+
phase1 = phase1.limit(cap).orderBy(qualify("id"), "asc");
|
|
800
|
+
}
|
|
801
|
+
if (debugEnabled && sqlDebugEnabled) {
|
|
802
|
+
const compiled = phase1.compile();
|
|
803
|
+
this.debug("query:sql:data:phase1", { entity, sql: compiled.sql, bindings: compiled.parameters });
|
|
804
|
+
}
|
|
805
|
+
const candidateRows = await this.captureSqlTiming(
|
|
806
|
+
"query:sql:data:phase1",
|
|
807
|
+
entity,
|
|
808
|
+
() => phase1.execute(),
|
|
809
|
+
{ phase: 1 },
|
|
810
|
+
profiler
|
|
792
811
|
);
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
812
|
+
const decryptedCandidates = await mapWithConcurrency(candidateRows, DECRYPT_CONCURRENCY, decryptRow);
|
|
813
|
+
const orderedCandidates = sortRowsInMemory(decryptedCandidates, resolvedSorts);
|
|
814
|
+
const pageIds = orderedCandidates.slice((page - 1) * pageSize, page * pageSize).map((row) => row.id);
|
|
815
|
+
if (cap !== null && total > cap) {
|
|
816
|
+
encryptedSortRowCapWarning = {
|
|
817
|
+
entity,
|
|
818
|
+
sortFields: resolvedSorts.map((s) => String(s.field)),
|
|
819
|
+
maxRows: cap,
|
|
820
|
+
totalMatched: total
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
if (pageIds.length === 0) {
|
|
824
|
+
items = [];
|
|
825
|
+
} else {
|
|
826
|
+
const phase2Root = db.selectFrom(`${baseTable} as b`);
|
|
827
|
+
let phase2 = await applyQueryShape(phase2Root);
|
|
828
|
+
phase2 = applySelection(phase2);
|
|
829
|
+
phase2 = phase2.where(qualify("id"), "in", pageIds);
|
|
830
|
+
if (debugEnabled && sqlDebugEnabled) {
|
|
831
|
+
const compiled = phase2.compile();
|
|
832
|
+
this.debug("query:sql:data:phase2", { entity, sql: compiled.sql, bindings: compiled.parameters });
|
|
833
|
+
}
|
|
834
|
+
const pageRowsRaw = await this.captureSqlTiming(
|
|
835
|
+
"query:sql:data:phase2",
|
|
836
|
+
entity,
|
|
837
|
+
() => phase2.execute(),
|
|
838
|
+
{ phase: 2 },
|
|
839
|
+
profiler
|
|
840
|
+
);
|
|
841
|
+
const decryptedPageRows = await mapWithConcurrency(pageRowsRaw, DECRYPT_CONCURRENCY, decryptRow);
|
|
842
|
+
const byId = new Map(decryptedPageRows.map((row) => [String(row.id), row]));
|
|
843
|
+
items = pageIds.map((id) => byId.get(String(id))).filter((row) => row != null);
|
|
844
|
+
}
|
|
845
|
+
} else {
|
|
846
|
+
const dataRoot = db.selectFrom(`${baseTable} as b`);
|
|
847
|
+
let dataBuilder = await applyQueryShape(dataRoot);
|
|
848
|
+
dataBuilder = applySelection(dataBuilder);
|
|
849
|
+
dataBuilder = applySort(dataBuilder);
|
|
850
|
+
dataBuilder = dataBuilder.limit(pageSize).offset((page - 1) * pageSize);
|
|
851
|
+
if (debugEnabled && sqlDebugEnabled) {
|
|
852
|
+
const compiled = dataBuilder.compile();
|
|
853
|
+
this.debug("query:sql:data", { entity, sql: compiled.sql, bindings: compiled.parameters, page, pageSize });
|
|
854
|
+
}
|
|
855
|
+
const itemsRaw = await this.captureSqlTiming(
|
|
856
|
+
"query:sql:data",
|
|
857
|
+
entity,
|
|
858
|
+
() => dataBuilder.execute(),
|
|
859
|
+
{ page, pageSize },
|
|
860
|
+
profiler
|
|
811
861
|
);
|
|
862
|
+
items = await mapWithConcurrency(itemsRaw, DECRYPT_CONCURRENCY, decryptRow);
|
|
812
863
|
}
|
|
813
|
-
if (
|
|
814
|
-
items = sortRowsInMemory(items, resolvedSorts).slice((page - 1) * pageSize, page * pageSize);
|
|
815
|
-
}
|
|
864
|
+
if (debugEnabled) this.debug("query:complete", { entity, total, items: items.length });
|
|
816
865
|
const typedItems = items;
|
|
817
866
|
let result = { items: typedItems, page, pageSize, total };
|
|
818
|
-
if (partialIndexWarning
|
|
867
|
+
if (partialIndexWarning || encryptedSortRowCapWarning) {
|
|
868
|
+
const meta = {};
|
|
869
|
+
if (partialIndexWarning) meta.partialIndexWarning = partialIndexWarning;
|
|
870
|
+
if (encryptedSortRowCapWarning) meta.encryptedSortRowCapWarning = encryptedSortRowCapWarning;
|
|
871
|
+
result.meta = meta;
|
|
872
|
+
}
|
|
819
873
|
result = await applyAfterExtensions(result);
|
|
820
874
|
finishProfile({
|
|
821
875
|
result: "ok",
|