pg_query 2.1.0 → 2.1.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ede23ff0f5d9af4599a7732fe624b3e2fba355ebb54bb6b88df3ef3932d7692a
4
- data.tar.gz: d11105ea7323b8908dde6b328908190858d150f12a13a109d943ae3bf8af475b
3
+ metadata.gz: b4db477ee95910b6c5d8e6c4e5f86c64cc985c0812cc461c017f652754848be1
4
+ data.tar.gz: 1167f0524fe8a23b19ee1beef4400cf5f0f817ba64f7d1893ac3d8ddb36ad0ce
5
5
  SHA512:
6
- metadata.gz: 37b3301df52b4743098ded9bbb69b05f4a360f7e1335386833ac619f4e5423f7b3c42a6506b3519c59ed3d0bb62e6e59ef04c8515fc9a3eb5daa95c53ab228e7
7
- data.tar.gz: cd94026574593d6e78ac41e88c3378a6c46ea6d0869f8ddd9a200c367a969ff67695a780b0609518eafcff7a8dfaff015ce6408662eff7616cb866b46b52593a
6
+ metadata.gz: 40d3c6157fc68c041f04bf9adcd6a6b018a821c45e877729ee1f657d97a1cea7d9b686ca2802c4dc56a21daae370433d8c96efb36f95a2a2a31f251307536c3d
7
+ data.tar.gz: 1ee0b3527a02b0e81c9177b37de943b51fe6b0f5ded846d298a659061133b3b76d25b5f76059dc5568484bca6939421b3e7d8c05e94d95f3f4a10d493a2301d6
data/CHANGELOG.md CHANGED
@@ -4,6 +4,32 @@
4
4
 
5
5
  * ...
6
6
 
7
+ ## 2.1.1 2021-10-13
8
+
9
+ * Update to libpg_query 13-2.1.0 ([#230](https://github.com/pganalyze/pg_query/pull/230))
10
+ - Normalize: add funcname error object
11
+ - Normalize: Match GROUP BY against target list and re-use param refs
12
+ - PL/pgSQL: Setup namespace items for parameters, support RECORD types
13
+ - This significantly improves parsing for PL/pgSQL functions, to the extent
14
+ that most functions should now parse successfully
15
+ - Normalize: Don't modify constants in TypeName typmods/arrayBounds fields
16
+ - This matches how pg_stat_statement behaves, and avoids causing parsing
17
+ errors on the normalized statement
18
+ - Don't fail builds on systems that have strchrnul support (FreeBSD)
19
+ * Fix build on FreeBSD ([#222](https://github.com/pganalyze/pg_query/pull/222))
20
+ * Add workaround for Ruby garbage collection bug ([#227](https://github.com/pganalyze/pg_query/pull/227))
21
+ - The Ruby interpreter has a bug in `String#concat` where the appended
22
+ array may be garbage collected prematurely because the compiler
23
+ optimized out a Ruby stack variable. We now call `to_ary` on the
24
+ Protobuf object to ensure the array lands on the Ruby stack so the
25
+ garbage collector sees it.
26
+ - The real fix in the interpreter is described in
27
+ https://bugs.ruby-lang.org/issues/18140#note-2, but most current Ruby
28
+ interpreters won't have this fix for some time.
29
+ * Table/function extraction: Support subselects and LATERAL better ([#229](https://github.com/pganalyze/pg_query/pull/229))
30
+ - This reworks the parsing logic so we don't ignore certain kinds of
31
+ subselects.
32
+
7
33
 
8
34
  ## 2.1.0 2021-07-04
9
35
 
data/Rakefile CHANGED
@@ -5,8 +5,8 @@ require 'rspec/core/rake_task'
5
5
  require 'rubocop/rake_task'
6
6
  require 'open-uri'
7
7
 
8
- LIB_PG_QUERY_TAG = '13-2.0.6'.freeze
9
- LIB_PG_QUERY_SHA256SUM = '61f384ac949bd7404efe6bcc37a8a6fca79030e59c02659f108ee1db45e93414'.freeze
8
+ LIB_PG_QUERY_TAG = '13-2.1.0'.freeze
9
+ LIB_PG_QUERY_SHA256SUM = 'a01329ae5bac19b10b8ddf8012bd663a20f85f180d6d7b900c1a1ca8444d19a5'.freeze
10
10
 
11
11
  Rake::ExtensionTask.new 'pg_query' do |ext|
12
12
  ext.lib_dir = 'lib/pg_query'
@@ -987,3 +987,6 @@
987
987
  #undef HAVE_EXECINFO_H
988
988
  #undef HAVE_BACKTRACE_SYMBOLS
989
989
  #undef HAVE__GET_CPUID
990
+ #ifdef __FreeBSD__
991
+ #define HAVE_STRCHRNUL
992
+ #endif
@@ -291,6 +291,21 @@ _fingerprintNode(FingerprintContext *ctx, const void *obj, const void *parent, c
291
291
  }
292
292
  }
293
293
 
294
+ uint64_t pg_query_fingerprint_node(const void *node)
295
+ {
296
+ FingerprintContext ctx;
297
+ uint64 result;
298
+
299
+ _fingerprintInitContext(&ctx, NULL, false);
300
+ _fingerprintNode(&ctx, node, NULL, NULL, 0);
301
+
302
+ result = XXH3_64bits_digest(ctx.xxh_state);
303
+
304
+ _fingerprintFreeContext(&ctx);
305
+
306
+ return result;
307
+ }
308
+
294
309
  PgQueryFingerprintResult pg_query_fingerprint_with_opts(const char* input, bool printTokens)
295
310
  {
296
311
  MemoryContext ctx = NULL;
@@ -3,6 +3,8 @@
3
3
 
4
4
  #include <stdbool.h>
5
5
 
6
- PgQueryFingerprintResult pg_query_fingerprint_with_opts(const char* input, bool printTokens);
6
+ extern PgQueryFingerprintResult pg_query_fingerprint_with_opts(const char* input, bool printTokens);
7
+
8
+ extern uint64_t pg_query_fingerprint_node(const void * node);
7
9
 
8
10
  #endif
@@ -1,5 +1,6 @@
1
1
  #include "pg_query.h"
2
2
  #include "pg_query_internal.h"
3
+ #include "pg_query_fingerprint.h"
3
4
 
4
5
  #include "parser/parser.h"
5
6
  #include "parser/scanner.h"
@@ -14,6 +15,7 @@ typedef struct pgssLocationLen
14
15
  {
15
16
  int location; /* start offset in query text */
16
17
  int length; /* length in bytes, or -1 to ignore */
18
+ int param_id; /* Param id to use - if negative prefix, need to abs(..) and add highest_extern_param_id */
17
19
  } pgssLocationLen;
18
20
 
19
21
  /*
@@ -30,14 +32,32 @@ typedef struct pgssConstLocations
30
32
  /* Current number of valid entries in clocations array */
31
33
  int clocations_count;
32
34
 
35
+ /* highest Param id we have assigned, not yet taking into account external param refs */
36
+ int highest_normalize_param_id;
37
+
33
38
  /* highest Param id we've seen, in order to start normalization correctly */
34
39
  int highest_extern_param_id;
35
40
 
36
41
  /* query text */
37
42
  const char * query;
38
43
  int query_len;
44
+
45
+ /* optional recording of assigned or discovered param refs, only active if param_refs is not NULL */
46
+ int *param_refs;
47
+ int param_refs_buf_size;
48
+ int param_refs_count;
39
49
  } pgssConstLocations;
40
50
 
51
+ /*
52
+ * Intermediate working state struct to remember param refs for individual target list elements
53
+ */
54
+ typedef struct FpAndParamRefs
55
+ {
56
+ uint64_t fp;
57
+ int* param_refs;
58
+ int param_refs_count;
59
+ } FpAndParamRefs;
60
+
41
61
  /*
42
62
  * comp_location: comparator for qsorting pgssLocationLen structs by location
43
63
  */
@@ -230,7 +250,8 @@ generate_normalized_query(pgssConstLocations *jstate, int query_loc, int* query_
230
250
  for (i = 0; i < jstate->clocations_count; i++)
231
251
  {
232
252
  int off, /* Offset from start for cur tok */
233
- tok_len; /* Length (in bytes) of that tok */
253
+ tok_len, /* Length (in bytes) of that tok */
254
+ param_id; /* Param ID to be assigned */
234
255
 
235
256
  off = jstate->clocations[i].location;
236
257
  /* Adjust recorded location if we're dealing with partial string */
@@ -250,8 +271,10 @@ generate_normalized_query(pgssConstLocations *jstate, int query_loc, int* query_
250
271
  n_quer_loc += len_to_wrt;
251
272
 
252
273
  /* And insert a param symbol in place of the constant token */
253
- n_quer_loc += sprintf(norm_query + n_quer_loc, "$%d",
254
- i + 1 + jstate->highest_extern_param_id);
274
+ param_id = (jstate->clocations[i].param_id < 0) ?
275
+ jstate->highest_extern_param_id + abs(jstate->clocations[i].param_id) :
276
+ jstate->clocations[i].param_id;
277
+ n_quer_loc += sprintf(norm_query + n_quer_loc, "$%d", param_id);
255
278
 
256
279
  quer_loc = off + tok_len;
257
280
  last_off = off;
@@ -292,6 +315,18 @@ static void RecordConstLocation(pgssConstLocations *jstate, int location)
292
315
  jstate->clocations[jstate->clocations_count].location = location;
293
316
  /* initialize lengths to -1 to simplify fill_in_constant_lengths */
294
317
  jstate->clocations[jstate->clocations_count].length = -1;
318
+ /* by default we assume that we need a new param ref */
319
+ jstate->clocations[jstate->clocations_count].param_id = - jstate->highest_normalize_param_id;
320
+ jstate->highest_normalize_param_id++;
321
+ /* record param ref number if requested */
322
+ if (jstate->param_refs != NULL) {
323
+ jstate->param_refs[jstate->param_refs_count] = jstate->clocations[jstate->clocations_count].param_id;
324
+ jstate->param_refs_count++;
325
+ if (jstate->param_refs_count >= jstate->param_refs_buf_size) {
326
+ jstate->param_refs_buf_size *= 2;
327
+ jstate->param_refs = (int *) repalloc(jstate->param_refs, jstate->param_refs_buf_size * sizeof(int));
328
+ }
329
+ }
295
330
  jstate->clocations_count++;
296
331
  }
297
332
  }
@@ -313,6 +348,15 @@ static bool const_record_walker(Node *node, pgssConstLocations *jstate)
313
348
  /* Track the highest ParamRef number */
314
349
  if (((ParamRef *) node)->number > jstate->highest_extern_param_id)
315
350
  jstate->highest_extern_param_id = castNode(ParamRef, node)->number;
351
+
352
+ if (jstate->param_refs != NULL) {
353
+ jstate->param_refs[jstate->param_refs_count] = ((ParamRef *) node)->number;
354
+ jstate->param_refs_count++;
355
+ if (jstate->param_refs_count >= jstate->param_refs_buf_size) {
356
+ jstate->param_refs_buf_size *= 2;
357
+ jstate->param_refs = (int *) repalloc(jstate->param_refs, jstate->param_refs_buf_size * sizeof(int));
358
+ }
359
+ }
316
360
  }
317
361
  break;
318
362
  case T_DefElem:
@@ -343,39 +387,95 @@ static bool const_record_walker(Node *node, pgssConstLocations *jstate)
343
387
  return const_record_walker((Node *) ((AlterRoleStmt *) node)->options, jstate);
344
388
  case T_DeclareCursorStmt:
345
389
  return const_record_walker((Node *) ((DeclareCursorStmt *) node)->query, jstate);
390
+ case T_TypeName:
391
+ /* Don't normalize constants in typmods or arrayBounds */
392
+ return false;
346
393
  case T_SelectStmt:
347
394
  {
348
395
  SelectStmt *stmt = (SelectStmt *) node;
349
396
  ListCell *lc;
397
+ List *fp_and_param_refs_list = NIL;
350
398
 
351
399
  if (const_record_walker((Node *) stmt->distinctClause, jstate))
352
400
  return true;
353
401
  if (const_record_walker((Node *) stmt->intoClause, jstate))
354
402
  return true;
355
- if (const_record_walker((Node *) stmt->targetList, jstate))
356
- return true;
403
+ foreach(lc, stmt->targetList)
404
+ {
405
+ ResTarget *res_target = lfirst_node(ResTarget, lc);
406
+ FpAndParamRefs *fp_and_param_refs = palloc0(sizeof(FpAndParamRefs));
407
+
408
+ /* Save all param refs we encounter or assign */
409
+ jstate->param_refs = palloc0(1 * sizeof(int));
410
+ jstate->param_refs_buf_size = 1;
411
+ jstate->param_refs_count = 0;
412
+
413
+ /* Walk the element */
414
+ if (const_record_walker((Node *) res_target, jstate))
415
+ return true;
416
+
417
+ /* Remember fingerprint and param refs for later */
418
+ fp_and_param_refs->fp = pg_query_fingerprint_node(res_target->val);
419
+ fp_and_param_refs->param_refs = jstate->param_refs;
420
+ fp_and_param_refs->param_refs_count = jstate->param_refs_count;
421
+ fp_and_param_refs_list = lappend(fp_and_param_refs_list, fp_and_param_refs);
422
+
423
+ /* Reset for next element, or stop recording if this is the last element */
424
+ jstate->param_refs = NULL;
425
+ jstate->param_refs_buf_size = 0;
426
+ jstate->param_refs_count = 0;
427
+ }
357
428
  if (const_record_walker((Node *) stmt->fromClause, jstate))
358
429
  return true;
359
430
  if (const_record_walker((Node *) stmt->whereClause, jstate))
360
431
  return true;
361
432
 
362
- // Instead of walking all of groupClause (like raw_expression_tree_walker does),
363
- // only walk certain items.
433
+ /*
434
+ * Instead of walking all of groupClause (like raw_expression_tree_walker does),
435
+ * only walk certain items.
436
+ */
364
437
  foreach(lc, stmt->groupClause)
365
438
  {
366
- // Do not walk A_Const values that are simple integers, this avoids
367
- // turning "GROUP BY 1" into "GROUP BY $n", which obscures an important
368
- // semantic meaning. This matches how pg_stat_statements handles the
369
- // GROUP BY clause (i.e. it doesn't touch these constants)
439
+ /*
440
+ * Do not walk A_Const values that are simple integers, this avoids
441
+ * turning "GROUP BY 1" into "GROUP BY $n", which obscures an important
442
+ * semantic meaning. This matches how pg_stat_statements handles the
443
+ * GROUP BY clause (i.e. it doesn't touch these constants)
444
+ */
370
445
  if (IsA(lfirst(lc), A_Const) && IsA(&castNode(A_Const, lfirst(lc))->val, Integer))
371
446
  continue;
372
447
 
448
+ /*
449
+ * Match up GROUP BY clauses against the target list, to assign the same
450
+ * param refs as used in the target list - this ensures the query is valid,
451
+ * instead of throwing a bogus "columns ... must appear in the GROUP BY
452
+ * clause or be used in an aggregate function" error
453
+ */
454
+ uint64_t fp = pg_query_fingerprint_node(lfirst(lc));
455
+ FpAndParamRefs *fppr = NULL;
456
+ ListCell *lc2;
457
+ foreach(lc2, fp_and_param_refs_list) {
458
+ if (fp == ((FpAndParamRefs *) lfirst(lc2))->fp) {
459
+ fppr = (FpAndParamRefs *) lfirst(lc2);
460
+ foreach_delete_current(fp_and_param_refs_list, lc2);
461
+ break;
462
+ }
463
+ }
464
+
465
+ int prev_cloc_count = jstate->clocations_count;
373
466
  if (const_record_walker((Node *) lfirst(lc), jstate))
374
467
  return true;
468
+
469
+ if (fppr != NULL && fppr->param_refs_count == jstate->clocations_count - prev_cloc_count) {
470
+ for (int i = prev_cloc_count; i < jstate->clocations_count; i++) {
471
+ jstate->clocations[i].param_id = fppr->param_refs[i - prev_cloc_count];
472
+ }
473
+ jstate->highest_normalize_param_id -= fppr->param_refs_count;
474
+ }
375
475
  }
376
476
  foreach(lc, stmt->sortClause)
377
477
  {
378
- // Similarly, don't turn "ORDER BY 1" into "ORDER BY $n"
478
+ /* Similarly, don't turn "ORDER BY 1" into "ORDER BY $n" */
379
479
  if (IsA(lfirst(lc), SortBy) && IsA(castNode(SortBy, lfirst(lc))->node, A_Const) &&
380
480
  IsA(&castNode(A_Const, castNode(SortBy, lfirst(lc))->node)->val, Integer))
381
481
  continue;
@@ -445,9 +545,13 @@ PgQueryNormalizeResult pg_query_normalize(const char* input)
445
545
  jstate.clocations = (pgssLocationLen *)
446
546
  palloc(jstate.clocations_buf_size * sizeof(pgssLocationLen));
447
547
  jstate.clocations_count = 0;
548
+ jstate.highest_normalize_param_id = 1;
448
549
  jstate.highest_extern_param_id = 0;
449
550
  jstate.query = input;
450
551
  jstate.query_len = query_len;
552
+ jstate.param_refs = NULL;
553
+ jstate.param_refs_buf_size = 0;
554
+ jstate.param_refs_count = 0;
451
555
 
452
556
  /* Walk tree and record const locations */
453
557
  const_record_walker((Node *) tree, &jstate);
@@ -466,6 +570,8 @@ PgQueryNormalizeResult pg_query_normalize(const char* input)
466
570
  error = malloc(sizeof(PgQueryError));
467
571
  error->message = strdup(error_data->message);
468
572
  error->filename = strdup(error_data->filename);
573
+ error->funcname = strdup(error_data->funcname);
574
+ error->context = NULL;
469
575
  error->lineno = error_data->lineno;
470
576
  error->cursorpos = error_data->cursorpos;
471
577
 
@@ -484,6 +590,7 @@ void pg_query_free_normalize_result(PgQueryNormalizeResult result)
484
590
  if (result.error) {
485
591
  free(result.error->message);
486
592
  free(result.error->filename);
593
+ free(result.error->funcname);
487
594
  free(result.error);
488
595
  }
489
596
 
@@ -108,7 +108,7 @@ static PLpgSQL_function *compile_create_function_stmt(CreateFunctionStmt* stmt)
108
108
  }
109
109
  }
110
110
 
111
- assert(proc_source);
111
+ assert(proc_source != NULL);
112
112
 
113
113
  if (stmt->returnType != NULL) {
114
114
  foreach(lc3, stmt->returnType->names)
@@ -179,6 +179,26 @@ static PLpgSQL_function *compile_create_function_stmt(CreateFunctionStmt* stmt)
179
179
  plpgsql_DumpExecTree = false;
180
180
  plpgsql_start_datums();
181
181
 
182
+ /* Setup parameter names */
183
+ foreach(lc, stmt->parameters)
184
+ {
185
+ FunctionParameter *param = lfirst_node(FunctionParameter, lc);
186
+ if (param->name != NULL)
187
+ {
188
+ char buf[32];
189
+ PLpgSQL_type *argdtype;
190
+ PLpgSQL_variable *argvariable;
191
+ PLpgSQL_nsitem_type argitemtype;
192
+ snprintf(buf, sizeof(buf), "$%d", foreach_current_index(lc) + 1);
193
+ argdtype = plpgsql_build_datatype(UNKNOWNOID, -1, InvalidOid, NULL);
194
+ argvariable = plpgsql_build_variable(param->name ? param->name : buf, 0, argdtype, false);
195
+ argitemtype = argvariable->dtype == PLPGSQL_DTYPE_VAR ? PLPGSQL_NSTYPE_VAR : PLPGSQL_NSTYPE_REC;
196
+ plpgsql_ns_additem(argitemtype, argvariable->dno, buf);
197
+ if (param->name != NULL)
198
+ plpgsql_ns_additem(argitemtype, argvariable->dno, param->name);
199
+ }
200
+ }
201
+
182
202
  /* Set up as though in a function returning VOID */
183
203
  function->fn_rettype = VOIDOID;
184
204
  function->fn_retset = is_setof;
@@ -1 +1,2 @@
1
1
  _Init_pg_query
2
+ Init_pg_query
@@ -6147,7 +6147,7 @@ plpgsql_sql_error_callback(void *arg)
6147
6147
  * This is handled the same as in check_sql_expr(), and we likewise
6148
6148
  * expect that the given string is a copy from the source text.
6149
6149
  */
6150
- static PLpgSQL_type * parse_datatype(const char *string, int location) { PLpgSQL_type *typ; typ = (PLpgSQL_type *) palloc0(sizeof(PLpgSQL_type)); typ->typname = pstrdup(string); typ->ttype = PLPGSQL_TTYPE_SCALAR; return typ; }
6150
+ static PLpgSQL_type * parse_datatype(const char *string, int location) { PLpgSQL_type *typ; typ = (PLpgSQL_type *) palloc0(sizeof(PLpgSQL_type)); typ->typname = pstrdup(string); typ->ttype = strcmp(string, "RECORD") == 0 ? PLPGSQL_TTYPE_REC : PLPGSQL_TTYPE_SCALAR; return typ; }
6151
6151
 
6152
6152
 
6153
6153
  /*
@@ -89,6 +89,10 @@ module PgQuery
89
89
 
90
90
  protected
91
91
 
92
+ # Parses the query and finds table and function references
93
+ #
94
+ # Note we use ".to_ary" on arrays from the Protobuf library before
95
+ # passing them to concat, because of https://bugs.ruby-lang.org/issues/18140
92
96
  def load_objects! # rubocop:disable Metrics/CyclomaticComplexity
93
97
  @tables = [] # types: select, dml, ddl
94
98
  @cte_names = []
@@ -108,20 +112,16 @@ module PgQuery
108
112
  # The following statement types do not modify tables and are added to from_clause_items
109
113
  # (and subsequently @tables)
110
114
  when :select_stmt
111
- subselect_items.concat(statement.select_stmt.target_list)
115
+ subselect_items.concat(statement.select_stmt.target_list.to_ary)
112
116
  subselect_items << statement.select_stmt.where_clause if statement.select_stmt.where_clause
113
117
  subselect_items.concat(statement.select_stmt.sort_clause.collect { |h| h.sort_by.node })
114
- subselect_items.concat(statement.select_stmt.group_clause)
118
+ subselect_items.concat(statement.select_stmt.group_clause.to_ary)
115
119
  subselect_items << statement.select_stmt.having_clause if statement.select_stmt.having_clause
116
120
 
117
121
  case statement.select_stmt.op
118
122
  when :SETOP_NONE
119
123
  (statement.select_stmt.from_clause || []).each do |item|
120
- if item.node == :range_subselect
121
- statements << item.range_subselect.subquery
122
- else
123
- from_clause_items << { item: item, type: :select }
124
- end
124
+ from_clause_items << { item: item, type: :select }
125
125
  end
126
126
  when :SETOP_UNION
127
127
  statements << PgQuery::Node.new(select_stmt: statement.select_stmt.larg) if statement.select_stmt.larg
@@ -143,7 +143,7 @@ module PgQuery
143
143
  value.from_clause.each do |item|
144
144
  from_clause_items << { item: item, type: :select }
145
145
  end
146
- subselect_items.concat(statement.update_stmt.target_list)
146
+ subselect_items.concat(statement.update_stmt.target_list.to_ary)
147
147
  end
148
148
 
149
149
  subselect_items << statement.update_stmt.where_clause if statement.node == :update_stmt && statement.update_stmt.where_clause
@@ -236,6 +236,8 @@ module PgQuery
236
236
  next_item = subselect_items.shift
237
237
  if next_item
238
238
  case next_item.node
239
+ when :list
240
+ subselect_items += next_item.list.items
239
241
  when :a_expr
240
242
  %w[lexpr rexpr].each do |side|
241
243
  elem = next_item.a_expr.public_send(side)
@@ -247,56 +249,54 @@ module PgQuery
247
249
  end
248
250
  end
249
251
  when :bool_expr
250
- subselect_items.concat(next_item.bool_expr.args)
252
+ subselect_items.concat(next_item.bool_expr.args.to_ary)
251
253
  when :coalesce_expr
252
- subselect_items.concat(next_item.coalesce_expr.args)
254
+ subselect_items.concat(next_item.coalesce_expr.args.to_ary)
253
255
  when :min_max_expr
254
- subselect_items.concat(next_item.min_max_expr.args)
256
+ subselect_items.concat(next_item.min_max_expr.args.to_ary)
255
257
  when :res_target
256
258
  subselect_items << next_item.res_target.val
257
259
  when :sub_link
258
260
  statements << next_item.sub_link.subselect
259
261
  when :func_call
262
+ subselect_items.concat(next_item.func_call.args.to_ary)
260
263
  @functions << {
261
- function: next_item.func_call.funcname[0].string.str,
264
+ function: next_item.func_call.funcname.map { |f| f.string.str }.join('.'),
262
265
  type: :call
263
266
  }
264
267
  end
265
268
  end
266
269
 
267
- break if subselect_items.empty? && statements.empty?
268
- end
269
-
270
- loop do
271
270
  next_item = from_clause_items.shift
272
- break unless next_item && next_item[:item]
273
-
274
- case next_item[:item].node
275
- when :join_expr
276
- from_clause_items << { item: next_item[:item].join_expr.larg, type: next_item[:type] }
277
- from_clause_items << { item: next_item[:item].join_expr.rarg, type: next_item[:type] }
278
- when :row_expr
279
- from_clause_items += next_item[:item].row_expr.args.map { |a| { item: a, type: next_item[:type] } }
280
- when :range_var
281
- rangevar = next_item[:item].range_var
282
- next if rangevar.schemaname.empty? && @cte_names.include?(rangevar.relname)
283
-
284
- table = [rangevar.schemaname, rangevar.relname].reject { |s| s.nil? || s.empty? }.join('.')
285
- @tables << {
286
- name: table,
287
- type: next_item[:type],
288
- location: rangevar.location,
289
- schemaname: (rangevar.schemaname unless rangevar.schemaname.empty?),
290
- relname: rangevar.relname,
291
- inh: rangevar.inh
292
- }
293
- @aliases[rangevar.alias.aliasname] = table if rangevar.alias
294
- when :range_subselect
295
- from_clause_items << { item: next_item[:item].range_subselect.subquery, type: next_item[:type] }
296
- when :select_stmt
297
- from_clause = next_item[:item].select_stmt.from_clause
298
- from_clause_items += from_clause.map { |r| { item: r, type: next_item[:type] } } if from_clause
271
+ if next_item && next_item[:item]
272
+ case next_item[:item].node
273
+ when :join_expr
274
+ from_clause_items << { item: next_item[:item].join_expr.larg, type: next_item[:type] }
275
+ from_clause_items << { item: next_item[:item].join_expr.rarg, type: next_item[:type] }
276
+ when :row_expr
277
+ from_clause_items += next_item[:item].row_expr.args.map { |a| { item: a, type: next_item[:type] } }
278
+ when :range_var
279
+ rangevar = next_item[:item].range_var
280
+ next if rangevar.schemaname.empty? && @cte_names.include?(rangevar.relname)
281
+
282
+ table = [rangevar.schemaname, rangevar.relname].reject { |s| s.nil? || s.empty? }.join('.')
283
+ @tables << {
284
+ name: table,
285
+ type: next_item[:type],
286
+ location: rangevar.location,
287
+ schemaname: (rangevar.schemaname unless rangevar.schemaname.empty?),
288
+ relname: rangevar.relname,
289
+ inh: rangevar.inh
290
+ }
291
+ @aliases[rangevar.alias.aliasname] = table if rangevar.alias
292
+ when :range_subselect
293
+ statements << next_item[:item].range_subselect.subquery
294
+ when :range_function
295
+ subselect_items += next_item[:item].range_function.functions
296
+ end
299
297
  end
298
+
299
+ break if subselect_items.empty? && statements.empty? && from_clause_items.empty?
300
300
  end
301
301
 
302
302
  @tables.uniq!
@@ -1,3 +1,3 @@
1
1
  module PgQuery
2
- VERSION = '2.1.0'.freeze
2
+ VERSION = '2.1.1'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pg_query
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lukas Fittl
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-07-04 00:00:00.000000000 Z
11
+ date: 2021-10-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake-compiler
@@ -561,7 +561,7 @@ files:
561
561
  - lib/pg_query/treewalker.rb
562
562
  - lib/pg_query/truncate.rb
563
563
  - lib/pg_query/version.rb
564
- homepage: http://github.com/pganalyze/pg_query
564
+ homepage: https://github.com/pganalyze/pg_query
565
565
  licenses:
566
566
  - BSD-3-Clause
567
567
  metadata: {}