diff --git a/lib/pg_query/parse.rb b/lib/pg_query/parse.rb index 1549b0c6..2bd3ff03 100644 --- a/lib/pg_query/parse.rb +++ b/lib/pg_query/parse.rb @@ -106,6 +106,7 @@ def load_objects! # rubocop:disable Metrics/CyclomaticComplexity statements = @tree.stmts.dup.to_a.map(&:stmt) from_clause_items = [] # types: select, dml, ddl subselect_items = [] + call_items = [] # CALL fn() loop do statement = statements.shift @@ -171,6 +172,8 @@ def load_objects! # rubocop:disable Metrics/CyclomaticComplexity when :copy_stmt from_clause_items << { item: PgQuery::Node.new(range_var: statement.copy_stmt.relation), type: :dml } if statement.copy_stmt.relation statements << statement.copy_stmt.query + when :call_stmt + call_items << statement.call_stmt # The following statement types are DDL (changing table structure) when :alter_table_stmt case statement.alter_table_stmt.objtype @@ -285,6 +288,7 @@ def load_objects! # rubocop:disable Metrics/CyclomaticComplexity when :sub_link statements << next_item.sub_link.subselect when :func_call + # See also CALL below subselect_items.concat(next_item.func_call.args.to_ary) @functions << { function: next_item.func_call.funcname.map { |f| f.string.sval }.join('.'), @@ -299,6 +303,17 @@ def load_objects! # rubocop:disable Metrics/CyclomaticComplexity end end + # CALL fn() + next_item = call_items.shift + if next_item + # Treat as a sub-select. Note the difference in underscore in func_call versus the above. + subselect_items.concat(next_item.funccall.args.to_ary) + @functions << { + function: next_item.funccall.funcname.map { |f| f.string.sval }.join('.'), + type: :call + } + end + next_item = from_clause_items.shift if next_item && next_item[:item] case next_item[:item].node diff --git a/spec/lib/parse_spec.rb b/spec/lib/parse_spec.rb index 998ccd60..8d57e510 100644 --- a/spec/lib/parse_spec.rb +++ b/spec/lib/parse_spec.rb @@ -1276,6 +1276,17 @@ expect(query.call_functions).to eq ['foo.testfunc'] end + it 'correctly finds functions invoked with CALL' do + query = described_class.parse(<<-SQL) + CALL foo.testfunc(1); + SQL + expect(query.tables).to eq [] + expect(query.warnings).to eq [] + expect(query.functions).to eq ['foo.testfunc'] + expect(query.ddl_functions).to eq [] + expect(query.call_functions).to eq ['foo.testfunc'] + end + it 'correctly finds dropped functions' do query = described_class.parse(<<-SQL) DROP FUNCTION IF EXISTS foo.testfunc(x integer);