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