Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,8 @@ The `if` expression must evaluate to a boolean. The syntax you can use inside it
depends on the data model format:

- In **YAML** data models, the expression inside `{ }` is evaluated as a Python
expression. Only logical operators, member access, and method calls are supported.
expression. Logical operators, equality and inequality, member access, and method
calls are supported.
- In **JavaScript** data models, the expression is evaluated as a JavaScript
expression, so a broader set of operators is available.

Expand All @@ -227,12 +228,15 @@ The following operators and constructs are supported:
| Logical NOT | `not` | `!` |
| Member access | `.` | `.` |
| Method call (e.g., `includes`) | `.includes(…)` | `.includes(…)` |
| Equality and inequality | | `===`, `!==`, `==`, `!=` |
| Equality and inequality | `==`, `!=` | `===`, `!==`, `==`, `!=` |
| Comparison | — | `<`, `>`, `<=`, `>=` |
| Arithmetic | — | `+`, `-`, `*`, `/`, `%`, `**` |
| Ternary | — | `? :` |
| Optional chaining | — | `?.` |

Chained comparisons aren't supported in YAML. Write `a == b and b == c` instead
of `a == b == c`.

For example, the following policy applies to users who are not blocked, are either
administrators or based in the EMEA region, and belong to the `admins` group. Each
requirement is expressed as a separate condition, and all conditions must be `true`
Expand Down
18 changes: 18 additions & 0 deletions packages/cubejs-schema-compiler/src/parser/PythonParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ import Python3Parser, {
And_testContext,
// eslint-disable-next-line camelcase
Or_testContext,
ComparisonContext,
// eslint-disable-next-line camelcase
Comp_opContext,
} from './Python3Parser';
import { UserError } from '../compiler/UserError';
import Python3ParserVisitor from './Python3ParserVisitor';
Expand Down Expand Up @@ -244,6 +247,21 @@ export class PythonParser {
return children[0];
}
return children.reduce((left, right) => t.logicalExpression('||', left, right));
} else if (node instanceof Comp_opContext) {
if (node.EQUALS()) {
return '===';
} else if (node.NOT_EQ_2()) {
return '!==';
}
throw new UserError(`Unsupported Python comparison operator: ${node.getText()}`);
} else if (node instanceof ComparisonContext) {
if (children.length === 1) {
return children[0];
}
if (children.length !== 3) {
throw new UserError('Chained Python comparisons are not supported');
}
return t.binaryExpression(children[1], children[0], children[2]);
} else if (node instanceof ArglistContext) {
return children;
} else {
Expand Down
65 changes: 65 additions & 0 deletions packages/cubejs-schema-compiler/test/unit/yaml-schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -839,6 +839,71 @@ cubes:
});

describe('Access policy: ', () => {
it('supports equality and inequality operators in conditions', async () => {
const compilers = prepareYamlCompiler(
`
cubes:
- name: Orders
sql: "select * from tbl"
measures:
- name: count
type: count
accessPolicy:
- role: admin
conditions:
- if: "{ securityContext.region == 'EMEA' }"
- if: "{ securityContext.department != 'Sales' }"
`
);

await compilers.compiler.compile();

const conditions = compilers.cubeEvaluator.cubeFromPath('Orders').accessPolicy![0].conditions!;
expect(conditions[0].if.toString()).toContain('securityContext.region === "EMEA"');
expect(conditions[1].if.toString()).toContain('securityContext.department !== "Sales"');
expect(conditions[0].if({ region: 'EMEA' })).toBe(true);
expect(conditions[1].if({ department: 'Sales' })).toBe(false);
expect(conditions[1].if({ department: 'Marketing' })).toBe(true);
});

it('does not support chained comparisons in conditions', async () => {
const { compiler } = prepareYamlCompiler(
`
cubes:
- name: Orders
sql: "select * from tbl"
measures:
- name: count
type: count
accessPolicy:
- role: admin
conditions:
- if: "{ securityContext.region == securityContext.department == 'Sales' }"
`
);

await expect(compiler.compile()).rejects.toThrow('Chained Python comparisons are not supported');
});

it.each(['===', '!=='])('does not support JavaScript operator %s in conditions', async (operator) => {
const { compiler } = prepareYamlCompiler(
`
cubes:
- name: Orders
sql: "select * from tbl"
measures:
- name: count
type: count
accessPolicy:
- role: admin
conditions:
- if: "{ securityContext.region ${operator} 'EMEA' }"
`
);

await expect(compiler.compile()).rejects.toThrow('Failed to parse Python expression');
});

it('defines a correct accessPolicy', async () => {
const { compiler } = prepareYamlCompiler(
`
Expand Down
Loading