forked from timescale/timescaledb
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathplanner.c
More file actions
2174 lines (1943 loc) · 57.7 KB
/
Copy pathplanner.c
File metadata and controls
2174 lines (1943 loc) · 57.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
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
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* This file and its contents are licensed under the Apache License 2.0.
* Please see the included NOTICE for copyright information and
* LICENSE-APACHE for a copy of the license.
*/
#include <postgres.h>
#include <access/tsmapi.h>
#include <access/xact.h>
#include <catalog/namespace.h>
#include <catalog/pg_inherits.h>
#include <commands/extension.h>
#include <executor/nodeAgg.h>
#include <miscadmin.h>
#include <nodes/makefuncs.h>
#include <nodes/nodeFuncs.h>
#include <nodes/parsenodes.h>
#include <nodes/plannodes.h>
#include <optimizer/appendinfo.h>
#include <optimizer/clauses.h>
#include <optimizer/optimizer.h>
#include <optimizer/pathnode.h>
#include <optimizer/paths.h>
#include <optimizer/plancat.h>
#include <optimizer/planner.h>
#include <optimizer/restrictinfo.h>
#include <optimizer/tlist.h>
#include <parser/parse_param.h>
#include <parser/parse_relation.h>
#include <parser/parsetree.h>
#include <utils/elog.h>
#include <utils/fmgroids.h>
#include <utils/guc.h>
#include <utils/lsyscache.h>
#include <utils/memutils.h>
#include <utils/selfuncs.h>
#include <utils/timestamp.h>
#include <math.h>
#include "annotations.h"
#include "chunk.h"
#include "cross_module_fn.h"
#include "debug_assert.h"
#include "dimension.h"
#include "dimension_slice.h"
#include "dimension_vector.h"
#include "extension.h"
#include "func_cache.h"
#include "guc.h"
#include "hypertable.h"
#include "hypertable_cache.h"
#include "import/allpaths.h"
#include "license_guc.h"
#include "nodes/chunk_append/chunk_append.h"
#include "nodes/constraint_aware_append/constraint_aware_append.h"
#include "nodes/modify_hypertable.h"
#include "partitioning.h"
#include "planner/planner.h"
#include "sort_transform.h"
#include "ts_catalog/compression_settings.h"
#include "utils.h"
#include "compat/compat.h"
#include <common/hashfn.h>
#ifdef USE_TELEMETRY
#include "telemetry/functions.h"
#endif
/* define parameters necessary to generate the baserel info hash table interface */
typedef struct BaserelInfoEntry
{
Oid reloid;
Hypertable *ht;
uint32 status; /* hash status */
} BaserelInfoEntry;
#define SH_PREFIX BaserelInfo
#define SH_ELEMENT_TYPE BaserelInfoEntry
#define SH_KEY_TYPE Oid
#define SH_KEY reloid
#define SH_EQUAL(tb, a, b) ((a) == (b))
#define SH_HASH_KEY(tb, key) murmurhash32(key)
#define SH_SCOPE static
#define SH_DECLARE
#define SH_DEFINE
// We don't need most of the generated functions and there is no way to not
// generate them.
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-function"
#endif
// Generate the baserel info hash table functions.
#include "lib/simplehash.h"
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
void _planner_init(void);
void _planner_fini(void);
/*
* PG19 replaced get_relation_info_hook with build_simple_rel_hook. Adopt the
* new name on older versions so the hook variable and its install/restore code
* are version independent; only the callback signature differs.
*/
#if PG19_LT
typedef get_relation_info_hook_type build_simple_rel_hook_type;
#define build_simple_rel_hook get_relation_info_hook
#endif
static planner_hook_type prev_planner_hook;
static set_rel_pathlist_hook_type prev_set_rel_pathlist_hook;
static build_simple_rel_hook_type prev_get_relation_info_hook;
static create_upper_paths_hook_type prev_create_upper_paths_hook;
static void cagg_reorder_groupby_clause(RangeTblEntry *subq_rte, Index rtno, List *outer_sortcl,
List *outer_tlist);
/*
* We mark range table entries (RTEs) in a query with TS_CTE_EXPAND if we'd like
* to control table expansion ourselves. We exploit the ctename for this purpose
* since it is not used for regular (base) relations.
*
* Note that we cannot use this mark as a general way to identify hypertable
* RTEs. Child RTEs, for instance, will inherit this value from the parent RTE
* during expansion. While we can prevent this happening in our custom table
* expansion, we also have to account for the case when our custom expansion
* is turned off with a GUC.
*/
static const char *TS_CTE_EXPAND = "ts_expand";
static const char *TS_FK_EXPAND = "ts_fk_expand";
static const char *TS_CTE_COMPRESSED_RELATION = "ts_compressed_relation";
/*
* A simplehash hash table that records the chunks and their corresponding
* hypertables, and also the plain baserels. We use it to tell whether a
* relation is a hypertable chunk, inside the classify_relation function.
* It is valid inside the scope of timescaledb_planner().
* That function can be called recursively, e.g. when we evaluate a SQL function,
* and this cache is initialized only at the top-level call.
*/
static struct BaserelInfo_hash *ts_baserel_info = NULL;
/*
* Add information about a chunk to the baserel info cache. Used to cache the
* chunk info at the plan time chunk exclusion.
*/
void
ts_add_baserel_cache_entry_for_chunk(Oid chunk_reloid, Hypertable *hypertable)
{
Assert(hypertable != NULL);
Assert(ts_baserel_info != NULL);
bool found = false;
BaserelInfoEntry *entry = BaserelInfo_insert(ts_baserel_info, chunk_reloid, &found);
if (found)
{
/* Already cached. */
Assert(entry->ht != NULL);
return;
}
Assert(ts_chunk_get_hypertable_id_by_reloid(chunk_reloid) == hypertable->fd.id);
/* Fill the cache entry. */
entry->ht = hypertable;
}
static void
rte_mark_for_expansion(RangeTblEntry *rte)
{
Assert(rte->rtekind == RTE_RELATION);
Assert(rte->ctename == NULL);
rte->ctename = (char *) TS_CTE_EXPAND;
/*
* Do not mark partitioned hypertables for inheritance, as Postgres
* is supposed to expand them.
*/
if (rte->relkind != RELKIND_PARTITIONED_TABLE)
{
rte->inh = false;
}
}
static void
rte_mark_for_fk_expansion(RangeTblEntry *rte)
{
Assert(rte->rtekind == RTE_RELATION);
Assert(rte->ctename == NULL);
rte->ctename = (char *) TS_FK_EXPAND;
/*
* If this is for an FK lookup query inherit should be false
* initially for hypertables.
*/
Assert(!rte->inh);
}
void
ts_rte_mark_compressed_relation(RangeTblEntry *rte)
{
Assert(rte->rtekind == RTE_RELATION);
Assert(rte->ctename == NULL);
rte->ctename = (char *) TS_CTE_COMPRESSED_RELATION;
}
static bool
ts_rte_is_compressed_relation(const RangeTblEntry *rte)
{
return rte->ctename == TS_CTE_COMPRESSED_RELATION;
}
bool
ts_rte_is_marked_for_expansion(const RangeTblEntry *rte)
{
if (NULL == rte->ctename)
{
return false;
}
if (rte->ctename == TS_CTE_EXPAND || rte->ctename == TS_FK_EXPAND)
{
return true;
}
return strcmp(rte->ctename, TS_CTE_EXPAND) == 0;
}
/*
* Planner-global hypertable cache.
*
* Each invocation of the planner (and our hooks) should reference the same
* cache object. Since we warm the cache when pre-processing the query (prior to
* invoking the planner), we'd like to ensure that we use the same cache object
* throughout the planning of that query so that we can trust that the cache
* holds the objects it was warmed with. Since the planner can be invoked
* recursively, we also need to stack and pop cache objects.
*/
static List *planner_hcaches = NIL;
static Cache *
planner_hcache_push(void)
{
Cache *hcache = ts_hypertable_cache_pin();
planner_hcaches = lcons(hcache, planner_hcaches);
return hcache;
}
static void
planner_hcache_pop(bool release)
{
Cache *hcache;
Assert(list_length(planner_hcaches) > 0);
hcache = linitial(planner_hcaches);
planner_hcaches = list_delete_first(planner_hcaches);
if (release)
{
ts_cache_release(&hcache);
/* If we pop a stack and discover a new hypertable cache, the basrel
* cache can contain invalid entries, so we reset it. */
if (planner_hcaches != NIL && hcache != linitial(planner_hcaches))
{
BaserelInfo_reset(ts_baserel_info);
}
}
}
static bool
planner_hcache_exists(void)
{
return planner_hcaches != NIL;
}
static Cache *
planner_hcache_get(void)
{
if (planner_hcaches == NIL)
{
return NULL;
}
return (Cache *) linitial(planner_hcaches);
}
/*
* Get the Hypertable corresponding to the given relid.
*
* This function gets a hypertable from a pre-warmed hypertable cache. If
* noresolve is specified (true), then it will do a cache-only lookup (i.e., it
* will not try to scan metadata for a new entry to put in the cache). This
* allows fast lookups during planning to also determine if something is _not_ a
* hypertable.
*/
Hypertable *
ts_planner_get_hypertable(const Oid relid, const unsigned int flags)
{
Cache *cache = planner_hcache_get();
if (NULL == cache)
{
return NULL;
}
return ts_hypertable_cache_get_entry(cache, relid, flags);
}
bool
ts_rte_is_hypertable(const RangeTblEntry *rte)
{
Hypertable *ht = ts_planner_get_hypertable(rte->relid, CACHE_FLAG_CHECK);
return ht != NULL;
}
#define IS_UPDL_CMD(parse) \
((parse)->commandType == CMD_UPDATE || (parse)->commandType == CMD_DELETE)
typedef struct
{
Query *rootquery;
Query *current_query;
PlannerInfo *root;
} PreprocessQueryContext;
static void preprocess_fk_checks(Query *query, Cache *hcache, PreprocessQueryContext *context);
void
replace_now_mock_walker(PlannerInfo *root, Node *clause, Oid funcid)
{
/* whenever we encounter a FuncExpr with now(), replace it with the supplied funcid */
switch (nodeTag(clause))
{
case T_FuncExpr:
{
if (is_valid_now_func(clause))
{
FuncExpr *fe = castNode(FuncExpr, clause);
fe->funcid = funcid;
return;
}
break;
}
case T_OpExpr:
{
ListCell *lc;
OpExpr *oe = castNode(OpExpr, clause);
foreach (lc, oe->args)
{
replace_now_mock_walker(root, (Node *) lfirst(lc), funcid);
}
break;
}
case T_BoolExpr:
{
ListCell *lc;
BoolExpr *be = castNode(BoolExpr, clause);
foreach (lc, be->args)
{
replace_now_mock_walker(root, (Node *) lfirst(lc), funcid);
}
break;
}
default:
return;
}
}
/*
* Preprocess the query tree, including, e.g., subqueries.
*
* Preprocessing includes:
*
* 1. Identifying all range table entries (RTEs) that reference
* hypertables. This will also warm the hypertable cache for faster lookup
* of both hypertables (cache hit) and non-hypertables (cache miss),
* without having to scan the metadata in either case.
*
* 2. Turning off inheritance for hypertable RTEs that we expand ourselves.
*
* 3. Reordering of GROUP BY clauses for continuous aggregates.
*
* 4. Constifying now() expressions for primary time dimension.
*/
static bool
preprocess_query(Node *node, PreprocessQueryContext *context)
{
if (node == NULL)
{
return false;
}
if (IsA(node, FromExpr) && ts_guc_enable_optimizations)
{
FromExpr *from = castNode(FromExpr, node);
if (from->quals)
{
if (ts_guc_enable_now_constify)
{
from->quals =
ts_constify_now(context->root, context->current_query->rtable, from->quals);
#ifdef TS_DEBUG
/*
* only replace if GUC is also set. This is used for testing purposes only,
* so no need to change the output for other tests in DEBUG builds
*/
if (ts_current_timestamp_mock != NULL && strlen(ts_current_timestamp_mock) != 0)
{
Oid funcid_mock;
const char *funcname = "ts_now_mock()";
funcid_mock = DatumGetObjectId(
DirectFunctionCall1(regprocedurein, CStringGetDatum(funcname)));
replace_now_mock_walker(context->root, from->quals, funcid_mock);
}
#endif
}
/*
* We only amend space constraints for UPDATE/DELETE and SELECT FOR UPDATE
* as for normal SELECT we use our own hypertable expansion which can handle
* constraints on hashed space dimensions without further help.
*/
if (context->current_query->commandType != CMD_SELECT ||
context->current_query->rowMarks != NIL)
{
from->quals = ts_add_space_constraints(context->root,
context->current_query->rtable,
from->quals);
}
}
}
else if (IsA(node, Query))
{
Query *query = castNode(Query, node);
Query *prev_query;
Cache *hcache = planner_hcache_get();
ListCell *lc;
Index rti = 1;
bool ret;
if (ts_guc_enable_foreign_key_propagation)
{
preprocess_fk_checks(query, hcache, context);
}
foreach (lc, query->rtable)
{
RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc);
Hypertable *ht;
switch (rte->rtekind)
{
case RTE_SUBQUERY:
if (ts_guc_enable_optimizations && ts_guc_enable_cagg_reorder_groupby &&
query->commandType == CMD_SELECT)
{
/* applicable to selects on continuous aggregates */
List *outer_tlist = query->targetList;
List *outer_sortcl = query->sortClause;
cagg_reorder_groupby_clause(rte, rti, outer_sortcl, outer_tlist);
}
break;
case RTE_RELATION:
/* This lookup will warm the cache with all hypertables in the query */
ht = ts_hypertable_cache_get_entry(hcache, rte->relid, CACHE_FLAG_MISSING_OK);
if (!ht)
{
/* To properly keep track of SELECT FROM ONLY <chunk> we
* have to mark the rte here because postgres will set
* rte->inh to false (when it detects the chunk has no
* children which is true for all our chunks) before it
* reaches set_rel_pathlist hook. But chunks from queries
* like SELECT .. FROM ONLY <chunk> has rte->inh set to
* false and other chunks have rte->inh set to true.
* We want to distinguish between the two cases here by
* marking the chunk when rte->inh is true.
*/
Chunk *chunk =
ts_chunk_get_by_relid_locked(rte->relid, NoLock, NULL, false);
if (chunk && rte->inh)
{
rte_mark_for_expansion(rte);
}
}
break;
default:
break;
}
rti++;
}
prev_query = context->current_query;
context->current_query = query;
ret = query_tree_walker(query, preprocess_query, context, 0);
context->current_query = prev_query;
return ret;
}
return expression_tree_walker(node, preprocess_query, context);
}
/*
* Detect FOREIGN KEY lookup queries and mark the RTE for expansion.
* Unfortunately postgres will create lookup queries for foreign keys
* with `ONLY` preventing hypertable expansion. Only for declarative
* partitioned tables the queries will be created without `ONLY`.
* We try to detect these queries here and undo the `ONLY` flag for
* these specific queries.
*
* The implementation of this on the postgres side can be found in
* src/backend/utils/adt/ri_triggers.c
*/
static void
preprocess_fk_checks(Query *query, Cache *hcache, PreprocessQueryContext *context)
{
/*
* RI_FKey_cascade_del
*
* DELETE FROM [ONLY] <fktable> WHERE $1 = fkatt1 [AND ...]
*/
if (query->commandType == CMD_DELETE && list_length(query->rtable) == 1 &&
query->jointree->quals && IsA(query->jointree->quals, OpExpr) &&
(context->root->glob->boundParams || query_contains_extern_params(query)))
{
RangeTblEntry *rte = linitial_node(RangeTblEntry, query->rtable);
if (!rte->inh && rte->rtekind == RTE_RELATION)
{
Hypertable *ht =
ts_hypertable_cache_get_entry(hcache, rte->relid, CACHE_FLAG_MISSING_OK);
if (ht)
{
rte->inh = true;
}
}
}
/*
* RI_FKey_cascade_upd
*
* UPDATE [ONLY] <fktable> SET fkatt1 = $1 [, ...]
* WHERE $n = fkatt1 [AND ...]
*/
if (query->commandType == CMD_UPDATE && list_length(query->rtable) == 1 &&
query->jointree->quals && IsA(query->jointree->quals, OpExpr) &&
(context->root->glob->boundParams || query_contains_extern_params(query)))
{
RangeTblEntry *rte = linitial_node(RangeTblEntry, query->rtable);
if (!rte->inh && rte->rtekind == RTE_RELATION)
{
Hypertable *ht =
ts_hypertable_cache_get_entry(hcache, rte->relid, CACHE_FLAG_MISSING_OK);
if (ht)
{
rte->inh = true;
}
}
}
/*
* RI_FKey_check
*
* The RI_FKey_check query string built is
* SELECT 1 FROM [ONLY] <pktable> x WHERE pkatt1 = $1 [AND ...]
* FOR KEY SHARE OF x
*/
if (query->commandType == CMD_SELECT && query->hasForUpdate &&
list_length(query->rtable) == 1 &&
(context->root->glob->boundParams || query_contains_extern_params(query)))
{
RangeTblEntry *rte = linitial_node(RangeTblEntry, query->rtable);
if (!rte->inh && rte->rtekind == RTE_RELATION && rte->rellockmode == RowShareLock &&
list_length(query->jointree->fromlist) == 1 && query->jointree->quals &&
strcmp(rte->eref->aliasname, "x") == 0)
{
Hypertable *ht =
ts_hypertable_cache_get_entry(hcache, rte->relid, CACHE_FLAG_MISSING_OK);
if (ht)
{
rte_mark_for_fk_expansion(rte);
if (TS_HYPERTABLE_HAS_COMPRESSION_ENABLED(ht))
{
query->rowMarks = NIL;
}
}
}
}
/*
* RI_Initial_Check query
*
* The RI_Initial_Check query string built is:
* SELECT fk.keycols FROM [ONLY] relname fk
* LEFT OUTER JOIN [ONLY] pkrelname pk
* ON (pk.pkkeycol1=fk.keycol1 [AND ...])
* WHERE pk.pkkeycol1 IS NULL AND
* For MATCH SIMPLE:
* (fk.keycol1 IS NOT NULL [AND ...])
* For MATCH FULL:
* (fk.keycol1 IS NOT NULL [OR ...])
*/
if (query->commandType == CMD_SELECT && list_length(query->rtable) == 3)
{
RangeTblEntry *rte1 = linitial_node(RangeTblEntry, query->rtable);
RangeTblEntry *rte2 = lsecond_node(RangeTblEntry, query->rtable);
if (!rte1->inh && !rte2->inh && rte1->rtekind == RTE_RELATION &&
rte2->rtekind == RTE_RELATION && strcmp(rte1->eref->aliasname, "fk") == 0 &&
strcmp(rte2->eref->aliasname, "pk") == 0)
{
if (ts_hypertable_cache_get_entry(hcache, rte1->relid, CACHE_FLAG_MISSING_OK))
{
rte_mark_for_fk_expansion(rte1);
}
if (ts_hypertable_cache_get_entry(hcache, rte2->relid, CACHE_FLAG_MISSING_OK))
{
rte_mark_for_fk_expansion(rte2);
}
}
}
}
static PlannedStmt *
timescaledb_planner(Query *parse, const char *query_string, int cursor_opts,
ParamListInfo bound_params
#if PG19_GE
,
ExplainState *es
#endif
)
{
PlannedStmt *stmt;
ListCell *lc;
/*
* Volatile is needed because these are the local variables that are
* modified between setjmp/longjmp calls.
*/
volatile bool reset_baserel_info = false;
/*
* If we are in an aborted transaction, reject all queries.
* While this state will not happen during normal operation it
* can happen when executing plpgsql procedures.
*/
if (IsAbortedTransactionBlockState())
{
ereport(ERROR,
(errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
errmsg("current transaction is aborted, "
"commands ignored until end of transaction block")));
}
planner_hcache_push();
if (ts_baserel_info == NULL)
{
/*
* The calls to timescaledb_planner can be recursive (e.g. when
* evaluating an immutable SQL function at planning time). We want to
* create and destroy the per-query baserel info table only at the
* top-level call, hence this flag.
*/
reset_baserel_info = true;
/*
* This is a per-query cache, so we create it in the current memory
* context for the top-level call of this function, which hopefully
* should exist for the duration of the query. Message or portal
* memory contexts could also be suitable, but they don't exist for
* SPI calls.
*/
ts_baserel_info = BaserelInfo_create(CurrentMemoryContext,
/* nelements = */ 1,
/* private_data = */ NULL);
}
PG_TRY();
{
PreprocessQueryContext context = { 0 };
PlannerGlobal glob = {
.boundParams = bound_params,
};
PlannerInfo root = {
.glob = &glob,
};
context.root = &root;
context.rootquery = parse;
context.current_query = parse;
if (ts_extension_is_loaded_and_not_upgrading())
{
#ifdef USE_TELEMETRY
ts_telemetry_function_info_gather(parse);
#endif
if (ts_guc_enable_optimizations &&
ts_cm_functions->continuous_agg_apply_rewrites_tsl != NULL)
{
context.rootquery = ts_cm_functions->continuous_agg_apply_rewrites_tsl(parse);
}
/*
* Preprocess the hypertables in the query and warm up the caches.
*/
preprocess_query((Node *) context.rootquery, &context);
if (ts_guc_enable_optimizations)
{
ts_cm_functions->preprocess_query_tsl(context.rootquery, &cursor_opts);
}
}
if (prev_planner_hook != NULL)
{
/* Call any earlier hooks */
stmt = (prev_planner_hook) (context.rootquery,
query_string,
cursor_opts,
bound_params
#if PG19_GE
,
es
#endif
);
}
else
{
/* Call the standard planner */
stmt = standard_planner(context.rootquery,
query_string,
cursor_opts,
bound_params
#if PG19_GE
,
es
#endif
);
}
if (ts_extension_is_loaded_and_not_upgrading())
{
/*
* Our top-level HypertableInsert plan node that wraps ModifyTable needs
* to have a final target list that is the same as the ModifyTable plan
* node, and we only have access to its final target list after
* set_plan_references() (setrefs.c) has run at the end of
* standard_planner. Therefore, we fixup the final target list for
* HypertableInsert here.
*/
ts_modify_hypertable_fixup_tlist(stmt->planTree);
foreach (lc, stmt->subplans)
{
Plan *subplan = (Plan *) lfirst(lc);
if (subplan)
{
ts_modify_hypertable_fixup_tlist(subplan);
}
}
ts_cm_functions->tsl_postprocess_plan(stmt);
}
if (reset_baserel_info)
{
Assert(ts_baserel_info != NULL);
BaserelInfo_destroy(ts_baserel_info);
ts_baserel_info = NULL;
}
}
PG_CATCH();
{
if (reset_baserel_info)
{
Assert(ts_baserel_info != NULL);
BaserelInfo_destroy(ts_baserel_info);
ts_baserel_info = NULL;
}
/* Pop the cache, but do not release since caches are auto-released on
* error */
planner_hcache_pop(false);
PG_RE_THROW();
}
PG_END_TRY();
planner_hcache_pop(true);
return stmt;
}
static RangeTblEntry *
get_parent_rte(const PlannerInfo *root, Index rti)
{
ListCell *lc;
/* Fast path when arrays are setup */
if (root->append_rel_array != NULL && root->append_rel_array[rti] != NULL)
{
AppendRelInfo *appinfo = root->append_rel_array[rti];
return planner_rt_fetch(appinfo->parent_relid, root);
}
foreach (lc, root->append_rel_list)
{
AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
if (appinfo->child_relid == rti)
{
return planner_rt_fetch(appinfo->parent_relid, root);
}
}
return NULL;
}
/*
* Fetch cached baserel entry. If it does not exists, create an entry for this
* relid.
* If this relid corresponds to a chunk, cache additional chunk
* related metadata: like chunk_status and pointer to hypertable entry.
* It is okay to cache a pointer to the hypertable, since this cache is
* confined to the lifetime of the query and not used across queries.
* If the parent relid is known, the caller can specify it to avoid the costly
* lookup. Otherwise pass InvalidOid.
*/
static BaserelInfoEntry *
get_or_add_baserel_from_cache(Oid chunk_reloid, Oid parent_reloid)
{
Hypertable *ht = NULL;
/* First, check if this reloid is in cache. */
bool found = false;
BaserelInfoEntry *entry = BaserelInfo_insert(ts_baserel_info, chunk_reloid, &found);
if (found)
{
return entry;
}
if (OidIsValid(parent_reloid))
{
ht = ts_planner_get_hypertable(parent_reloid, CACHE_FLAG_CHECK);
#ifdef USE_ASSERT_CHECKING
/* Sanity check on the caller-specified hypertable reloid. */
int32 parent_hypertable_id = ts_chunk_get_hypertable_id_by_reloid(chunk_reloid);
if (parent_hypertable_id != INVALID_HYPERTABLE_ID)
{
Assert(ts_hypertable_id_to_relid(parent_hypertable_id, false) == parent_reloid);
if (ht != NULL)
{
Assert(ht->fd.id == parent_hypertable_id);
}
}
#endif
}
else
{
/* Hypertable reloid not specified by the caller, look it up by
* an expensive metadata scan.
*/
int32 hypertable_id = ts_chunk_get_hypertable_id_by_reloid(chunk_reloid);
if (hypertable_id != INVALID_HYPERTABLE_ID)
{
/* Hypertable reloid not specified by the caller, look it up. */
parent_reloid = ts_hypertable_id_to_relid(hypertable_id, /* return_invalid */ false);
ht = ts_planner_get_hypertable(parent_reloid, CACHE_FLAG_NONE);
Assert(ht != NULL);
Assert(ht->fd.id == hypertable_id);
}
}
/* Cache the result. */
entry->ht = ht;
return entry;
}
/*
* Classify a planned relation.
*
* This makes use of cache warming that happened during Query preprocessing in
* the first planner hook.
*/
TsRelType
ts_classify_relation(const PlannerInfo *root, const RelOptInfo *rel, Hypertable **ht)
{
Assert(ht != NULL);
*ht = NULL;
if (rel->reloptkind != RELOPT_BASEREL && rel->reloptkind != RELOPT_OTHER_MEMBER_REL)
{
return TS_REL_OTHER;
}
RangeTblEntry *rte = planner_rt_fetch(rel->relid, root);
if (rte->relkind == RELKIND_FOREIGN_TABLE)
{
/*
* OSM chunk or other foreign chunk. We can't even access the
* fdw_private for it, because it's a foreign chunk managed by a
* different extension. Try to ignore it as much as possible.
*/
return TS_REL_OTHER;
}
if (!OidIsValid(rte->relid))
{
return TS_REL_OTHER;
}
if (rel->reloptkind == RELOPT_BASEREL)
{
/*
* To correctly classify relations in subqueries we cannot call
* ts_planner_get_hypertable with CACHE_FLAG_CHECK which includes
* CACHE_FLAG_NOCREATE flag because the rel might not be in cache yet.
*/
*ht = ts_planner_get_hypertable(rte->relid, CACHE_FLAG_MISSING_OK);
if (*ht != NULL)
{
return TS_REL_HYPERTABLE;
}
/*
* This is either a chunk seen as a standalone table, a compressed chunk
* table, or a non-chunk baserel. We need a costly chunk metadata scan
* to distinguish between them, so we cache the result of this lookup to
* avoid doing it repeatedly.
*/
BaserelInfoEntry *entry = get_or_add_baserel_from_cache(rte->relid, InvalidOid);
*ht = entry->ht;
if (*ht)
{
/*
* Note that this works in a slightly weird way for compressed
* chunks expanded from a normal hypertable, always saying that they
* are standalone. In practice we filter them out by also checking
* that the respective hypertable is not an internal compression
* hypertable.
*/
return TS_REL_CHUNK_STANDALONE;
}
return TS_REL_OTHER;
}
Assert(rel->reloptkind == RELOPT_OTHER_MEMBER_REL);
RangeTblEntry *parent_rte = get_parent_rte(root, rel->relid);
/*
* An entry of reloptkind RELOPT_OTHER_MEMBER_REL might still
* be a hypertable or a chunk here if it was pulled up from a
* subquery as happens with UNION ALL for example. So we have to
* check for that to properly detect that pattern.
*/
if (parent_rte->rtekind == RTE_SUBQUERY)
{
*ht = ts_planner_get_hypertable(rte->relid,
rte->inh ? CACHE_FLAG_MISSING_OK : CACHE_FLAG_CHECK);
if (*ht)
{
return TS_REL_HYPERTABLE;
}
/*
* This is either a chunk seen as a standalone table or a non-chunk baserel.
* We need a costly chunk metadata scan to distinguish between them, so we
* cache the result of this lookup to avoid doing it repeatedly.
*/
BaserelInfoEntry *entry = get_or_add_baserel_from_cache(rte->relid, InvalidOid);
*ht = entry->ht;
if (*ht)
{
return TS_REL_CHUNK_STANDALONE;
}
return TS_REL_OTHER;
}
if (parent_rte->relid == rte->relid)
{
/*
* A PostgreSQL table expansion peculiarity -- "self child", the root
* table that is expanded as a child of itself. This happens when our
* expansion code is turned off.
*/
*ht = ts_planner_get_hypertable(rte->relid, CACHE_FLAG_CHECK);
return *ht != NULL ? TS_REL_HYPERTABLE_CHILD : TS_REL_OTHER;
}