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 +4 -4
- data/CHANGELOG.md +55 -0
- data/Rakefile +2 -2
- data/ext/pg_query/extconf.rb +8 -2
- data/ext/pg_query/include/pg_config.h +3 -0
- data/ext/pg_query/pg_query_fingerprint.c +15 -0
- data/ext/pg_query/pg_query_fingerprint.h +3 -1
- data/ext/pg_query/pg_query_normalize.c +119 -12
- data/ext/pg_query/pg_query_parse_plpgsql.c +21 -1
- data/ext/pg_query/pg_query_ruby.c +1 -1
- data/ext/pg_query/pg_query_ruby_freebsd.sym +2 -0
- data/ext/pg_query/src_pl_plpgsql_src_pl_gram.c +1 -1
- data/lib/pg_query/filter_columns.rb +5 -3
- data/lib/pg_query/parse.rb +69 -45
- data/lib/pg_query/truncate.rb +53 -7
- data/lib/pg_query/version.rb +1 -1
- metadata +6 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fe9be3677078ec8a4990a53c9e0aa74c21f2a66672fe3a0990b23c00a6da34af
|
4
|
+
data.tar.gz: 31e3bbcdfac0cd27a1553ac0f0d33f7cbd7e36ce8c5080f29a314da67adcc76a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
9
|
-
LIB_PG_QUERY_SHA256SUM = '
|
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'
|
data/ext/pg_query/extconf.rb
CHANGED
@@ -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 =
|
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
|
@@ -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
|
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
|
-
|
254
|
-
|
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
|
-
|
356
|
-
|
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
|
-
|
363
|
-
|
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
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
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
|
-
|
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
|
|
@@ -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
|
|
data/lib/pg_query/parse.rb
CHANGED
@@ -3,7 +3,13 @@ module PgQuery
|
|
3
3
|
result, stderr = parse_protobuf(query)
|
4
4
|
|
5
5
|
begin
|
6
|
-
result = PgQuery::ParseResult.decode
|
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
|
-
|
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
|
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
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
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!
|
data/lib/pg_query/truncate.rb
CHANGED
@@ -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 =
|
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 =
|
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
|
data/lib/pg_query/version.rb
CHANGED
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.
|
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:
|
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.
|
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.
|
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:
|
565
|
+
homepage: https://github.com/pganalyze/pg_query
|
565
566
|
licenses:
|
566
567
|
- BSD-3-Clause
|
567
568
|
metadata: {}
|