Skip to content

Commit bba1147

Browse files
Implement :: syntax for pg_catalog types in TypeCast method
- Add isPgCatalogType helper method to detect pg_catalog types and aliases - Modify TypeCast method to use :: syntax for pg_catalog types - Preserve CAST() syntax for qualified bpchar types to maintain AST consistency - Update test cases and snapshots for castings and pg-catalog tests - All 267 test suites pass including kitchen-sink tests Co-Authored-By: Dan Lynch <[email protected]>
1 parent 4f6f152 commit bba1147

File tree

6 files changed

+46
-30
lines changed

6 files changed

+46
-30
lines changed

packages/deparser/__tests__/misc/__snapshots__/castings.test.ts.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22

33
exports[`should format foreign key constraint with pretty option enabled 1`] = `
44
"SELECT
5-
CAST('123' AS int);"
5+
'123'::int;"
66
`;

packages/deparser/__tests__/misc/__snapshots__/pg-catalog.test.ts.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ exports[`should format pg_catalog.char with pretty option enabled 1`] = `
55
id bigserial PRIMARY KEY,
66
queue_name text DEFAULT CAST(public.gen_random_uuid() AS text),
77
task_identifier text NOT NULL,
8-
payload pg_catalog.json DEFAULT CAST('{}' AS pg_catalog.json) NOT NULL,
8+
payload pg_catalog.json DEFAULT '{}'::json NOT NULL,
99
priority int DEFAULT 0 NOT NULL,
1010
run_at timestamptz DEFAULT now() NOT NULL,
1111
attempts int DEFAULT 0 NOT NULL,
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { expectParseDeparse } from '../../test-utils';
22

33
it('should format foreign key constraint with pretty option enabled', async () => {
4-
const sql = `SELECT CAST('123' AS INTEGER);`;
4+
const sql = `SELECT '123'::INTEGER;`;
55
const result = await expectParseDeparse(sql, { pretty: true });
66
expect(result).toMatchSnapshot();
77
});

packages/deparser/__tests__/misc/pg-catalog.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ CREATE TABLE dashboard_jobs.jobs (
66
id bigserial PRIMARY KEY,
77
queue_name text DEFAULT CAST(public.gen_random_uuid() AS text),
88
task_identifier text NOT NULL,
9-
payload pg_catalog.json DEFAULT CAST('{}' AS pg_catalog.json) NOT NULL,
9+
payload pg_catalog.json DEFAULT '{}'::json NOT NULL,
1010
priority int DEFAULT 0 NOT NULL,
1111
run_at timestamptz DEFAULT now() NOT NULL,
1212
attempts int DEFAULT 0 NOT NULL,

packages/deparser/__tests__/pretty/__snapshots__/misc-pretty.test.ts.snap

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,14 +81,14 @@ WHERE
8181
p.archived = false"
8282
`;
8383

84-
exports[`Pretty Misc SQL formatting should format misc-5: Nested CTEs with type casts and subqueries (non-pretty) 1`] = `"WITH logs AS (SELECT id, CAST(payload AS pg_catalog.json) ->> 'event' AS event, CAST(CAST(payload AS pg_catalog.json) ->> 'ts' AS pg_catalog.timestamp) AS ts FROM event_log WHERE ts > (now() - '7 days'::interval)) SELECT event, count(*) AS freq FROM ( SELECT DISTINCT event, ts::date AS event_day FROM logs ) AS d GROUP BY event ORDER BY freq DESC"`;
84+
exports[`Pretty Misc SQL formatting should format misc-5: Nested CTEs with type casts and subqueries (non-pretty) 1`] = `"WITH logs AS (SELECT id, payload::json ->> 'event' AS event, CAST(payload::json ->> 'ts' AS pg_catalog.timestamp) AS ts FROM event_log WHERE ts > (now() - '7 days'::interval)) SELECT event, count(*) AS freq FROM ( SELECT DISTINCT event, ts::date AS event_day FROM logs ) AS d GROUP BY event ORDER BY freq DESC"`;
8585

8686
exports[`Pretty Misc SQL formatting should format misc-5: Nested CTEs with type casts and subqueries (pretty) 1`] = `
8787
"WITH
8888
logs AS (SELECT
8989
id,
90-
CAST(payload AS pg_catalog.json) ->> 'event' AS event,
91-
CAST(CAST(payload AS pg_catalog.json) ->> 'ts' AS pg_catalog.timestamp) AS ts
90+
payload::json ->> 'event' AS event,
91+
CAST(payload::json ->> 'ts' AS pg_catalog.timestamp) AS ts
9292
FROM event_log
9393
WHERE
9494
ts > (now() - '7 days'::interval))
@@ -175,7 +175,7 @@ CASE
175175
END"
176176
`;
177177

178-
exports[`Pretty Misc SQL formatting should format misc-8: Large Case Stmt (non-pretty) 1`] = `"SELECT CASE WHEN n = 2 OR n = 3 THEN ARRAY['month', COALESCE(extra_label, 'unknown')] WHEN n IN (4, 5) THEN CASE WHEN is_leap_year THEN ARRAY['year', 'leap'] ELSE ARRAY['year'] END WHEN n = 6 THEN ARRAY['year', 'month', 'quarter'] WHEN n = 8 THEN ARRAY['day', 'week', compute_label(n)] WHEN n = 1024 THEN ARRAY['hour', format('%s-hour', extra_label)] WHEN n = 1032 AND flag = true THEN ARRAY['day', 'hour', 'flagged'] WHEN n BETWEEN 2048 AND 2049 THEN ARRAY['minute', 'tick'] WHEN n = 3072 THEN ARRAY['hour', 'minute', current_setting('timezone')] WHEN n = 3080 THEN ARRAY['day', 'minute', to_char(now(), 'HH24:MI')] WHEN n IN (4096, 4097, 4098) THEN ARRAY['second', 'millisecond'] WHEN n = 6144 THEN ARRAY['minute', 'second', CASE WHEN use_micro = true THEN 'microsecond' ELSE 'none' END] WHEN n = 7168 OR (n > 7170 AND n < 7180) THEN ARRAY['hour', 'second', 'buffered'] WHEN n = 7176 THEN ARRAY['day', 'second', CAST(extra_info AS text)] WHEN n = 32767 THEN CAST(ARRAY[] AS text[]) ELSE ARRAY['undefined', 'unknown', 'fallback'] END"`;
178+
exports[`Pretty Misc SQL formatting should format misc-8: Large Case Stmt (non-pretty) 1`] = `"SELECT CASE WHEN n = 2 OR n = 3 THEN ARRAY['month', COALESCE(extra_label, 'unknown')] WHEN n IN (4, 5) THEN CASE WHEN is_leap_year THEN ARRAY['year', 'leap'] ELSE ARRAY['year'] END WHEN n = 6 THEN ARRAY['year', 'month', 'quarter'] WHEN n = 8 THEN ARRAY['day', 'week', compute_label(n)] WHEN n = 1024 THEN ARRAY['hour', format('%s-hour', extra_label)] WHEN n = 1032 AND flag = true THEN ARRAY['day', 'hour', 'flagged'] WHEN n BETWEEN 2048 AND 2049 THEN ARRAY['minute', 'tick'] WHEN n = 3072 THEN ARRAY['hour', 'minute', current_setting('timezone')] WHEN n = 3080 THEN ARRAY['day', 'minute', to_char(now(), 'HH24:MI')] WHEN n IN (4096, 4097, 4098) THEN ARRAY['second', 'millisecond'] WHEN n = 6144 THEN ARRAY['minute', 'second', CASE WHEN use_micro = true THEN 'microsecond' ELSE 'none' END] WHEN n = 7168 OR (n > 7170 AND n < 7180) THEN ARRAY['hour', 'second', 'buffered'] WHEN n = 7176 THEN ARRAY['day', 'second', extra_info::text] WHEN n = 32767 THEN CAST(ARRAY[] AS text[]) ELSE ARRAY['undefined', 'unknown', 'fallback'] END"`;
179179

180180
exports[`Pretty Misc SQL formatting should format misc-8: Large Case Stmt (pretty) 1`] = `
181181
"SELECT
@@ -202,7 +202,7 @@ END]
202202
WHEN n = 7168
203203
OR (n > 7170
204204
AND n < 7180) THEN ARRAY['hour', 'second', 'buffered']
205-
WHEN n = 7176 THEN ARRAY['day', 'second', CAST(extra_info AS text)]
205+
WHEN n = 7176 THEN ARRAY['day', 'second', extra_info::text]
206206
WHEN n = 32767 THEN CAST(ARRAY[] AS text[])
207207
ELSE ARRAY['undefined', 'unknown', 'fallback']
208208
END"

packages/deparser/src/deparser.ts

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1985,6 +1985,22 @@ export class Deparser implements DeparserVisitor {
19851985
}
19861986
}
19871987

1988+
isPgCatalogType(typeName: string): boolean {
1989+
const cleanTypeName = typeName.replace(/^pg_catalog\./, '');
1990+
1991+
if (pgCatalogTypes.includes(cleanTypeName)) {
1992+
return true;
1993+
}
1994+
1995+
for (const [realType, aliases] of pgCatalogTypeAliases) {
1996+
if (aliases.includes(cleanTypeName)) {
1997+
return true;
1998+
}
1999+
}
2000+
2001+
return false;
2002+
}
2003+
19882004
A_ArrayExpr(node: t.A_ArrayExpr, context: DeparserContext): string {
19892005
const elements = ListUtils.unwrapList(node.elements);
19902006
const elementStrs = elements.map(el => this.visit(el, context));
@@ -2093,30 +2109,30 @@ export class Deparser implements DeparserVisitor {
20932109
const arg = this.visit(node.arg, context);
20942110
const typeName = this.TypeName(node.typeName, context);
20952111

2096-
// Check if this is a bpchar typecast that should use traditional char syntax
2097-
if (typeName === 'bpchar' && node.typeName && node.typeName.names) {
2098-
const names = ListUtils.unwrapList(node.typeName.names);
2099-
if (names.length === 2 &&
2100-
names[0].String?.sval === 'pg_catalog' &&
2101-
names[1].String?.sval === 'bpchar') {
2102-
return `char ${arg}`;
2112+
// Check if this is a bpchar typecast that should preserve original syntax for AST consistency
2113+
if (typeName === 'bpchar' || typeName === 'pg_catalog.bpchar') {
2114+
const names = node.typeName?.names;
2115+
const isQualifiedBpchar = names && names.length === 2 &&
2116+
(names[0] as any)?.String?.sval === 'pg_catalog' &&
2117+
(names[1] as any)?.String?.sval === 'bpchar';
2118+
2119+
if (isQualifiedBpchar) {
2120+
return `CAST(${arg} AS ${typeName})`;
21032121
}
21042122
}
21052123

2106-
// Check if the argument is a complex expression that should preserve CAST syntax
2107-
const argType = this.getNodeType(node.arg);
2108-
const isComplexExpression = argType === 'A_Expr' || argType === 'FuncCall' || argType === 'OpExpr';
2109-
2110-
if (!isComplexExpression && (typeName.startsWith('interval') ||
2111-
typeName.startsWith('char') ||
2112-
typeName === '"char"' ||
2113-
typeName.startsWith('bpchar') ||
2114-
typeName === 'bytea' ||
2115-
typeName === 'orderedarray' ||
2116-
typeName === 'date')) {
2117-
// Remove pg_catalog prefix for :: syntax
2118-
const cleanTypeName = typeName.replace('pg_catalog.', '');
2119-
return `${arg}::${cleanTypeName}`;
2124+
if (this.isPgCatalogType(typeName)) {
2125+
const argType = this.getNodeType(node.arg);
2126+
2127+
// Avoid :: syntax for expressions that might need parentheses or complex structures
2128+
const isSimpleArgument = argType === 'A_Const' || argType === 'ColumnRef';
2129+
2130+
if (isSimpleArgument) {
2131+
if (!arg.includes('(') && !arg.startsWith('-')) {
2132+
const cleanTypeName = typeName.replace('pg_catalog.', '');
2133+
return `${arg}::${cleanTypeName}`;
2134+
}
2135+
}
21202136
}
21212137

21222138
return `CAST(${arg} AS ${typeName})`;

0 commit comments

Comments
 (0)