Skip to content

Commit 5d3772b

Browse files
authored
Merge pull request #263 from constructive-io/devin/1767666458-fix-hydrate-already-hydrated
fix(plpgsql-deparser): handle already hydrated expressions in hydrateExpression
2 parents 6c38be7 + 2b24932 commit 5d3772b

File tree

2 files changed

+66
-1
lines changed

2 files changed

+66
-1
lines changed

packages/plpgsql-deparser/__tests__/hydrate.test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,66 @@ $$`;
153153
});
154154
});
155155

156+
describe('idempotent hydration', () => {
157+
it('should handle already hydrated AST without errors', () => {
158+
const sql = `CREATE FUNCTION test_func() RETURNS void
159+
LANGUAGE plpgsql
160+
AS $$
161+
DECLARE
162+
v_user "my-schema".users;
163+
BEGIN
164+
v_user := (SELECT * FROM "my-schema".users LIMIT 1);
165+
RETURN;
166+
END;
167+
$$`;
168+
169+
const parsed = parsePlPgSQLSync(sql) as unknown as PLpgSQLParseResult;
170+
171+
// First hydration
172+
const result1 = hydratePlpgsqlAst(parsed);
173+
expect(result1.errors).toHaveLength(0);
174+
175+
// Second hydration on already hydrated AST should not throw
176+
const result2 = hydratePlpgsqlAst(result1.ast);
177+
expect(result2.errors).toHaveLength(0);
178+
179+
// The AST should still be valid and deparseable
180+
const dehydrated = dehydratePlpgsqlAst(result2.ast);
181+
const deparsed = deparseSync(dehydrated);
182+
expect(deparsed).toContain('my-schema');
183+
});
184+
185+
it('should return already hydrated expressions unchanged', () => {
186+
const sql = `CREATE FUNCTION test_func() RETURNS integer
187+
LANGUAGE plpgsql
188+
AS $$
189+
DECLARE
190+
v_result integer;
191+
BEGIN
192+
v_result := 10 + 20;
193+
RETURN v_result;
194+
END;
195+
$$`;
196+
197+
const parsed = parsePlPgSQLSync(sql) as unknown as PLpgSQLParseResult;
198+
199+
// First hydration
200+
const result1 = hydratePlpgsqlAst(parsed);
201+
const assignExpr1 = findExprByKind(result1.ast, 'assign');
202+
expect(assignExpr1).toBeDefined();
203+
204+
// Second hydration
205+
const result2 = hydratePlpgsqlAst(result1.ast);
206+
const assignExpr2 = findExprByKind(result2.ast, 'assign');
207+
208+
// The expression should be the same (unchanged)
209+
expect(assignExpr2).toBeDefined();
210+
expect(assignExpr2.kind).toBe('assign');
211+
expect(assignExpr2.target).toBe(assignExpr1.target);
212+
expect(assignExpr2.value).toBe(assignExpr1.value);
213+
});
214+
});
215+
156216
describe('heterogeneous deparse (AST-based transformations)', () => {
157217
it('should deparse modified sql-expr AST nodes (schema renaming)', () => {
158218
// Note: This test only checks RangeVar nodes in SQL expressions.

packages/plpgsql-deparser/src/hydrate.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,13 +115,18 @@ function hydrateNode(
115115
}
116116

117117
function hydrateExpression(
118-
query: string,
118+
query: string | HydratedExprQuery,
119119
parseMode: number,
120120
path: string,
121121
options: HydrationOptions,
122122
errors: HydrationError[],
123123
stats: HydrationStats
124124
): HydratedExprQuery {
125+
// If query is already hydrated (from a previous hydration call), return it unchanged
126+
if (isHydratedExpr(query)) {
127+
return query;
128+
}
129+
125130
if (parseMode === ParseMode.RAW_PARSE_PLPGSQL_ASSIGN1 ||
126131
parseMode === ParseMode.RAW_PARSE_PLPGSQL_ASSIGN2 ||
127132
parseMode === ParseMode.RAW_PARSE_PLPGSQL_ASSIGN3 ||

0 commit comments

Comments
 (0)