pg_query 6.2.2 → 6.2.3
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 +17 -0
- data/README.md +25 -2
- data/Rakefile +2 -2
- data/ext/pg_query/pg_query_ruby.c +41 -0
- data/ext/pg_query/postgres_deparse.c +10 -8
- data/lib/pg_query/parse.rb +18 -0
- data/lib/pg_query/summary.rb +145 -0
- data/lib/pg_query/version.rb +1 -1
- data/lib/pg_query.rb +1 -0
- 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: c200160a0eb146a7f9fa38a6d9dd904e3ba617b82bb249af4677e8ea37279a91
|
|
4
|
+
data.tar.gz: 023c133937589b4edd3f3e156b11df6ad4d2940a661a21c3a1ab82574a853c80
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e4ccb4134993fabd3883cac1753e7da88fdab116e6e9915f58fc9d3ee659550f65d570e161d3c2033a4635c5898b5e89ee0c7039e92783bc423b7c7fc9e9523b
|
|
7
|
+
data.tar.gz: 2d143a73b0b1edf30f839e0804a2d010716a795312e26389aa17eee15cea94ed2c70c1b40210c3b092a014f68cbe633594db616c35db699b3a3119d17cea7303
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,23 @@
|
|
|
4
4
|
|
|
5
5
|
* ...
|
|
6
6
|
|
|
7
|
+
## 6.2.3 2026-08-26
|
|
8
|
+
|
|
9
|
+
* Upgrade to libpg_query 17-6.2.3 ([#350](https://github.com/pganalyze/pg_query/pull/350))
|
|
10
|
+
- Deparser:
|
|
11
|
+
- Preserve parentheses around subscripted array constructors
|
|
12
|
+
- This prevents `(ARRAY[...])[...]` from being deparsed as invalid SQL
|
|
13
|
+
- Fix handling of constraint key named `value` in `ALTER TABLE` [#329](https://github.com/pganalyze/libpg_query/pull/329)
|
|
14
|
+
- Preserve `ARFLAGS` when overriding `AR` [#328](https://github.com/pganalyze/libpg_query/pull/328)
|
|
15
|
+
* Add `PgQuery.summary` method ([#349](https://github.com/pganalyze/pg_query/pull/349))
|
|
16
|
+
- This uses the new `pg_query_summary` C function that significantly improves performance when
|
|
17
|
+
you need metadata (like a list of referenced tables) but don't need the full parse tree.
|
|
18
|
+
* Support finding tables inside function/aggregate clauses ([#348](https://github.com/pganalyze/pg_query/pull/348))
|
|
19
|
+
* Add support for Ruby 4.0, drop Ruby 3.0 and 3.1 ([#354](https://github.com/pganalyze/pg_query/pull/354))
|
|
20
|
+
- Bump `google-protobuf` dependency to `>= 4.34.0`
|
|
21
|
+
- 4.34.0 or newer is required for Ruby 4.0 support
|
|
22
|
+
|
|
23
|
+
|
|
7
24
|
## 6.2.2 2026-01-26
|
|
8
25
|
|
|
9
26
|
* Upgrade to libpg_query 17-6.2.2
|
data/README.md
CHANGED
|
@@ -115,6 +115,30 @@ PgQuery.parse("SELECT $1 FROM x WHERE x.y = $2 AND z = $3").filter_columns
|
|
|
115
115
|
=> [["x", "y"], [nil, "z"]]
|
|
116
116
|
```
|
|
117
117
|
|
|
118
|
+
### Summarizing a query
|
|
119
|
+
|
|
120
|
+
`PgQuery.summary` returns the same kind of information as `PgQuery.parse` (tables, functions,
|
|
121
|
+
CTE names, aliases, filter columns and statement types), but gathers it inside the native C
|
|
122
|
+
library instead of sending the full parse tree over protobuf, which is significantly faster
|
|
123
|
+
for large queries.
|
|
124
|
+
|
|
125
|
+
```ruby
|
|
126
|
+
summary = PgQuery.summary("SELECT $1 FROM x JOIN y USING (id) WHERE z = $2")
|
|
127
|
+
|
|
128
|
+
summary.tables
|
|
129
|
+
=> ["x", "y"]
|
|
130
|
+
|
|
131
|
+
summary.statement_types
|
|
132
|
+
=> ["SelectStmt"]
|
|
133
|
+
|
|
134
|
+
# Like PgQuery.parse(query).truncate(40), except the limit has to be passed in up front
|
|
135
|
+
PgQuery.summary("SELECT a, b, c, d, e, f FROM xyz WHERE a = b", truncate_limit: 40).truncated_query
|
|
136
|
+
|
|
137
|
+
=> "SELECT ... FROM xyz WHERE a = b"
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
See the comments on `PgQuery::SummaryParserResult` for how it differs from `PgQuery::ParserResult`.
|
|
141
|
+
|
|
118
142
|
### Fingerprinting a query
|
|
119
143
|
|
|
120
144
|
```ruby
|
|
@@ -178,11 +202,10 @@ not correctly handle all CTEs, or rewrite columns with explicit table names.
|
|
|
178
202
|
|
|
179
203
|
Currently tested and officially supported Ruby versions:
|
|
180
204
|
|
|
181
|
-
* CRuby 3.0
|
|
182
|
-
* CRuby 3.1
|
|
183
205
|
* CRuby 3.2
|
|
184
206
|
* CRuby 3.3
|
|
185
207
|
* CRuby 3.4
|
|
208
|
+
* CRuby 4.0
|
|
186
209
|
|
|
187
210
|
Not supported:
|
|
188
211
|
|
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 = '17-6.2.
|
|
9
|
-
LIB_PG_QUERY_SHA256SUM = '
|
|
8
|
+
LIB_PG_QUERY_TAG = '17-6.2.3'.freeze
|
|
9
|
+
LIB_PG_QUERY_SHA256SUM = '1049617789dd0bea1f6e59e15a6c4906e59045f2f5271836fdce68466efe8d87'.freeze
|
|
10
10
|
|
|
11
11
|
Rake::ExtensionTask.new 'pg_query' do |ext|
|
|
12
12
|
ext.lib_dir = 'lib/pg_query'
|
|
@@ -8,6 +8,7 @@ void raise_ruby_normalize_error(PgQueryNormalizeResult result);
|
|
|
8
8
|
void raise_ruby_fingerprint_error(PgQueryFingerprintResult result);
|
|
9
9
|
void raise_ruby_scan_error(PgQueryScanResult result);
|
|
10
10
|
void raise_ruby_split_error(PgQuerySplitResult result);
|
|
11
|
+
void raise_ruby_summary_error(PgQuerySummaryParseResult result);
|
|
11
12
|
|
|
12
13
|
VALUE pg_query_ruby_parse_protobuf(VALUE self, VALUE input);
|
|
13
14
|
VALUE pg_query_ruby_deparse_protobuf(VALUE self, VALUE input);
|
|
@@ -17,6 +18,7 @@ VALUE pg_query_ruby_normalize(VALUE self, VALUE input);
|
|
|
17
18
|
VALUE pg_query_ruby_fingerprint(VALUE self, VALUE input);
|
|
18
19
|
VALUE pg_query_ruby_scan(VALUE self, VALUE input);
|
|
19
20
|
VALUE pg_query_ruby_split_with_parser(VALUE self, VALUE input);
|
|
21
|
+
VALUE pg_query_ruby_summary(VALUE self, VALUE input, VALUE truncate_limit);
|
|
20
22
|
VALUE pg_query_ruby_hash_xxh3_64(VALUE self, VALUE input, VALUE seed);
|
|
21
23
|
|
|
22
24
|
__attribute__((visibility ("default"))) void Init_pg_query(void)
|
|
@@ -33,6 +35,7 @@ __attribute__((visibility ("default"))) void Init_pg_query(void)
|
|
|
33
35
|
rb_define_singleton_method(cPgQuery, "fingerprint", pg_query_ruby_fingerprint, 1);
|
|
34
36
|
rb_define_singleton_method(cPgQuery, "_raw_scan", pg_query_ruby_scan, 1);
|
|
35
37
|
rb_define_singleton_method(cPgQuery, "_raw_split_with_parser", pg_query_ruby_split_with_parser, 1);
|
|
38
|
+
rb_define_singleton_method(cPgQuery, "_raw_summary", pg_query_ruby_summary, 2);
|
|
36
39
|
rb_define_singleton_method(cPgQuery, "hash_xxh3_64", pg_query_ruby_hash_xxh3_64, 2);
|
|
37
40
|
rb_define_const(cPgQuery, "PG_VERSION", rb_str_new2(PG_VERSION));
|
|
38
41
|
rb_define_const(cPgQuery, "PG_MAJORVERSION", rb_str_new2(PG_MAJORVERSION));
|
|
@@ -165,6 +168,24 @@ void raise_ruby_split_error(PgQuerySplitResult result)
|
|
|
165
168
|
rb_exc_raise(rb_class_new_instance(4, args, cSplitError));
|
|
166
169
|
}
|
|
167
170
|
|
|
171
|
+
void raise_ruby_summary_error(PgQuerySummaryParseResult result)
|
|
172
|
+
{
|
|
173
|
+
VALUE cPgQuery, cParseError;
|
|
174
|
+
VALUE args[4];
|
|
175
|
+
|
|
176
|
+
cPgQuery = rb_const_get(rb_cObject, rb_intern("PgQuery"));
|
|
177
|
+
cParseError = rb_const_get_at(cPgQuery, rb_intern("ParseError"));
|
|
178
|
+
|
|
179
|
+
args[0] = rb_str_new2(result.error->message);
|
|
180
|
+
args[1] = rb_str_new2(result.error->filename);
|
|
181
|
+
args[2] = INT2NUM(result.error->lineno);
|
|
182
|
+
args[3] = INT2NUM(result.error->cursorpos);
|
|
183
|
+
|
|
184
|
+
pg_query_free_summary_parse_result(result);
|
|
185
|
+
|
|
186
|
+
rb_exc_raise(rb_class_new_instance(4, args, cParseError));
|
|
187
|
+
}
|
|
188
|
+
|
|
168
189
|
VALUE pg_query_ruby_parse_protobuf(VALUE self, VALUE input)
|
|
169
190
|
{
|
|
170
191
|
Check_Type(input, T_STRING);
|
|
@@ -366,6 +387,26 @@ VALUE pg_query_ruby_split_with_parser(VALUE self, VALUE input)
|
|
|
366
387
|
return output;
|
|
367
388
|
}
|
|
368
389
|
|
|
390
|
+
VALUE pg_query_ruby_summary(VALUE self, VALUE input, VALUE truncate_limit)
|
|
391
|
+
{
|
|
392
|
+
Check_Type(input, T_STRING);
|
|
393
|
+
Check_Type(truncate_limit, T_FIXNUM);
|
|
394
|
+
|
|
395
|
+
VALUE output;
|
|
396
|
+
PgQuerySummaryParseResult result = pg_query_summary(StringValueCStr(input), 0, NUM2INT(truncate_limit));
|
|
397
|
+
|
|
398
|
+
if (result.error) raise_ruby_summary_error(result);
|
|
399
|
+
|
|
400
|
+
output = rb_ary_new();
|
|
401
|
+
|
|
402
|
+
rb_ary_push(output, rb_str_new(result.summary.data, result.summary.len));
|
|
403
|
+
rb_ary_push(output, rb_str_new2(result.stderr_buffer));
|
|
404
|
+
|
|
405
|
+
pg_query_free_summary_parse_result(result);
|
|
406
|
+
|
|
407
|
+
return output;
|
|
408
|
+
}
|
|
409
|
+
|
|
369
410
|
VALUE pg_query_ruby_hash_xxh3_64(VALUE self, VALUE input, VALUE seed)
|
|
370
411
|
{
|
|
371
412
|
Check_Type(input, T_STRING);
|
|
@@ -93,6 +93,7 @@ typedef enum DeparseNodeContext {
|
|
|
93
93
|
DEPARSE_NODE_CONTEXT_A_EXPR,
|
|
94
94
|
DEPARSE_NODE_CONTEXT_CREATE_TYPE,
|
|
95
95
|
DEPARSE_NODE_CONTEXT_ALTER_TYPE,
|
|
96
|
+
DEPARSE_NODE_CONTEXT_ALTER_DOMAIN,
|
|
96
97
|
DEPARSE_NODE_CONTEXT_SET_STATEMENT,
|
|
97
98
|
DEPARSE_NODE_CONTEXT_FUNC_EXPR,
|
|
98
99
|
DEPARSE_NODE_CONTEXT_SELECT_SETOP,
|
|
@@ -231,7 +232,7 @@ static void deparseJsonFuncExpr(DeparseState *state, JsonFuncExpr *json_func_exp
|
|
|
231
232
|
static void deparseJsonQuotesClauseOpt(DeparseState *state, JsonQuotes quotes);
|
|
232
233
|
static void deparseJsonOnErrorClauseOpt(DeparseState *state, JsonBehavior *behavior);
|
|
233
234
|
static void deparseJsonOnEmptyClauseOpt(DeparseState *state, JsonBehavior *behavior);
|
|
234
|
-
static void deparseConstraint(DeparseState *state, Constraint *constraint);
|
|
235
|
+
static void deparseConstraint(DeparseState *state, Constraint *constraint, DeparseNodeContext context);
|
|
235
236
|
static void deparseSchemaStmt(DeparseState *state, Node *node);
|
|
236
237
|
static void deparseExecuteStmt(DeparseState *state, ExecuteStmt *execute_stmt);
|
|
237
238
|
static void deparseTriggerTransition(DeparseState *state, TriggerTransition *trigger_transition);
|
|
@@ -4638,6 +4639,7 @@ static void deparseAIndirection(DeparseState *state, A_Indirection *a_indirectio
|
|
|
4638
4639
|
IsA(a_indirection->arg, A_Expr) ||
|
|
4639
4640
|
IsA(a_indirection->arg, TypeCast) ||
|
|
4640
4641
|
IsA(a_indirection->arg, RowExpr) ||
|
|
4642
|
+
IsA(a_indirection->arg, A_ArrayExpr) ||
|
|
4641
4643
|
(IsA(a_indirection->arg, ColumnRef) && !IsA(linitial(a_indirection->indirection), A_Indices)) ||
|
|
4642
4644
|
IsA(a_indirection->arg, JsonFuncExpr);
|
|
4643
4645
|
|
|
@@ -4769,7 +4771,7 @@ static void deparseColumnDef(DeparseState *state, ColumnDef *column_def)
|
|
|
4769
4771
|
|
|
4770
4772
|
foreach(lc, column_def->constraints)
|
|
4771
4773
|
{
|
|
4772
|
-
deparseConstraint(state, castNode(Constraint, lfirst(lc)));
|
|
4774
|
+
deparseConstraint(state, castNode(Constraint, lfirst(lc)), DEPARSE_NODE_CONTEXT_NONE);
|
|
4773
4775
|
deparseAppendStringInfoChar(state, ' ');
|
|
4774
4776
|
}
|
|
4775
4777
|
|
|
@@ -5332,7 +5334,7 @@ static void deparseCreateDomainStmt(DeparseState *state, CreateDomainStmt *creat
|
|
|
5332
5334
|
|
|
5333
5335
|
foreach(lc, create_domain_stmt->constraints)
|
|
5334
5336
|
{
|
|
5335
|
-
deparseConstraint(state, castNode(Constraint, lfirst(lc)));
|
|
5337
|
+
deparseConstraint(state, castNode(Constraint, lfirst(lc)), DEPARSE_NODE_CONTEXT_NONE);
|
|
5336
5338
|
deparseAppendStringInfoChar(state, ' ');
|
|
5337
5339
|
}
|
|
5338
5340
|
|
|
@@ -5381,7 +5383,7 @@ static void deparseCreateExtensionStmt(DeparseState *state, CreateExtensionStmt
|
|
|
5381
5383
|
}
|
|
5382
5384
|
|
|
5383
5385
|
// "ColConstraintElem" and "ConstraintElem" in gram.y
|
|
5384
|
-
static void deparseConstraint(DeparseState *state, Constraint *constraint)
|
|
5386
|
+
static void deparseConstraint(DeparseState *state, Constraint *constraint, DeparseNodeContext context)
|
|
5385
5387
|
{
|
|
5386
5388
|
ListCell *lc;
|
|
5387
5389
|
|
|
@@ -5487,7 +5489,7 @@ static void deparseConstraint(DeparseState *state, Constraint *constraint)
|
|
|
5487
5489
|
{
|
|
5488
5490
|
bool valueOnly = false;
|
|
5489
5491
|
|
|
5490
|
-
if (list_length(constraint->keys) == 1) {
|
|
5492
|
+
if (context == DEPARSE_NODE_CONTEXT_ALTER_DOMAIN && list_length(constraint->keys) == 1) {
|
|
5491
5493
|
Node* firstKey = constraint->keys->elements[0].ptr_value;
|
|
5492
5494
|
valueOnly = IsA(firstKey, String) && !strcmp("value", ((String*)firstKey)->sval);
|
|
5493
5495
|
}
|
|
@@ -5997,7 +5999,7 @@ static void deparseTableElement(DeparseState *state, Node *node)
|
|
|
5997
5999
|
deparseTableLikeClause(state, castNode(TableLikeClause, node));
|
|
5998
6000
|
break;
|
|
5999
6001
|
case T_Constraint:
|
|
6000
|
-
deparseConstraint(state, castNode(Constraint, node));
|
|
6002
|
+
deparseConstraint(state, castNode(Constraint, node), DEPARSE_NODE_CONTEXT_NONE);
|
|
6001
6003
|
break;
|
|
6002
6004
|
default:
|
|
6003
6005
|
Assert(false);
|
|
@@ -7286,7 +7288,7 @@ static void deparseAlterTableCmd(DeparseState *state, AlterTableCmd *alter_table
|
|
|
7286
7288
|
case AT_AddIdentity:
|
|
7287
7289
|
case AT_AddConstraint:
|
|
7288
7290
|
case AT_AlterConstraint:
|
|
7289
|
-
deparseConstraint(state, castNode(Constraint, alter_table_cmd->def));
|
|
7291
|
+
deparseConstraint(state, castNode(Constraint, alter_table_cmd->def), DEPARSE_NODE_CONTEXT_NONE);
|
|
7290
7292
|
deparseAppendStringInfoChar(state, ' ');
|
|
7291
7293
|
break;
|
|
7292
7294
|
case AT_SetIdentity:
|
|
@@ -7452,7 +7454,7 @@ static void deparseAlterDomainStmt(DeparseState *state, AlterDomainStmt *alter_d
|
|
|
7452
7454
|
break;
|
|
7453
7455
|
case 'C':
|
|
7454
7456
|
deparseAppendStringInfoString(state, "ADD ");
|
|
7455
|
-
deparseConstraint(state, castNode(Constraint, alter_domain_stmt->def));
|
|
7457
|
+
deparseConstraint(state, castNode(Constraint, alter_domain_stmt->def), DEPARSE_NODE_CONTEXT_ALTER_DOMAIN);
|
|
7456
7458
|
break;
|
|
7457
7459
|
case 'X':
|
|
7458
7460
|
deparseAppendStringInfoString(state, "DROP CONSTRAINT ");
|
data/lib/pg_query/parse.rb
CHANGED
|
@@ -122,6 +122,7 @@ module PgQuery
|
|
|
122
122
|
subselect_items.concat(statement.select_stmt.sort_clause.collect { |h| h.sort_by.node })
|
|
123
123
|
subselect_items.concat(statement.select_stmt.group_clause.to_ary)
|
|
124
124
|
subselect_items << statement.select_stmt.having_clause if statement.select_stmt.having_clause
|
|
125
|
+
subselect_items.concat(statement.select_stmt.values_lists.to_ary)
|
|
125
126
|
|
|
126
127
|
case statement.select_stmt.op
|
|
127
128
|
when :SETOP_NONE
|
|
@@ -164,6 +165,13 @@ module PgQuery
|
|
|
164
165
|
end
|
|
165
166
|
end
|
|
166
167
|
|
|
168
|
+
subselect_items.concat(value.returning_list.to_ary)
|
|
169
|
+
|
|
170
|
+
if statement.node == :insert_stmt && value.on_conflict_clause
|
|
171
|
+
subselect_items.concat(value.on_conflict_clause.target_list.to_ary)
|
|
172
|
+
subselect_items << value.on_conflict_clause.where_clause if value.on_conflict_clause.where_clause
|
|
173
|
+
end
|
|
174
|
+
|
|
167
175
|
if value.with_clause
|
|
168
176
|
cte_statements, cte_names = statements_and_cte_names_for_with_clause(value.with_clause)
|
|
169
177
|
@cte_names.concat(cte_names)
|
|
@@ -294,10 +302,20 @@ module PgQuery
|
|
|
294
302
|
when :func_call
|
|
295
303
|
# See also CALL below
|
|
296
304
|
subselect_items.concat(next_item.func_call.args.to_ary)
|
|
305
|
+
subselect_items.concat(next_item.func_call.agg_order.to_ary)
|
|
306
|
+
subselect_items << next_item.func_call.agg_filter if next_item.func_call.agg_filter
|
|
307
|
+
subselect_items << PgQuery::Node.new(window_def: next_item.func_call.over) if next_item.func_call.over
|
|
297
308
|
@functions << {
|
|
298
309
|
function: next_item.func_call.funcname.map { |f| f.string.sval }.join('.'),
|
|
299
310
|
type: :call
|
|
300
311
|
}
|
|
312
|
+
when :sort_by
|
|
313
|
+
subselect_items << next_item.sort_by.node
|
|
314
|
+
when :window_def
|
|
315
|
+
subselect_items.concat(next_item.window_def.partition_clause.to_ary)
|
|
316
|
+
subselect_items.concat(next_item.window_def.order_clause.to_ary)
|
|
317
|
+
subselect_items << next_item.window_def.start_offset if next_item.window_def.start_offset
|
|
318
|
+
subselect_items << next_item.window_def.end_offset if next_item.window_def.end_offset
|
|
301
319
|
when :case_expr
|
|
302
320
|
subselect_items.concat(next_item.case_expr.args.map { |arg| arg.case_when.expr })
|
|
303
321
|
subselect_items.concat(next_item.case_expr.args.map { |arg| arg.case_when.result })
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
module PgQuery
|
|
2
|
+
# Parses the given SQL statement and returns a summary of it.
|
|
3
|
+
#
|
|
4
|
+
# It is possible to gather the same information using PgQuery.parse and
|
|
5
|
+
# walking the parse tree, but summary does this work in C, and only sends
|
|
6
|
+
# the summarized information over protobuf. Avoiding sending the full parse
|
|
7
|
+
# tree over protobuf can be significantly faster for large queries.
|
|
8
|
+
#
|
|
9
|
+
# Note that truncation happens on the C side, and so the maximum length has
|
|
10
|
+
# to be passed in ahead of time. summary(query, truncate_limit: 100).truncated_query
|
|
11
|
+
# is the equivalent of parse(query).truncate(100).
|
|
12
|
+
def self.summary(query, truncate_limit: -1)
|
|
13
|
+
result, stderr = _raw_summary(query, truncate_limit)
|
|
14
|
+
|
|
15
|
+
begin
|
|
16
|
+
result = PgQuery::SummaryResult.decode(result)
|
|
17
|
+
rescue Google::Protobuf::ParseError => e
|
|
18
|
+
raise PgQuery::ParseError.new(format('Failed to parse summary: %s', e.message), __FILE__, __LINE__, -1)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
warnings = []
|
|
22
|
+
stderr.each_line do |line|
|
|
23
|
+
next unless line[/^WARNING/]
|
|
24
|
+
warnings << line.strip
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
PgQuery::SummaryParserResult.new(query, result, warnings, truncate_limit)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Result of PgQuery.summary.
|
|
31
|
+
#
|
|
32
|
+
# Where possible this is API compatible with ParserResult, with these caveats:
|
|
33
|
+
#
|
|
34
|
+
# - tables_with_details omits the location, inh and relpersistence values that
|
|
35
|
+
# ParserResult reports, since the summary does not carry them
|
|
36
|
+
# - filter_columns only covers the WHERE clause of SELECT statements, so unlike
|
|
37
|
+
# ParserResult#filter_columns it does not include JOIN conditions, or the WHERE
|
|
38
|
+
# clause of UPDATE/DELETE statements
|
|
39
|
+
# - functions_with_details additionally returns the schema name and the bare
|
|
40
|
+
# function name, which ParserResult does not provide
|
|
41
|
+
# - statement_types has no ParserResult equivalent, returns the statement types of
|
|
42
|
+
# the query, e.g. ["SelectStmt"]
|
|
43
|
+
# - truncated_query is offered instead of ParserResult#truncate(max_length), since
|
|
44
|
+
# truncation has already happened when the result is returned - it returns nil if
|
|
45
|
+
# no truncate_limit was passed to PgQuery.summary
|
|
46
|
+
#
|
|
47
|
+
# The underlying protobuf message is available through #protobuf.
|
|
48
|
+
class SummaryParserResult
|
|
49
|
+
attr_reader :query, :protobuf, :warnings
|
|
50
|
+
|
|
51
|
+
def initialize(query, protobuf, warnings = [], truncate_limit = -1)
|
|
52
|
+
@query = query
|
|
53
|
+
@protobuf = protobuf
|
|
54
|
+
@warnings = warnings
|
|
55
|
+
@truncate_limit = truncate_limit
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def tables
|
|
59
|
+
tables_with_details.map { |t| t[:name] }.uniq
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def select_tables
|
|
63
|
+
tables_with_details.select { |t| t[:type] == :select }.map { |t| t[:name] }.uniq
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def dml_tables
|
|
67
|
+
tables_with_details.select { |t| t[:type] == :dml }.map { |t| t[:name] }.uniq
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def ddl_tables
|
|
71
|
+
tables_with_details.select { |t| t[:type] == :ddl }.map { |t| t[:name] }.uniq
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def functions
|
|
75
|
+
functions_with_details.map { |f| f[:function] }.uniq
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def ddl_functions
|
|
79
|
+
functions_with_details.select { |f| f[:type] == :ddl }.map { |f| f[:function] }.uniq
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def call_functions
|
|
83
|
+
functions_with_details.select { |f| f[:type] == :call }.map { |f| f[:function] }.uniq
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def cte_names
|
|
87
|
+
@cte_names ||= @protobuf.cte_names.to_a.uniq
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def aliases
|
|
91
|
+
@aliases ||= @protobuf.aliases.to_h
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def tables_with_details
|
|
95
|
+
@tables_with_details ||= @protobuf.tables.map do |table|
|
|
96
|
+
{
|
|
97
|
+
name: table.name,
|
|
98
|
+
type: context_to_type(table.context),
|
|
99
|
+
schemaname: (table.schema_name unless table.schema_name.empty?),
|
|
100
|
+
relname: table.table_name
|
|
101
|
+
}
|
|
102
|
+
end.uniq
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def functions_with_details
|
|
106
|
+
@functions_with_details ||= @protobuf.functions.map do |function|
|
|
107
|
+
{
|
|
108
|
+
function: function.name,
|
|
109
|
+
type: context_to_type(function.context),
|
|
110
|
+
schemaname: (function.schema_name unless function.schema_name.empty?),
|
|
111
|
+
funcname: function.function_name
|
|
112
|
+
}
|
|
113
|
+
end.uniq
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def filter_columns
|
|
117
|
+
@filter_columns ||= @protobuf.filter_columns.map do |filter_column|
|
|
118
|
+
table = [filter_column.schema_name, filter_column.table_name].reject(&:empty?).join('.')
|
|
119
|
+
table = nil if table.empty?
|
|
120
|
+
[aliases[table] || table, filter_column.column]
|
|
121
|
+
end.uniq
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def statement_types
|
|
125
|
+
@statement_types ||= @protobuf.statement_types.to_a
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def truncated_query
|
|
129
|
+
@protobuf.truncated_query unless @truncate_limit == -1
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
private
|
|
133
|
+
|
|
134
|
+
CONTEXT_TYPES = {
|
|
135
|
+
Select: :select,
|
|
136
|
+
DML: :dml,
|
|
137
|
+
DDL: :ddl,
|
|
138
|
+
Call: :call
|
|
139
|
+
}.freeze
|
|
140
|
+
|
|
141
|
+
def context_to_type(context)
|
|
142
|
+
CONTEXT_TYPES[context]
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
data/lib/pg_query/version.rb
CHANGED
data/lib/pg_query.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: 6.2.
|
|
4
|
+
version: 6.2.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Lukas Fittl
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-09-04 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: google-protobuf
|
|
@@ -16,14 +16,14 @@ dependencies:
|
|
|
16
16
|
requirements:
|
|
17
17
|
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version:
|
|
19
|
+
version: 4.34.0
|
|
20
20
|
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
24
|
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version:
|
|
26
|
+
version: 4.34.0
|
|
27
27
|
description: Parses SQL queries using a copy of the PostgreSQL server query parser
|
|
28
28
|
email: lukas@fittl.com
|
|
29
29
|
executables: []
|
|
@@ -576,6 +576,7 @@ files:
|
|
|
576
576
|
- lib/pg_query/pg_query_pb.rb
|
|
577
577
|
- lib/pg_query/scan.rb
|
|
578
578
|
- lib/pg_query/split.rb
|
|
579
|
+
- lib/pg_query/summary.rb
|
|
579
580
|
- lib/pg_query/treewalker.rb
|
|
580
581
|
- lib/pg_query/truncate.rb
|
|
581
582
|
- lib/pg_query/version.rb
|
|
@@ -596,7 +597,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
596
597
|
requirements:
|
|
597
598
|
- - ">="
|
|
598
599
|
- !ruby/object:Gem::Version
|
|
599
|
-
version: '3.
|
|
600
|
+
version: '3.2'
|
|
600
601
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
601
602
|
requirements:
|
|
602
603
|
- - ">="
|