@narrative.io/data-collaboration-sdk-ts 2.66.0 → 2.68.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/build/collaboration-policy/core/policy-traverser.js +1 -2
- package/build/collaboration-policy/core/sql-builder.js +142 -3
- package/build/collaboration-policy/core/types.d.ts +5 -1
- package/build/collaboration-policy/index.d.ts +1 -1
- package/build/collaboration-policy/utils/sql-helpers.d.ts +2 -2
- package/build/collaboration-policy/utils/sql-helpers.js +24 -18
- package/package.json +1 -1
|
@@ -58,11 +58,10 @@ function traverseSatisfiedStructure(node, attributeByName, visitor) {
|
|
|
58
58
|
return;
|
|
59
59
|
}
|
|
60
60
|
if ("anyOf" in node && Array.isArray(node.anyOf)) {
|
|
61
|
-
// For anyOf,
|
|
61
|
+
// For anyOf, traverse ALL satisfied branches
|
|
62
62
|
for (const child of node.anyOf) {
|
|
63
63
|
if (isBranchSatisfied(child, attributeByName)) {
|
|
64
64
|
traverseSatisfiedStructure(child, attributeByName, visitor);
|
|
65
|
-
return; // Only process the first satisfied branch
|
|
66
65
|
}
|
|
67
66
|
}
|
|
68
67
|
return;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { normalizePathValue } from "../utils/path-helpers";
|
|
1
2
|
import { buildSelectAlias, resolveAttributeExpression, } from "../utils/sql-helpers";
|
|
3
|
+
import { filterToSql } from "./filter-builder";
|
|
2
4
|
import { traversePolicy } from "./policy-traverser";
|
|
3
5
|
import { validatePolicyAgainstDataset } from "./policy-validator";
|
|
4
6
|
function buildAttributeIndexes(attributes) {
|
|
@@ -41,14 +43,134 @@ function getSmallestRefreshSchedule(schedules) {
|
|
|
41
43
|
}
|
|
42
44
|
return smallestSchedule;
|
|
43
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Builds structured WHERE clauses for a single branch node
|
|
48
|
+
*/
|
|
49
|
+
function buildBranchWhere(node, attributeByName, datasetName) {
|
|
50
|
+
if (!node || typeof node !== "object") {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
// Handle field nodes (leaf nodes with attributes)
|
|
54
|
+
if ("field" in node) {
|
|
55
|
+
const extendedNode = node;
|
|
56
|
+
const field = extendedNode.field;
|
|
57
|
+
const attributeName = field.attribute_name;
|
|
58
|
+
const attribute = attributeByName.get(attributeName);
|
|
59
|
+
if (!attribute) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const clauses = [];
|
|
63
|
+
// Add IS NOT NULL filters for additional_required_properties
|
|
64
|
+
if (field.additional_required_properties) {
|
|
65
|
+
for (const path of field.additional_required_properties) {
|
|
66
|
+
const pathStr = normalizePathValue(path);
|
|
67
|
+
const expression = `company_data."${datasetName}"."_rosetta_stone"."${attribute.name}"."${pathStr}"`;
|
|
68
|
+
clauses.push(`(${expression} IS NOT NULL)`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Add filters
|
|
72
|
+
if (extendedNode.filters) {
|
|
73
|
+
for (const filter of extendedNode.filters) {
|
|
74
|
+
const sql = filterToSql(filter, attributeByName, datasetName);
|
|
75
|
+
clauses.push(sql);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// If only one clause, return as string
|
|
79
|
+
if (clauses.length === 0) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
if (clauses.length === 1) {
|
|
83
|
+
return clauses[0];
|
|
84
|
+
}
|
|
85
|
+
// Multiple clauses for a single field - AND them together
|
|
86
|
+
return {
|
|
87
|
+
operation: "AND",
|
|
88
|
+
fragments: clauses,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
// Handle anyOf nodes
|
|
92
|
+
if ("anyOf" in node && Array.isArray(node.anyOf)) {
|
|
93
|
+
const satisfiedBranches = [];
|
|
94
|
+
for (const child of node.anyOf) {
|
|
95
|
+
if (isBranchSatisfied(child, attributeByName)) {
|
|
96
|
+
const branchWhere = buildBranchWhere(child, attributeByName, datasetName);
|
|
97
|
+
if (branchWhere) {
|
|
98
|
+
satisfiedBranches.push(branchWhere);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (satisfiedBranches.length === 0) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
if (satisfiedBranches.length === 1) {
|
|
106
|
+
return satisfiedBranches[0];
|
|
107
|
+
}
|
|
108
|
+
// Multiple satisfied anyOf branches - OR them together
|
|
109
|
+
return {
|
|
110
|
+
operation: "OR",
|
|
111
|
+
fragments: satisfiedBranches,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
// Handle allOf nodes
|
|
115
|
+
if ("allOf" in node && Array.isArray(node.allOf)) {
|
|
116
|
+
const allBranches = [];
|
|
117
|
+
for (const child of node.allOf) {
|
|
118
|
+
const branchWhere = buildBranchWhere(child, attributeByName, datasetName);
|
|
119
|
+
if (branchWhere) {
|
|
120
|
+
allBranches.push(branchWhere);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (allBranches.length === 0) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
if (allBranches.length === 1) {
|
|
127
|
+
return allBranches[0];
|
|
128
|
+
}
|
|
129
|
+
// Multiple allOf branches - AND them together
|
|
130
|
+
return {
|
|
131
|
+
operation: "AND",
|
|
132
|
+
fragments: allBranches,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Checks if a branch is satisfied (all required attributes exist)
|
|
139
|
+
*/
|
|
140
|
+
function isBranchSatisfied(node, attributeByName) {
|
|
141
|
+
if (!node || typeof node !== "object") {
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
if ("field" in node) {
|
|
145
|
+
const field = node
|
|
146
|
+
.field;
|
|
147
|
+
if (field.type === "attribute") {
|
|
148
|
+
const attributeName = field.attribute_name;
|
|
149
|
+
return attributeByName.has(attributeName);
|
|
150
|
+
}
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
if ("anyOf" in node && Array.isArray(node.anyOf)) {
|
|
154
|
+
return node.anyOf.some((child) => isBranchSatisfied(child, attributeByName));
|
|
155
|
+
}
|
|
156
|
+
if ("allOf" in node && Array.isArray(node.allOf)) {
|
|
157
|
+
return node.allOf.every((child) => isBranchSatisfied(child, attributeByName));
|
|
158
|
+
}
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
44
161
|
function buildPolicySqlUsingIndexes(policies, attributeByName, datasetName) {
|
|
45
162
|
const globalRequiredAttributePaths = new Map();
|
|
46
|
-
const
|
|
163
|
+
const policyWhereGroups = [];
|
|
47
164
|
for (const policy of policies) {
|
|
48
165
|
const policyRequiredAttributePaths = new Map();
|
|
49
166
|
const policyWhereClausesSet = new Set();
|
|
167
|
+
// Still use traversePolicy to collect required attributes for SELECT
|
|
50
168
|
traversePolicy(policy, attributeByName, policyRequiredAttributePaths, policyWhereClausesSet, datasetName);
|
|
51
|
-
|
|
169
|
+
// Build structured WHERE for this policy
|
|
170
|
+
const policyWhere = buildBranchWhere(policy.policy.definition.structure, attributeByName, datasetName);
|
|
171
|
+
if (policyWhere) {
|
|
172
|
+
policyWhereGroups.push(policyWhere);
|
|
173
|
+
}
|
|
52
174
|
for (const [attributeName, paths] of policyRequiredAttributePaths) {
|
|
53
175
|
const globalPaths = globalRequiredAttributePaths.get(attributeName) ?? new Set();
|
|
54
176
|
for (const path of paths) {
|
|
@@ -69,9 +191,26 @@ function buildPolicySqlUsingIndexes(policies, attributeByName, datasetName) {
|
|
|
69
191
|
const alias = buildSelectAlias(attributeName, "");
|
|
70
192
|
selectClauses.add(`${expression} AS ${alias}`);
|
|
71
193
|
}
|
|
194
|
+
// Build the root AND structure
|
|
195
|
+
// Root level is always AND (combines policies)
|
|
196
|
+
// Each policy's branches are already OR'd/AND'd by buildBranchWhere
|
|
197
|
+
let where;
|
|
198
|
+
if (policyWhereGroups.length === 0) {
|
|
199
|
+
where = [];
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
// Wrap all policies in root AND
|
|
203
|
+
// Each policy in policyWhereGroups is already a complete policy structure
|
|
204
|
+
where = [
|
|
205
|
+
{
|
|
206
|
+
fragments: policyWhereGroups,
|
|
207
|
+
operation: "AND",
|
|
208
|
+
},
|
|
209
|
+
];
|
|
210
|
+
}
|
|
72
211
|
return {
|
|
73
212
|
select: Array.from(selectClauses),
|
|
74
|
-
where
|
|
213
|
+
where,
|
|
75
214
|
};
|
|
76
215
|
}
|
|
77
216
|
export function buildPolicySqlWithValidation(dataset, policies, attributes) {
|
|
@@ -7,9 +7,13 @@ export type ExtendedAttributeNode = Extract<StructureNode, {
|
|
|
7
7
|
export type PolicyFilter = NonNullable<PolicyDefinition["filters"]>[number];
|
|
8
8
|
export type PathValue = NonNullable<ExtendedAttributeNode["field"]["additional_required_properties"]>[number];
|
|
9
9
|
export type AttributePathIndex = Map<string, Set<string>>;
|
|
10
|
+
export interface PolicySqlGroup {
|
|
11
|
+
fragments: Array<string | PolicySqlGroup>;
|
|
12
|
+
operation: "AND" | "OR";
|
|
13
|
+
}
|
|
10
14
|
export interface PolicySqlFragments {
|
|
11
15
|
select: string[];
|
|
12
|
-
where: string
|
|
16
|
+
where: Array<PolicySqlGroup | string>;
|
|
13
17
|
}
|
|
14
18
|
export interface PolicyMatchResult extends PolicySqlFragments {
|
|
15
19
|
appliedPolicies: string[];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { buildPolicySql, buildPolicySqlWithValidation, categorizePolicies, } from "./core/sql-builder";
|
|
2
|
-
export type { AttributePathIndex, ExtendedAttributeNode, PathValue, PolicyFilter, PolicyMatchResult, PolicySqlFragments, StructureNode, } from "./core/types";
|
|
2
|
+
export type { AttributePathIndex, ExtendedAttributeNode, PathValue, PolicyFilter, PolicyMatchResult, PolicySqlFragments, PolicySqlGroup, StructureNode, } from "./core/types";
|
|
3
3
|
export type { CollaborationPolicyInput, CollaborationPolicyType, } from "./types";
|
|
4
4
|
export { buildCollaborationPolicyJsonSchema, CollaborationPolicy, } from "./types";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Attribute } from "../../attributes/types";
|
|
2
|
+
import type { PolicySqlGroup } from "../core/types";
|
|
2
3
|
export declare function resolveAttributeExpression(attribute: Attribute, path: string, datasetName: string): string;
|
|
3
4
|
export declare function buildSelectAlias(attributeName: string, path: string): string;
|
|
4
|
-
export declare function
|
|
5
|
-
export declare function formatWhereClause(policyWhereClauses: string[][]): string;
|
|
5
|
+
export declare function formatWhereClause(where: Array<PolicySqlGroup | string>): string;
|
|
@@ -35,29 +35,35 @@ export function buildSelectAlias(attributeName, path) {
|
|
|
35
35
|
const suffix = path.replace(/[^a-zA-Z0-9]+/g, "_");
|
|
36
36
|
return quoteIdentifier(`${attributeName}_${suffix}`);
|
|
37
37
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const firstClause = clauses[0];
|
|
45
|
-
if (firstClause === undefined) {
|
|
46
|
-
continue;
|
|
47
|
-
}
|
|
48
|
-
groups.push(clauses.length === 1 ? firstClause : `(${clauses.join(" AND ")})`);
|
|
38
|
+
/**
|
|
39
|
+
* Recursively builds SQL string from PolicySqlGroup structure
|
|
40
|
+
*/
|
|
41
|
+
function buildSqlFromGroup(item) {
|
|
42
|
+
if (typeof item === "string") {
|
|
43
|
+
return item;
|
|
49
44
|
}
|
|
50
|
-
|
|
45
|
+
const { fragments, operation } = item;
|
|
46
|
+
const separator = operation === "AND" ? " AND " : " OR ";
|
|
47
|
+
const parts = fragments.map(buildSqlFromGroup);
|
|
48
|
+
if (parts.length === 0) {
|
|
49
|
+
return "";
|
|
50
|
+
}
|
|
51
|
+
if (parts.length === 1) {
|
|
52
|
+
return parts[0];
|
|
53
|
+
}
|
|
54
|
+
return `(${parts.join(separator)})`;
|
|
51
55
|
}
|
|
52
|
-
export function formatWhereClause(
|
|
53
|
-
|
|
54
|
-
if (groups.length === 0) {
|
|
56
|
+
export function formatWhereClause(where) {
|
|
57
|
+
if (where.length === 0) {
|
|
55
58
|
return "";
|
|
56
59
|
}
|
|
57
|
-
|
|
58
|
-
|
|
60
|
+
const sqlParts = where
|
|
61
|
+
.map(buildSqlFromGroup)
|
|
62
|
+
.filter((part) => part.length > 0);
|
|
63
|
+
if (sqlParts.length === 0) {
|
|
64
|
+
return "";
|
|
59
65
|
}
|
|
60
|
-
return `\nWHERE\n ${
|
|
66
|
+
return `\nWHERE\n ${sqlParts.join("\n ")}`;
|
|
61
67
|
}
|
|
62
68
|
function quoteIdentifier(identifier) {
|
|
63
69
|
const safe = identifier.replace(/"/g, "");
|