Skip to content
Merged
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
8 changes: 8 additions & 0 deletions __fixtures__/generated/generated.json
Original file line number Diff line number Diff line change
Expand Up @@ -21294,6 +21294,14 @@
"misc/missing-types-12.sql": "ALTER SUBSCRIPTION my_subscription OWNER TO new_owner",
"misc/missing-types-13.sql": "ALTER STATISTICS my_statistics OWNER TO new_owner",
"misc/missing-types-14.sql": "ALTER TABLESPACE my_tablespace OWNER TO new_owner",
"misc/merge-seclabel-1.sql": "MERGE INTO tgt.users u USING src.staged s ON u.id = s.id WHEN MATCHED THEN UPDATE SET name = s.name WHEN NOT MATCHED THEN INSERT (id, name) VALUES (s.id, s.name)",
"misc/merge-seclabel-2.sql": "MERGE INTO tgt.users u USING src.staged s ON u.id = s.id WHEN MATCHED AND s.deleted THEN DELETE WHEN MATCHED THEN UPDATE SET name = s.name, updated_at = now() WHEN NOT MATCHED THEN DO NOTHING",
"misc/merge-seclabel-3.sql": "MERGE INTO tgt.users u USING src.staged s ON u.id = s.id WHEN NOT MATCHED BY SOURCE THEN DELETE WHEN NOT MATCHED THEN INSERT (id) VALUES (s.id)",
"misc/merge-seclabel-4.sql": "MERGE INTO tgt.users u USING src.staged s ON u.id = s.id WHEN NOT MATCHED THEN INSERT DEFAULT VALUES",
"misc/merge-seclabel-5.sql": "MERGE INTO tgt.users u USING src.staged s ON u.id = s.id WHEN MATCHED THEN UPDATE SET name = s.name RETURNING u.id, u.name",
"misc/merge-seclabel-6.sql": "SECURITY LABEL FOR anon ON COLUMN myschema.users.name IS 'MASKED WITH FUNCTION anon.fake_name()'",
"misc/merge-seclabel-7.sql": "SECURITY LABEL FOR anon ON TABLE myschema.users IS 'sensitive'",
"misc/merge-seclabel-8.sql": "SECURITY LABEL ON FUNCTION myschema.fn() IS 'labeled'",
"misc/launchql-ext-types-1.sql": "CREATE DOMAIN attachment AS jsonb CHECK ( value ?& ARRAY['url', 'mime'] AND (value->>'url') ~ '^(https?)://[^\\s/$.?#].[^\\s]*$' )",
"misc/launchql-ext-types-2.sql": "COMMENT ON DOMAIN attachment IS E'@name launchqlInternalTypeAttachment'",
"misc/launchql-ext-types-3.sql": "CREATE DOMAIN email AS citext CHECK ( value ~ '^[a-zA-Z0-9.!#$%&''*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$' )",
Expand Down
11 changes: 11 additions & 0 deletions __fixtures__/kitchen-sink/misc/merge-seclabel.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
-- MergeWhenClause deparse support (previously: "Deparser does not handle node type: MergeWhenClause")
MERGE INTO tgt.users u USING src.staged s ON u.id = s.id WHEN MATCHED THEN UPDATE SET name = s.name WHEN NOT MATCHED THEN INSERT (id, name) VALUES (s.id, s.name);
MERGE INTO tgt.users u USING src.staged s ON u.id = s.id WHEN MATCHED AND s.deleted THEN DELETE WHEN MATCHED THEN UPDATE SET name = s.name, updated_at = now() WHEN NOT MATCHED THEN DO NOTHING;
MERGE INTO tgt.users u USING src.staged s ON u.id = s.id WHEN NOT MATCHED BY SOURCE THEN DELETE WHEN NOT MATCHED THEN INSERT (id) VALUES (s.id);
MERGE INTO tgt.users u USING src.staged s ON u.id = s.id WHEN NOT MATCHED THEN INSERT DEFAULT VALUES;
MERGE INTO tgt.users u USING src.staged s ON u.id = s.id WHEN MATCHED THEN UPDATE SET name = s.name RETURNING u.id, u.name;

-- SecLabelStmt qualified object names must be dot-joined (previously emitted "schema", table, column)
SECURITY LABEL FOR anon ON COLUMN myschema.users.name IS 'MASKED WITH FUNCTION anon.fake_name()';
SECURITY LABEL FOR anon ON TABLE myschema.users IS 'sensitive';
SECURITY LABEL ON FUNCTION myschema.fn() IS 'labeled';
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

import { FixtureTestUtils } from '../../test-utils';
const fixtures = new FixtureTestUtils();

it('misc-merge-seclabel', async () => {
await fixtures.runFixtureTests([
"misc/merge-seclabel-1.sql",
"misc/merge-seclabel-2.sql",
"misc/merge-seclabel-3.sql",
"misc/merge-seclabel-4.sql",
"misc/merge-seclabel-5.sql",
"misc/merge-seclabel-6.sql",
"misc/merge-seclabel-7.sql",
"misc/merge-seclabel-8.sql"
]);
});
86 changes: 85 additions & 1 deletion packages/deparser/src/deparser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8720,7 +8720,16 @@ export class Deparser implements DeparserVisitor {
}

if (node.object) {
output.push(this.visit(node.object, context));
// Qualified names arrive as a List of String nodes and must be
// dot-joined (visiting the List directly would comma-join them).
if (typeof node.object === 'object' && 'List' in node.object) {
const list = node.object.List as t.List;
const objectParts = ListUtils.unwrapList(list.items)
.map(item => this.visit(item, context));
output.push(objectParts.join('.'));
} else {
output.push(this.visit(node.object, context));
}
}

output.push('IS');
Expand Down Expand Up @@ -9318,6 +9327,81 @@ export class Deparser implements DeparserVisitor {
output.push(whenClauses);
}

if (node.returningList && node.returningList.length > 0) {
output.push('RETURNING');
const returningList = ListUtils.unwrapList(node.returningList)
.map(target => this.visit(target, context))
.join(', ');
output.push(returningList);
}

return output.join(' ');
}

MergeWhenClause(node: t.MergeWhenClause, context: DeparserContext): string {
const output: string[] = ['WHEN'];

switch (node.matchKind) {
case 'MERGE_WHEN_MATCHED':
output.push('MATCHED');
break;
case 'MERGE_WHEN_NOT_MATCHED_BY_SOURCE':
output.push('NOT MATCHED BY SOURCE');
break;
case 'MERGE_WHEN_NOT_MATCHED_BY_TARGET':
output.push('NOT MATCHED');
break;
}

if (node.condition) {
output.push('AND');
output.push(this.visit(node.condition, context));
}

output.push('THEN');

switch (node.commandType) {
case 'CMD_UPDATE': {
output.push('UPDATE SET');
const assignments = ListUtils.unwrapList(node.targetList)
.map(target => this.visit(target, context.spawn('UpdateStmt', { update: true })))
.join(', ');
output.push(assignments);
break;
}
case 'CMD_INSERT': {
output.push('INSERT');
const targetList = ListUtils.unwrapList(node.targetList);
if (targetList.length > 0) {
const columns = targetList
.map(target => QuoteUtils.quoteIdentifier(target.ResTarget?.name))
.join(', ');
output.push(`(${columns})`);
}
if (node.override === 'OVERRIDING_USER_VALUE') {
output.push('OVERRIDING USER VALUE');
} else if (node.override === 'OVERRIDING_SYSTEM_VALUE') {
output.push('OVERRIDING SYSTEM VALUE');
}
const values = ListUtils.unwrapList(node.values);
if (values.length > 0) {
const valueItems = values
.map(value => this.visit(value, context))
.join(', ');
output.push(`VALUES (${valueItems})`);
} else {
output.push('DEFAULT VALUES');
}
break;
}
case 'CMD_DELETE':
output.push('DELETE');
break;
case 'CMD_NOTHING':
output.push('DO NOTHING');
break;
}

return output.join(' ');
}

Expand Down
Loading