@objectstack/formula 9.11.0 → 10.0.0
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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +76 -0
- package/dist/index.d.mts +78 -2
- package/dist/index.d.ts +78 -2
- package/dist/index.js +369 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +365 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/cel-engine.ts +6 -0
- package/src/cel-to-filter.test.ts +218 -0
- package/src/cel-to-filter.ts +411 -0
- package/src/index.ts +6 -0
- package/src/matches-filter.test.ts +110 -0
- package/src/matches-filter.ts +115 -0
package/dist/index.mjs
CHANGED
|
@@ -174,7 +174,14 @@ var SCOPE_ROOTS = [
|
|
|
174
174
|
"data",
|
|
175
175
|
"params",
|
|
176
176
|
"config",
|
|
177
|
-
"settings"
|
|
177
|
+
"settings",
|
|
178
|
+
// UI action / predicate context (ActionEngine, renderers): the current
|
|
179
|
+
// record plus ambient globals exposed to `visible`/`disabled` predicates.
|
|
180
|
+
"ctx",
|
|
181
|
+
"features",
|
|
182
|
+
// Master-detail inline grids inject the header record as `parent` for a
|
|
183
|
+
// child field's `readonlyWhen`/`requiredWhen` predicate (ADR-0036, #1581).
|
|
184
|
+
"parent"
|
|
178
185
|
];
|
|
179
186
|
function buildScopedEnv(knownFields) {
|
|
180
187
|
const env = new Environment({
|
|
@@ -749,13 +756,364 @@ function looksLikeExpression(value) {
|
|
|
749
756
|
return ExpressionSchema2.safeParse(v).success;
|
|
750
757
|
}
|
|
751
758
|
|
|
759
|
+
// src/cel-to-filter.ts
|
|
760
|
+
import { Environment as Environment2 } from "@marcbachmann/cel-js";
|
|
761
|
+
var SHAPE_VALUE = /* @__PURE__ */ Symbol("cel-filter-shape-placeholder");
|
|
762
|
+
var CompileError = class extends Error {
|
|
763
|
+
constructor(reason, message) {
|
|
764
|
+
super(message);
|
|
765
|
+
this.reason = reason;
|
|
766
|
+
this.name = "CelFilterCompileError";
|
|
767
|
+
}
|
|
768
|
+
};
|
|
769
|
+
var parseEnv;
|
|
770
|
+
function getParseEnv() {
|
|
771
|
+
if (!parseEnv) {
|
|
772
|
+
parseEnv = new Environment2({ unlistedVariablesAreDyn: true, enableOptionalTypes: true });
|
|
773
|
+
}
|
|
774
|
+
return parseEnv;
|
|
775
|
+
}
|
|
776
|
+
function toSource(input) {
|
|
777
|
+
if (typeof input === "string") return input.trim() || null;
|
|
778
|
+
if (input && typeof input === "object" && typeof input.source === "string") {
|
|
779
|
+
return input.source.trim() || null;
|
|
780
|
+
}
|
|
781
|
+
return null;
|
|
782
|
+
}
|
|
783
|
+
function compileCelToFilter(input, opts = {}) {
|
|
784
|
+
const source = toSource(input);
|
|
785
|
+
if (!source) return { ok: false, reason: "parse-error", detail: "empty expression" };
|
|
786
|
+
let ast;
|
|
787
|
+
try {
|
|
788
|
+
ast = getParseEnv().parse(source).ast;
|
|
789
|
+
} catch (err) {
|
|
790
|
+
return { ok: false, reason: "parse-error", detail: err.message?.split("\n")[0] ?? "parse error" };
|
|
791
|
+
}
|
|
792
|
+
return lowerCelAst(ast, opts, "value");
|
|
793
|
+
}
|
|
794
|
+
function isPushdownableCel(input, opts = {}) {
|
|
795
|
+
const source = toSource(input);
|
|
796
|
+
if (!source) return { ok: false, reason: "parse-error", detail: "empty expression" };
|
|
797
|
+
let ast;
|
|
798
|
+
try {
|
|
799
|
+
ast = getParseEnv().parse(source).ast;
|
|
800
|
+
} catch (err) {
|
|
801
|
+
return { ok: false, reason: "parse-error", detail: err.message?.split("\n")[0] ?? "parse error" };
|
|
802
|
+
}
|
|
803
|
+
const res = lowerCelAst(ast, opts, "shape");
|
|
804
|
+
return res.ok ? { ok: true } : { ok: false, reason: res.reason, detail: res.detail };
|
|
805
|
+
}
|
|
806
|
+
function lowerCelAst(ast, opts = {}, mode = "value") {
|
|
807
|
+
const ctx = {
|
|
808
|
+
fieldRoots: new Set(opts.fieldRoots ?? ["record"]),
|
|
809
|
+
variableRoots: new Set(opts.variableRoots ?? ["current_user"]),
|
|
810
|
+
variables: opts.variables ?? {},
|
|
811
|
+
mode
|
|
812
|
+
};
|
|
813
|
+
try {
|
|
814
|
+
return { ok: true, filter: lowerCondition(ast, ctx) };
|
|
815
|
+
} catch (err) {
|
|
816
|
+
if (err instanceof CompileError) return { ok: false, reason: err.reason, detail: err.message };
|
|
817
|
+
return { ok: false, reason: "unsupported", detail: err.message ?? "compile error" };
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
var FLIP = { ">": "<", "<": ">", ">=": "<=", "<=": ">=", "==": "==", "!=": "!=" };
|
|
821
|
+
var CMP_OP = { ">": "$gt", ">=": "$gte", "<": "$lt", "<=": "$lte" };
|
|
822
|
+
var STRING_METHOD = { startsWith: "$startsWith", endsWith: "$endsWith", contains: "$contains" };
|
|
823
|
+
function lowerCondition(node, ctx) {
|
|
824
|
+
const op = node.op;
|
|
825
|
+
const args = node.args;
|
|
826
|
+
switch (op) {
|
|
827
|
+
case "&&":
|
|
828
|
+
return combine("$and", node, ctx);
|
|
829
|
+
case "||":
|
|
830
|
+
return combine("$or", node, ctx);
|
|
831
|
+
case "!_":
|
|
832
|
+
return { $not: lowerCondition(args, ctx) };
|
|
833
|
+
case "==":
|
|
834
|
+
case "!=":
|
|
835
|
+
case ">":
|
|
836
|
+
case ">=":
|
|
837
|
+
case "<":
|
|
838
|
+
case "<=":
|
|
839
|
+
return lowerComparison(op, args[0], args[1], ctx);
|
|
840
|
+
case "in":
|
|
841
|
+
return lowerMembership(args[0], args[1], ctx);
|
|
842
|
+
case "rcall":
|
|
843
|
+
return lowerStringMethod(args, ctx);
|
|
844
|
+
case "value": {
|
|
845
|
+
const v = coerceLiteral(args);
|
|
846
|
+
if (v === true) return {};
|
|
847
|
+
throw new CompileError("unsupported", `constant non-true predicate (${String(v)})`);
|
|
848
|
+
}
|
|
849
|
+
default:
|
|
850
|
+
throw new CompileError("unsupported", `unsupported operator "${String(op)}"`);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
function combine(key, node, ctx) {
|
|
854
|
+
const [l, r] = node.args;
|
|
855
|
+
const parts = [];
|
|
856
|
+
for (const child of [lowerCondition(l, ctx), lowerCondition(r, ctx)]) {
|
|
857
|
+
const nested = child[key];
|
|
858
|
+
if (Array.isArray(nested) && Object.keys(child).length === 1) parts.push(...nested);
|
|
859
|
+
else parts.push(child);
|
|
860
|
+
}
|
|
861
|
+
return { [key]: parts };
|
|
862
|
+
}
|
|
863
|
+
function lowerComparison(op, lNode, rNode, ctx) {
|
|
864
|
+
const L = classify(lNode, ctx);
|
|
865
|
+
const R = classify(rNode, ctx);
|
|
866
|
+
const lField = L.kind === "field";
|
|
867
|
+
const rField = R.kind === "field";
|
|
868
|
+
if (lField && rField) {
|
|
869
|
+
return emit(L.path, op, { $field: R.path }, true);
|
|
870
|
+
}
|
|
871
|
+
if (lField) return emit(L.path, op, resolveValue(R, ctx), false);
|
|
872
|
+
if (rField) return emit(R.path, FLIP[op] ?? op, resolveValue(L, ctx), false);
|
|
873
|
+
const lv = resolveValue(L, ctx);
|
|
874
|
+
const rv = resolveValue(R, ctx);
|
|
875
|
+
if (ctx.mode === "shape") return {};
|
|
876
|
+
const truth = constFold(op, lv, rv);
|
|
877
|
+
if (truth === true) return {};
|
|
878
|
+
throw new CompileError("unsupported", `constant ${op} predicate that is not always-true`);
|
|
879
|
+
}
|
|
880
|
+
function lowerMembership(elemNode, containerNode, ctx) {
|
|
881
|
+
const elem = classify(elemNode, ctx);
|
|
882
|
+
if (elem.kind !== "field") {
|
|
883
|
+
throw new CompileError("unsupported", `\`in\` requires a field on the left (got ${elem.kind})`);
|
|
884
|
+
}
|
|
885
|
+
const container = classify(containerNode, ctx);
|
|
886
|
+
const value = resolveValue(container, ctx);
|
|
887
|
+
if (value !== SHAPE_VALUE && !Array.isArray(value)) {
|
|
888
|
+
throw new CompileError("unsupported", `\`in\` requires an array/list on the right`);
|
|
889
|
+
}
|
|
890
|
+
return { [elem.path]: { $in: value } };
|
|
891
|
+
}
|
|
892
|
+
function lowerStringMethod(args, ctx) {
|
|
893
|
+
const [method, receiver, callArgs] = args;
|
|
894
|
+
const mapped = STRING_METHOD[method];
|
|
895
|
+
if (!mapped) throw new CompileError("unsupported", `unsupported method "${method}()"`);
|
|
896
|
+
const recv = classify(receiver, ctx);
|
|
897
|
+
if (recv.kind !== "field") throw new CompileError("unsupported", `"${method}()" must be called on a field`);
|
|
898
|
+
if (!Array.isArray(callArgs) || callArgs.length !== 1) {
|
|
899
|
+
throw new CompileError("unsupported", `"${method}()" takes exactly one argument`);
|
|
900
|
+
}
|
|
901
|
+
const arg = resolveValue(classify(callArgs[0], ctx), ctx);
|
|
902
|
+
if (arg !== SHAPE_VALUE && typeof arg !== "string") {
|
|
903
|
+
throw new CompileError("unsupported", `"${method}()" argument must be a string literal`);
|
|
904
|
+
}
|
|
905
|
+
return { [recv.path]: { [mapped]: arg } };
|
|
906
|
+
}
|
|
907
|
+
function emit(field, op, value, isRef) {
|
|
908
|
+
if (op === "==") {
|
|
909
|
+
if (!isRef && value === null) return { [field]: { $null: true } };
|
|
910
|
+
if (isRef) return { [field]: { $eq: value } };
|
|
911
|
+
return { [field]: value };
|
|
912
|
+
}
|
|
913
|
+
if (op === "!=") {
|
|
914
|
+
if (!isRef && value === null) return { [field]: { $null: false } };
|
|
915
|
+
return { [field]: { $ne: value } };
|
|
916
|
+
}
|
|
917
|
+
const cmp = CMP_OP[op];
|
|
918
|
+
if (cmp) return { [field]: { [cmp]: value } };
|
|
919
|
+
throw new CompileError("unsupported", `unsupported comparison "${op}"`);
|
|
920
|
+
}
|
|
921
|
+
function classify(node, ctx) {
|
|
922
|
+
switch (node.op) {
|
|
923
|
+
case "value":
|
|
924
|
+
return { kind: "literal", value: coerceLiteral(node.args) };
|
|
925
|
+
case "list": {
|
|
926
|
+
const items = node.args.map((n) => {
|
|
927
|
+
const leaf = classify(n, ctx);
|
|
928
|
+
if (leaf.kind !== "literal") {
|
|
929
|
+
throw new CompileError("unsupported", "list elements must be literals");
|
|
930
|
+
}
|
|
931
|
+
return leaf.value;
|
|
932
|
+
});
|
|
933
|
+
return { kind: "literal", value: items };
|
|
934
|
+
}
|
|
935
|
+
case "id": {
|
|
936
|
+
const name = node.args;
|
|
937
|
+
if (ctx.variableRoots.has(name)) return { kind: "var", path: [name] };
|
|
938
|
+
return { kind: "field", path: name };
|
|
939
|
+
}
|
|
940
|
+
case ".":
|
|
941
|
+
case ".?": {
|
|
942
|
+
const [recv, field] = node.args;
|
|
943
|
+
const chain = memberChain(recv, field);
|
|
944
|
+
if (!chain) throw new CompileError("unsupported", "unsupported member-access expression");
|
|
945
|
+
const [root, ...rest] = chain;
|
|
946
|
+
if (ctx.variableRoots.has(root)) return { kind: "var", path: chain };
|
|
947
|
+
if (ctx.fieldRoots.has(root)) {
|
|
948
|
+
if (rest.length !== 1) {
|
|
949
|
+
throw new CompileError("unsupported", `cross-object/nested field path "${chain.join(".")}" is not pushdown-able`);
|
|
950
|
+
}
|
|
951
|
+
return { kind: "field", path: rest[0] };
|
|
952
|
+
}
|
|
953
|
+
throw new CompileError("unsupported", `cross-object field path "${chain.join(".")}" is not pushdown-able`);
|
|
954
|
+
}
|
|
955
|
+
default:
|
|
956
|
+
throw new CompileError("unsupported", `unsupported operand "${String(node.op)}"`);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
function memberChain(recv, field) {
|
|
960
|
+
if (recv.op === "id") return [recv.args, field];
|
|
961
|
+
if (recv.op === "." || recv.op === ".?") {
|
|
962
|
+
const [innerRecv, innerField] = recv.args;
|
|
963
|
+
const inner = memberChain(innerRecv, innerField);
|
|
964
|
+
return inner ? [...inner, field] : null;
|
|
965
|
+
}
|
|
966
|
+
return null;
|
|
967
|
+
}
|
|
968
|
+
function resolveValue(leaf, ctx) {
|
|
969
|
+
if (leaf.kind === "literal") return leaf.value;
|
|
970
|
+
if (leaf.kind === "field") {
|
|
971
|
+
throw new CompileError("unsupported", `expected a value but got field "${leaf.path}"`);
|
|
972
|
+
}
|
|
973
|
+
if (ctx.mode === "shape") return SHAPE_VALUE;
|
|
974
|
+
let cur = ctx.variables;
|
|
975
|
+
for (const seg of leaf.path) {
|
|
976
|
+
if (cur == null || typeof cur !== "object") {
|
|
977
|
+
throw new CompileError("unresolved-variable", `variable "${leaf.path.join(".")}" is not resolvable`);
|
|
978
|
+
}
|
|
979
|
+
cur = cur[seg];
|
|
980
|
+
}
|
|
981
|
+
if (cur === void 0 || cur === null) {
|
|
982
|
+
throw new CompileError("unresolved-variable", `variable "${leaf.path.join(".")}" is ${String(cur)}`);
|
|
983
|
+
}
|
|
984
|
+
return cur;
|
|
985
|
+
}
|
|
986
|
+
function coerceLiteral(v) {
|
|
987
|
+
if (typeof v === "bigint") return Number(v);
|
|
988
|
+
if (v === null || typeof v === "string" || typeof v === "number" || typeof v === "boolean") return v;
|
|
989
|
+
if (typeof v === "object" && typeof v.valueOf === "function") {
|
|
990
|
+
const prim = v.valueOf();
|
|
991
|
+
if (typeof prim === "bigint") return Number(prim);
|
|
992
|
+
if (typeof prim === "number" || typeof prim === "string" || typeof prim === "boolean") return prim;
|
|
993
|
+
}
|
|
994
|
+
throw new CompileError("unsupported", `unsupported literal type "${typeof v}"`);
|
|
995
|
+
}
|
|
996
|
+
function constFold(op, l, r) {
|
|
997
|
+
switch (op) {
|
|
998
|
+
case "==":
|
|
999
|
+
return l === r;
|
|
1000
|
+
case "!=":
|
|
1001
|
+
return l !== r;
|
|
1002
|
+
case ">":
|
|
1003
|
+
return l > r;
|
|
1004
|
+
case ">=":
|
|
1005
|
+
return l >= r;
|
|
1006
|
+
case "<":
|
|
1007
|
+
return l < r;
|
|
1008
|
+
case "<=":
|
|
1009
|
+
return l <= r;
|
|
1010
|
+
default:
|
|
1011
|
+
return void 0;
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
// src/matches-filter.ts
|
|
1016
|
+
function matchesFilterCondition(record, filter) {
|
|
1017
|
+
if (filter == null) return true;
|
|
1018
|
+
if (typeof filter !== "object" || Array.isArray(filter)) return false;
|
|
1019
|
+
return evalNode(record, filter);
|
|
1020
|
+
}
|
|
1021
|
+
function evalNode(record, node) {
|
|
1022
|
+
for (const [key, val] of Object.entries(node)) {
|
|
1023
|
+
if (key === "$and") {
|
|
1024
|
+
if (!Array.isArray(val) || !val.every((c) => evalNode(record, c))) return false;
|
|
1025
|
+
} else if (key === "$or") {
|
|
1026
|
+
if (!Array.isArray(val) || val.length === 0 || !val.some((c) => evalNode(record, c))) return false;
|
|
1027
|
+
} else if (key === "$not") {
|
|
1028
|
+
if (val == null || typeof val !== "object") return false;
|
|
1029
|
+
if (evalNode(record, val)) return false;
|
|
1030
|
+
} else if (key.startsWith("$")) {
|
|
1031
|
+
return false;
|
|
1032
|
+
} else {
|
|
1033
|
+
if (!evalField(record, key, val)) return false;
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
return true;
|
|
1037
|
+
}
|
|
1038
|
+
function evalField(record, field, spec) {
|
|
1039
|
+
const actual = getPath(record, field);
|
|
1040
|
+
if (spec === null) return actual == null;
|
|
1041
|
+
if (typeof spec !== "object" || spec instanceof Date) return looseEq(actual, spec);
|
|
1042
|
+
if (Array.isArray(spec)) return false;
|
|
1043
|
+
const ops = spec;
|
|
1044
|
+
const keys = Object.keys(ops);
|
|
1045
|
+
if (keys.length === 0 || keys.some((k) => !k.startsWith("$"))) return false;
|
|
1046
|
+
for (const op of keys) {
|
|
1047
|
+
if (!evalOp(actual, op, ops[op], record)) return false;
|
|
1048
|
+
}
|
|
1049
|
+
return true;
|
|
1050
|
+
}
|
|
1051
|
+
function evalOp(actual, op, raw, record) {
|
|
1052
|
+
const v = resolveValue2(raw, record);
|
|
1053
|
+
switch (op) {
|
|
1054
|
+
case "$eq":
|
|
1055
|
+
return v === null ? actual == null : looseEq(actual, v);
|
|
1056
|
+
case "$ne":
|
|
1057
|
+
return v === null ? actual != null : !looseEq(actual, v);
|
|
1058
|
+
case "$gt":
|
|
1059
|
+
return actual != null && v != null && actual > v;
|
|
1060
|
+
case "$gte":
|
|
1061
|
+
return actual != null && v != null && actual >= v;
|
|
1062
|
+
case "$lt":
|
|
1063
|
+
return actual != null && v != null && actual < v;
|
|
1064
|
+
case "$lte":
|
|
1065
|
+
return actual != null && v != null && actual <= v;
|
|
1066
|
+
case "$in":
|
|
1067
|
+
return Array.isArray(v) && v.some((x) => looseEq(actual, x));
|
|
1068
|
+
case "$nin":
|
|
1069
|
+
return Array.isArray(v) && !v.some((x) => looseEq(actual, x));
|
|
1070
|
+
case "$between":
|
|
1071
|
+
return Array.isArray(v) && v.length === 2 && actual != null && actual >= v[0] && actual <= v[1];
|
|
1072
|
+
case "$contains":
|
|
1073
|
+
return typeof actual === "string" && typeof v === "string" && actual.includes(v);
|
|
1074
|
+
case "$notContains":
|
|
1075
|
+
return !(typeof actual === "string" && typeof v === "string" && actual.includes(v));
|
|
1076
|
+
case "$startsWith":
|
|
1077
|
+
return typeof actual === "string" && typeof v === "string" && actual.startsWith(v);
|
|
1078
|
+
case "$endsWith":
|
|
1079
|
+
return typeof actual === "string" && typeof v === "string" && actual.endsWith(v);
|
|
1080
|
+
case "$null":
|
|
1081
|
+
return v === true ? actual == null : actual != null;
|
|
1082
|
+
case "$exists":
|
|
1083
|
+
return v === true ? actual !== void 0 : actual === void 0;
|
|
1084
|
+
default:
|
|
1085
|
+
return false;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
function resolveValue2(raw, record) {
|
|
1089
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw) && "$field" in raw) {
|
|
1090
|
+
return getPath(record, String(raw.$field));
|
|
1091
|
+
}
|
|
1092
|
+
return raw;
|
|
1093
|
+
}
|
|
1094
|
+
function getPath(record, path) {
|
|
1095
|
+
if (!path.includes(".")) return record[path];
|
|
1096
|
+
let cur = record;
|
|
1097
|
+
for (const seg of path.split(".")) {
|
|
1098
|
+
if (cur == null || typeof cur !== "object") return void 0;
|
|
1099
|
+
cur = cur[seg];
|
|
1100
|
+
}
|
|
1101
|
+
return cur;
|
|
1102
|
+
}
|
|
1103
|
+
function looseEq(a, b) {
|
|
1104
|
+
if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
|
|
1105
|
+
if (a instanceof Date && (typeof b === "string" || typeof b === "number")) return a.getTime() === new Date(b).getTime();
|
|
1106
|
+
if (b instanceof Date && (typeof a === "string" || typeof a === "number")) return new Date(a).getTime() === b.getTime();
|
|
1107
|
+
return a === b;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
752
1110
|
// src/validate.ts
|
|
753
1111
|
var SINGLE_BRACE_RE = /(?:^|[^{])\{\s*([A-Za-z_$][\w.$]*)\s*\}(?!\})/;
|
|
754
1112
|
var RECORD_REF_RE = /\b(?:record|previous)\.([A-Za-z_$][\w$]*)/g;
|
|
755
1113
|
function expectedDialect(role) {
|
|
756
1114
|
return role === "template" ? "template" : "cel";
|
|
757
1115
|
}
|
|
758
|
-
function
|
|
1116
|
+
function toSource2(input) {
|
|
759
1117
|
if (input == null) return { source: "" };
|
|
760
1118
|
if (typeof input === "string") return { source: input };
|
|
761
1119
|
return { dialect: input.dialect, source: input.source ?? "" };
|
|
@@ -810,7 +1168,7 @@ function levenshtein(a, b) {
|
|
|
810
1168
|
return dp[m];
|
|
811
1169
|
}
|
|
812
1170
|
function validateExpression(role, input, schema) {
|
|
813
|
-
const { dialect, source } =
|
|
1171
|
+
const { dialect, source } = toSource2(input);
|
|
814
1172
|
const errors = [];
|
|
815
1173
|
const warnings = [];
|
|
816
1174
|
if (!source.trim()) return { ok: true, errors, warnings };
|
|
@@ -923,12 +1281,16 @@ export {
|
|
|
923
1281
|
TEMPLATE_FORMATTERS,
|
|
924
1282
|
buildScope,
|
|
925
1283
|
celEngine,
|
|
1284
|
+
compileCelToFilter,
|
|
926
1285
|
cronEngine,
|
|
927
1286
|
expectedDialect,
|
|
928
1287
|
formatValue,
|
|
929
1288
|
getEngine,
|
|
930
1289
|
hasDialect,
|
|
931
1290
|
introspectScope,
|
|
1291
|
+
isPushdownableCel,
|
|
1292
|
+
lowerCelAst,
|
|
1293
|
+
matchesFilterCondition,
|
|
932
1294
|
normalizeExpression,
|
|
933
1295
|
normalizeExpressionTree,
|
|
934
1296
|
register,
|