pg_query 2.1.0 → 2.1.4

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: 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: {}