pg_query 2.1.0 → 2.1.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ede23ff0f5d9af4599a7732fe624b3e2fba355ebb54bb6b88df3ef3932d7692a
4
- data.tar.gz: d11105ea7323b8908dde6b328908190858d150f12a13a109d943ae3bf8af475b
3
+ metadata.gz: fe9be3677078ec8a4990a53c9e0aa74c21f2a66672fe3a0990b23c00a6da34af
4
+ data.tar.gz: 31e3bbcdfac0cd27a1553ac0f0d33f7cbd7e36ce8c5080f29a314da67adcc76a
5
5
  SHA512:
6
- metadata.gz: 37b3301df52b4743098ded9bbb69b05f4a360f7e1335386833ac619f4e5423f7b3c42a6506b3519c59ed3d0bb62e6e59ef04c8515fc9a3eb5daa95c53ab228e7
7
- data.tar.gz: cd94026574593d6e78ac41e88c3378a6c46ea6d0869f8ddd9a200c367a969ff67695a780b0609518eafcff7a8dfaff015ce6408662eff7616cb866b46b52593a
6
+ metadata.gz: 7a8e0ef4b389446f952988903aa6b5772c6caabaea80d97e4f40affdf0830227285e60af0848a2786fcdab98232e96a5a75d3b5f73b24378b9ef67e9d24d9bf8
7
+ data.tar.gz: ffd90f7bdf9a155cb7a014eaf9c8bfbf06d0905e70bc5dadc739015c4d11695ead10ae1960e1bcee44e3d81d3c8a5b7d47b6705a32ef93daa8d909b328ee9602
data/CHANGELOG.md CHANGED
@@ -4,6 +4,61 @@
4
4
 
5
5
  * ...
6
6
 
7
+ ## 2.1.4 2022-09-19
8
+
9
+ * Truncate: Simplify VALUES(...) lists
10
+ * Truncate: Correctly handle UPDATE and ON CONFLICT target lists
11
+ * Support complex queries with deeply nested ASTs ([#238](https://github.com/pganalyze/pg_query/pull/238))
12
+ * Find table references inside type casts
13
+ * Find function calls referenced in expression indexes ([#249](https://github.com/pganalyze/pg_query/pull/249))
14
+ * Drop `Init_pg_query` from exported symbol map ([#256](https://github.com/pganalyze/pg_query/pull/256))
15
+
16
+ ## 2.1.3 2022-01-28
17
+
18
+ * Track tables in EXCEPT and INTERSECT queries ([#239](https://github.com/pganalyze/pg_query/pull/239))
19
+ * Get filter_columns working with UNION/EXCEPT/INTERSECT ([#240](https://github.com/pganalyze/pg_query/pull/240))
20
+ * Update google-protobuf to address CVE scanner complaints
21
+ - Note that none of the CVEs apply to pg_query, but this avoids unnecessary errors when
22
+ the google-protobuf dependency is pulled in
23
+
24
+
25
+ ## 2.1.2 2021-11-12
26
+
27
+ * Find tables in using clause of delete statement ([#234](https://github.com/pganalyze/pg_query/pull/234))
28
+ * Find tables in case statements ([#235](https://github.com/pganalyze/pg_query/pull/235))
29
+ * Correctly find nested tables in a subselect in a join condition ([#233](https://github.com/pganalyze/pg_query/pull/233))
30
+ * Mark Postgres methods as visibility hidden, to avoid bloating dynamic symbol table ([#232](https://github.com/pganalyze/pg_query/pull/232))
31
+ - This is required on ELF platforms (i.e. Linux, etc) to avoid including all global
32
+ symbols in the shared library's symbol table, bloating the size, and causing
33
+ potential conflicts with other C libraries using the same symbol names.
34
+
35
+
36
+ ## 2.1.1 2021-10-13
37
+
38
+ * Update to libpg_query 13-2.1.0 ([#230](https://github.com/pganalyze/pg_query/pull/230))
39
+ - Normalize: add funcname error object
40
+ - Normalize: Match GROUP BY against target list and re-use param refs
41
+ - PL/pgSQL: Setup namespace items for parameters, support RECORD types
42
+ - This significantly improves parsing for PL/pgSQL functions, to the extent
43
+ that most functions should now parse successfully
44
+ - Normalize: Don't modify constants in TypeName typmods/arrayBounds fields
45
+ - This matches how pg_stat_statement behaves, and avoids causing parsing
46
+ errors on the normalized statement
47
+ - Don't fail builds on systems that have strchrnul support (FreeBSD)
48
+ * Fix build on FreeBSD ([#222](https://github.com/pganalyze/pg_query/pull/222))
49
+ * Add workaround for Ruby garbage collection bug ([#227](https://github.com/pganalyze/pg_query/pull/227))
50
+ - The Ruby interpreter has a bug in `String#concat` where the appended
51
+ array may be garbage collected prematurely because the compiler
52
+ optimized out a Ruby stack variable. We now call `to_ary` on the
53
+ Protobuf object to ensure the array lands on the Ruby stack so the
54
+ garbage collector sees it.
55
+ - The real fix in the interpreter is described in
56
+ https://bugs.ruby-lang.org/issues/18140#note-2, but most current Ruby
57
+ interpreters won't have this fix for some time.
58
+ * Table/function extraction: Support subselects and LATERAL better ([#229](https://github.com/pganalyze/pg_query/pull/229))
59
+ - This reworks the parsing logic so we don't ignore certain kinds of
60
+ subselects.
61
+
7
62
 
8
63
  ## 2.1.0 2021-07-04
9
64
 
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'
@@ -7,11 +7,17 @@ require 'pathname'
7
7
 
8
8
  $objs = Dir.glob(File.join(__dir__, '*.c')).map { |f| Pathname.new(f).sub_ext('.o').to_s }
9
9
 
10
- $CFLAGS << " -O3 -Wall -fno-strict-aliasing -fwrapv -fstack-protector -Wno-unused-function -Wno-unused-variable -g"
10
+ $CFLAGS << " -fvisibility=hidden -O3 -Wall -fno-strict-aliasing -fwrapv -fstack-protector -Wno-unused-function -Wno-unused-variable -Wno-clobbered -Wno-sign-compare -Wno-discarded-qualifiers -g"
11
11
 
12
12
  $INCFLAGS = "-I#{File.join(__dir__, 'include')} " + $INCFLAGS
13
13
 
14
- SYMFILE = File.join(__dir__, 'pg_query_ruby.sym')
14
+ SYMFILE =
15
+ if RUBY_PLATFORM =~ /freebsd/
16
+ File.join(__dir__, 'pg_query_ruby_freebsd.sym')
17
+ else
18
+ File.join(__dir__, 'pg_query_ruby.sym')
19
+ end
20
+
15
21
  if RUBY_PLATFORM =~ /darwin/
16
22
  $DLDFLAGS << " -Wl,-exported_symbols_list #{SYMFILE}" unless defined?(::Rubinius)
17
23
  else
@@ -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;
@@ -14,7 +14,7 @@ VALUE pg_query_ruby_fingerprint(VALUE self, VALUE input);
14
14
  VALUE pg_query_ruby_scan(VALUE self, VALUE input);
15
15
  VALUE pg_query_ruby_hash_xxh3_64(VALUE self, VALUE input, VALUE seed);
16
16
 
17
- void Init_pg_query(void)
17
+ __attribute__((visibility ("default"))) void Init_pg_query(void)
18
18
  {
19
19
  VALUE cPgQuery;
20
20
 
@@ -0,0 +1,2 @@
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
  /*
@@ -42,14 +42,16 @@ module PgQuery
42
42
  statements << item.common_table_expr.ctequery if item.node == :common_table_expr
43
43
  end
44
44
  end
45
- when :SETOP_UNION
46
- statements << statement.select_stmt.larg if statement.select_stmt.larg
47
- statements << statement.select_stmt.rarg if statement.select_stmt.rarg
45
+ when :SETOP_UNION, :SETOP_EXCEPT, :SETOP_INTERSECT
46
+ statements << PgQuery::Node.new(select_stmt: statement.select_stmt.larg) if statement.select_stmt.larg
47
+ statements << PgQuery::Node.new(select_stmt: statement.select_stmt.rarg) if statement.select_stmt.rarg
48
48
  end
49
49
  when :update_stmt
50
50
  condition_items << statement.update_stmt.where_clause if statement.update_stmt.where_clause
51
51
  when :delete_stmt
52
52
  condition_items << statement.delete_stmt.where_clause if statement.delete_stmt.where_clause
53
+ when :index_stmt
54
+ condition_items << statement.index_stmt.where_clause if statement.index_stmt.where_clause
53
55
  end
54
56
  end
55
57
 
@@ -3,7 +3,13 @@ module PgQuery
3
3
  result, stderr = parse_protobuf(query)
4
4
 
5
5
  begin
6
- result = PgQuery::ParseResult.decode(result)
6
+ result = if PgQuery::ParseResult.method(:decode).arity == 1
7
+ PgQuery::ParseResult.decode(result)
8
+ elsif PgQuery::ParseResult.method(:decode).arity == -1
9
+ PgQuery::ParseResult.decode(result, recursion_limit: 1_000)
10
+ else
11
+ raise ArgumentError, 'Unsupported protobuf Ruby API'
12
+ end
7
13
  rescue Google::Protobuf::ParseError => e
8
14
  raise PgQuery::ParseError.new(format('Failed to parse tree: %s', e.message), __FILE__, __LINE__, -1)
9
15
  end
@@ -89,6 +95,10 @@ module PgQuery
89
95
 
90
96
  protected
91
97
 
98
+ # Parses the query and finds table and function references
99
+ #
100
+ # Note we use ".to_ary" on arrays from the Protobuf library before
101
+ # passing them to concat, because of https://bugs.ruby-lang.org/issues/18140
92
102
  def load_objects! # rubocop:disable Metrics/CyclomaticComplexity
93
103
  @tables = [] # types: select, dml, ddl
94
104
  @cte_names = []
@@ -108,22 +118,18 @@ module PgQuery
108
118
  # The following statement types do not modify tables and are added to from_clause_items
109
119
  # (and subsequently @tables)
110
120
  when :select_stmt
111
- subselect_items.concat(statement.select_stmt.target_list)
121
+ subselect_items.concat(statement.select_stmt.target_list.to_ary)
112
122
  subselect_items << statement.select_stmt.where_clause if statement.select_stmt.where_clause
113
123
  subselect_items.concat(statement.select_stmt.sort_clause.collect { |h| h.sort_by.node })
114
- subselect_items.concat(statement.select_stmt.group_clause)
124
+ subselect_items.concat(statement.select_stmt.group_clause.to_ary)
115
125
  subselect_items << statement.select_stmt.having_clause if statement.select_stmt.having_clause
116
126
 
117
127
  case statement.select_stmt.op
118
128
  when :SETOP_NONE
119
129
  (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
130
+ from_clause_items << { item: item, type: :select }
125
131
  end
126
- when :SETOP_UNION
132
+ when :SETOP_UNION, :SETOP_EXCEPT, :SETOP_INTERSECT
127
133
  statements << PgQuery::Node.new(select_stmt: statement.select_stmt.larg) if statement.select_stmt.larg
128
134
  statements << PgQuery::Node.new(select_stmt: statement.select_stmt.rarg) if statement.select_stmt.rarg
129
135
  end
@@ -143,12 +149,18 @@ module PgQuery
143
149
  value.from_clause.each do |item|
144
150
  from_clause_items << { item: item, type: :select }
145
151
  end
146
- subselect_items.concat(statement.update_stmt.target_list)
152
+ subselect_items.concat(statement.update_stmt.target_list.to_ary)
147
153
  end
148
154
 
149
155
  subselect_items << statement.update_stmt.where_clause if statement.node == :update_stmt && statement.update_stmt.where_clause
150
156
  subselect_items << statement.delete_stmt.where_clause if statement.node == :delete_stmt && statement.delete_stmt.where_clause
151
157
 
158
+ if statement.node == :delete_stmt
159
+ statement.delete_stmt.using_clause.each do |using_clause|
160
+ from_clause_items << { item: using_clause, type: :select }
161
+ end
162
+ end
163
+
152
164
  if value.with_clause
153
165
  cte_statements, cte_names = statements_and_cte_names_for_with_clause(value.with_clause)
154
166
  @cte_names.concat(cte_names)
@@ -174,6 +186,11 @@ module PgQuery
174
186
  statements << statement.view_stmt.query
175
187
  when :index_stmt
176
188
  from_clause_items << { item: PgQuery::Node.new(range_var: statement.index_stmt.relation), type: :ddl }
189
+ statement.index_stmt.index_params.each do |p|
190
+ next if p.index_elem.expr.nil?
191
+ subselect_items << p.index_elem.expr
192
+ end
193
+ subselect_items << statement.index_stmt.where_clause if statement.index_stmt.where_clause
177
194
  when :create_trig_stmt
178
195
  from_clause_items << { item: PgQuery::Node.new(range_var: statement.create_trig_stmt.relation), type: :ddl }
179
196
  when :rule_stmt
@@ -236,6 +253,8 @@ module PgQuery
236
253
  next_item = subselect_items.shift
237
254
  if next_item
238
255
  case next_item.node
256
+ when :list
257
+ subselect_items += next_item.list.items.to_ary
239
258
  when :a_expr
240
259
  %w[lexpr rexpr].each do |side|
241
260
  elem = next_item.a_expr.public_send(side)
@@ -247,56 +266,61 @@ module PgQuery
247
266
  end
248
267
  end
249
268
  when :bool_expr
250
- subselect_items.concat(next_item.bool_expr.args)
269
+ subselect_items.concat(next_item.bool_expr.args.to_ary)
251
270
  when :coalesce_expr
252
- subselect_items.concat(next_item.coalesce_expr.args)
271
+ subselect_items.concat(next_item.coalesce_expr.args.to_ary)
253
272
  when :min_max_expr
254
- subselect_items.concat(next_item.min_max_expr.args)
273
+ subselect_items.concat(next_item.min_max_expr.args.to_ary)
255
274
  when :res_target
256
275
  subselect_items << next_item.res_target.val
257
276
  when :sub_link
258
277
  statements << next_item.sub_link.subselect
259
278
  when :func_call
279
+ subselect_items.concat(next_item.func_call.args.to_ary)
260
280
  @functions << {
261
- function: next_item.func_call.funcname[0].string.str,
281
+ function: next_item.func_call.funcname.map { |f| f.string.str }.join('.'),
262
282
  type: :call
263
283
  }
284
+ when :case_expr
285
+ subselect_items.concat(next_item.case_expr.args.map { |arg| arg.case_when.expr })
286
+ subselect_items.concat(next_item.case_expr.args.map { |arg| arg.case_when.result })
287
+ subselect_items << next_item.case_expr.defresult
288
+ when :type_cast
289
+ subselect_items << next_item.type_cast.arg
264
290
  end
265
291
  end
266
292
 
267
- break if subselect_items.empty? && statements.empty?
268
- end
269
-
270
- loop do
271
293
  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
294
+ if next_item && next_item[:item]
295
+ case next_item[:item].node
296
+ when :join_expr
297
+ from_clause_items << { item: next_item[:item].join_expr.larg, type: next_item[:type] }
298
+ from_clause_items << { item: next_item[:item].join_expr.rarg, type: next_item[:type] }
299
+ subselect_items << next_item[:item].join_expr.quals
300
+ when :row_expr
301
+ from_clause_items += next_item[:item].row_expr.args.map { |a| { item: a, type: next_item[:type] } }
302
+ when :range_var
303
+ rangevar = next_item[:item].range_var
304
+ next if rangevar.schemaname.empty? && @cte_names.include?(rangevar.relname)
305
+
306
+ table = [rangevar.schemaname, rangevar.relname].reject { |s| s.nil? || s.empty? }.join('.')
307
+ @tables << {
308
+ name: table,
309
+ type: next_item[:type],
310
+ location: rangevar.location,
311
+ schemaname: (rangevar.schemaname unless rangevar.schemaname.empty?),
312
+ relname: rangevar.relname,
313
+ inh: rangevar.inh
314
+ }
315
+ @aliases[rangevar.alias.aliasname] = table if rangevar.alias
316
+ when :range_subselect
317
+ statements << next_item[:item].range_subselect.subquery
318
+ when :range_function
319
+ subselect_items += next_item[:item].range_function.functions
320
+ end
299
321
  end
322
+
323
+ break if subselect_items.empty? && statements.empty? && from_clause_items.empty?
300
324
  end
301
325
 
302
326
  @tables.uniq!
@@ -32,6 +32,12 @@ module PgQuery
32
32
  )
33
33
  when :where_clause
34
34
  node.where_clause = dummy_column_ref
35
+ when :values_lists
36
+ node.values_lists.replace(
37
+ [
38
+ PgQuery::Node.new(list: PgQuery::List.new(items: [dummy_column_ref]))
39
+ ]
40
+ )
35
41
  when :ctequery
36
42
  node.ctequery = PgQuery::Node.new(select_stmt: PgQuery::SelectStmt.new(where_clause: dummy_column_ref, op: :SETOP_NONE))
37
43
  when :cols
@@ -58,7 +64,11 @@ module PgQuery
58
64
  case k
59
65
  when :target_list
60
66
  next unless node.is_a?(PgQuery::SelectStmt) || node.is_a?(PgQuery::UpdateStmt) || node.is_a?(PgQuery::OnConflictClause)
61
- length = PgQuery.deparse_stmt(PgQuery::SelectStmt.new(k => v.to_a, op: :SETOP_NONE)).size - 7 # 'SELECT '.size
67
+ length = if node.is_a?(PgQuery::SelectStmt)
68
+ select_target_list_len(v)
69
+ else # UpdateStmt / OnConflictClause
70
+ update_target_list_len(v)
71
+ end
62
72
  truncations << PossibleTruncation.new(location, :target_list, length, true)
63
73
  when :where_clause
64
74
  next unless node.is_a?(PgQuery::SelectStmt) || node.is_a?(PgQuery::UpdateStmt) || node.is_a?(PgQuery::DeleteStmt) ||
@@ -67,23 +77,59 @@ module PgQuery
67
77
 
68
78
  length = PgQuery.deparse_expr(v).size
69
79
  truncations << PossibleTruncation.new(location, :where_clause, length, false)
80
+ when :values_lists
81
+ length = select_values_lists_len(v)
82
+ truncations << PossibleTruncation.new(location, :values_lists, length, false)
70
83
  when :ctequery
71
84
  next unless node.is_a?(PgQuery::CommonTableExpr)
72
85
  length = PgQuery.deparse_stmt(v[v.node.to_s]).size
73
86
  truncations << PossibleTruncation.new(location, :ctequery, length, false)
74
87
  when :cols
75
88
  next unless node.is_a?(PgQuery::InsertStmt)
76
- length = PgQuery.deparse_stmt(
77
- PgQuery::InsertStmt.new(
78
- relation: PgQuery::RangeVar.new(relname: 'x', inh: true),
79
- cols: v.to_a
80
- )
81
- ).size - 31 # "INSERT INTO x () DEFAULT VALUES".size
89
+ length = cols_len(v)
82
90
  truncations << PossibleTruncation.new(location, :cols, length, true)
83
91
  end
84
92
  end
85
93
 
86
94
  truncations
87
95
  end
96
+
97
+ def select_target_list_len(target_list)
98
+ deparsed_len = PgQuery.deparse_stmt(
99
+ PgQuery::SelectStmt.new(
100
+ target_list: target_list.to_a, op: :SETOP_NONE
101
+ )
102
+ ).size
103
+ deparsed_len - 7 # 'SELECT '.size
104
+ end
105
+
106
+ def select_values_lists_len(values_lists)
107
+ deparsed_len = PgQuery.deparse_stmt(
108
+ PgQuery::SelectStmt.new(
109
+ values_lists: values_lists.to_a, op: :SETOP_NONE
110
+ )
111
+ ).size
112
+ deparsed_len - 7 # 'SELECT '.size
113
+ end
114
+
115
+ def update_target_list_len(target_list)
116
+ deparsed_len = PgQuery.deparse_stmt(
117
+ PgQuery::UpdateStmt.new(
118
+ target_list: target_list.to_a,
119
+ relation: PgQuery::RangeVar.new(relname: 'x', inh: true)
120
+ )
121
+ ).size
122
+ deparsed_len - 13 # 'UPDATE x SET '.size
123
+ end
124
+
125
+ def cols_len(cols)
126
+ deparsed_len = PgQuery.deparse_stmt(
127
+ PgQuery::InsertStmt.new(
128
+ relation: PgQuery::RangeVar.new(relname: 'x', inh: true),
129
+ cols: cols.to_a
130
+ )
131
+ ).size
132
+ deparsed_len - 31 # "INSERT INTO x () DEFAULT VALUES".size
133
+ end
88
134
  end
89
135
  end
@@ -1,3 +1,3 @@
1
1
  module PgQuery
2
- VERSION = '2.1.0'.freeze
2
+ VERSION = '2.1.4'.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.4
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: 2022-09-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake-compiler
@@ -72,14 +72,14 @@ dependencies:
72
72
  requirements:
73
73
  - - ">="
74
74
  - !ruby/object:Gem::Version
75
- version: 3.17.1
75
+ version: 3.19.2
76
76
  type: :runtime
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
- version: 3.17.1
82
+ version: 3.19.2
83
83
  description: Parses SQL queries using a copy of the PostgreSQL server query parser
84
84
  email: lukas@fittl.com
85
85
  executables: []
@@ -484,6 +484,7 @@ files:
484
484
  - ext/pg_query/pg_query_readfuncs_protobuf.c
485
485
  - ext/pg_query/pg_query_ruby.c
486
486
  - ext/pg_query/pg_query_ruby.sym
487
+ - ext/pg_query/pg_query_ruby_freebsd.sym
487
488
  - ext/pg_query/pg_query_scan.c
488
489
  - ext/pg_query/pg_query_split.c
489
490
  - ext/pg_query/protobuf-c.c
@@ -561,7 +562,7 @@ files:
561
562
  - lib/pg_query/treewalker.rb
562
563
  - lib/pg_query/truncate.rb
563
564
  - lib/pg_query/version.rb
564
- homepage: http://github.com/pganalyze/pg_query
565
+ homepage: https://github.com/pganalyze/pg_query
565
566
  licenses:
566
567
  - BSD-3-Clause
567
568
  metadata: {}