@@ -209,4 +209,105 @@ describe('plpgsql-parser', () => {
209209 expect ( result ) . not . toMatch ( / R E T U R N \s + N U L L \s * ; / ) ;
210210 } ) ;
211211 } ) ;
212+
213+ describe ( 'SELECT INTO statement parsing' , ( ) => {
214+ // This test documents a bug in @libpg -query/parser where PL/pgSQL functions
215+ // containing SELECT INTO statements fail to parse, causing the function
216+ // to not be recognized as a PL/pgSQL function and preventing hydration.
217+ //
218+ // Bug: parsePlPgSQLSync throws "Unexpected non-whitespace character after JSON"
219+ // when the function body contains SELECT INTO statements.
220+ //
221+ // This causes inconsistent behavior:
222+ // - Functions with DELETE/INSERT/UPDATE: parse successfully, get hydrated
223+ // - Functions with SELECT INTO: fail to parse, not recognized as PL/pgSQL
224+ //
225+ // Related issue: https://github.com/pganalyze/libpg_query/issues/XXX
226+
227+ it ( 'should parse function with SELECT INTO statement' , ( ) => {
228+ const selectIntoSql = `
229+ CREATE FUNCTION get_data()
230+ RETURNS void
231+ LANGUAGE plpgsql AS $$
232+ DECLARE
233+ v_result text;
234+ BEGIN
235+ SELECT * INTO v_result FROM some_table WHERE id = 1;
236+ END;
237+ $$;
238+ ` ;
239+
240+ const parsed = parse ( selectIntoSql ) ;
241+
242+ // This currently fails because @libpg -query/parser cannot parse
243+ // PL/pgSQL functions with SELECT INTO statements
244+ expect ( parsed . functions ) . toHaveLength ( 1 ) ;
245+ expect ( parsed . functions [ 0 ] . kind ) . toBe ( 'plpgsql-function' ) ;
246+ expect ( parsed . functions [ 0 ] . plpgsql . hydrated ) . toBeDefined ( ) ;
247+ } ) ;
248+
249+ it ( 'should parse function with SELECT INTO and schema-qualified table' , ( ) => {
250+ const selectIntoSchemaSql = `
251+ CREATE FUNCTION "my_schema".get_data()
252+ RETURNS void
253+ LANGUAGE plpgsql AS $$
254+ DECLARE
255+ v_result text;
256+ BEGIN
257+ SELECT * INTO v_result FROM "my_schema".some_table WHERE id = 1;
258+ END;
259+ $$;
260+ ` ;
261+
262+ const parsed = parse ( selectIntoSchemaSql ) ;
263+
264+ // This currently fails because @libpg -query/parser cannot parse
265+ // PL/pgSQL functions with SELECT INTO statements
266+ expect ( parsed . functions ) . toHaveLength ( 1 ) ;
267+ expect ( parsed . functions [ 0 ] . kind ) . toBe ( 'plpgsql-function' ) ;
268+ } ) ;
269+
270+ it ( 'should deparse function with SELECT INTO correctly' , ( ) => {
271+ const selectIntoSql = `
272+ CREATE FUNCTION get_data()
273+ RETURNS void
274+ LANGUAGE plpgsql AS $$
275+ DECLARE
276+ v_result text;
277+ BEGIN
278+ SELECT * INTO v_result FROM "quoted_schema".some_table WHERE id = 1;
279+ END;
280+ $$;
281+ ` ;
282+
283+ const parsed = parse ( selectIntoSql ) ;
284+ const result = deparseSync ( parsed ) ;
285+
286+ // When this works, the deparsed SQL should have consistent quoting
287+ // (either all quoted or all unquoted based on QuoteUtils rules)
288+ expect ( result ) . toContain ( 'SELECT' ) ;
289+ expect ( result ) . toContain ( 'INTO' ) ;
290+ expect ( result ) . toContain ( 'v_result' ) ;
291+ } ) ;
292+
293+ // Contrast: DELETE statements parse correctly
294+ it ( 'should parse function with DELETE statement (works correctly)' , ( ) => {
295+ const deleteSql = `
296+ CREATE FUNCTION delete_data()
297+ RETURNS void
298+ LANGUAGE plpgsql AS $$
299+ BEGIN
300+ DELETE FROM some_table WHERE id = 1;
301+ END;
302+ $$;
303+ ` ;
304+
305+ const parsed = parse ( deleteSql ) ;
306+
307+ // DELETE statements work correctly
308+ expect ( parsed . functions ) . toHaveLength ( 1 ) ;
309+ expect ( parsed . functions [ 0 ] . kind ) . toBe ( 'plpgsql-function' ) ;
310+ expect ( parsed . functions [ 0 ] . plpgsql . hydrated ) . toBeDefined ( ) ;
311+ } ) ;
312+ } ) ;
212313} ) ;
0 commit comments