Skip to content

Commit 8488238

Browse files
feat: implement .jitCode() support for Assertion actions (#2170)
- Add `jit_code` field to the `Assertion` message in `protos/core.proto` - Fix missing protobuf imports and configuration in `protos/BUILD` - Introduce `JitAssertionResult` type alias and `jitCode` method to `Assertion` builder (`core/actions/assertion.ts`) - Add strictly typed `jitContextable` parameter to `Session.sqlxAction` in `core/session.ts` - Update assertion `compile()` to handle `jitCode` execution bypassing standard string evaluation - Parse and apply `jitContextable` safely for assertions in `core/session.ts` - Add unit test coverage in `core/main_test.ts` to verify compilation output
1 parent 636d085 commit 8488238

3 files changed

Lines changed: 95 additions & 3 deletions

File tree

core/actions/assertion.ts

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { verifyObjectMatchesProto, VerifyProtoErrorBehaviour } from "df/common/protos";
22
import { ActionBuilder } from "df/core/actions";
3-
import { IActionContext, Resolvable } from "df/core/contextables";
3+
import { IActionContext, JitContextable, Resolvable } from "df/core/contextables";
44
import * as Path from "df/core/path";
55
import { Session } from "df/core/session";
66
import {
@@ -32,6 +32,9 @@ interface ILegacyAssertionConfig extends dataform.ActionConfig.AssertionConfig {
3232
/** @hidden */
3333
export type AContextable<T> = T | ((ctx: AssertionContext) => T);
3434

35+
/** JiT compilation stage result for assertions. */
36+
export type JitAssertionResult = string;
37+
3538
/**
3639
* An assertion is a data quality test query that finds rows that violate one or more conditions
3740
* specified in the query. If the query returns any rows, the assertion fails.
@@ -90,6 +93,9 @@ export class Assertion extends ActionBuilder<dataform.Assertion> {
9093
/** @hidden We delay contextification until the final compile step, so hold these here for now. */
9194
private contextableQuery: AContextable<string>;
9295

96+
/** @hidden */
97+
private contextableJitCode: JitContextable<AssertionContext, JitAssertionResult> | undefined;
98+
9399
/** @hidden */
94100
constructor(session?: Session, unverifiedConfig?: any, configPath?: string) {
95101
super(session);
@@ -166,6 +172,15 @@ export class Assertion extends ActionBuilder<dataform.Assertion> {
166172
return this;
167173
}
168174

175+
public jitCode(jitCode: JitContextable<AssertionContext, JitAssertionResult>) {
176+
if (!this.proto.actionDescriptor) {
177+
this.proto.actionDescriptor = {};
178+
}
179+
this.proto.actionDescriptor.compilationMode = dataform.ActionCompilationMode.ACTION_COMPILATION_MODE_JIT;
180+
this.contextableJitCode = jitCode;
181+
return this;
182+
}
183+
169184
/**
170185
* @deprecated Deprecated in favor of
171186
* [AssertionConfig.dependencies](configs#dataform-ActionConfig-AssertionConfig).
@@ -293,8 +308,24 @@ export class Assertion extends ActionBuilder<dataform.Assertion> {
293308
public compile() {
294309
const context = new AssertionContext(this);
295310

296-
this.proto.query = context.apply(this.contextableQuery);
297-
validateQueryString(this.session, this.proto.query, this.proto.fileName);
311+
if (this.contextableJitCode && this.contextableQuery) {
312+
this.session.compileError(
313+
new Error("Assertion may set either .jitCode() or .query(), but not both."),
314+
this.proto.fileName,
315+
this.proto.target
316+
);
317+
return this.proto;
318+
}
319+
320+
if (this.contextableJitCode) {
321+
if (!this.proto.actionDescriptor) {
322+
this.proto.actionDescriptor = {};
323+
}
324+
this.proto.jitCode = this.contextableJitCode.toString();
325+
} else {
326+
this.proto.query = context.apply(this.contextableQuery);
327+
validateQueryString(this.session, this.proto.query, this.proto.fileName);
328+
}
298329

299330
return verifyObjectMatchesProto(
300331
dataform.Assertion,

core/main_test.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1537,6 +1537,65 @@ assert("name", {
15371537
]);
15381538
});
15391539

1540+
test("jitCode correctly populates the jitCode field", () => {
1541+
const projectDir = tmpDirFixture.createNewTmpDir();
1542+
fs.writeFileSync(
1543+
path.join(projectDir, "workflow_settings.yaml"),
1544+
VALID_WORKFLOW_SETTINGS_YAML
1545+
);
1546+
fs.mkdirSync(path.join(projectDir, "definitions"));
1547+
fs.writeFileSync(
1548+
path.join(projectDir, "definitions/assert.js"),
1549+
`
1550+
assert("name").jitCode(ctx => "jit");`
1551+
);
1552+
1553+
const result = runMainInVm(coreExecutionRequestFromPath(projectDir));
1554+
1555+
expect(result.compile.compiledGraph.graphErrors.compilationErrors).deep.equals([]);
1556+
expect(asPlainObject(result.compile.compiledGraph.assertions)).deep.equals([
1557+
{
1558+
actionDescriptor: {
1559+
compilationMode: "ACTION_COMPILATION_MODE_JIT"
1560+
},
1561+
canonicalTarget: {
1562+
database: "defaultProject",
1563+
name: "name",
1564+
schema: "defaultDataset"
1565+
},
1566+
fileName: "definitions/assert.js",
1567+
jitCode: 'ctx => "jit"',
1568+
target: {
1569+
database: "defaultProject",
1570+
name: "name",
1571+
schema: "defaultDataset"
1572+
}
1573+
}
1574+
]);
1575+
});
1576+
1577+
test("fails when both jitCode and query are set on an assertion", () => {
1578+
const projectDir = tmpDirFixture.createNewTmpDir();
1579+
fs.writeFileSync(
1580+
path.join(projectDir, "workflow_settings.yaml"),
1581+
VALID_WORKFLOW_SETTINGS_YAML
1582+
);
1583+
fs.mkdirSync(path.join(projectDir, "definitions"));
1584+
fs.writeFileSync(
1585+
path.join(projectDir, "definitions/assert.js"),
1586+
`
1587+
assert("name").query("SELECT 1").jitCode(ctx => "jit");`
1588+
);
1589+
1590+
const result = runMainInVm(coreExecutionRequestFromPath(projectDir));
1591+
1592+
expect(
1593+
result.compile.compiledGraph.graphErrors.compilationErrors?.map(error => error.message)
1594+
).deep.equals([
1595+
"Assertion may set either .jitCode() or .query(), but not both."
1596+
]);
1597+
});
1598+
15401599
test("assert API returns disabled assertions when disableAssertions is true", () => {
15411600
const projectDir = tmpDirFixture.createNewTmpDir();
15421601
fs.writeFileSync(

protos/core.proto

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,8 @@ message Assertion {
260260
// Only present for auto assertions.
261261
Target parent_action = 15;
262262

263+
string jit_code = 16;
264+
263265
// Generated.
264266
string file_name = 7;
265267

0 commit comments

Comments
 (0)