From f0247a133f78e8cf03165101d69a64c2d84126d7 Mon Sep 17 00:00:00 2001 From: Ivan Histand Date: Mon, 29 Jun 2026 14:12:34 -0500 Subject: [PATCH 1/2] Escape newlines in sqlString to keep quoted literals single-line A multiline `rowConditions` entry compiled to an invalid `failing_row_condition` string literal, because BigQuery single-quoted literals cannot span multiple lines (issue #2201). `sqlString` only escaped backslashes and single quotes, so a raw newline survived into the quoted label. Escape newlines/carriage-returns too, after backslash escaping so the introduced escapes are not themselves doubled. The raw `WHERE NOT (...)` clause is untouched. --- core/compilation_sql/index.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/core/compilation_sql/index.ts b/core/compilation_sql/index.ts index 4639a5453..37c2bd851 100644 --- a/core/compilation_sql/index.ts +++ b/core/compilation_sql/index.ts @@ -15,8 +15,17 @@ export class CompilationSql { } public sqlString(stringContents: string) { - // Escape escape characters, then escape single quotes, then wrap the string in single quotes. - return `'${stringContents.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`; + // Escape escape characters, then single quotes, then newlines/carriage-returns, + // and wrap the string in single quotes. BigQuery single-quoted string literals + // cannot span multiple lines, so a raw newline becomes the two-char \n escape + // (which parses back to a newline, keeping the literal single-line). Backslash + // escaping runs first so the escape sequences introduced here are not themselves + // doubled. + return `'${stringContents + .replace(/\\/g, "\\\\") + .replace(/'/g, "\\'") + .replace(/\n/g, "\\n") + .replace(/\r/g, "\\r")}'`; } public indexAssertion(dataset: string, indexCols: string[]) { From f91208617caf1f008847915881d9ecedae44c4b7 Mon Sep 17 00:00:00 2001 From: Ivan Histand Date: Mon, 29 Jun 2026 14:13:33 -0500 Subject: [PATCH 2/2] Add regression test for multiline rowConditions assertion SQL Compiles a table with a multiline rowConditions entry and asserts the failing_row_condition literal stays single-line (escaped \n) while the WHERE NOT (...) clause keeps the raw multiline expression. Covers #2201. --- core/actions/assertion_test.ts | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/core/actions/assertion_test.ts b/core/actions/assertion_test.ts index 9deeedad8..54cc5d943 100644 --- a/core/actions/assertion_test.ts +++ b/core/actions/assertion_test.ts @@ -306,6 +306,38 @@ actions: ); }); + test(`multiline rowConditions compile to single-line failing_row_condition literals`, () => { + // Regression test for #2201: a multiline rowConditions entry must not produce a + // multiline single-quoted string literal, which BigQuery rejects. + const projectDir = tmpDirFixture.createNewTmpDir(); + fs.writeFileSync(path.join(projectDir, "workflow_settings.yaml"), VALID_WORKFLOW_SETTINGS_YAML); + fs.mkdirSync(path.join(projectDir, "definitions")); + fs.writeFileSync( + path.join(projectDir, "definitions/test.sqlx"), + `config { + type: "table", + assertions: { + rowConditions: ["a > 0\n AND b > 0"] + } + } + SELECT 1 AS a, 1 AS b` + ); + + const result = runMainInVm(coreExecutionRequestFromPath(projectDir)); + + expect(result.compile.compiledGraph.graphErrors.compilationErrors).deep.equals([]); + const rowConditionsAssertion = result.compile.compiledGraph.assertions.find( + assertion => assertion.target.name === "defaultDataset_test_assertions_rowConditions" + ); + // The failing_row_condition string literal has its newline escaped, so the + // single-quoted literal stays on one line... + expect(rowConditionsAssertion.query).contains( + "'a > 0\\n AND b > 0' AS failing_row_condition" + ); + // ...while the WHERE NOT (...) clause keeps the raw, multiline expression. + expect(rowConditionsAssertion.query).contains("WHERE NOT (a > 0\n AND b > 0)"); + }); + suite("Assertions as dependencies", ({ beforeEach }) => { [ WorkflowSettingsTemplates.bigquery,