pg_query 6.1.0 → 6.2.1

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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +38 -1
  3. data/README.md +1 -1
  4. data/Rakefile +3 -2
  5. data/ext/pg_query/extconf.rb +2 -2
  6. data/ext/pg_query/include/pg_query.h +32 -2
  7. data/ext/pg_query/include/postgres/access/amapi.h +1 -1
  8. data/ext/pg_query/include/postgres/access/slru.h +1 -1
  9. data/ext/pg_query/include/postgres/access/tableam.h +11 -4
  10. data/ext/pg_query/include/postgres/access/xlog.h +1 -0
  11. data/ext/pg_query/include/postgres/access/xlogdefs.h +2 -1
  12. data/ext/pg_query/include/postgres/commands/defrem.h +1 -1
  13. data/ext/pg_query/include/postgres/commands/trigger.h +18 -0
  14. data/ext/pg_query/include/postgres/executor/executor.h +4 -0
  15. data/ext/pg_query/include/postgres/mb/pg_wchar.h +2 -0
  16. data/ext/pg_query/include/postgres/miscadmin.h +2 -1
  17. data/ext/pg_query/include/postgres/nodes/execnodes.h +6 -8
  18. data/ext/pg_query/include/postgres/nodes/pathnodes.h +1 -2
  19. data/ext/pg_query/include/postgres/pg_config.h +10 -9
  20. data/ext/pg_query/include/postgres/pg_config_manual.h +2 -0
  21. data/ext/pg_query/include/postgres/port/atomics/generic-gcc.h +10 -2
  22. data/ext/pg_query/include/postgres/port/pg_iovec.h +9 -3
  23. data/ext/pg_query/include/postgres/replication/reorderbuffer.h +29 -9
  24. data/ext/pg_query/include/postgres/replication/slot.h +2 -0
  25. data/ext/pg_query/include/postgres/utils/elog.h +1 -0
  26. data/ext/pg_query/include/postgres/utils/guc.h +1 -1
  27. data/ext/pg_query/include/postgres/utils/guc_hooks.h +0 -2
  28. data/ext/pg_query/include/postgres/utils/pg_locale.h +5 -0
  29. data/ext/pg_query/include/postgres_deparse.h +34 -0
  30. data/ext/pg_query/include/protobuf/pg_query.pb-c.h +673 -516
  31. data/ext/pg_query/pg_query.pb-c.c +488 -0
  32. data/ext/pg_query/pg_query_deparse.c +148 -15
  33. data/ext/pg_query/pg_query_internal.h +9 -8
  34. data/ext/pg_query/pg_query_is_utility_stmt.c +70 -0
  35. data/ext/pg_query/pg_query_normalize.c +3 -0
  36. data/ext/pg_query/pg_query_raw_tree_walker_supports.c +117 -0
  37. data/ext/pg_query/pg_query_ruby.c +150 -0
  38. data/ext/pg_query/pg_query_summary.c +941 -0
  39. data/ext/pg_query/pg_query_summary.h +109 -0
  40. data/ext/pg_query/pg_query_summary_statement_type.c +797 -0
  41. data/ext/pg_query/pg_query_summary_truncate.c +530 -0
  42. data/ext/pg_query/postgres_deparse.c +4481 -3870
  43. data/ext/pg_query/src_backend_catalog_namespace.c +29 -0
  44. data/ext/pg_query/src_backend_nodes_bitmapset.c +84 -1
  45. data/ext/pg_query/src_backend_nodes_list.c +60 -1
  46. data/ext/pg_query/src_backend_parser_gram.c +739 -732
  47. data/ext/pg_query/src_backend_utils_activity_pgstat_database.c +2 -2
  48. data/ext/pg_query/src_backend_utils_error_elog.c +11 -0
  49. data/ext/pg_query/src_backend_utils_mb_mbutils.c +43 -4
  50. data/ext/pg_query/src_backend_utils_mmgr_alignedalloc.c +22 -7
  51. data/ext/pg_query/src_backend_utils_mmgr_aset.c +3 -3
  52. data/ext/pg_query/src_backend_utils_mmgr_bump.c +1 -1
  53. data/ext/pg_query/src_common_stringinfo.c +20 -0
  54. data/ext/pg_query/src_common_wchar.c +46 -6
  55. data/lib/pg_query/deparse.rb +29 -8
  56. data/lib/pg_query/parse.rb +19 -0
  57. data/lib/pg_query/pg_query_pb.rb +7 -2
  58. data/lib/pg_query/split.rb +20 -0
  59. data/lib/pg_query/treewalker.rb +9 -7
  60. data/lib/pg_query/version.rb +1 -1
  61. data/lib/pg_query.rb +1 -0
  62. metadata +10 -3
  63. data/ext/pg_query/postgres_deparse.h +0 -9
@@ -0,0 +1,941 @@
1
+ #include "pg_query.h"
2
+ #include "pg_query_internal.h"
3
+ #include "pg_query_outfuncs.h"
4
+
5
+ #include "catalog/namespace.h"
6
+
7
+ #include "parser/parser.h"
8
+ #include "parser/scanner.h"
9
+ #include "parser/scansup.h"
10
+
11
+ #include "nodes/makefuncs.h"
12
+ #include "nodes/nodeFuncs.h"
13
+ #include "utils/builtins.h"
14
+
15
+ #include "protobuf/pg_query.pb-c.h"
16
+
17
+ #include <unistd.h>
18
+ #include <fcntl.h>
19
+ #include <string.h>
20
+
21
+ #include "pg_query_summary.h"
22
+
23
+ static char *range_var_to_name(RangeVar *rv);
24
+ static bool cte_names_include(WalkState * state, char *relname);
25
+
26
+
27
+ static PgQueryError * make_pg_query_error(const char *message)
28
+ {
29
+ PgQueryError *error = malloc(sizeof(PgQueryError));
30
+
31
+ error->message = strdup(message);
32
+ return error;
33
+ }
34
+
35
+ /*
36
+ * Given a Node (node) and WalkState (state), wraps node in a RangeVarWithContext
37
+ * and adds it to the state->range_vars list.
38
+ */
39
+ static void
40
+ add_range_var(Node *node, ContextType context, WalkState * state)
41
+ {
42
+ if (!node)
43
+ return;
44
+
45
+ switch (node->type)
46
+ {
47
+ case T_JoinExpr:
48
+ {
49
+ JoinExpr *je = castNode(JoinExpr, node);
50
+
51
+ add_range_var(je->larg, context, state);
52
+ add_range_var(je->rarg, context, state);
53
+ /* je->quals is handled by the regular tree walk. */
54
+ break;
55
+ }
56
+
57
+ case T_RangeTableSample:
58
+ {
59
+ RangeTableSample *rts = castNode(RangeTableSample, node);
60
+
61
+ add_range_var(rts->relation, context, state);
62
+ break;
63
+ }
64
+
65
+ case T_JsonTable:
66
+ case T_RangeTableFunc:
67
+ case T_RangeFunction:
68
+ case T_RangeSubselect:
69
+ /* These are handled by the tree walker. */
70
+ break;
71
+
72
+ case T_RangeVar:
73
+ {
74
+ RangeVarWithContext *rvwc = palloc(sizeof(RangeVarWithContext));
75
+
76
+ rvwc->node = castNode(RangeVar, node);
77
+ rvwc->context = context;
78
+
79
+ state->range_vars = lappend(state->range_vars, rvwc);
80
+ break;
81
+ }
82
+
83
+ default:
84
+ elog(ERROR, "unexpected node type: %d", nodeTag(node));
85
+ }
86
+ }
87
+
88
+ /*
89
+ * Given a List*-of-Nodes (clause) and WalkState (state), wrap them all
90
+ * in RangeVarWithContext structs and add them to the state->range_vars list.
91
+ */
92
+ static void
93
+ add_range_var_list(List *clause, ContextType context, WalkState * state)
94
+ {
95
+ if (!clause)
96
+ return;
97
+
98
+ ListCell *lc = NULL;
99
+
100
+ foreach(lc, clause)
101
+ {
102
+ add_range_var(lfirst(lc), context, state);
103
+ }
104
+ }
105
+
106
+ static void
107
+ add_function(List *funcname, ContextType context, WalkState * state)
108
+ {
109
+ SummaryFunction *func = palloc(sizeof(SummaryFunction));
110
+
111
+ func->name = NameListToString(funcname);
112
+
113
+ if (list_length(funcname) == 3)
114
+ func->schema_name = lsecond_node(String, funcname)->sval;
115
+ else if (list_length(funcname) == 2)
116
+ func->schema_name = linitial_node(String, funcname)->sval;
117
+ else
118
+ func->schema_name = NULL;
119
+
120
+ func->function_name = llast_node(String, funcname)->sval;
121
+
122
+ func->context = context;
123
+
124
+ state->functions = lappend(state->functions, func);
125
+ }
126
+
127
+ bool
128
+ pg_query_summary_walk_impl(Node *node, WalkState * state)
129
+ {
130
+ if (node == NULL)
131
+ return false;
132
+
133
+ switch (nodeTag(node))
134
+ {
135
+ case T_CommonTableExpr:
136
+ {
137
+ CommonTableExpr *cte = castNode(CommonTableExpr, node);
138
+
139
+ state->cte_names = lappend(state->cte_names, pstrdup(cte->ctename));
140
+ break;
141
+ }
142
+
143
+ case T_CallStmt:
144
+ {
145
+ CallStmt *stmt = castNode(CallStmt, node);
146
+
147
+ if (stmt->funccall)
148
+ return pg_query_summary_walk_impl((Node *) stmt->funccall, state);
149
+ }
150
+
151
+ case T_SelectStmt:
152
+ {
153
+ /*
154
+ * Don't descend into sub-SELECTs in the WHERE clause when
155
+ * saving filter columns.
156
+ */
157
+ if (state->save_filter_columns)
158
+ return true;
159
+
160
+ SelectStmt *stmt = castNode(SelectStmt, node);
161
+
162
+ if (stmt->op == SETOP_NONE)
163
+ add_range_var_list(stmt->fromClause, CONTEXT_SELECT, state);
164
+
165
+ /*
166
+ * For SETOP_UNION, SETOP_EXCEPT, and SETOP_INTERSECT, the
167
+ * raw_expression_tree_walker() call at the end of this
168
+ * function will handle stmt->larg/stmt->rarg.
169
+ */
170
+
171
+ if (stmt->intoClause)
172
+ add_range_var((Node *) stmt->intoClause->rel, CONTEXT_DDL, state);
173
+
174
+ if (stmt->whereClause)
175
+ {
176
+ /*
177
+ * If we have a WHERE clause, that means we have to check
178
+ * for filter columns.
179
+ *
180
+ * NOTE: the WHERE clause is intentionally walked twice,
181
+ * but when saving filter columns we don't descend into
182
+ * sub-SELECTs.
183
+ */
184
+ state->save_filter_columns = true;
185
+ pg_query_summary_walk_impl((Node *) stmt->whereClause, state);
186
+ state->save_filter_columns = false;
187
+ }
188
+
189
+ break;
190
+ }
191
+
192
+ case T_InsertStmt:
193
+ {
194
+ add_range_var((Node *) castNode(InsertStmt, node)->relation, CONTEXT_DML, state);
195
+ break;
196
+ }
197
+
198
+ case T_UpdateStmt:
199
+ {
200
+ UpdateStmt *stmt = castNode(UpdateStmt, node);
201
+
202
+ add_range_var((Node *) stmt->relation, CONTEXT_DML, state);
203
+ add_range_var_list(stmt->fromClause, CONTEXT_SELECT, state);
204
+ break;
205
+ }
206
+
207
+ case T_MergeStmt:
208
+ {
209
+ MergeStmt *stmt = castNode(MergeStmt, node);
210
+
211
+ add_range_var((Node *) stmt->relation, CONTEXT_DML, state);
212
+ break;
213
+ }
214
+
215
+ case T_DeleteStmt:
216
+ {
217
+ DeleteStmt *stmt = castNode(DeleteStmt, node);
218
+
219
+ add_range_var((Node *) stmt->relation, CONTEXT_DML, state);
220
+ add_range_var_list(stmt->usingClause, CONTEXT_SELECT, state);
221
+ break;
222
+ }
223
+
224
+ case T_CopyStmt:
225
+ {
226
+ CopyStmt *stmt = castNode(CopyStmt, node);
227
+
228
+ add_range_var((Node *) stmt->relation, CONTEXT_SELECT, state);
229
+ pg_query_summary_walk_impl((Node *) stmt->query, state);
230
+ break;
231
+ }
232
+
233
+ case T_AlterTableStmt:
234
+ {
235
+ AlterTableStmt *stmt = castNode(AlterTableStmt, node);
236
+
237
+ /* `ALTER INDEX index_name` is ignored. */
238
+ if (stmt->objtype != OBJECT_INDEX)
239
+ add_range_var((Node *) stmt->relation, CONTEXT_DDL, state);
240
+
241
+ break;
242
+ }
243
+
244
+ case T_CreateStmt:
245
+ add_range_var((Node *) castNode(CreateStmt, node)->relation, CONTEXT_DDL, state);
246
+ break;
247
+
248
+ case T_CreateTableAsStmt:
249
+ {
250
+ CreateTableAsStmt *stmt = castNode(CreateTableAsStmt, node);
251
+
252
+ if (stmt->into && stmt->into->rel)
253
+ add_range_var((Node *) stmt->into->rel, CONTEXT_DDL, state);
254
+
255
+ pg_query_summary_walk_impl(stmt->query, state);
256
+
257
+ break;
258
+ }
259
+
260
+ case T_TruncateStmt:
261
+ add_range_var_list(castNode(TruncateStmt, node)->relations, CONTEXT_DDL, state);
262
+ break;
263
+
264
+ case T_ViewStmt:
265
+ {
266
+ ViewStmt *stmt = castNode(ViewStmt, node);
267
+
268
+ add_range_var((Node *) stmt->view, CONTEXT_DDL, state);
269
+ pg_query_summary_walk_impl(stmt->query, state);
270
+ break;
271
+ }
272
+
273
+ case T_IndexStmt:
274
+ {
275
+ IndexStmt *stmt = castNode(IndexStmt, node);
276
+
277
+ add_range_var((Node *) stmt->relation, CONTEXT_DDL, state);
278
+
279
+ ListCell *lc = NULL;
280
+
281
+ foreach(lc, stmt->indexParams)
282
+ {
283
+ IndexElem *index_elem = lfirst(lc);
284
+
285
+ pg_query_summary_walk_impl(index_elem->expr, state);
286
+ }
287
+
288
+ pg_query_summary_walk_impl(stmt->whereClause, state);
289
+
290
+ break;
291
+ }
292
+
293
+ case T_CreateTrigStmt:
294
+ add_range_var((Node *) castNode(CreateTrigStmt, node)->relation, CONTEXT_DDL, state);
295
+ break;
296
+
297
+ case T_RuleStmt:
298
+ add_range_var((Node *) castNode(RuleStmt, node)->relation, CONTEXT_DDL, state);
299
+ break;
300
+
301
+ case T_VacuumStmt:
302
+ {
303
+ VacuumStmt *stmt = castNode(VacuumStmt, node);
304
+
305
+ ListCell *lc = NULL;
306
+
307
+ foreach(lc, stmt->rels)
308
+ {
309
+ VacuumRelation *rel = lfirst(lc);
310
+
311
+ add_range_var((Node *) rel->relation, CONTEXT_DDL, state);
312
+ }
313
+
314
+ break;
315
+ }
316
+
317
+ case T_RefreshMatViewStmt:
318
+ add_range_var((Node *) castNode(RefreshMatViewStmt, node)->relation, CONTEXT_DDL, state);
319
+ break;
320
+
321
+ case T_DropStmt:
322
+ {
323
+ DropStmt *stmt = castNode(DropStmt, node);
324
+
325
+ switch (stmt->removeType)
326
+ {
327
+ case OBJECT_TABLE:
328
+ {
329
+ ListCell *lc = NULL;
330
+
331
+ foreach(lc, stmt->objects)
332
+ {
333
+ Node *obj = lfirst(lc);
334
+
335
+ if (obj->type == T_List)
336
+ {
337
+ List *list = castNode(List, obj);
338
+
339
+ if (list_length(list) == 0)
340
+ continue;
341
+
342
+ RangeVar *range_var = makeRangeVarFromNameList(list);
343
+
344
+ add_range_var((Node *) range_var, CONTEXT_DDL, state);
345
+ }
346
+ }
347
+ break;
348
+ }
349
+ case OBJECT_RULE:
350
+ case OBJECT_TRIGGER:
351
+ {
352
+ ListCell *lc = NULL;
353
+
354
+ foreach(lc, stmt->objects)
355
+ {
356
+ Node *obj = lfirst(lc);
357
+
358
+ if (obj->type == T_List)
359
+ {
360
+ List *olist = castNode(List, obj);
361
+
362
+ /*
363
+ * Ignore the last string (the
364
+ * rule/trigger name)
365
+ */
366
+ List *list = list_copy(olist);
367
+
368
+ list = list_truncate(list, list_length(list) - 1);
369
+
370
+ if (list_length(list) == 0)
371
+ continue;
372
+
373
+ RangeVar *range_var = makeRangeVarFromNameList(list);
374
+
375
+ add_range_var((Node *) range_var, CONTEXT_DDL, state);
376
+ }
377
+ }
378
+ break;
379
+ }
380
+
381
+ case OBJECT_FUNCTION:
382
+ {
383
+ Node *obj_node = linitial(stmt->objects);
384
+
385
+ if (obj_node != NULL && obj_node->type == T_ObjectWithArgs)
386
+ {
387
+ ObjectWithArgs *obj = castNode(ObjectWithArgs, obj_node);
388
+
389
+ if (obj->objname)
390
+ add_function(obj->objname, CONTEXT_DDL, state);
391
+ }
392
+
393
+ break;
394
+ }
395
+
396
+ default:
397
+ break;
398
+ }
399
+ break;
400
+ }
401
+
402
+ case T_GrantStmt:
403
+ {
404
+ GrantStmt *stmt = castNode(GrantStmt, node);
405
+ List *objects = stmt->objects;
406
+
407
+ switch (stmt->objtype)
408
+ {
409
+ case OBJECT_TABLE:
410
+ {
411
+ ListCell *lc = NULL;
412
+
413
+ foreach(lc, stmt->objects)
414
+ {
415
+ if (IsA(lfirst(lc), RangeVar))
416
+ add_range_var(lfirst(lc), CONTEXT_DDL, state);
417
+ }
418
+ break;
419
+ }
420
+ case OBJECT_FUNCTION:
421
+ case OBJECT_PROCEDURE:
422
+ case OBJECT_ROUTINE:
423
+ {
424
+ ListCell *lc = NULL;
425
+
426
+ foreach(lc, stmt->objects)
427
+ {
428
+ Node *obj_node = lfirst(lc);
429
+
430
+ if (IsA(obj_node, ObjectWithArgs))
431
+ {
432
+ ObjectWithArgs *obj = castNode(ObjectWithArgs, obj_node);
433
+
434
+ if (obj->objname)
435
+ add_function(obj->objname, CONTEXT_DDL, state);
436
+ }
437
+ }
438
+ }
439
+ default:
440
+
441
+ /*
442
+ * Do nothing.
443
+ *
444
+ * Other grant types don't contain table/function
445
+ * references.
446
+ */
447
+ break;
448
+ }
449
+ break;
450
+ }
451
+
452
+ case T_LockStmt:
453
+ add_range_var_list(castNode(LockStmt, node)->relations, CONTEXT_DDL, state);
454
+ break;
455
+
456
+ case T_ExplainStmt:
457
+ return pg_query_summary_walk_impl(castNode(ExplainStmt, node)->query, state);
458
+
459
+ case T_CreateFunctionStmt:
460
+ add_function(castNode(CreateFunctionStmt, node)->funcname, CONTEXT_DDL, state);
461
+ break;
462
+
463
+ case T_RenameStmt:
464
+ {
465
+ RenameStmt *stmt = castNode(RenameStmt, node);
466
+
467
+ switch (stmt->renameType)
468
+ {
469
+ case OBJECT_TABLE:
470
+ add_range_var((Node *) stmt->relation, CONTEXT_DDL, state);
471
+ add_range_var((Node *) makeRangeVar(stmt->relation->schemaname, stmt->newname, -1), CONTEXT_DDL, state);
472
+ break;
473
+ case OBJECT_FUNCTION:
474
+ case OBJECT_PROCEDURE:
475
+ case OBJECT_ROUTINE:
476
+ {
477
+ ObjectWithArgs *obj = castNode(ObjectWithArgs, stmt->object);
478
+
479
+ add_function(obj->objname, CONTEXT_DDL, state);
480
+ add_function(list_make1(makeString(stmt->newname)), CONTEXT_DDL, state);
481
+ break;
482
+ }
483
+ default:
484
+
485
+ /*
486
+ * Do nothing.
487
+ *
488
+ * Other rename types don't contain table/function
489
+ * references.
490
+ */
491
+ break;
492
+ }
493
+ break;
494
+ }
495
+
496
+ case T_PrepareStmt:
497
+ return pg_query_summary_walk_impl(castNode(PrepareStmt, node)->query, state);
498
+
499
+ case T_RawStmt:
500
+ return pg_query_summary_walk_impl(castNode(RawStmt, node)->stmt, state);
501
+
502
+ case T_FuncCall:
503
+ add_function(castNode(FuncCall, node)->funcname, CONTEXT_CALL, state);
504
+ break;
505
+
506
+ /*
507
+ * The following are added to avoid needing subselect_items,
508
+ *
509
+ * like is used in Rust and Ruby.
510
+ */
511
+ case T_ColumnRef:
512
+ {
513
+ if (!state->save_filter_columns)
514
+ break;
515
+
516
+ ColumnRef *ref = castNode(ColumnRef, node);
517
+
518
+ Node *column_node = llast(ref->fields);
519
+
520
+ /*
521
+ * Check that we are not in the A_Star case here. (Columns can
522
+ * be stars, but nothing else can.)
523
+ */
524
+ if (!IsA(column_node, String))
525
+ break;
526
+
527
+ String *schema_name = NULL;
528
+ String *table_name = NULL;
529
+ String *column = castNode(String, column_node);
530
+
531
+ size_t num_fields = list_length(ref->fields);
532
+
533
+ if (num_fields == 2)
534
+ {
535
+ table_name = linitial(ref->fields);
536
+ }
537
+ else if (num_fields == 3)
538
+ {
539
+ schema_name = linitial(ref->fields);
540
+ table_name = lsecond_node(String, ref->fields);
541
+ }
542
+
543
+ FilterColumn *fc = palloc(sizeof(FilterColumn));
544
+
545
+ fc->schema_name = schema_name ? schema_name->sval : NULL;
546
+ fc->table_name = table_name ? table_name->sval : NULL;
547
+ fc->column = column ? column->sval : NULL;
548
+
549
+ state->filter_columns = lappend(state->filter_columns, fc);
550
+
551
+ break;
552
+ }
553
+
554
+ default:
555
+ break;
556
+ }
557
+
558
+ if (!pg_query_raw_tree_walker_supports(node))
559
+ return false;
560
+
561
+ return raw_expression_tree_walker(node, pg_query_summary_walk_impl, (void *) state);
562
+ }
563
+
564
+ static char *
565
+ range_var_to_name(RangeVar *rv)
566
+ {
567
+ StringInfoData buf;
568
+
569
+ initStringInfo(&buf);
570
+ if (rv->schemaname != NULL)
571
+ {
572
+ appendStringInfoString(&buf, quote_identifier(rv->schemaname));
573
+ appendStringInfoChar(&buf, '.');
574
+ }
575
+ appendStringInfoString(&buf, quote_identifier(rv->relname));
576
+ return buf.data;
577
+ }
578
+
579
+ /*
580
+ * Walk through all collected cte_names, and compares them to relname.
581
+ *
582
+ * Returns true if any are an exact match, false otherwise.
583
+ */
584
+ static bool
585
+ cte_names_include(WalkState * state, char *relname)
586
+ {
587
+ if (relname == NULL)
588
+ return false;
589
+
590
+ ListCell *lc = NULL;
591
+
592
+ foreach(lc, state->cte_names)
593
+ {
594
+ char *ctename = lfirst(lc);
595
+
596
+ if (strcmp(ctename, relname) == 0)
597
+ return true;
598
+ }
599
+ return false;
600
+ }
601
+
602
+ static void
603
+ handle_range_var(RangeVar *node, ContextType context, WalkState * state)
604
+ {
605
+ if (node == NULL)
606
+ return;
607
+
608
+ RangeVar *rv = node;
609
+
610
+ if (!cte_names_include(state, rv->relname))
611
+ {
612
+ SummaryTable *table = palloc(sizeof(SummaryTable));
613
+
614
+ table->name = range_var_to_name(rv);
615
+ table->schema_name = pstrdup(rv->schemaname ? rv->schemaname : "");
616
+ table->table_name = pstrdup(rv->relname ? rv->relname : "");
617
+ table->context = context;
618
+
619
+ state->tables = lappend(state->tables, table);
620
+ }
621
+
622
+ if (rv->alias)
623
+ {
624
+ SummaryAlias *alias = palloc(sizeof(SummaryAlias));
625
+
626
+ alias->key = rv->alias->aliasname;
627
+ alias->value = range_var_to_name(rv);
628
+ state->aliases = lappend(state->aliases, alias);
629
+ }
630
+ }
631
+
632
+ /* Walk the parse tree, storing the results in `summary`. */
633
+ static void
634
+ pg_query_summary_walk(Summary * summary, Node *tree)
635
+ {
636
+ WalkState state = {0};
637
+
638
+ /*
639
+ * NOTE regarding cte_names and range_vars:
640
+ * - We process cte_names as we iterate through the tree.
641
+ * - We only add items to tables if they are *not* in cte_names.
642
+ * - CTEs can be defined *after* they're referenced as names.
643
+ * - Due to this, we store range_vars and process it after the tree walk.
644
+ *
645
+ * If we try to do it entirely on the initial pass, we wind up with CTEs
646
+ * being included in state->tables, and thus in summary->tables.
647
+ */
648
+
649
+ pg_query_summary_walk_impl(tree, &state);
650
+
651
+ ListCell *lc = NULL;
652
+
653
+ foreach(lc, state.range_vars)
654
+ {
655
+ RangeVarWithContext *rvwc = lfirst(lc);
656
+
657
+ handle_range_var(rvwc->node, rvwc->context, &state);
658
+
659
+ pfree(rvwc);
660
+ }
661
+
662
+ if (state.tables != NULL)
663
+ summary->tables = state.tables;
664
+
665
+ if (state.aliases != NULL)
666
+ summary->aliases = state.aliases;
667
+
668
+ if (state.cte_names != NULL)
669
+ summary->cte_names = state.cte_names;
670
+
671
+ if (state.functions != NULL)
672
+ summary->functions = state.functions;
673
+
674
+ if (state.filter_columns != NULL)
675
+ summary->filter_columns = state.filter_columns;
676
+ }
677
+
678
+ static PgQuery__SummaryResult__Context ctx_to_protobuf_ctx(ContextType ctx)
679
+ {
680
+ /*
681
+ * We're converting from an enum we control (ContextType) to an enum which
682
+ * is auto-generated by protobuf (PgQuery__SummaryResult__Context).
683
+ *
684
+ * While these should always be equivalent, we can't guarantee correctness
685
+ * with a simple cast. (It is theoretically possible for the internal
686
+ * representations to diverge while being functionally equivalent.)
687
+ *
688
+ * So, instead, we do a switch/case and manually convert it.
689
+ */
690
+ switch (ctx)
691
+ {
692
+ case CONTEXT_NONE:
693
+ return PG_QUERY__SUMMARY_RESULT__CONTEXT__None;
694
+ case CONTEXT_SELECT:
695
+ return PG_QUERY__SUMMARY_RESULT__CONTEXT__Select;
696
+ case CONTEXT_DML:
697
+ return PG_QUERY__SUMMARY_RESULT__CONTEXT__DML;
698
+ case CONTEXT_DDL:
699
+ return PG_QUERY__SUMMARY_RESULT__CONTEXT__DDL;
700
+ case CONTEXT_CALL:
701
+ return PG_QUERY__SUMMARY_RESULT__CONTEXT__Call;
702
+ default:
703
+ return PG_QUERY__SUMMARY_RESULT__CONTEXT__None;
704
+ }
705
+ }
706
+
707
+ /* Given the summarized query, convert it to protobuf, and store it in `result`. */
708
+ static void
709
+ summary_to_protobuf(PgQuerySummaryParseResult * result, Summary * summary)
710
+ {
711
+ PgQueryProtobuf protobuf;
712
+ PgQuery__SummaryResult sr = PG_QUERY__SUMMARY_RESULT__INIT;
713
+
714
+ sr.n_tables = list_length(summary->tables);
715
+ sr.tables = palloc(sizeof(PgQuery__SummaryResult__Table *) * sr.n_tables);
716
+
717
+ ListCell *lc = NULL;
718
+
719
+ foreach(lc, summary->tables)
720
+ {
721
+ size_t i = foreach_current_index(lc);
722
+ SummaryTable *table = lfirst(lc);
723
+
724
+ sr.tables[i] = palloc(sizeof(PgQuery__SummaryResult__Table));
725
+ pg_query__summary_result__table__init(sr.tables[i]);
726
+ sr.tables[i]->name = table->name;
727
+ sr.tables[i]->schema_name = table->schema_name;
728
+ sr.tables[i]->table_name = table->table_name;
729
+ sr.tables[i]->context = ctx_to_protobuf_ctx(table->context);
730
+ }
731
+
732
+ sr.n_aliases = list_length(summary->aliases);
733
+ sr.aliases = palloc(sizeof(PgQuery__SummaryResult__AliasesEntry *) * sr.n_aliases);
734
+ foreach(lc, summary->aliases)
735
+ {
736
+ size_t i = foreach_current_index(lc);
737
+ SummaryAlias *alias = lfirst(lc);
738
+
739
+ sr.aliases[i] = palloc(sizeof(PgQuery__SummaryResult__AliasesEntry));
740
+ pg_query__summary_result__aliases_entry__init(sr.aliases[i]);
741
+ sr.aliases[i]->key = alias->key;
742
+ sr.aliases[i]->value = alias->value;
743
+ }
744
+
745
+ sr.n_cte_names = list_length(summary->cte_names);
746
+ sr.cte_names = palloc(sizeof(char *) * sr.n_cte_names);
747
+ foreach(lc, summary->cte_names)
748
+ {
749
+ size_t i = foreach_current_index(lc);
750
+ char *ctename = lfirst(lc);
751
+
752
+ sr.cte_names[i] = ctename;
753
+ }
754
+
755
+ sr.n_functions = list_length(summary->functions);
756
+ sr.functions = palloc(sizeof(PgQuery__SummaryResult__Function *) * sr.n_functions);
757
+ foreach(lc, summary->functions)
758
+ {
759
+ size_t i = foreach_current_index(lc);
760
+ SummaryFunction *fn = lfirst(lc);
761
+
762
+ sr.functions[i] = palloc(sizeof(PgQuery__SummaryResult__Function));
763
+ pg_query__summary_result__function__init(sr.functions[i]);
764
+ sr.functions[i]->name = fn->name;
765
+ sr.functions[i]->function_name = fn->function_name;
766
+ sr.functions[i]->schema_name = fn->schema_name;
767
+ sr.functions[i]->context = ctx_to_protobuf_ctx(fn->context);
768
+ }
769
+
770
+ sr.n_filter_columns = list_length(summary->filter_columns);
771
+ sr.filter_columns = palloc(sizeof(PgQuery__SummaryResult__FilterColumn *) * sr.n_filter_columns);
772
+ foreach(lc, summary->filter_columns)
773
+ {
774
+ size_t i = foreach_current_index(lc);
775
+ FilterColumn *fc = lfirst(lc);
776
+
777
+ sr.filter_columns[i] = palloc(sizeof(PgQuery__SummaryResult__FilterColumn));
778
+ pg_query__summary_result__filter_column__init(sr.filter_columns[i]);
779
+ sr.filter_columns[i]->schema_name = fc->schema_name;
780
+ sr.filter_columns[i]->table_name = fc->table_name;
781
+ sr.filter_columns[i]->column = fc->column;
782
+ }
783
+
784
+ sr.n_statement_types = list_length(summary->statement_types);
785
+ sr.statement_types = palloc(sizeof(char *) * sr.n_statement_types);
786
+ foreach(lc, summary->statement_types)
787
+ {
788
+ size_t i = foreach_current_index(lc);
789
+ char *st = lfirst(lc);
790
+
791
+ sr.statement_types[i] = pstrdup(st);
792
+ }
793
+
794
+ if (summary->truncated_query == NULL)
795
+ sr.truncated_query = pstrdup("");
796
+ else
797
+ sr.truncated_query = summary->truncated_query;
798
+
799
+ size_t len = pg_query__summary_result__get_packed_size(&sr);
800
+
801
+ /*
802
+ * Note: This is intentionally malloc so exiting the memory context
803
+ * doesn't free this
804
+ */
805
+ char *data = malloc(sizeof(char) * len);
806
+
807
+ size_t written = pg_query__summary_result__pack(&sr, (void *) data);
808
+
809
+ if (written == len)
810
+ {
811
+ result->summary.len = len;
812
+ result->summary.data = data;
813
+ }
814
+ else
815
+ {
816
+ result->error = make_pg_query_error("summary_to_protobuf() wrote wrong amount of data?");
817
+ }
818
+
819
+ for (size_t i = 0; i < sr.n_tables; i++)
820
+ pfree(sr.tables[i]);
821
+ pfree(sr.tables);
822
+
823
+ for (size_t i = 0; i < sr.n_aliases; i++)
824
+ pfree(sr.aliases[i]);
825
+ pfree(sr.aliases);
826
+
827
+ for (size_t i = 0; i < sr.n_functions; i++)
828
+ pfree(sr.functions[i]);
829
+ pfree(sr.functions);
830
+
831
+ for (size_t i = 0; i < sr.n_filter_columns; i++)
832
+ pfree(sr.filter_columns[i]);
833
+ pfree(sr.filter_columns);
834
+
835
+ pfree(sr.truncated_query);
836
+ }
837
+
838
+ /*
839
+ * Given a query, parser options, and truncate limit, provide a summary of the query.
840
+ *
841
+ * If `truncate_limit` is -1, no truncation is performed.
842
+ */
843
+ PgQuerySummaryParseResultInternal
844
+ pg_query_summary_internal(const char *input, int parser_options, int truncate_limit)
845
+ {
846
+ PgQueryInternalParsetreeAndError parsetree_and_error;
847
+ PgQuerySummaryParseResultInternal result = {0};
848
+ Summary summary = {0};
849
+ PgQueryError *error = NULL;
850
+ bool should_truncate = (truncate_limit != -1);
851
+
852
+ parsetree_and_error = pg_query_raw_parse(input, parser_options);
853
+ result.stderr_buffer = parsetree_and_error.stderr_buffer;
854
+ result.error = parsetree_and_error.error;
855
+
856
+ if (result.error == NULL)
857
+ {
858
+ pg_query_summary_walk(&summary, (Node *) parsetree_and_error.tree);
859
+ pg_query_summary_statement_walk(&summary, (Node *) parsetree_and_error.tree);
860
+ }
861
+
862
+ if (result.error == NULL && should_truncate)
863
+ pg_query_summary_truncate(&summary, (Node *) parsetree_and_error.tree, truncate_limit);
864
+
865
+ result.summary = summary;
866
+
867
+ return result;
868
+ }
869
+
870
+ PgQuerySummaryParseResult
871
+ pg_query_summary(const char *input, int parser_options, int truncate_limit)
872
+ {
873
+ PgQuerySummaryParseResult result = {0};
874
+ MemoryContext ctx = pg_query_enter_memory_context();
875
+
876
+ PG_TRY();
877
+ {
878
+ PgQuerySummaryParseResultInternal internal_result =
879
+ pg_query_summary_internal(input, parser_options, truncate_limit);
880
+
881
+ result.stderr_buffer = internal_result.stderr_buffer;
882
+ result.error = internal_result.error;
883
+
884
+ if (result.error == NULL)
885
+ summary_to_protobuf(&result, &internal_result.summary);
886
+ }
887
+ PG_CATCH();
888
+ {
889
+ ErrorData *error_data;
890
+ PgQueryError *error;
891
+
892
+ MemoryContextSwitchTo(ctx);
893
+ error_data = CopyErrorData();
894
+
895
+ /*
896
+ * Note: This is intentionally malloc so exiting the memory context
897
+ * doesn't free this
898
+ */
899
+ error = malloc(sizeof(PgQueryError));
900
+ error->message = strdup(error_data->message);
901
+ error->filename = strdup(error_data->filename);
902
+ error->funcname = strdup(error_data->funcname);
903
+ error->context = NULL;
904
+ error->lineno = error_data->lineno;
905
+ error->cursorpos = error_data->cursorpos;
906
+
907
+ result.error = error;
908
+ FlushErrorState();
909
+ }
910
+ PG_END_TRY();
911
+
912
+ pg_query_exit_memory_context(ctx);
913
+
914
+ return result;
915
+ }
916
+
917
+ /* Free all of the various parts of PgQuerySummaryParseResultInternal. */
918
+ void
919
+ pg_query_free_summary_parse_result_internal(PgQuerySummaryParseResultInternal result)
920
+ {
921
+ if (result.error)
922
+ pg_query_free_error(result.error);
923
+
924
+ if (result.stderr_buffer)
925
+ free(result.stderr_buffer);
926
+ }
927
+
928
+ /* Free all of the various parts of a PgQuerySummaryParseResult. */
929
+ void
930
+ pg_query_free_summary_parse_result(PgQuerySummaryParseResult result)
931
+ {
932
+ if (result.error)
933
+ pg_query_free_error(result.error);
934
+
935
+ if (result.summary.data)
936
+ free(result.summary.data);
937
+
938
+ /* This is allocated by strdup(), so it uses malloc() instead of palloc(). */
939
+ if (result.stderr_buffer)
940
+ free(result.stderr_buffer);
941
+ }