forked from timescale/timescaledb
-
Notifications
You must be signed in to change notification settings - Fork 0
/
planner.c
1665 lines (1464 loc) · 48.7 KB
/
planner.c
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 <commands/extension.h>
#include <executor/nodeAgg.h>
#include <miscadmin.h>
#include <nodes/makefuncs.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/planner.h>
#include <optimizer/restrictinfo.h>
#include <optimizer/tlist.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 "compat/compat-msvc-enter.h"
#include <catalog/pg_constraint.h>
#include <nodes/nodeFuncs.h>
#include <optimizer/cost.h>
#include <optimizer/plancat.h>
#include <parser/analyze.h>
#include <tcop/tcopprot.h>
#include "compat/compat-msvc-exit.h"
#include <math.h>
#include "annotations.h"
#include "chunk.h"
#include "cross_module_fn.h"
#include "dimension.h"
#include "dimension_slice.h"
#include "dimension_vector.h"
#include "extension.h"
#include "func_cache.h"
#include "guc.h"
#include "hypertable_cache.h"
#include "import/allpaths.h"
#include "license_guc.h"
#include "nodes/chunk_append/chunk_append.h"
#include "nodes/chunk_dispatch_plan.h"
#include "nodes/constraint_aware_append/constraint_aware_append.h"
#include "nodes/hypertable_modify.h"
#include "partitioning.h"
#include "planner/planner.h"
#include "utils.h"
#include "compat/compat.h"
#if PG13_GE
#include <common/hashfn.h>
#else
#include <utils/hashutils.h>
#endif
#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 chunk_status; /* status of chunk, if this is a chunk */
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);
static planner_hook_type prev_planner_hook;
static set_rel_pathlist_hook_type prev_set_rel_pathlist_hook;
static get_relation_info_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";
/*
* Controls which type of fetcher to use to fetch data from the data nodes.
* There is no place to store planner-global custom information (such as in
* PlannerInfo). Because of this, we have to use the global variable that is
* valid inside the scope of timescaledb_planner().
* Note that that function can be called recursively, e.g. when evaluating a
* SQL function at the planning time. We only have to determine the fetcher type
* in the outermost scope, so we distinguish it by that the fetcher type is set
* to the invalid value of 'auto'.
*/
DataFetcherType ts_data_node_fetcher_scan_type = AutoFetcherType;
/*
* 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
add_baserel_cache_entry_for_chunk(Oid chunk_reloid, uint32 chunk_status, 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, check that the parameters are the same. */
Assert(entry->ht != NULL);
Assert(entry->chunk_status == chunk_status);
return;
}
/* Fill the cache entry. */
entry->ht = hypertable;
entry->chunk_status = chunk_status;
}
static void
rte_mark_for_expansion(RangeTblEntry *rte)
{
Assert(rte->rtekind == RTE_RELATION);
Assert(rte->ctename == NULL);
rte->ctename = (char *) TS_CTE_EXPAND;
rte->inh = false;
}
bool
ts_rte_is_marked_for_expansion(const RangeTblEntry *rte)
{
if (NULL == rte->ctename)
return false;
if (rte->ctename == TS_CTE_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);
if (release)
ts_cache_release(hcache);
planner_hcaches = list_delete_first(planner_hcaches);
}
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, bool *isdistributed)
{
Hypertable *ht = ts_planner_get_hypertable(rte->relid, CACHE_FLAG_CHECK);
if (isdistributed && ht != NULL)
*isdistributed = hypertable_is_distributed(ht);
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;
/*
* The number of distributed hypertables in the query and its subqueries.
* Specifically, we count range table entries here, so using the same
* distributed table twice counts as two tables. No matter whether it's the
* same physical table or not, the range table entries can be scanned
* concurrently, and more than one of them being distributed means we have
* to use the cursor fetcher so that these scans can be interleaved.
*/
int num_distributed_tables;
} PreprocessQueryContext;
/*
* 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);
}
/*
* 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;
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)
{
/* Mark hypertable RTEs we'd like to expand ourselves */
if (ts_guc_enable_optimizations && ts_guc_enable_constraint_exclusion &&
!IS_UPDL_CMD(context->rootquery) && query->resultRelation == 0 &&
query->rowMarks == NIL && rte->inh)
rte_mark_for_expansion(rte);
if (TS_HYPERTABLE_HAS_COMPRESSION_TABLE(ht))
{
int compr_htid = ht->fd.compressed_hypertable_id;
/* Also warm the cache with the compressed
* companion hypertable */
ht = ts_hypertable_cache_get_entry_by_id(hcache, compr_htid);
Assert(ht != NULL);
}
if (hypertable_is_distributed(ht))
{
context->num_distributed_tables++;
}
}
else
{
/* 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(rte->relid, 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);
}
static PlannedStmt *
#if PG13_GE
timescaledb_planner(Query *parse, const char *query_string, int cursor_opts,
ParamListInfo bound_params)
#else
timescaledb_planner(Query *parse, int cursor_opts, ParamListInfo bound_params)
#endif
{
PlannedStmt *stmt;
ListCell *lc;
/*
* Volatile is needed because these are the local variables that are
* modified between setjmp/longjmp calls.
*/
volatile bool reset_fetcher_type = false;
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();
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())
{
#ifdef USE_TELEMETRY
ts_telemetry_function_info_gather(parse);
#endif
/*
* Preprocess the hypertables in the query and warm up the caches.
*/
preprocess_query((Node *) parse, &context);
/*
* Determine which type of fetcher to use. If set by GUC, use what
* is set. If the GUC says 'auto', use the COPY fetcher if we
* have at most one distributed table in the query. This enables
* parallel plans on data nodes, which speeds up the query.
* We can't use parallel plans with the cursor fetcher, because the
* cursors don't support parallel execution. This is because a
* cursor can be suspended at any time, then some arbitrary user
* code can be executed, and then the cursor is resumed. The
* parallel infrastructure doesn't have enough reentrability to
* survive this.
* We have to use a cursor fetcher when we have multiple distributed
* tables, because we might first have to get some rows from one
* table and then from another, without running either of them to
* completion first. This happens e.g. when doing a join. If we had
* a connection per table, we could avoid this requirement.
*
* Note that this function can be called recursively, e.g. when
* trying to evaluate an SQL function at the planning stage. We must
* only set/reset the fetcher type at the topmost level, that's why
* we check it's not already set.
*/
if (ts_data_node_fetcher_scan_type == AutoFetcherType)
{
reset_fetcher_type = true;
if (context.num_distributed_tables >= 2)
{
if (ts_guc_remote_data_fetcher == CopyFetcherType)
{
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("COPY fetcher not supported"),
errhint("COPY fetching of data is not supported in "
"queries with multiple distributed hypertables."
" Use cursor fetcher instead.")));
}
ts_data_node_fetcher_scan_type = CursorFetcherType;
}
else
{
if (ts_guc_remote_data_fetcher == AutoFetcherType)
{
ts_data_node_fetcher_scan_type = CopyFetcherType;
}
else
{
ts_data_node_fetcher_scan_type = ts_guc_remote_data_fetcher;
}
}
}
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.
*/
MemoryContextStats(CurrentMemoryContext);
ts_baserel_info = BaserelInfo_create(CurrentMemoryContext,
/* nelements = */ 1,
/* private_data = */ NULL);
}
}
if (prev_planner_hook != NULL)
/* Call any earlier hooks */
#if PG13_GE
stmt = (prev_planner_hook) (parse, query_string, cursor_opts, bound_params);
#else
stmt = (prev_planner_hook) (parse, cursor_opts, bound_params);
#endif
else
/* Call the standard planner */
#if PG13_GE
stmt = standard_planner(parse, query_string, cursor_opts, bound_params);
#else
stmt = standard_planner(parse, cursor_opts, bound_params);
#endif
if (ts_extension_is_loaded())
{
/*
* 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_hypertable_modify_fixup_tlist(stmt->planTree);
foreach (lc, stmt->subplans)
{
Plan *subplan = (Plan *) lfirst(lc);
if (subplan)
ts_hypertable_modify_fixup_tlist(subplan);
}
}
if (reset_baserel_info)
{
Assert(ts_baserel_info != NULL);
BaserelInfo_destroy(ts_baserel_info);
ts_baserel_info = NULL;
}
if (reset_fetcher_type)
{
ts_data_node_fetcher_scan_type = AutoFetcherType;
}
}
PG_CATCH();
{
if (reset_baserel_info)
{
Assert(ts_baserel_info != NULL);
BaserelInfo_destroy(ts_baserel_info);
ts_baserel_info = NULL;
}
if (reset_fetcher_type)
{
ts_data_node_fetcher_scan_type = AutoFetcherType;
}
/* 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 reolid 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;
}
/*
* This reloid is not in the chunk cache, so do the full metadata
* lookup.
*/
int32 hypertable_id = 0;
int32 chunk_status = 0;
if (ts_chunk_get_hypertable_id_and_status_by_relid(chunk_reloid, &hypertable_id, &chunk_status))
{
/*
* This is a chunk. Look up the hypertable for it.
*/
if (OidIsValid(parent_reloid))
{
/* Sanity check on the caller-specified hypertable reloid. */
Assert(ts_hypertable_id_to_relid(hypertable_id) == parent_reloid);
}
else
{
/* Hypertable reloid not specified by the caller, look it up. */
parent_reloid = ts_hypertable_id_to_relid(hypertable_id);
}
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;
entry->chunk_status = chunk_status;
return entry;
}
/*
* Classify a planned relation.
*
* This makes use of cache warming that happened during Query preprocessing in
* the first planner hook.
*/
static TsRelType
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 (!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, 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;
return *ht ? TS_REL_CHUNK_STANDALONE : 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 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);
return *ht ? TS_REL_HYPERTABLE : 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;
}
/*
* Either an other baserel or a chunk seen when expanding the hypertable.
* Use the baserel cache to determine what it is.
*/
BaserelInfoEntry *entry = get_or_add_baserel_from_cache(rte->relid, parent_rte->relid);
*ht = entry->ht;
return *ht ? TS_REL_CHUNK_CHILD : TS_REL_OTHER;
}
extern void ts_sort_transform_optimization(PlannerInfo *root, RelOptInfo *rel);
static inline bool
should_chunk_append(Hypertable *ht, PlannerInfo *root, RelOptInfo *rel, Path *path, bool ordered,
int order_attno)
{
if (
#if PG14_LT
root->parse->commandType != CMD_SELECT ||
#else
/*
* We only support chunk exclusion on UPDATE/DELETE when no JOIN is involved on PG14+.
*/
((root->parse->commandType == CMD_DELETE || root->parse->commandType == CMD_UPDATE) &&
bms_num_members(root->all_baserels) > 1) ||
#endif
!ts_guc_enable_chunk_append || hypertable_is_distributed(ht))
return false;
switch (nodeTag(path))
{
case T_AppendPath:
/*
* If there are clauses that have mutable functions, or clauses that reference
* Params this Path might benefit from startup or runtime exclusion
*/
{
AppendPath *append = castNode(AppendPath, path);
ListCell *lc;
/* Don't create ChunkAppend with no children */
if (list_length(append->subpaths) == 0)
return false;
foreach (lc, rel->baserestrictinfo)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
if (contain_mutable_functions((Node *) rinfo->clause) ||
ts_contain_param((Node *) rinfo->clause))
return true;
}
return false;
break;
}
case T_MergeAppendPath:
/*
* Can we do ordered append
*/
{
MergeAppendPath *merge = castNode(MergeAppendPath, path);
PathKey *pk;
if (!ordered || path->pathkeys == NIL || list_length(merge->subpaths) == 0)
return false;
/* cannot support ordered append with OSM chunks. OSM chunk
* ranges are not recorded with the catalog
*/
if (ht && ts_chunk_get_osm_chunk_id(ht->fd.id) != INVALID_CHUNK_ID)
return false;
pk = linitial_node(PathKey, path->pathkeys);
/*
* Check PathKey is compatible with Ordered Append ordering
* we created when expanding hypertable.
* Even though ordered is true on the RelOptInfo we have to
* double check that current Path fulfills requirements for
* Ordered Append transformation because the RelOptInfo may
* be used for multiple Pathes.
*/
Expr *em_expr = find_em_expr_for_rel(pk->pk_eclass, rel);
/*
* If this is a join the ordering information might not be
* for the current rel and have no EquivalenceMember.
*/
if (!em_expr)
return false;
if (IsA(em_expr, Var) && castNode(Var, em_expr)->varattno == order_attno)
return true;
else if (IsA(em_expr, FuncExpr) && list_length(path->pathkeys) == 1)
{
FuncExpr *func = castNode(FuncExpr, em_expr);
FuncInfo *info = ts_func_cache_get_bucketing_func(func->funcid);
Expr *transformed;
if (info != NULL)
{
transformed = info->sort_transform(func);
if (IsA(transformed, Var) &&
castNode(Var, transformed)->varattno == order_attno)
return true;
}
}
return false;
break;
}
default:
return false;
}
}
static inline bool
should_constraint_aware_append(PlannerInfo *root, Hypertable *ht, Path *path)
{
/* Constraint-aware append currently expects children that scans a real
* "relation" (e.g., not an "upper" relation). So, we do not run it on a
* distributed hypertable because the append children are typically
* per-server relations without a corresponding "real" table in the
* system. Further, per-server appends shouldn't need runtime pruning in any
* case. */
if (root->parse->commandType != CMD_SELECT || hypertable_is_distributed(ht))
return false;
return ts_constraint_aware_append_possible(path);
}
static bool
rte_should_expand(const RangeTblEntry *rte)
{
bool is_hypertable = ts_rte_is_hypertable(rte, NULL);
return is_hypertable && !rte->inh && ts_rte_is_marked_for_expansion(rte);
}
static void
reenable_inheritance(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte)
{
bool set_pathlist_for_current_rel = false;
double total_pages;
bool reenabled_inheritance = false;
for (int i = 1; i < root->simple_rel_array_size; i++)
{
RangeTblEntry *in_rte = root->simple_rte_array[i];
if (rte_should_expand(in_rte))
{
RelOptInfo *in_rel = root->simple_rel_array[i];
Hypertable *ht = ts_planner_get_hypertable(in_rte->relid, CACHE_FLAG_NOCREATE);
Assert(ht != NULL && in_rel != NULL);
ts_plan_expand_hypertable_chunks(ht, root, in_rel);
in_rte->inh = true;
reenabled_inheritance = true;
/* Redo set_rel_consider_parallel, as results of the call may no longer be valid here
* (due to adding more tables to the set of tables under consideration here). This is
* especially true if dealing with foreign data wrappers. */
/*
* An entry of reloptkind RELOPT_OTHER_MEMBER_REL might still
* be a hypertable here if it was pulled up from a subquery
* as happens with UNION ALL for example.
*/
if (in_rel->reloptkind == RELOPT_BASEREL ||
in_rel->reloptkind == RELOPT_OTHER_MEMBER_REL)
{
Assert(in_rte->relkind == RELKIND_RELATION);
ts_set_rel_size(root, in_rel, i, in_rte);
}
/* if we're activating inheritance during a hypertable's pathlist
* creation then we're past the point at which postgres will add
* paths for the children, and we have to do it ourselves. We delay
* the actual setting of the pathlists until after this loop,
* because set_append_rel_pathlist will eventually call this hook again.
*/
if (in_rte == rte)
{
Assert(rti == (Index) i);
set_pathlist_for_current_rel = true;
}
}
}
if (!reenabled_inheritance)
return;
total_pages = 0;
for (int i = 1; i < root->simple_rel_array_size; i++)
{
RelOptInfo *brel = root->simple_rel_array[i];
if (brel == NULL)
continue;
Assert(brel->relid == (Index) i); /* sanity check on array */
if (IS_DUMMY_REL(brel))
continue;
if (IS_SIMPLE_REL(brel))
total_pages += (double) brel->pages;
}
root->total_table_pages = total_pages;