Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(schema-compiler): make sure view members are resolvable in DAP #9059

Merged
merged 1 commit into from
Feb 4, 2025
Merged
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
@@ -23,7 +23,9 @@ export class DataSchemaCompiler {
this.cubeCompilers = options.cubeCompilers || [];
this.contextCompilers = options.contextCompilers || [];
this.transpilers = options.transpilers || [];
this.viewCompilers = options.viewCompilers || [];
this.preTranspileCubeCompilers = options.preTranspileCubeCompilers || [];
this.viewCompilationGate = options.viewCompilationGate;
this.cubeNameCompilers = options.cubeNameCompilers || [];
this.extensions = options.extensions || {};
this.cubeFactory = options.cubeFactory;
@@ -93,7 +95,10 @@ export class DataSchemaCompiler {
const compilePhase = (compilers) => this.compileCubeFiles(compilers, transpile(), errorsReport);

return compilePhase({ cubeCompilers: this.cubeNameCompilers })
.then(() => compilePhase({ cubeCompilers: this.preTranspileCubeCompilers }))
.then(() => compilePhase({ cubeCompilers: this.preTranspileCubeCompilers.concat([this.viewCompilationGate]) }))
.then(() => (this.viewCompilationGate.shouldCompileViews() ?
compilePhase({ cubeCompilers: this.viewCompilers })
: Promise.resolve()))
.then(() => compilePhase({
cubeCompilers: this.cubeCompilers,
contextCompilers: this.contextCompilers,
Original file line number Diff line number Diff line change
@@ -20,6 +20,7 @@ import { JoinGraph } from './JoinGraph';
import { CubeToMetaTransformer } from './CubeToMetaTransformer';
import { CompilerCache } from './CompilerCache';
import { YamlCompiler } from './YamlCompiler';
import { ViewCompilationGate } from './ViewCompilationGate';

export type PrepareCompilerOptions = {
nativeInstance?: NativeInstance,
@@ -37,19 +38,21 @@ export const prepareCompiler = (repo: SchemaFileRepository, options: PrepareComp
const nativeInstance = options.nativeInstance || new NativeInstance();
const cubeDictionary = new CubeDictionary();
const cubeSymbols = new CubeSymbols();
const viewCompiler = new CubeSymbols(true);
const viewCompilationGate = new ViewCompilationGate();
const cubeValidator = new CubeValidator(cubeSymbols);
const cubeEvaluator = new CubeEvaluator(cubeValidator);
const contextEvaluator = new ContextEvaluator(cubeEvaluator);
const joinGraph = new JoinGraph(cubeValidator, cubeEvaluator);
const metaTransformer = new CubeToMetaTransformer(cubeValidator, cubeEvaluator, contextEvaluator, joinGraph);
const { maxQueryCacheSize, maxQueryCacheAge } = options;
const compilerCache = new CompilerCache({ maxQueryCacheSize, maxQueryCacheAge });
const yamlCompiler = new YamlCompiler(cubeSymbols, cubeDictionary, nativeInstance);
const yamlCompiler = new YamlCompiler(cubeSymbols, cubeDictionary, nativeInstance, viewCompiler);

const transpilers: TranspilerInterface[] = [
new ValidationTranspiler(),
new ImportExportTranspiler(),
new CubePropContextTranspiler(cubeSymbols, cubeDictionary),
new CubePropContextTranspiler(cubeSymbols, cubeDictionary, viewCompiler),
];

if (!options.allowJsDuplicatePropsInSchema) {
@@ -60,6 +63,8 @@ export const prepareCompiler = (repo: SchemaFileRepository, options: PrepareComp
cubeNameCompilers: [cubeDictionary],
preTranspileCubeCompilers: [cubeSymbols, cubeValidator],
transpilers,
viewCompilationGate,
viewCompilers: [viewCompiler],
cubeCompilers: [cubeEvaluator, joinGraph, metaTransformer],
contextCompilers: [contextEvaluator],
cubeFactory: cubeSymbols.createCube.bind(cubeSymbols),
@@ -72,7 +77,7 @@ export const prepareCompiler = (repo: SchemaFileRepository, options: PrepareComp
compileContext: options.compileContext,
standalone: options.standalone,
nativeInstance,
yamlCompiler
yamlCompiler,
}, options));

return {
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export class ViewCompilationGate {
private shouldCompile: any;

public constructor() {
this.shouldCompile = false;
}

public compile(cubes: any[]) {
// When developing Data Access Policies feature, we've came across a
// limitation that Cube members can't be referenced in access policies defined on Views,
// because views aren't (yet) compiled at the time of access policy evaluation.
// To workaround this limitation and additional compilation pass is necessary,
// however it comes with a significant performance penalty.
// This gate check whether the data model contains views with access policies,
// and only then allows the additional compilation pass.
//
// Check out the DataSchemaCompiler.ts to see how this gate is used.
if (this.viewsHaveAccessPolicies(cubes)) {
this.shouldCompile = true;
}
}

private viewsHaveAccessPolicies(cubes: any[]) {
return cubes.some(c => c.isView && c.accessPolicy);
}

public shouldCompileViews() {
return this.shouldCompile;
}
}
Original file line number Diff line number Diff line change
@@ -33,6 +33,7 @@ export class YamlCompiler {
private readonly cubeSymbols: CubeSymbols,
private readonly cubeDictionary: CubeDictionary,
private readonly nativeInstance: NativeInstance,
private readonly viewCompiler: CubeSymbols,
) {
}

@@ -288,7 +289,9 @@ export class YamlCompiler {
},
);

resolveSymbol = resolveSymbol || (n => this.cubeSymbols.resolveSymbol(cubeName, n) || this.cubeSymbols.isCurrentCube(n));
resolveSymbol = resolveSymbol || (n => this.viewCompiler.resolveSymbol(cubeName, n) ||
this.cubeSymbols.resolveSymbol(cubeName, n) ||
this.cubeSymbols.isCurrentCube(n));

const traverseObj = {
Program: (babelPath) => {
Original file line number Diff line number Diff line change
@@ -41,6 +41,7 @@ export class CubePropContextTranspiler implements TranspilerInterface {
public constructor(
protected readonly cubeSymbols: CubeSymbols,
protected readonly cubeDictionary: CubeDictionary,
protected readonly viewCompiler: CubeSymbols,
) {
}

@@ -88,7 +89,9 @@ export class CubePropContextTranspiler implements TranspilerInterface {
}

protected sqlAndReferencesFieldVisitor(cubeName): TraverseObject {
const resolveSymbol = n => this.cubeSymbols.resolveSymbol(cubeName, n) || this.cubeSymbols.isCurrentCube(n);
const resolveSymbol = n => this.viewCompiler.resolveSymbol(cubeName, n) ||
this.cubeSymbols.resolveSymbol(cubeName, n) ||
this.cubeSymbols.isCurrentCube(n);

return {
ObjectProperty: (path) => {
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
views:
- name: users_view
cubes:
- join_path: users
includes: "*"

access_policy:
- role: '*'
row_level:
filters:
- member: id
operator: gt
values: [10]
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
view('users_view', {
cubes: [{
join_path: users,
includes: '*',
}],
accessPolicy: [{
role: '*',
rowLevel: {
filters: [{
member: 'id',
operator: 'gt',
values: [10],
}],
},
}]
});
150 changes: 150 additions & 0 deletions packages/cubejs-testing/test/__snapshots__/smoke-rbac.test.ts.snap
Original file line number Diff line number Diff line change
@@ -8,6 +8,81 @@ Array [
]
`;

exports[`Cube RBAC Engine [Python config] RBAC via SQL API [python config] SELECT * from users_view: users_view_python 1`] = `
Array [
Object {
"__cubeJoinField": null,
"__user": null,
"city": "Austin",
"count": "1",
"id": 400,
},
Object {
"__cubeJoinField": null,
"__user": null,
"city": "Austin",
"count": "1",
"id": 401,
},
Object {
"__cubeJoinField": null,
"__user": null,
"city": "Austin",
"count": "1",
"id": 402,
},
Object {
"__cubeJoinField": null,
"__user": null,
"city": "Austin",
"count": "1",
"id": 403,
},
Object {
"__cubeJoinField": null,
"__user": null,
"city": "Austin",
"count": "1",
"id": 404,
},
Object {
"__cubeJoinField": null,
"__user": null,
"city": "Austin",
"count": "1",
"id": 405,
},
Object {
"__cubeJoinField": null,
"__user": null,
"city": "Austin",
"count": "1",
"id": 406,
},
Object {
"__cubeJoinField": null,
"__user": null,
"city": "Austin",
"count": "1",
"id": 407,
},
Object {
"__cubeJoinField": null,
"__user": null,
"city": "Austin",
"count": "1",
"id": 408,
},
Object {
"__cubeJoinField": null,
"__user": null,
"city": "Austin",
"count": "1",
"id": 409,
},
]
`;

exports[`Cube RBAC Engine [Python config][dev mode] products with no matching policy: products_no_policy_python 1`] = `
Array [
Object {
@@ -611,6 +686,81 @@ Array [
]
`;

exports[`Cube RBAC Engine RBAC via SQL API SELECT * from users_view: users_view_js 1`] = `
Array [
Object {
"__cubeJoinField": null,
"__user": null,
"city": "Austin",
"count": "1",
"id": 400,
},
Object {
"__cubeJoinField": null,
"__user": null,
"city": "Austin",
"count": "1",
"id": 401,
},
Object {
"__cubeJoinField": null,
"__user": null,
"city": "Austin",
"count": "1",
"id": 402,
},
Object {
"__cubeJoinField": null,
"__user": null,
"city": "Austin",
"count": "1",
"id": 403,
},
Object {
"__cubeJoinField": null,
"__user": null,
"city": "Austin",
"count": "1",
"id": 404,
},
Object {
"__cubeJoinField": null,
"__user": null,
"city": "Austin",
"count": "1",
"id": 405,
},
Object {
"__cubeJoinField": null,
"__user": null,
"city": "Austin",
"count": "1",
"id": 406,
},
Object {
"__cubeJoinField": null,
"__user": null,
"city": "Austin",
"count": "1",
"id": 407,
},
Object {
"__cubeJoinField": null,
"__user": null,
"city": "Austin",
"count": "1",
"id": 408,
},
Object {
"__cubeJoinField": null,
"__user": null,
"city": "Austin",
"count": "1",
"id": 409,
},
]
`;

exports[`Cube RBAC Engine RBAC via SQL API default policy SELECT with member expressions: users_member_expression 1`] = `
Array [
Object {
12 changes: 12 additions & 0 deletions packages/cubejs-testing/test/smoke-rbac.test.ts
Original file line number Diff line number Diff line change
@@ -152,6 +152,12 @@ describe('Cube RBAC Engine', () => {
// Querying a cube with nested filters and mixed values should not cause any issues
expect(res.rows).toMatchSnapshot('users');
});

test('SELECT * from users_view', async () => {
const res = await connection.query('SELECT * FROM users_view limit 10');
// Make sure view policies are evaluated correctly in yaml schemas
expect(res.rows).toMatchSnapshot('users_view_js');
});
});

describe('RBAC via SQL API manager', () => {
@@ -398,6 +404,12 @@ describe('Cube RBAC Engine [Python config]', () => {
// It should also exclude the `created_at` dimension as per memberLevel policy
expect(res.rows).toMatchSnapshot('users_python');
});

test('SELECT * from users_view', async () => {
const res = await connection.query('SELECT * FROM users_view limit 10');
// Make sure view policies are evaluated correctly in yaml schemas
expect(res.rows).toMatchSnapshot('users_view_python');
});
});
});

2 changes: 1 addition & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
@@ -9890,7 +9890,7 @@
dependencies:
"@types/node" "*"

"@types/node@*", "@types/node@^12", "@types/node@^16", "@types/node@^18":
"@types/node@*", "@types/node@^12", "@types/node@^18":
version "18.19.46"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.46.tgz#51801396c01153e0626e36f43386e83bc768b072"
integrity sha512-vnRgMS7W6cKa1/0G3/DTtQYpVrZ8c0Xm6UkLaVFrb9jtcVC3okokW09Ki1Qdrj9ISokszD69nY4WDLRlvHlhAA==

Unchanged files with check annotations Beta

// TODO switch parsing to microseconds
if timestamp.and_utc().timestamp_millis() > (((1i64) << 62) / 1_000_000) {
builder.append_null()?;
} else if let Some(nanos) = timestamp.timestamp_nanos_opt() {

Check warning on line 1132 in rust/cubesql/cubesql/src/compile/engine/df/scan.rs

GitHub Actions / build

use of deprecated method `chrono::NaiveDateTime::timestamp_nanos_opt`: use `.and_utc().timestamp_nanos_opt()` instead

Check warning on line 1132 in rust/cubesql/cubesql/src/compile/engine/df/scan.rs

GitHub Actions / unit (20.x, 3.11)

use of deprecated method `chrono::NaiveDateTime::timestamp_nanos_opt`: use `.and_utc().timestamp_nanos_opt()` instead

Check warning on line 1132 in rust/cubesql/cubesql/src/compile/engine/df/scan.rs

GitHub Actions / unit (22.x, 3.11)

use of deprecated method `chrono::NaiveDateTime::timestamp_nanos_opt`: use `.and_utc().timestamp_nanos_opt()` instead
builder.append_value(nanos)?;
} else {
log::error!(
let secs = duration.num_seconds();
let nanosecs = duration.num_nanoseconds().unwrap_or(0) - secs * 1_000_000_000;
let timestamp = NaiveDateTime::from_timestamp_opt(secs, nanosecs as u32)

Check warning on line 1672 in rust/cubesql/cubesql/src/compile/engine/udf/common.rs

GitHub Actions / build

use of deprecated associated function `chrono::NaiveDateTime::from_timestamp_opt`: use `DateTime::from_timestamp` instead

Check warning on line 1672 in rust/cubesql/cubesql/src/compile/engine/udf/common.rs

GitHub Actions / unit (20.x, 3.11)

use of deprecated associated function `chrono::NaiveDateTime::from_timestamp_opt`: use `DateTime::from_timestamp` instead

Check warning on line 1672 in rust/cubesql/cubesql/src/compile/engine/udf/common.rs

GitHub Actions / unit (22.x, 3.11)

use of deprecated associated function `chrono::NaiveDateTime::from_timestamp_opt`: use `DateTime::from_timestamp` instead
.unwrap_or_else(|| panic!("Invalid secs {} nanosecs {}", secs, nanosecs));
// chrono's strftime is missing quarter format, as such a workaround is required
macro_rules! generate_series_helper_date32 {
($CURRENT:ident, $STEP:ident, $PRIMITIVE_TYPE: ident) => {
let current_dt = NaiveDateTime::from_timestamp_opt(($CURRENT as i64) * 86400, 0)

Check warning on line 2319 in rust/cubesql/cubesql/src/compile/engine/udf/common.rs

GitHub Actions / build

use of deprecated associated function `chrono::NaiveDateTime::from_timestamp_opt`: use `DateTime::from_timestamp` instead

Check warning on line 2319 in rust/cubesql/cubesql/src/compile/engine/udf/common.rs

GitHub Actions / unit (20.x, 3.11)

use of deprecated associated function `chrono::NaiveDateTime::from_timestamp_opt`: use `DateTime::from_timestamp` instead

Check warning on line 2319 in rust/cubesql/cubesql/src/compile/engine/udf/common.rs

GitHub Actions / unit (22.x, 3.11)

use of deprecated associated function `chrono::NaiveDateTime::from_timestamp_opt`: use `DateTime::from_timestamp` instead
.ok_or_else(|| {
DataFusionError::Execution(format!(
"Cannot convert date to NaiveDateTime: {}",
))
})?;
let res = date_addsub_month_day_nano(current_dt, $STEP, true)?;
$CURRENT = (res.timestamp() / 86400) as $PRIMITIVE_TYPE;

Check warning on line 2327 in rust/cubesql/cubesql/src/compile/engine/udf/common.rs

GitHub Actions / build

use of deprecated method `chrono::NaiveDateTime::timestamp`: use `.and_utc().timestamp()` instead

Check warning on line 2327 in rust/cubesql/cubesql/src/compile/engine/udf/common.rs

GitHub Actions / unit (20.x, 3.11)

use of deprecated method `chrono::NaiveDateTime::timestamp`: use `.and_utc().timestamp()` instead

Check warning on line 2327 in rust/cubesql/cubesql/src/compile/engine/udf/common.rs

GitHub Actions / unit (22.x, 3.11)

use of deprecated method `chrono::NaiveDateTime::timestamp`: use `.and_utc().timestamp()` instead
};
}
macro_rules! generate_series_helper_timestamp {
($CURRENT:ident, $STEP:ident, $PRIMITIVE_TYPE: ident) => {
let current_dt = NaiveDateTime::from_timestamp_opt(

Check warning on line 2333 in rust/cubesql/cubesql/src/compile/engine/udf/common.rs

GitHub Actions / build

use of deprecated associated function `chrono::NaiveDateTime::from_timestamp_opt`: use `DateTime::from_timestamp` instead

Check warning on line 2333 in rust/cubesql/cubesql/src/compile/engine/udf/common.rs

GitHub Actions / unit (20.x, 3.11)

use of deprecated associated function `chrono::NaiveDateTime::from_timestamp_opt`: use `DateTime::from_timestamp` instead

Check warning on line 2333 in rust/cubesql/cubesql/src/compile/engine/udf/common.rs

GitHub Actions / unit (22.x, 3.11)

use of deprecated associated function `chrono::NaiveDateTime::from_timestamp_opt`: use `DateTime::from_timestamp` instead
($CURRENT as i64) / 1_000_000_000,
($CURRENT % 1_000_000_000) as u32,
)
};
let ts_seconds = *ts / 1_000_000_000;
let ts_nanos = (*ts % 1_000_000_000) as u32;
let dt = NaiveDateTime::from_timestamp_opt(ts_seconds, ts_nanos).map(|dt| Some(dt));

Check warning on line 4817 in rust/cubesql/cubesql/src/compile/rewrite/rules/filters.rs

GitHub Actions / build

use of deprecated associated function `chrono::NaiveDateTime::from_timestamp_opt`: use `DateTime::from_timestamp` instead

Check warning on line 4817 in rust/cubesql/cubesql/src/compile/rewrite/rules/filters.rs

GitHub Actions / unit (20.x, 3.11)

use of deprecated associated function `chrono::NaiveDateTime::from_timestamp_opt`: use `DateTime::from_timestamp` instead

Check warning on line 4817 in rust/cubesql/cubesql/src/compile/rewrite/rules/filters.rs

GitHub Actions / unit (22.x, 3.11)

use of deprecated associated function `chrono::NaiveDateTime::from_timestamp_opt`: use `DateTime::from_timestamp` instead
return dt;
};
return Some(false);
}
let seconds = ns / ns_in_seconds;
let dt = NaiveDateTime::from_timestamp_opt(seconds, 0)?;

Check warning on line 460 in rust/cubesql/cubesql/src/compile/rewrite/rules/utils.rs

GitHub Actions / build

use of deprecated associated function `chrono::NaiveDateTime::from_timestamp_opt`: use `DateTime::from_timestamp` instead

Check warning on line 460 in rust/cubesql/cubesql/src/compile/rewrite/rules/utils.rs

GitHub Actions / unit (20.x, 3.11)

use of deprecated associated function `chrono::NaiveDateTime::from_timestamp_opt`: use `DateTime::from_timestamp` instead

Check warning on line 460 in rust/cubesql/cubesql/src/compile/rewrite/rules/utils.rs

GitHub Actions / unit (22.x, 3.11)

use of deprecated associated function `chrono::NaiveDateTime::from_timestamp_opt`: use `DateTime::from_timestamp` instead
let is_minute_trunced = |dt: NaiveDateTime| dt.second() == 0;
let is_hour_trunced = |dt| is_minute_trunced(dt) && dt.minute() == 0;