pg_query 6.2.2 → 6.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -93,6 +93,12 @@ module PgQuery
93
93
  case postgres_field_name
94
94
  when 'location'
95
95
  next
96
+ when 'arg_location'
97
+ next if node.is_a?(DefElem)
98
+ when 'payload_location'
99
+ next if node.is_a?(NotifyStmt)
100
+ when 'conninfo_location'
101
+ next if [CreateSubscriptionStmt, AlterSubscriptionStmt].include?(node.class)
96
102
  when 'name'
97
103
  next if [PrepareStmt, ExecuteStmt, DeallocateStmt, FunctionParameter].include?(node.class)
98
104
  next if node.is_a?(ResTarget) && parent_node_name == 'SelectStmt' && parent_field_name == 'targetList'
@@ -102,6 +102,7 @@ module PgQuery
102
102
  @cte_names = []
103
103
  @aliases = {}
104
104
  @functions = [] # types: call, ddl
105
+ @cte_self_reference_locations = []
105
106
 
106
107
  statements = @tree.stmts.dup.to_a.map(&:stmt)
107
108
  from_clause_items = [] # types: select, dml, ddl
@@ -122,6 +123,7 @@ module PgQuery
122
123
  subselect_items.concat(statement.select_stmt.sort_clause.collect { |h| h.sort_by.node })
123
124
  subselect_items.concat(statement.select_stmt.group_clause.to_ary)
124
125
  subselect_items << statement.select_stmt.having_clause if statement.select_stmt.having_clause
126
+ subselect_items.concat(statement.select_stmt.values_lists.to_ary)
125
127
 
126
128
  case statement.select_stmt.op
127
129
  when :SETOP_NONE
@@ -134,6 +136,7 @@ module PgQuery
134
136
  end
135
137
 
136
138
  if statement.select_stmt.with_clause
139
+ record_cte_self_references!(statement.select_stmt.with_clause)
137
140
  cte_statements, cte_names = statements_and_cte_names_for_with_clause(statement.select_stmt.with_clause)
138
141
  @cte_names.concat(cte_names)
139
142
  statements.concat(cte_statements)
@@ -164,7 +167,15 @@ module PgQuery
164
167
  end
165
168
  end
166
169
 
170
+ subselect_items.concat(value.returning_list.to_ary)
171
+
172
+ if statement.node == :insert_stmt && value.on_conflict_clause
173
+ subselect_items.concat(value.on_conflict_clause.target_list.to_ary)
174
+ subselect_items << value.on_conflict_clause.where_clause if value.on_conflict_clause.where_clause
175
+ end
176
+
167
177
  if value.with_clause
178
+ record_cte_self_references!(value.with_clause)
168
179
  cte_statements, cte_names = statements_and_cte_names_for_with_clause(value.with_clause)
169
180
  @cte_names.concat(cte_names)
170
181
  statements.concat(cte_statements)
@@ -294,10 +305,20 @@ module PgQuery
294
305
  when :func_call
295
306
  # See also CALL below
296
307
  subselect_items.concat(next_item.func_call.args.to_ary)
308
+ subselect_items.concat(next_item.func_call.agg_order.to_ary)
309
+ subselect_items << next_item.func_call.agg_filter if next_item.func_call.agg_filter
310
+ subselect_items << PgQuery::Node.new(window_def: next_item.func_call.over) if next_item.func_call.over
297
311
  @functions << {
298
312
  function: next_item.func_call.funcname.map { |f| f.string.sval }.join('.'),
299
313
  type: :call
300
314
  }
315
+ when :sort_by
316
+ subselect_items << next_item.sort_by.node
317
+ when :window_def
318
+ subselect_items.concat(next_item.window_def.partition_clause.to_ary)
319
+ subselect_items.concat(next_item.window_def.order_clause.to_ary)
320
+ subselect_items << next_item.window_def.start_offset if next_item.window_def.start_offset
321
+ subselect_items << next_item.window_def.end_offset if next_item.window_def.end_offset
301
322
  when :case_expr
302
323
  subselect_items.concat(next_item.case_expr.args.map { |arg| arg.case_when.expr })
303
324
  subselect_items.concat(next_item.case_expr.args.map { |arg| arg.case_when.result })
@@ -329,7 +350,7 @@ module PgQuery
329
350
  from_clause_items += next_item[:item].row_expr.args.map { |a| { item: a, type: next_item[:type] } }
330
351
  when :range_var
331
352
  rangevar = next_item[:item].range_var
332
- next if rangevar.schemaname.empty? && @cte_names.include?(rangevar.relname)
353
+ next if cte_reference?(rangevar, next_item[:type])
333
354
 
334
355
  table = [rangevar.schemaname, rangevar.relname].reject { |s| s.nil? || s.empty? }.join('.')
335
356
  @tables << {
@@ -368,5 +389,78 @@ module PgQuery
368
389
 
369
390
  [statements, cte_names]
370
391
  end
392
+
393
+ # Determines whether a RangeVar refers to a CTE rather than to a relation.
394
+ #
395
+ # Only plain (SELECT-style) references can resolve to a CTE: a CTE is not a
396
+ # valid target for DML (INSERT/UPDATE/DELETE/COPY) or DDL, so those always
397
+ # name a real relation even when a CTE in the same statement shares the name.
398
+ def cte_reference?(rangevar, type)
399
+ return false unless type == :select
400
+ return false unless rangevar.schemaname.empty?
401
+ return false unless @cte_names.include?(rangevar.relname)
402
+
403
+ !@cte_self_reference_locations.include?(rangevar.location)
404
+ end
405
+
406
+ # A non-recursive CTE is not visible inside its own definition, so a
407
+ # reference to its own name there resolves to a real relation:
408
+ #
409
+ # WITH users AS (SELECT * FROM users) SELECT * FROM users
410
+ # ^^^^^ the table ^^^^^ the CTE
411
+ #
412
+ # Records the locations of those self-references so they are not mistaken
413
+ # for CTE references. Locations uniquely identify a RangeVar occurrence,
414
+ # which keeps the outer (genuine) CTE reference above excluded.
415
+ def record_cte_self_references!(with_clause)
416
+ return if with_clause.recursive
417
+
418
+ with_clause.ctes.each do |item|
419
+ next unless item.node == :common_table_expr
420
+
421
+ record_self_references_for_cte!(item.common_table_expr)
422
+ end
423
+ end
424
+
425
+ def record_self_references_for_cte!(cte)
426
+ return if cte.ctequery.nil?
427
+ return if @cte_names.include?(cte.ctename)
428
+
429
+ record_self_references_in(cte.ctequery, cte.ctename)
430
+ end
431
+
432
+ # Depth-first scan of a CTE definition for unqualified RangeVars matching
433
+ # the CTE's own name.
434
+ #
435
+ # This deliberately avoids #walk!, which yields every node and iterates all
436
+ # of PgQuery::Node's oneof fields. Following the oneof directly via #inner
437
+ # keeps this proportional to the nodes actually present.
438
+ def record_self_references_in(node, cte_name)
439
+ case node
440
+ when PgQuery::Node
441
+ record_self_references_in(node.inner, cte_name)
442
+ when PgQuery::RangeVar
443
+ @cte_self_reference_locations << node.location if cte_self_reference?(node, cte_name)
444
+ when Google::Protobuf::RepeatedField
445
+ node.each { |child| record_self_references_in(child, cte_name) }
446
+ when Google::Protobuf::MessageExts
447
+ record_self_references_in_message(node, cte_name)
448
+ end
449
+ end
450
+
451
+ def record_self_references_in_message(message, cte_name)
452
+ message.class.descriptor.each do |field_descriptor|
453
+ child = field_descriptor.get(message)
454
+ next unless child.is_a?(Google::Protobuf::MessageExts) || child.is_a?(Google::Protobuf::RepeatedField)
455
+
456
+ record_self_references_in(child, cte_name)
457
+ end
458
+ end
459
+
460
+ def cte_self_reference?(node, cte_name)
461
+ node.schemaname.empty? &&
462
+ node.relname == cte_name &&
463
+ !node.location.negative?
464
+ end
371
465
  end
372
466
  end