Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * partbounds.c
4 : : * Support routines for manipulating partition bounds
5 : : *
6 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
7 : : * Portions Copyright (c) 1994, Regents of the University of California
8 : : *
9 : : * IDENTIFICATION
10 : : * src/backend/partitioning/partbounds.c
11 : : *
12 : : *-------------------------------------------------------------------------
13 : : */
14 : :
15 : : #include "postgres.h"
16 : :
17 : : #include "access/relation.h"
18 : : #include "access/table.h"
19 : : #include "access/tableam.h"
20 : : #include "catalog/partition.h"
21 : : #include "catalog/pg_inherits.h"
22 : : #include "catalog/pg_type.h"
23 : : #include "commands/tablecmds.h"
24 : : #include "common/hashfn.h"
25 : : #include "executor/executor.h"
26 : : #include "miscadmin.h"
27 : : #include "nodes/makefuncs.h"
28 : : #include "nodes/nodeFuncs.h"
29 : : #include "nodes/pathnodes.h"
30 : : #include "parser/parse_coerce.h"
31 : : #include "partitioning/partbounds.h"
32 : : #include "partitioning/partdesc.h"
33 : : #include "utils/array.h"
34 : : #include "utils/builtins.h"
35 : : #include "utils/datum.h"
36 : : #include "utils/fmgroids.h"
37 : : #include "utils/lsyscache.h"
38 : : #include "utils/partcache.h"
39 : : #include "utils/ruleutils.h"
40 : : #include "utils/snapmgr.h"
41 : : #include "utils/syscache.h"
42 : :
43 : : /*
44 : : * When qsort'ing partition bounds after reading from the catalog, each bound
45 : : * is represented with one of the following structs.
46 : : */
47 : :
48 : : /* One bound of a hash partition */
49 : : typedef struct PartitionHashBound
50 : : {
51 : : int modulus;
52 : : int remainder;
53 : : int index;
54 : : } PartitionHashBound;
55 : :
56 : : /* One value coming from some (index'th) list partition */
57 : : typedef struct PartitionListValue
58 : : {
59 : : int index;
60 : : Datum value;
61 : : } PartitionListValue;
62 : :
63 : : /* One bound of a range partition */
64 : : typedef struct PartitionRangeBound
65 : : {
66 : : int index;
67 : : Datum *datums; /* range bound datums */
68 : : PartitionRangeDatumKind *kind; /* the kind of each datum */
69 : : bool lower; /* this is the lower (vs upper) bound */
70 : : } PartitionRangeBound;
71 : :
72 : : /*
73 : : * Mapping from partitions of a joining relation to partitions of a join
74 : : * relation being computed (a.k.a merged partitions)
75 : : */
76 : : typedef struct PartitionMap
77 : : {
78 : : int nparts; /* number of partitions */
79 : : int *merged_indexes; /* indexes of merged partitions */
80 : : bool *merged; /* flags to indicate whether partitions are
81 : : * merged with non-dummy partitions */
82 : : bool did_remapping; /* did we re-map partitions? */
83 : : int *old_indexes; /* old indexes of merged partitions if
84 : : * did_remapping */
85 : : } PartitionMap;
86 : :
87 : : /* Macro for comparing two range bounds */
88 : : #define compare_range_bounds(partnatts, partsupfunc, partcollations, \
89 : : bound1, bound2) \
90 : : (partition_rbound_cmp(partnatts, partsupfunc, partcollations, \
91 : : (bound1)->datums, (bound1)->kind, (bound1)->lower, \
92 : : bound2))
93 : :
94 : : static int32 qsort_partition_hbound_cmp(const void *a, const void *b);
95 : : static int32 qsort_partition_list_value_cmp(const void *a, const void *b,
96 : : void *arg);
97 : : static int32 qsort_partition_rbound_cmp(const void *a, const void *b,
98 : : void *arg);
99 : : static PartitionBoundInfo create_hash_bounds(PartitionBoundSpec **boundspecs,
100 : : int nparts, PartitionKey key, int **mapping);
101 : : static PartitionBoundInfo create_list_bounds(PartitionBoundSpec **boundspecs,
102 : : int nparts, PartitionKey key, int **mapping);
103 : : static PartitionBoundInfo create_range_bounds(PartitionBoundSpec **boundspecs,
104 : : int nparts, PartitionKey key, int **mapping);
105 : : static PartitionBoundInfo merge_list_bounds(FmgrInfo *partsupfunc,
106 : : Oid *partcollation,
107 : : RelOptInfo *outer_rel,
108 : : RelOptInfo *inner_rel,
109 : : JoinType jointype,
110 : : List **outer_parts,
111 : : List **inner_parts);
112 : : static PartitionBoundInfo merge_range_bounds(int partnatts,
113 : : FmgrInfo *partsupfuncs,
114 : : Oid *partcollations,
115 : : RelOptInfo *outer_rel,
116 : : RelOptInfo *inner_rel,
117 : : JoinType jointype,
118 : : List **outer_parts,
119 : : List **inner_parts);
120 : : static void init_partition_map(RelOptInfo *rel, PartitionMap *map);
121 : : static void free_partition_map(PartitionMap *map);
122 : : static bool is_dummy_partition(RelOptInfo *rel, int part_index);
123 : : static int merge_matching_partitions(PartitionMap *outer_map,
124 : : PartitionMap *inner_map,
125 : : int outer_index,
126 : : int inner_index,
127 : : int *next_index);
128 : : static int process_outer_partition(PartitionMap *outer_map,
129 : : PartitionMap *inner_map,
130 : : bool outer_has_default,
131 : : bool inner_has_default,
132 : : int outer_index,
133 : : int inner_default,
134 : : JoinType jointype,
135 : : int *next_index,
136 : : int *default_index);
137 : : static int process_inner_partition(PartitionMap *outer_map,
138 : : PartitionMap *inner_map,
139 : : bool outer_has_default,
140 : : bool inner_has_default,
141 : : int inner_index,
142 : : int outer_default,
143 : : JoinType jointype,
144 : : int *next_index,
145 : : int *default_index);
146 : : static void merge_null_partitions(PartitionMap *outer_map,
147 : : PartitionMap *inner_map,
148 : : bool outer_has_null,
149 : : bool inner_has_null,
150 : : int outer_null,
151 : : int inner_null,
152 : : JoinType jointype,
153 : : int *next_index,
154 : : int *null_index);
155 : : static void merge_default_partitions(PartitionMap *outer_map,
156 : : PartitionMap *inner_map,
157 : : bool outer_has_default,
158 : : bool inner_has_default,
159 : : int outer_default,
160 : : int inner_default,
161 : : JoinType jointype,
162 : : int *next_index,
163 : : int *default_index);
164 : : static int merge_partition_with_dummy(PartitionMap *map, int index,
165 : : int *next_index);
166 : : static void fix_merged_indexes(PartitionMap *outer_map,
167 : : PartitionMap *inner_map,
168 : : int nmerged, List *merged_indexes);
169 : : static void generate_matching_part_pairs(RelOptInfo *outer_rel,
170 : : RelOptInfo *inner_rel,
171 : : PartitionMap *outer_map,
172 : : PartitionMap *inner_map,
173 : : int nmerged,
174 : : List **outer_parts,
175 : : List **inner_parts);
176 : : static PartitionBoundInfo build_merged_partition_bounds(char strategy,
177 : : List *merged_datums,
178 : : List *merged_kinds,
179 : : List *merged_indexes,
180 : : int null_index,
181 : : int default_index);
182 : : static int get_range_partition(RelOptInfo *rel,
183 : : PartitionBoundInfo bi,
184 : : int *lb_pos,
185 : : PartitionRangeBound *lb,
186 : : PartitionRangeBound *ub);
187 : : static int get_range_partition_internal(PartitionBoundInfo bi,
188 : : int *lb_pos,
189 : : PartitionRangeBound *lb,
190 : : PartitionRangeBound *ub);
191 : : static bool compare_range_partitions(int partnatts, FmgrInfo *partsupfuncs,
192 : : Oid *partcollations,
193 : : PartitionRangeBound *outer_lb,
194 : : PartitionRangeBound *outer_ub,
195 : : PartitionRangeBound *inner_lb,
196 : : PartitionRangeBound *inner_ub,
197 : : int *lb_cmpval, int *ub_cmpval);
198 : : static void get_merged_range_bounds(int partnatts, FmgrInfo *partsupfuncs,
199 : : Oid *partcollations, JoinType jointype,
200 : : PartitionRangeBound *outer_lb,
201 : : PartitionRangeBound *outer_ub,
202 : : PartitionRangeBound *inner_lb,
203 : : PartitionRangeBound *inner_ub,
204 : : int lb_cmpval, int ub_cmpval,
205 : : PartitionRangeBound *merged_lb,
206 : : PartitionRangeBound *merged_ub);
207 : : static void add_merged_range_bounds(int partnatts, FmgrInfo *partsupfuncs,
208 : : Oid *partcollations,
209 : : PartitionRangeBound *merged_lb,
210 : : PartitionRangeBound *merged_ub,
211 : : int merged_index,
212 : : List **merged_datums,
213 : : List **merged_kinds,
214 : : List **merged_indexes);
215 : : static PartitionRangeBound *make_one_partition_rbound(PartitionKey key, int index,
216 : : List *datums, bool lower);
217 : : static int32 partition_hbound_cmp(int modulus1, int remainder1, int modulus2,
218 : : int remainder2);
219 : : static int32 partition_rbound_cmp(int partnatts, FmgrInfo *partsupfunc,
220 : : Oid *partcollation, Datum *datums1,
221 : : PartitionRangeDatumKind *kind1, bool lower1,
222 : : PartitionRangeBound *b2);
223 : : static int partition_range_bsearch(int partnatts, FmgrInfo *partsupfunc,
224 : : Oid *partcollation,
225 : : PartitionBoundInfo boundinfo,
226 : : PartitionRangeBound *probe, int32 *cmpval);
227 : : static Expr *make_partition_op_expr(PartitionKey key, int keynum,
228 : : uint16 strategy, Expr *arg1, Expr *arg2);
229 : : static Oid get_partition_operator(PartitionKey key, int col,
230 : : StrategyNumber strategy, bool *need_relabel);
231 : : static List *get_qual_for_hash(Relation parent, PartitionBoundSpec *spec);
232 : : static List *get_qual_for_list(Relation parent, PartitionBoundSpec *spec);
233 : : static List *get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
234 : : bool for_default);
235 : : static void get_range_key_properties(PartitionKey key, int keynum,
236 : : PartitionRangeDatum *ldatum,
237 : : PartitionRangeDatum *udatum,
238 : : ListCell **partexprs_item,
239 : : Expr **keyCol,
240 : : Const **lower_val, Const **upper_val);
241 : : static List *get_range_nulltest(PartitionKey key);
242 : :
243 : : /*
244 : : * get_qual_from_partbound
245 : : * Given a parser node for partition bound, return the list of executable
246 : : * expressions as partition constraint
247 : : */
248 : : List *
1870 john.naylor@postgres 249 :CBC 3689 : get_qual_from_partbound(Relation parent, PartitionBoundSpec *spec)
250 : : {
3057 alvherre@alvh.no-ip. 251 : 3689 : PartitionKey key = RelationGetPartitionKey(parent);
252 : 3689 : List *my_qual = NIL;
253 : :
254 [ - + ]: 3689 : Assert(key != NULL);
255 : :
256 [ + + + - ]: 3689 : switch (key->strategy)
257 : : {
258 : 93 : case PARTITION_STRATEGY_HASH:
259 [ - + ]: 93 : Assert(spec->strategy == PARTITION_STRATEGY_HASH);
260 : 93 : my_qual = get_qual_for_hash(parent, spec);
261 : 93 : break;
262 : :
263 : 1714 : case PARTITION_STRATEGY_LIST:
264 [ - + ]: 1714 : Assert(spec->strategy == PARTITION_STRATEGY_LIST);
265 : 1714 : my_qual = get_qual_for_list(parent, spec);
266 : 1714 : break;
267 : :
268 : 1882 : case PARTITION_STRATEGY_RANGE:
269 [ - + ]: 1882 : Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
270 : 1882 : my_qual = get_qual_for_range(parent, spec, false);
271 : 1882 : break;
272 : : }
273 : :
274 : 3689 : return my_qual;
275 : : }
276 : :
277 : : /*
278 : : * partition_bounds_create
279 : : * Build a PartitionBoundInfo struct from a list of PartitionBoundSpec
280 : : * nodes
281 : : *
282 : : * This function creates a PartitionBoundInfo and fills the values of its
283 : : * various members based on the input list. Importantly, 'datums' array will
284 : : * contain Datum representation of individual bounds (possibly after
285 : : * de-duplication as in case of range bounds), sorted in a canonical order
286 : : * defined by qsort_partition_* functions of respective partitioning methods.
287 : : * 'indexes' array will contain as many elements as there are bounds (specific
288 : : * exceptions to this rule are listed in the function body), which represent
289 : : * the 0-based canonical positions of partitions.
290 : : *
291 : : * Upon return from this function, *mapping is set to an array of
292 : : * list_length(boundspecs) elements, each of which maps the original index of
293 : : * a partition to its canonical index.
294 : : *
295 : : * Note: The objects returned by this function are wholly allocated in the
296 : : * current memory context.
297 : : */
298 : : PartitionBoundInfo
2838 rhaas@postgresql.org 299 : 11395 : partition_bounds_create(PartitionBoundSpec **boundspecs, int nparts,
300 : : PartitionKey key, int **mapping)
301 : : {
302 : : int i;
303 : :
2843 michael@paquier.xyz 304 [ - + ]: 11395 : Assert(nparts > 0);
305 : :
306 : : /*
307 : : * For each partitioning method, we first convert the partition bounds
308 : : * from their parser node representation to the internal representation,
309 : : * along with any additional preprocessing (such as de-duplicating range
310 : : * bounds). Resulting bound datums are then added to the 'datums' array
311 : : * in PartitionBoundInfo. For each datum added, an integer indicating the
312 : : * canonical partition index is added to the 'indexes' array.
313 : : *
314 : : * For each bound, we remember its partition's position (0-based) in the
315 : : * original list to later map it to the canonical index.
316 : : */
317 : :
318 : : /*
319 : : * Initialize mapping array with invalid values, this is filled within
320 : : * each sub-routine below depending on the bound type.
321 : : */
260 322 : 11395 : *mapping = palloc_array(int, nparts);
2843 323 [ + + ]: 71321 : for (i = 0; i < nparts; i++)
324 : 59926 : (*mapping)[i] = -1;
325 : :
326 [ + + + - ]: 11395 : switch (key->strategy)
327 : : {
328 : 522 : case PARTITION_STRATEGY_HASH:
2838 rhaas@postgresql.org 329 : 522 : return create_hash_bounds(boundspecs, nparts, key, mapping);
330 : :
2843 michael@paquier.xyz 331 : 5322 : case PARTITION_STRATEGY_LIST:
2838 rhaas@postgresql.org 332 : 5322 : return create_list_bounds(boundspecs, nparts, key, mapping);
333 : :
2843 michael@paquier.xyz 334 : 5551 : case PARTITION_STRATEGY_RANGE:
2838 rhaas@postgresql.org 335 : 5551 : return create_range_bounds(boundspecs, nparts, key, mapping);
336 : : }
337 : :
2843 michael@paquier.xyz 338 :UBC 0 : Assert(false);
339 : : return NULL; /* keep compiler quiet */
340 : : }
341 : :
342 : : /*
343 : : * create_hash_bounds
344 : : * Create a PartitionBoundInfo for a hash partitioned table
345 : : */
346 : : static PartitionBoundInfo
2838 rhaas@postgresql.org 347 :CBC 522 : create_hash_bounds(PartitionBoundSpec **boundspecs, int nparts,
348 : : PartitionKey key, int **mapping)
349 : : {
350 : : PartitionBoundInfo boundinfo;
351 : : PartitionHashBound *hbounds;
352 : : int i;
353 : : int greatest_modulus;
354 : : Datum *boundDatums;
355 : :
260 michael@paquier.xyz 356 : 522 : boundinfo = palloc0_object(PartitionBoundInfoData);
2843 357 : 522 : boundinfo->strategy = key->strategy;
358 : : /* No special hash partitions. */
359 : 522 : boundinfo->null_index = -1;
360 : 522 : boundinfo->default_index = -1;
361 : :
260 362 : 522 : hbounds = palloc_array(PartitionHashBound, nparts);
363 : :
364 : : /* Convert from node to the internal representation */
2838 rhaas@postgresql.org 365 [ + + ]: 1644 : for (i = 0; i < nparts; i++)
366 : : {
367 : 1122 : PartitionBoundSpec *spec = boundspecs[i];
368 : :
2843 michael@paquier.xyz 369 [ - + ]: 1122 : if (spec->strategy != PARTITION_STRATEGY_HASH)
2843 michael@paquier.xyz 370 [ # # ]:UBC 0 : elog(ERROR, "invalid strategy in partition bound spec");
371 : :
1878 drowley@postgresql.o 372 :CBC 1122 : hbounds[i].modulus = spec->modulus;
373 : 1122 : hbounds[i].remainder = spec->remainder;
374 : 1122 : hbounds[i].index = i;
375 : : }
376 : :
377 : : /* Sort all the bounds in ascending order */
378 : 522 : qsort(hbounds, nparts, sizeof(PartitionHashBound),
379 : : qsort_partition_hbound_cmp);
380 : :
381 : : /* After sorting, moduli are now stored in ascending order. */
382 : 522 : greatest_modulus = hbounds[nparts - 1].modulus;
383 : :
384 : 522 : boundinfo->ndatums = nparts;
260 michael@paquier.xyz 385 : 522 : boundinfo->datums = palloc0_array(Datum *, nparts);
1878 drowley@postgresql.o 386 : 522 : boundinfo->kind = NULL;
1850 387 : 522 : boundinfo->interleaved_parts = NULL;
2037 tgl@sss.pgh.pa.us 388 : 522 : boundinfo->nindexes = greatest_modulus;
10 michael@paquier.xyz 389 :GNC 522 : boundinfo->indexes = palloc_array(int, greatest_modulus);
2843 michael@paquier.xyz 390 [ + + ]:CBC 4182 : for (i = 0; i < greatest_modulus; i++)
391 : 3660 : boundinfo->indexes[i] = -1;
392 : :
393 : : /*
394 : : * In the loop below, to save from allocating a series of small datum
395 : : * arrays, here we just allocate a single array and below we'll just
396 : : * assign a portion of this array per partition.
397 : : */
10 michael@paquier.xyz 398 :GNC 522 : boundDatums = palloc_array(Datum, nparts * 2);
399 : :
400 : : /*
401 : : * For hash partitioning, there are as many datums (modulus and remainder
402 : : * pairs) as there are partitions. Indexes are simply values ranging from
403 : : * 0 to (nparts - 1).
404 : : */
2843 michael@paquier.xyz 405 [ + + ]:CBC 1644 : for (i = 0; i < nparts; i++)
406 : : {
1878 drowley@postgresql.o 407 : 1122 : int modulus = hbounds[i].modulus;
408 : 1122 : int remainder = hbounds[i].remainder;
409 : :
410 : 1122 : boundinfo->datums[i] = &boundDatums[i * 2];
2843 michael@paquier.xyz 411 : 1122 : boundinfo->datums[i][0] = Int32GetDatum(modulus);
412 : 1122 : boundinfo->datums[i][1] = Int32GetDatum(remainder);
413 : :
414 [ + + ]: 2560 : while (remainder < greatest_modulus)
415 : : {
416 : : /* overlap? */
417 [ - + ]: 1438 : Assert(boundinfo->indexes[remainder] == -1);
418 : 1438 : boundinfo->indexes[remainder] = i;
419 : 1438 : remainder += modulus;
420 : : }
421 : :
1878 drowley@postgresql.o 422 : 1122 : (*mapping)[hbounds[i].index] = i;
423 : : }
2843 michael@paquier.xyz 424 : 522 : pfree(hbounds);
425 : :
426 : 522 : return boundinfo;
427 : : }
428 : :
429 : : /*
430 : : * get_non_null_list_datum_count
431 : : * Counts the number of non-null Datums in each partition.
432 : : */
433 : : static int
1878 drowley@postgresql.o 434 : 5322 : get_non_null_list_datum_count(PartitionBoundSpec **boundspecs, int nparts)
435 : : {
436 : : int i;
437 : 5322 : int count = 0;
438 : :
439 [ + + ]: 15711 : for (i = 0; i < nparts; i++)
440 : : {
441 : : ListCell *lc;
442 : :
443 [ + + + + : 25884 : foreach(lc, boundspecs[i]->listdatums)
+ + ]
444 : : {
1865 peter@eisentraut.org 445 : 15495 : Const *val = lfirst_node(Const, lc);
446 : :
1878 drowley@postgresql.o 447 [ + + ]: 15495 : if (!val->constisnull)
448 : 15090 : count++;
449 : : }
450 : : }
451 : :
452 : 5322 : return count;
453 : : }
454 : :
455 : : /*
456 : : * create_list_bounds
457 : : * Create a PartitionBoundInfo for a list partitioned table
458 : : */
459 : : static PartitionBoundInfo
2838 rhaas@postgresql.org 460 : 5322 : create_list_bounds(PartitionBoundSpec **boundspecs, int nparts,
461 : : PartitionKey key, int **mapping)
462 : : {
463 : : PartitionBoundInfo boundinfo;
464 : : PartitionListValue *all_values;
465 : : int i;
466 : : int j;
467 : : int ndatums;
2843 michael@paquier.xyz 468 : 5322 : int next_index = 0;
469 : 5322 : int default_index = -1;
470 : 5322 : int null_index = -1;
471 : : Datum *boundDatums;
472 : :
260 473 : 5322 : boundinfo = palloc0_object(PartitionBoundInfoData);
2843 474 : 5322 : boundinfo->strategy = key->strategy;
475 : : /* Will be set correctly below. */
476 : 5322 : boundinfo->null_index = -1;
477 : 5322 : boundinfo->default_index = -1;
478 : :
1878 drowley@postgresql.o 479 : 5322 : ndatums = get_non_null_list_datum_count(boundspecs, nparts);
10 michael@paquier.xyz 480 :GNC 5322 : all_values = palloc_array(PartitionListValue, ndatums);
481 : :
482 : : /* Create a unified list of non-null values across all partitions. */
1878 drowley@postgresql.o 483 [ + + ]:CBC 15711 : for (j = 0, i = 0; i < nparts; i++)
484 : : {
2838 rhaas@postgresql.org 485 : 10389 : PartitionBoundSpec *spec = boundspecs[i];
486 : : ListCell *c;
487 : :
2843 michael@paquier.xyz 488 [ - + ]: 10389 : if (spec->strategy != PARTITION_STRATEGY_LIST)
2843 michael@paquier.xyz 489 [ # # ]:UBC 0 : elog(ERROR, "invalid strategy in partition bound spec");
490 : :
491 : : /*
492 : : * Note the index of the partition bound spec for the default
493 : : * partition. There's no datum to add to the list on non-null datums
494 : : * for this partition.
495 : : */
2843 michael@paquier.xyz 496 [ + + ]:CBC 10389 : if (spec->is_default)
497 : : {
498 : 570 : default_index = i;
499 : 570 : continue;
500 : : }
501 : :
502 [ + - + + : 25314 : foreach(c, spec->listdatums)
+ + ]
503 : : {
1865 peter@eisentraut.org 504 : 15495 : Const *val = lfirst_node(Const, c);
505 : :
2843 michael@paquier.xyz 506 [ + + ]: 15495 : if (!val->constisnull)
507 : : {
1878 drowley@postgresql.o 508 : 15090 : all_values[j].index = i;
509 : 15090 : all_values[j].value = val->constvalue;
510 : 15090 : j++;
511 : : }
512 : : else
513 : : {
514 : : /*
515 : : * Never put a null into the values array; save the index of
516 : : * the partition that stores nulls, instead.
517 : : */
2843 michael@paquier.xyz 518 [ - + ]: 405 : if (null_index != -1)
2843 michael@paquier.xyz 519 [ # # ]:UBC 0 : elog(ERROR, "found null more than once");
2843 michael@paquier.xyz 520 :CBC 405 : null_index = i;
521 : : }
522 : : }
523 : : }
524 : :
525 : : /* ensure we found a Datum for every slot in the all_values array */
1878 drowley@postgresql.o 526 [ - + ]: 5322 : Assert(j == ndatums);
527 : :
528 : 5322 : qsort_arg(all_values, ndatums, sizeof(PartitionListValue),
529 : : qsort_partition_list_value_cmp, key);
530 : :
2843 michael@paquier.xyz 531 : 5322 : boundinfo->ndatums = ndatums;
260 532 : 5322 : boundinfo->datums = palloc0_array(Datum *, ndatums);
1878 drowley@postgresql.o 533 : 5322 : boundinfo->kind = NULL;
1850 534 : 5322 : boundinfo->interleaved_parts = NULL;
2037 tgl@sss.pgh.pa.us 535 : 5322 : boundinfo->nindexes = ndatums;
10 michael@paquier.xyz 536 :GNC 5322 : boundinfo->indexes = palloc_array(int, ndatums);
537 : :
538 : : /*
539 : : * In the loop below, to save from allocating a series of small datum
540 : : * arrays, here we just allocate a single array and below we'll just
541 : : * assign a portion of this array per datum.
542 : : */
543 : 5322 : boundDatums = palloc_array(Datum, ndatums);
544 : :
545 : : /*
546 : : * Copy values. Canonical indexes are values ranging from 0 to (nparts -
547 : : * 1) assigned to each partition such that all datums of a given partition
548 : : * receive the same value. The value for a given partition is the index of
549 : : * that partition's smallest datum in the all_values[] array.
550 : : */
2843 michael@paquier.xyz 551 [ + + ]:CBC 20412 : for (i = 0; i < ndatums; i++)
552 : : {
1878 drowley@postgresql.o 553 : 15090 : int orig_index = all_values[i].index;
554 : :
555 : 15090 : boundinfo->datums[i] = &boundDatums[i];
556 : 30180 : boundinfo->datums[i][0] = datumCopy(all_values[i].value,
2843 michael@paquier.xyz 557 : 15090 : key->parttypbyval[0],
558 : 15090 : key->parttyplen[0]);
559 : :
560 : : /* If the old index has no mapping, assign one */
561 [ + + ]: 15090 : if ((*mapping)[orig_index] == -1)
562 : 9679 : (*mapping)[orig_index] = next_index++;
563 : :
564 : 15090 : boundinfo->indexes[i] = (*mapping)[orig_index];
565 : : }
566 : :
1878 drowley@postgresql.o 567 : 5322 : pfree(all_values);
568 : :
569 : : /*
570 : : * Set the canonical value for null_index, if any.
571 : : *
572 : : * It is possible that the null-accepting partition has not been assigned
573 : : * an index yet, which could happen if such partition accepts only null
574 : : * and hence not handled in the above loop which only looked at non-null
575 : : * values.
576 : : */
2843 michael@paquier.xyz 577 [ + + ]: 5322 : if (null_index != -1)
578 : : {
579 [ - + ]: 405 : Assert(null_index >= 0);
580 [ + + ]: 405 : if ((*mapping)[null_index] == -1)
581 : 140 : (*mapping)[null_index] = next_index++;
582 : 405 : boundinfo->null_index = (*mapping)[null_index];
583 : : }
584 : :
585 : : /* Set the canonical value for default_index, if any. */
586 [ + + ]: 5322 : if (default_index != -1)
587 : : {
588 : : /*
589 : : * The default partition accepts any value not specified in the lists
590 : : * of other partitions, hence it should not get mapped index while
591 : : * assigning those for non-null datums.
592 : : */
593 [ - + ]: 570 : Assert(default_index >= 0);
594 [ - + ]: 570 : Assert((*mapping)[default_index] == -1);
595 : 570 : (*mapping)[default_index] = next_index++;
596 : 570 : boundinfo->default_index = (*mapping)[default_index];
597 : : }
598 : :
599 : : /*
600 : : * Calculate interleaved partitions. Here we look for partitions which
601 : : * might be interleaved with other partitions and set a bit in
602 : : * interleaved_parts for any partitions which may be interleaved with
603 : : * another partition.
604 : : */
605 : :
606 : : /*
607 : : * There must be multiple partitions to have any interleaved partitions,
608 : : * otherwise there's nothing to interleave with.
609 : : */
1850 drowley@postgresql.o 610 [ + + ]: 5322 : if (nparts > 1)
611 : : {
612 : : /*
613 : : * Short-circuit check to see if only 1 Datum is allowed per
614 : : * partition. When this is true there's no need to do the more
615 : : * expensive checks to look for interleaved values.
616 : : */
617 : 3209 : if (boundinfo->ndatums +
618 : 3209 : partition_bound_accepts_nulls(boundinfo) +
619 [ + + ]: 3209 : partition_bound_has_default(boundinfo) != nparts)
620 : : {
621 : 1347 : int last_index = -1;
622 : :
623 : : /*
624 : : * Since the indexes array is sorted in Datum order, if any
625 : : * partitions are interleaved then it will show up by the
626 : : * partition indexes not being in ascending order. Here we check
627 : : * for that and record all partitions that are out of order.
628 : : */
629 [ + + ]: 9517 : for (i = 0; i < boundinfo->nindexes; i++)
630 : : {
631 : 8170 : int index = boundinfo->indexes[i];
632 : :
633 [ + + ]: 8170 : if (index < last_index)
634 : 703 : boundinfo->interleaved_parts = bms_add_member(boundinfo->interleaved_parts,
635 : : index);
636 : :
637 : : /*
638 : : * Otherwise, if the null_index exists in the indexes array,
639 : : * then the NULL partition must also allow some other Datum,
640 : : * therefore it's "interleaved".
641 : : */
1506 642 [ + + ]: 7467 : else if (partition_bound_accepts_nulls(boundinfo) &&
643 [ + + ]: 2107 : index == boundinfo->null_index)
1850 644 : 577 : boundinfo->interleaved_parts = bms_add_member(boundinfo->interleaved_parts,
645 : : index);
646 : :
647 : 8170 : last_index = index;
648 : : }
649 : : }
650 : :
651 : : /*
652 : : * The DEFAULT partition is the "catch-all" partition that can contain
653 : : * anything that does not belong to any other partition. If there are
654 : : * any other partitions then the DEFAULT partition must be marked as
655 : : * interleaved.
656 : : */
657 [ + + ]: 3209 : if (partition_bound_has_default(boundinfo))
658 : 497 : boundinfo->interleaved_parts = bms_add_member(boundinfo->interleaved_parts,
659 : : boundinfo->default_index);
660 : : }
661 : :
662 : :
663 : : /* All partitions must now have been assigned canonical indexes. */
2838 rhaas@postgresql.org 664 [ - + ]: 5322 : Assert(next_index == nparts);
2843 michael@paquier.xyz 665 : 5322 : return boundinfo;
666 : : }
667 : :
668 : : /*
669 : : * create_range_bounds
670 : : * Create a PartitionBoundInfo for a range partitioned table
671 : : */
672 : : static PartitionBoundInfo
2838 rhaas@postgresql.org 673 : 5551 : create_range_bounds(PartitionBoundSpec **boundspecs, int nparts,
674 : : PartitionKey key, int **mapping)
675 : : {
676 : : PartitionBoundInfo boundinfo;
2843 michael@paquier.xyz 677 : 5551 : PartitionRangeBound **rbounds = NULL;
678 : : PartitionRangeBound **all_bounds,
679 : : *prev;
680 : : int i,
681 : : k,
682 : : partnatts;
683 : 5551 : int ndatums = 0;
684 : 5551 : int default_index = -1;
685 : 5551 : int next_index = 0;
686 : : Datum *boundDatums;
687 : : PartitionRangeDatumKind *boundKinds;
688 : :
260 689 : 5551 : boundinfo = palloc0_object(PartitionBoundInfoData);
2843 690 : 5551 : boundinfo->strategy = key->strategy;
691 : : /* There is no special null-accepting range partition. */
692 : 5551 : boundinfo->null_index = -1;
693 : : /* Will be set correctly below. */
694 : 5551 : boundinfo->default_index = -1;
695 : :
260 696 : 5551 : all_bounds = palloc0_array(PartitionRangeBound *, 2 * nparts);
697 : :
698 : : /* Create a unified list of range bounds across all the partitions. */
2838 rhaas@postgresql.org 699 : 5551 : ndatums = 0;
700 [ + + ]: 53966 : for (i = 0; i < nparts; i++)
701 : : {
702 : 48415 : PartitionBoundSpec *spec = boundspecs[i];
703 : : PartitionRangeBound *lower,
704 : : *upper;
705 : :
2843 michael@paquier.xyz 706 [ - + ]: 48415 : if (spec->strategy != PARTITION_STRATEGY_RANGE)
2843 michael@paquier.xyz 707 [ # # ]:UBC 0 : elog(ERROR, "invalid strategy in partition bound spec");
708 : :
709 : : /*
710 : : * Note the index of the partition bound spec for the default
711 : : * partition. There's no datum to add to the all_bounds array for
712 : : * this partition.
713 : : */
2843 michael@paquier.xyz 714 [ + + ]:CBC 48415 : if (spec->is_default)
715 : : {
2838 rhaas@postgresql.org 716 : 515 : default_index = i;
2843 michael@paquier.xyz 717 : 515 : continue;
718 : : }
719 : :
720 : 47900 : lower = make_one_partition_rbound(key, i, spec->lowerdatums, true);
721 : 47900 : upper = make_one_partition_rbound(key, i, spec->upperdatums, false);
722 : 47900 : all_bounds[ndatums++] = lower;
723 : 47900 : all_bounds[ndatums++] = upper;
724 : : }
725 : :
726 [ + + + - : 5551 : Assert(ndatums == nparts * 2 ||
- + ]
727 : : (default_index != -1 && ndatums == (nparts - 1) * 2));
728 : :
729 : : /* Sort all the bounds in ascending order */
730 : 5551 : qsort_arg(all_bounds, ndatums,
731 : : sizeof(PartitionRangeBound *),
732 : : qsort_partition_rbound_cmp,
733 : : key);
734 : :
735 : : /* Save distinct bounds from all_bounds into rbounds. */
10 michael@paquier.xyz 736 :GNC 5551 : rbounds = palloc_array(PartitionRangeBound *, ndatums);
2843 michael@paquier.xyz 737 :CBC 5551 : k = 0;
738 : 5551 : prev = NULL;
739 [ + + ]: 101351 : for (i = 0; i < ndatums; i++)
740 : : {
741 : 95800 : PartitionRangeBound *cur = all_bounds[i];
742 : 95800 : bool is_distinct = false;
743 : : int j;
744 : :
745 : : /* Is the current bound distinct from the previous one? */
746 [ + + ]: 140142 : for (j = 0; j < key->partnatts; j++)
747 : : {
748 : : Datum cmpval;
749 : :
750 [ + + + + ]: 98385 : if (prev == NULL || cur->kind[j] != prev->kind[j])
751 : : {
752 : 6396 : is_distinct = true;
753 : 6396 : break;
754 : : }
755 : :
756 : : /*
757 : : * If the bounds are both MINVALUE or MAXVALUE, stop now and treat
758 : : * them as equal, since any values after this point must be
759 : : * ignored.
760 : : */
761 [ + + ]: 91989 : if (cur->kind[j] != PARTITION_RANGE_DATUM_VALUE)
762 : 124 : break;
763 : :
764 : 91865 : cmpval = FunctionCall2Coll(&key->partsupfunc[j],
765 : 91865 : key->partcollation[j],
766 : 91865 : cur->datums[j],
767 : 91865 : prev->datums[j]);
768 [ + + ]: 91865 : if (DatumGetInt32(cmpval) != 0)
769 : : {
770 : 47523 : is_distinct = true;
771 : 47523 : break;
772 : : }
773 : : }
774 : :
775 : : /*
776 : : * Only if the bound is distinct save it into a temporary array, i.e,
777 : : * rbounds which is later copied into boundinfo datums array.
778 : : */
779 [ + + ]: 95800 : if (is_distinct)
780 : 53919 : rbounds[k++] = all_bounds[i];
781 : :
782 : 95800 : prev = cur;
783 : : }
784 : :
1878 drowley@postgresql.o 785 : 5551 : pfree(all_bounds);
786 : :
787 : : /* Update ndatums to hold the count of distinct datums. */
2843 michael@paquier.xyz 788 : 5551 : ndatums = k;
789 : :
790 : : /*
791 : : * Add datums to boundinfo. Canonical indexes are values ranging from 0
792 : : * to nparts - 1, assigned in that order to each partition's upper bound.
793 : : * For 'datums' elements that are lower bounds, there is -1 in the
794 : : * 'indexes' array to signify that no partition exists for the values less
795 : : * than such a bound and greater than or equal to the previous upper
796 : : * bound.
797 : : */
798 : 5551 : boundinfo->ndatums = ndatums;
260 799 : 5551 : boundinfo->datums = palloc0_array(Datum *, ndatums);
800 : 5551 : boundinfo->kind = palloc0_array(PartitionRangeDatumKind *, ndatums);
1850 drowley@postgresql.o 801 : 5551 : boundinfo->interleaved_parts = NULL;
802 : :
803 : : /*
804 : : * For range partitioning, an additional value of -1 is stored as the last
805 : : * element of the indexes[] array.
806 : : */
2037 tgl@sss.pgh.pa.us 807 : 5551 : boundinfo->nindexes = ndatums + 1;
260 michael@paquier.xyz 808 : 5551 : boundinfo->indexes = palloc_array(int, (ndatums + 1));
809 : :
810 : : /*
811 : : * In the loop below, to save from allocating a series of small arrays,
812 : : * here we just allocate a single array for Datums and another for
813 : : * PartitionRangeDatumKinds, below we'll just assign a portion of these
814 : : * arrays in each loop.
815 : : */
1878 drowley@postgresql.o 816 : 5551 : partnatts = key->partnatts;
10 michael@paquier.xyz 817 :GNC 5551 : boundDatums = palloc_array(Datum, ndatums * partnatts);
260 michael@paquier.xyz 818 :CBC 5551 : boundKinds = palloc_array(PartitionRangeDatumKind, ndatums * partnatts);
819 : :
2843 820 [ + + ]: 59470 : for (i = 0; i < ndatums; i++)
821 : : {
822 : : int j;
823 : :
1878 drowley@postgresql.o 824 : 53919 : boundinfo->datums[i] = &boundDatums[i * partnatts];
825 : 53919 : boundinfo->kind[i] = &boundKinds[i * partnatts];
826 [ + + ]: 111763 : for (j = 0; j < partnatts; j++)
827 : : {
2843 michael@paquier.xyz 828 [ + + ]: 57844 : if (rbounds[i]->kind[j] == PARTITION_RANGE_DATUM_VALUE)
829 : 56210 : boundinfo->datums[i][j] =
830 : 56210 : datumCopy(rbounds[i]->datums[j],
831 : 56210 : key->parttypbyval[j],
832 : 56210 : key->parttyplen[j]);
833 : 57844 : boundinfo->kind[i][j] = rbounds[i]->kind[j];
834 : : }
835 : :
836 : : /*
837 : : * There is no mapping for invalid indexes.
838 : : *
839 : : * Any lower bounds in the rbounds array have invalid indexes
840 : : * assigned, because the values between the previous bound (if there
841 : : * is one) and this (lower) bound are not part of the range of any
842 : : * existing partition.
843 : : */
844 [ + + ]: 53919 : if (rbounds[i]->lower)
845 : 6019 : boundinfo->indexes[i] = -1;
846 : : else
847 : : {
848 : 47900 : int orig_index = rbounds[i]->index;
849 : :
850 : : /* If the old index has no mapping, assign one */
851 [ + - ]: 47900 : if ((*mapping)[orig_index] == -1)
852 : 47900 : (*mapping)[orig_index] = next_index++;
853 : :
854 : 47900 : boundinfo->indexes[i] = (*mapping)[orig_index];
855 : : }
856 : : }
857 : :
1878 drowley@postgresql.o 858 : 5551 : pfree(rbounds);
859 : :
860 : : /* Set the canonical value for default_index, if any. */
2843 michael@paquier.xyz 861 [ + + ]: 5551 : if (default_index != -1)
862 : : {
863 [ + - - + ]: 515 : Assert(default_index >= 0 && (*mapping)[default_index] == -1);
864 : 515 : (*mapping)[default_index] = next_index++;
865 : 515 : boundinfo->default_index = (*mapping)[default_index];
866 : : }
867 : :
868 : : /* The extra -1 element. */
869 [ - + ]: 5551 : Assert(i == ndatums);
870 : 5551 : boundinfo->indexes[i] = -1;
871 : :
872 : : /* All partitions must now have been assigned canonical indexes. */
873 [ - + ]: 5551 : Assert(next_index == nparts);
874 : 5551 : return boundinfo;
875 : : }
876 : :
877 : : /*
878 : : * Are two partition bound collections logically equal?
879 : : *
880 : : * Used in the keep logic of relcache.c (ie, in RelationClearRelation()).
881 : : * This is also useful when b1 and b2 are bound collections of two separate
882 : : * relations, respectively, because PartitionBoundInfo is a canonical
883 : : * representation of partition bounds.
884 : : */
885 : : bool
3057 alvherre@alvh.no-ip. 886 : 5902 : partition_bounds_equal(int partnatts, int16 *parttyplen, bool *parttypbyval,
887 : : PartitionBoundInfo b1, PartitionBoundInfo b2)
888 : : {
889 : : int i;
890 : :
891 [ - + ]: 5902 : if (b1->strategy != b2->strategy)
3057 alvherre@alvh.no-ip. 892 :UBC 0 : return false;
893 : :
3057 alvherre@alvh.no-ip. 894 [ + + ]:CBC 5902 : if (b1->ndatums != b2->ndatums)
895 : 185 : return false;
896 : :
2037 tgl@sss.pgh.pa.us 897 [ - + ]: 5717 : if (b1->nindexes != b2->nindexes)
2037 tgl@sss.pgh.pa.us 898 :UBC 0 : return false;
899 : :
3057 alvherre@alvh.no-ip. 900 [ + + ]:CBC 5717 : if (b1->null_index != b2->null_index)
901 : 60 : return false;
902 : :
903 [ - + ]: 5657 : if (b1->default_index != b2->default_index)
3057 alvherre@alvh.no-ip. 904 :UBC 0 : return false;
905 : :
906 : : /* For all partition strategies, the indexes[] arrays have to match */
2037 tgl@sss.pgh.pa.us 907 [ + + ]:CBC 31638 : for (i = 0; i < b1->nindexes; i++)
908 : : {
909 [ + + ]: 26021 : if (b1->indexes[i] != b2->indexes[i])
3057 alvherre@alvh.no-ip. 910 : 40 : return false;
911 : : }
912 : :
913 : : /* Finally, compare the datums[] arrays */
2037 tgl@sss.pgh.pa.us 914 [ + + ]: 5617 : if (b1->strategy == PARTITION_STRATEGY_HASH)
915 : : {
916 : : /*
917 : : * We arrange the partitions in the ascending order of their moduli
918 : : * and remainders. Also every modulus is factor of next larger
919 : : * modulus. Therefore we can safely store index of a given partition
920 : : * in indexes array at remainder of that partition. Also entries at
921 : : * (remainder + N * modulus) positions in indexes array are all same
922 : : * for (modulus, remainder) specification for any partition. Thus the
923 : : * datums arrays from the given bounds are the same, if and only if
924 : : * their indexes arrays are the same. So, it suffices to compare the
925 : : * indexes arrays.
926 : : *
927 : : * Nonetheless make sure that the bounds are indeed the same when the
928 : : * indexes match. Hash partition bound stores modulus and remainder
929 : : * at b1->datums[i][0] and b1->datums[i][1] position respectively.
930 : : */
931 : : #ifdef USE_ASSERT_CHECKING
3057 alvherre@alvh.no-ip. 932 [ + + ]: 240 : for (i = 0; i < b1->ndatums; i++)
933 [ + - - + ]: 180 : Assert((b1->datums[i][0] == b2->datums[i][0] &&
934 : : b1->datums[i][1] == b2->datums[i][1]));
935 : : #endif
936 : : }
937 : : else
938 : : {
939 [ + + ]: 25256 : for (i = 0; i < b1->ndatums; i++)
940 : : {
941 : : int j;
942 : :
943 [ + + ]: 39593 : for (j = 0; j < partnatts; j++)
944 : : {
945 : : /* For range partitions, the bounds might not be finite. */
946 [ + + ]: 19894 : if (b1->kind != NULL)
947 : : {
948 : : /* The different kinds of bound all differ from each other */
949 [ - + ]: 18819 : if (b1->kind[i][j] != b2->kind[i][j])
3057 alvherre@alvh.no-ip. 950 :UBC 0 : return false;
951 : :
952 : : /*
953 : : * Non-finite bounds are equal without further
954 : : * examination.
955 : : */
3057 alvherre@alvh.no-ip. 956 [ - + ]:CBC 18819 : if (b1->kind[i][j] != PARTITION_RANGE_DATUM_VALUE)
3057 alvherre@alvh.no-ip. 957 :UBC 0 : continue;
958 : : }
959 : :
960 : : /*
961 : : * Compare the actual values. Note that it would be both
962 : : * incorrect and unsafe to invoke the comparison operator
963 : : * derived from the partitioning specification here. It would
964 : : * be incorrect because we want the relcache entry to be
965 : : * updated for ANY change to the partition bounds, not just
966 : : * those that the partitioning operator thinks are
967 : : * significant. It would be unsafe because we might reach
968 : : * this code in the context of an aborted transaction, and an
969 : : * arbitrary partitioning operator might not be safe in that
970 : : * context. datumIsEqual() should be simple enough to be
971 : : * safe.
972 : : */
3057 alvherre@alvh.no-ip. 973 [ + + ]:CBC 19894 : if (!datumIsEqual(b1->datums[i][j], b2->datums[i][j],
974 : 19894 : parttypbyval[j], parttyplen[j]))
975 : 155 : return false;
976 : : }
977 : : }
978 : : }
979 : 5462 : return true;
980 : : }
981 : :
982 : : /*
983 : : * Return a copy of given PartitionBoundInfo structure. The data types of bounds
984 : : * are described by given partition key specification.
985 : : *
986 : : * Note: it's important that this function and its callees not do any catalog
987 : : * access, nor anything else that would result in allocating memory other than
988 : : * the returned data structure. Since this is called in a long-lived context,
989 : : * that would result in unwanted memory leaks.
990 : : */
991 : : PartitionBoundInfo
992 : 11395 : partition_bounds_copy(PartitionBoundInfo src,
993 : : PartitionKey key)
994 : : {
995 : : PartitionBoundInfo dest;
996 : : int i;
997 : : int ndatums;
998 : : int nindexes;
999 : : int partnatts;
1000 : :
260 michael@paquier.xyz 1001 : 11395 : dest = (PartitionBoundInfo) palloc_object(PartitionBoundInfoData);
1002 : :
3057 alvherre@alvh.no-ip. 1003 : 11395 : dest->strategy = src->strategy;
1004 : 11395 : ndatums = dest->ndatums = src->ndatums;
2037 tgl@sss.pgh.pa.us 1005 : 11395 : nindexes = dest->nindexes = src->nindexes;
3057 alvherre@alvh.no-ip. 1006 : 11395 : partnatts = key->partnatts;
1007 : :
1008 : : /* List partitioned tables have only a single partition key. */
1009 [ + + - + ]: 11395 : Assert(key->strategy != PARTITION_STRATEGY_LIST || partnatts == 1);
1010 : :
260 michael@paquier.xyz 1011 : 11395 : dest->datums = palloc_array(Datum *, ndatums);
1012 : :
390 tgl@sss.pgh.pa.us 1013 [ + + + + ]: 11395 : if (src->kind != NULL && ndatums > 0)
3057 alvherre@alvh.no-ip. 1014 : 5442 : {
1015 : : PartitionRangeDatumKind *boundKinds;
1016 : :
1017 : : /* only RANGE partition should have a non-NULL kind */
1878 drowley@postgresql.o 1018 [ - + ]: 5442 : Assert(key->strategy == PARTITION_STRATEGY_RANGE);
1019 : :
10 michael@paquier.xyz 1020 :GNC 5442 : dest->kind = palloc_array(PartitionRangeDatumKind *, ndatums);
1021 : :
1022 : : /*
1023 : : * In the loop below, to save from allocating a series of small arrays
1024 : : * for storing the PartitionRangeDatumKind, we allocate a single chunk
1025 : : * here and use a smaller portion of it for each datum.
1026 : : */
1027 : 5442 : boundKinds = palloc_array(PartitionRangeDatumKind, ndatums * partnatts);
1028 : :
3057 alvherre@alvh.no-ip. 1029 [ + + ]:CBC 59361 : for (i = 0; i < ndatums; i++)
1030 : : {
1878 drowley@postgresql.o 1031 : 53919 : dest->kind[i] = &boundKinds[i * partnatts];
3057 alvherre@alvh.no-ip. 1032 : 53919 : memcpy(dest->kind[i], src->kind[i],
1033 : : sizeof(PartitionRangeDatumKind) * partnatts);
1034 : : }
1035 : : }
1036 : : else
1037 : 5953 : dest->kind = NULL;
1038 : :
1039 : : /* copy interleaved partitions for LIST partitioned tables */
1850 drowley@postgresql.o 1040 : 11395 : dest->interleaved_parts = bms_copy(src->interleaved_parts);
1041 : :
1042 : : /*
1043 : : * For hash partitioning, datums array will have two elements - modulus
1044 : : * and remainder.
1045 : : */
390 tgl@sss.pgh.pa.us 1046 [ + + ]: 11395 : if (ndatums > 0)
1047 : : {
1048 : 11197 : bool hash_part = (key->strategy == PARTITION_STRATEGY_HASH);
1049 [ + + ]: 11197 : int natts = hash_part ? 2 : partnatts;
10 michael@paquier.xyz 1050 :GNC 11197 : Datum *boundDatums = palloc_array(Datum, ndatums * natts);
1051 : :
390 tgl@sss.pgh.pa.us 1052 [ + + ]:CBC 81328 : for (i = 0; i < ndatums; i++)
1053 : : {
1054 : : int j;
1055 : :
1056 : 70131 : dest->datums[i] = &boundDatums[i * natts];
1057 : :
1058 [ + + ]: 145309 : for (j = 0; j < natts; j++)
1059 : : {
1060 [ + + ]: 75178 : if (dest->kind == NULL ||
1061 [ + + ]: 57844 : dest->kind[i][j] == PARTITION_RANGE_DATUM_VALUE)
1062 : : {
1063 : : bool byval;
1064 : : int typlen;
1065 : :
1066 [ + + ]: 73544 : if (hash_part)
1067 : : {
1068 : 2244 : typlen = sizeof(int32); /* Always int4 */
1069 : 2244 : byval = true; /* int4 is pass-by-value */
1070 : : }
1071 : : else
1072 : : {
1073 : 71300 : byval = key->parttypbyval[j];
1074 : 71300 : typlen = key->parttyplen[j];
1075 : : }
1076 : 73544 : dest->datums[i][j] = datumCopy(src->datums[i][j],
1077 : : byval, typlen);
1078 : : }
1079 : : }
1080 : : }
1081 : : }
1082 : :
260 michael@paquier.xyz 1083 : 11395 : dest->indexes = palloc_array(int, nindexes);
2037 tgl@sss.pgh.pa.us 1084 : 11395 : memcpy(dest->indexes, src->indexes, sizeof(int) * nindexes);
1085 : :
3057 alvherre@alvh.no-ip. 1086 : 11395 : dest->null_index = src->null_index;
1087 : 11395 : dest->default_index = src->default_index;
1088 : :
1089 : 11395 : return dest;
1090 : : }
1091 : :
1092 : : /*
1093 : : * partition_bounds_merge
1094 : : * Check to see whether every partition of 'outer_rel' matches/overlaps
1095 : : * one partition of 'inner_rel' at most, and vice versa; and if so, build
1096 : : * and return the partition bounds for a join relation between the rels,
1097 : : * generating two lists of the matching/overlapping partitions, which are
1098 : : * returned to *outer_parts and *inner_parts respectively.
1099 : : *
1100 : : * The lists contain the same number of partitions, and the partitions at the
1101 : : * same positions in the lists indicate join pairs used for partitioned join.
1102 : : * If a partition on one side matches/overlaps multiple partitions on the other
1103 : : * side, this function returns NULL, setting *outer_parts and *inner_parts to
1104 : : * NIL.
1105 : : */
1106 : : PartitionBoundInfo
2332 efujita@postgresql.o 1107 : 706 : partition_bounds_merge(int partnatts,
1108 : : FmgrInfo *partsupfunc, Oid *partcollation,
1109 : : RelOptInfo *outer_rel, RelOptInfo *inner_rel,
1110 : : JoinType jointype,
1111 : : List **outer_parts, List **inner_parts)
1112 : : {
1113 : : /*
1114 : : * Currently, this function is called only from try_partitionwise_join(),
1115 : : * so the join type should be INNER, LEFT, FULL, SEMI, or ANTI.
1116 : : */
1117 [ + + + + : 706 : Assert(jointype == JOIN_INNER || jointype == JOIN_LEFT ||
+ + + + -
+ ]
1118 : : jointype == JOIN_FULL || jointype == JOIN_SEMI ||
1119 : : jointype == JOIN_ANTI);
1120 : :
1121 : : /* The partitioning strategies should be the same. */
2177 1122 [ - + ]: 706 : Assert(outer_rel->boundinfo->strategy == inner_rel->boundinfo->strategy);
1123 : :
2332 1124 : 706 : *outer_parts = *inner_parts = NIL;
2177 1125 [ - + + - ]: 706 : switch (outer_rel->boundinfo->strategy)
1126 : : {
2332 efujita@postgresql.o 1127 :UBC 0 : case PARTITION_STRATEGY_HASH:
1128 : :
1129 : : /*
1130 : : * For hash partitioned tables, we currently support partitioned
1131 : : * join only when they have exactly the same partition bounds.
1132 : : *
1133 : : * XXX: it might be possible to relax the restriction to support
1134 : : * cases where hash partitioned tables have missing partitions
1135 : : * and/or different moduli, but it's not clear if it would be
1136 : : * useful to support the former case since it's unusual to have
1137 : : * missing partitions. On the other hand, it would be useful to
1138 : : * support the latter case, but in that case, there is a high
1139 : : * probability that a partition on one side will match multiple
1140 : : * partitions on the other side, which is the scenario the current
1141 : : * implementation of partitioned join can't handle.
1142 : : */
1143 : 0 : return NULL;
1144 : :
2332 efujita@postgresql.o 1145 :CBC 405 : case PARTITION_STRATEGY_LIST:
1146 : 405 : return merge_list_bounds(partsupfunc,
1147 : : partcollation,
1148 : : outer_rel,
1149 : : inner_rel,
1150 : : jointype,
1151 : : outer_parts,
1152 : : inner_parts);
1153 : :
1154 : 301 : case PARTITION_STRATEGY_RANGE:
1155 : 301 : return merge_range_bounds(partnatts,
1156 : : partsupfunc,
1157 : : partcollation,
1158 : : outer_rel,
1159 : : inner_rel,
1160 : : jointype,
1161 : : outer_parts,
1162 : : inner_parts);
1163 : : }
1164 : :
1393 alvherre@alvh.no-ip. 1165 :UBC 0 : return NULL;
1166 : : }
1167 : :
1168 : : /*
1169 : : * merge_list_bounds
1170 : : * Create the partition bounds for a join relation between list
1171 : : * partitioned tables, if possible
1172 : : *
1173 : : * In this function we try to find sets of matching partitions from both sides
1174 : : * by comparing list values stored in their partition bounds. Since the list
1175 : : * values appear in the ascending order, an algorithm similar to merge join is
1176 : : * used for that. If a partition on one side doesn't have a matching
1177 : : * partition on the other side, the algorithm tries to match it with the
1178 : : * default partition on the other side if any; if not, the algorithm tries to
1179 : : * match it with a dummy partition on the other side if it's on the
1180 : : * non-nullable side of an outer join. Also, if both sides have the default
1181 : : * partitions, the algorithm tries to match them with each other. We give up
1182 : : * if the algorithm finds a partition matching multiple partitions on the
1183 : : * other side, which is the scenario the current implementation of partitioned
1184 : : * join can't handle.
1185 : : */
1186 : : static PartitionBoundInfo
2332 efujita@postgresql.o 1187 :CBC 405 : merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation,
1188 : : RelOptInfo *outer_rel, RelOptInfo *inner_rel,
1189 : : JoinType jointype,
1190 : : List **outer_parts, List **inner_parts)
1191 : : {
1192 : 405 : PartitionBoundInfo merged_bounds = NULL;
1193 : 405 : PartitionBoundInfo outer_bi = outer_rel->boundinfo;
1194 : 405 : PartitionBoundInfo inner_bi = inner_rel->boundinfo;
1195 : 405 : bool outer_has_default = partition_bound_has_default(outer_bi);
1196 : 405 : bool inner_has_default = partition_bound_has_default(inner_bi);
1197 : 405 : int outer_default = outer_bi->default_index;
1198 : 405 : int inner_default = inner_bi->default_index;
1199 : 405 : bool outer_has_null = partition_bound_accepts_nulls(outer_bi);
1200 : 405 : bool inner_has_null = partition_bound_accepts_nulls(inner_bi);
1201 : : PartitionMap outer_map;
1202 : : PartitionMap inner_map;
1203 : : int outer_pos;
1204 : : int inner_pos;
1205 : 405 : int next_index = 0;
1206 : 405 : int null_index = -1;
1207 : 405 : int default_index = -1;
1208 : 405 : List *merged_datums = NIL;
1209 : 405 : List *merged_indexes = NIL;
1210 : :
1211 [ - + ]: 405 : Assert(*outer_parts == NIL);
1212 [ - + ]: 405 : Assert(*inner_parts == NIL);
1213 [ + - - + ]: 405 : Assert(outer_bi->strategy == inner_bi->strategy &&
1214 : : outer_bi->strategy == PARTITION_STRATEGY_LIST);
1215 : : /* List partitioning doesn't require kinds. */
1216 [ + - - + ]: 405 : Assert(!outer_bi->kind && !inner_bi->kind);
1217 : :
1218 : 405 : init_partition_map(outer_rel, &outer_map);
1219 : 405 : init_partition_map(inner_rel, &inner_map);
1220 : :
1221 : : /*
1222 : : * If the default partitions (if any) have been proven empty, deem them
1223 : : * non-existent.
1224 : : */
1225 [ + + + + ]: 405 : if (outer_has_default && is_dummy_partition(outer_rel, outer_default))
1226 : 20 : outer_has_default = false;
1227 [ + + - + ]: 405 : if (inner_has_default && is_dummy_partition(inner_rel, inner_default))
2332 efujita@postgresql.o 1228 :UBC 0 : inner_has_default = false;
1229 : :
1230 : : /*
1231 : : * Merge partitions from both sides. In each iteration we compare a pair
1232 : : * of list values, one from each side, and decide whether the
1233 : : * corresponding partitions match or not. If the two values match
1234 : : * exactly, move to the next pair of list values, otherwise move to the
1235 : : * next list value on the side with a smaller list value.
1236 : : */
2332 efujita@postgresql.o 1237 :CBC 405 : outer_pos = inner_pos = 0;
1238 [ + + + + ]: 3185 : while (outer_pos < outer_bi->ndatums || inner_pos < inner_bi->ndatums)
1239 : : {
1240 : 2820 : int outer_index = -1;
1241 : 2820 : int inner_index = -1;
1242 : : Datum *outer_datums;
1243 : : Datum *inner_datums;
1244 : : int cmpval;
1245 : 2820 : Datum *merged_datum = NULL;
1246 : 2820 : int merged_index = -1;
1247 : :
1248 [ + + ]: 2820 : if (outer_pos < outer_bi->ndatums)
1249 : : {
1250 : : /*
1251 : : * If the partition on the outer side has been proven empty,
1252 : : * ignore it and move to the next datum on the outer side.
1253 : : */
1254 : 2780 : outer_index = outer_bi->indexes[outer_pos];
1255 [ + + ]: 2780 : if (is_dummy_partition(outer_rel, outer_index))
1256 : : {
1257 : 140 : outer_pos++;
1258 : 140 : continue;
1259 : : }
1260 : : }
1261 [ + - ]: 2680 : if (inner_pos < inner_bi->ndatums)
1262 : : {
1263 : : /*
1264 : : * If the partition on the inner side has been proven empty,
1265 : : * ignore it and move to the next datum on the inner side.
1266 : : */
1267 : 2680 : inner_index = inner_bi->indexes[inner_pos];
1268 [ - + ]: 2680 : if (is_dummy_partition(inner_rel, inner_index))
1269 : : {
2332 efujita@postgresql.o 1270 :UBC 0 : inner_pos++;
1271 : 0 : continue;
1272 : : }
1273 : : }
1274 : :
1275 : : /* Get the list values. */
2332 efujita@postgresql.o 1276 :CBC 5360 : outer_datums = outer_pos < outer_bi->ndatums ?
1277 [ + + ]: 2680 : outer_bi->datums[outer_pos] : NULL;
1278 : 5360 : inner_datums = inner_pos < inner_bi->ndatums ?
1279 [ + - ]: 2680 : inner_bi->datums[inner_pos] : NULL;
1280 : :
1281 : : /*
1282 : : * We run this loop till both sides finish. This allows us to avoid
1283 : : * duplicating code to handle the remaining values on the side which
1284 : : * finishes later. For that we set the comparison parameter cmpval in
1285 : : * such a way that it appears as if the side which finishes earlier
1286 : : * has an extra value higher than any other value on the unfinished
1287 : : * side. That way we advance the values on the unfinished side till
1288 : : * all of its values are exhausted.
1289 : : */
1290 [ + + ]: 2680 : if (outer_pos >= outer_bi->ndatums)
1291 : 40 : cmpval = 1;
1292 [ - + ]: 2640 : else if (inner_pos >= inner_bi->ndatums)
2332 efujita@postgresql.o 1293 :UBC 0 : cmpval = -1;
1294 : : else
1295 : : {
2332 efujita@postgresql.o 1296 [ + - - + ]:CBC 2640 : Assert(outer_datums != NULL && inner_datums != NULL);
1297 : 2640 : cmpval = DatumGetInt32(FunctionCall2Coll(&partsupfunc[0],
1298 : : partcollation[0],
1299 : : outer_datums[0],
1300 : : inner_datums[0]));
1301 : : }
1302 : :
1303 [ + + ]: 2680 : if (cmpval == 0)
1304 : : {
1305 : : /* Two list values match exactly. */
1306 [ - + ]: 1370 : Assert(outer_pos < outer_bi->ndatums);
1307 [ - + ]: 1370 : Assert(inner_pos < inner_bi->ndatums);
1308 [ - + ]: 1370 : Assert(outer_index >= 0);
1309 [ - + ]: 1370 : Assert(inner_index >= 0);
1310 : :
1311 : : /*
1312 : : * Try merging both partitions. If successful, add the list value
1313 : : * and index of the merged partition below.
1314 : : */
1315 : 1370 : merged_index = merge_matching_partitions(&outer_map, &inner_map,
1316 : : outer_index, inner_index,
1317 : : &next_index);
1318 [ + + ]: 1370 : if (merged_index == -1)
1319 : 25 : goto cleanup;
1320 : :
1321 : 1345 : merged_datum = outer_datums;
1322 : :
1323 : : /* Move to the next pair of list values. */
1324 : 1345 : outer_pos++;
1325 : 1345 : inner_pos++;
1326 : : }
1327 [ + + ]: 1310 : else if (cmpval < 0)
1328 : : {
1329 : : /* A list value missing from the inner side. */
1330 [ - + ]: 530 : Assert(outer_pos < outer_bi->ndatums);
1331 : :
1332 : : /*
1333 : : * If the inner side has the default partition, or this is an
1334 : : * outer join, try to assign a merged partition to the outer
1335 : : * partition (see process_outer_partition()). Otherwise, the
1336 : : * outer partition will not contribute to the result.
1337 : : */
1338 [ + + + + ]: 530 : if (inner_has_default || IS_OUTER_JOIN(jointype))
1339 : : {
1340 : : /* Get the outer partition. */
1341 : 340 : outer_index = outer_bi->indexes[outer_pos];
1342 [ - + ]: 340 : Assert(outer_index >= 0);
1343 : 340 : merged_index = process_outer_partition(&outer_map,
1344 : : &inner_map,
1345 : : outer_has_default,
1346 : : inner_has_default,
1347 : : outer_index,
1348 : : inner_default,
1349 : : jointype,
1350 : : &next_index,
1351 : : &default_index);
1352 [ + + ]: 340 : if (merged_index == -1)
1353 : 5 : goto cleanup;
1354 : 335 : merged_datum = outer_datums;
1355 : : }
1356 : :
1357 : : /* Move to the next list value on the outer side. */
1358 : 525 : outer_pos++;
1359 : : }
1360 : : else
1361 : : {
1362 : : /* A list value missing from the outer side. */
1363 [ - + ]: 780 : Assert(cmpval > 0);
1364 [ - + ]: 780 : Assert(inner_pos < inner_bi->ndatums);
1365 : :
1366 : : /*
1367 : : * If the outer side has the default partition, or this is a FULL
1368 : : * join, try to assign a merged partition to the inner partition
1369 : : * (see process_inner_partition()). Otherwise, the inner
1370 : : * partition will not contribute to the result.
1371 : : */
1372 [ + + + + ]: 780 : if (outer_has_default || jointype == JOIN_FULL)
1373 : : {
1374 : : /* Get the inner partition. */
1375 : 210 : inner_index = inner_bi->indexes[inner_pos];
1376 [ - + ]: 210 : Assert(inner_index >= 0);
1377 : 210 : merged_index = process_inner_partition(&outer_map,
1378 : : &inner_map,
1379 : : outer_has_default,
1380 : : inner_has_default,
1381 : : inner_index,
1382 : : outer_default,
1383 : : jointype,
1384 : : &next_index,
1385 : : &default_index);
1386 [ + + ]: 210 : if (merged_index == -1)
1387 : 10 : goto cleanup;
1388 : 200 : merged_datum = inner_datums;
1389 : : }
1390 : :
1391 : : /* Move to the next list value on the inner side. */
1392 : 770 : inner_pos++;
1393 : : }
1394 : :
1395 : : /*
1396 : : * If we assigned a merged partition, add the list value and index of
1397 : : * the merged partition if appropriate.
1398 : : */
1399 [ + + + + ]: 2640 : if (merged_index >= 0 && merged_index != default_index)
1400 : : {
1401 : 1820 : merged_datums = lappend(merged_datums, merged_datum);
1402 : 1820 : merged_indexes = lappend_int(merged_indexes, merged_index);
1403 : : }
1404 : : }
1405 : :
1406 : : /*
1407 : : * If the NULL partitions (if any) have been proven empty, deem them
1408 : : * non-existent.
1409 : : */
1410 [ + + - + ]: 525 : if (outer_has_null &&
1411 : 160 : is_dummy_partition(outer_rel, outer_bi->null_index))
2332 efujita@postgresql.o 1412 :UBC 0 : outer_has_null = false;
2332 efujita@postgresql.o 1413 [ + + - + ]:CBC 525 : if (inner_has_null &&
1414 : 160 : is_dummy_partition(inner_rel, inner_bi->null_index))
2332 efujita@postgresql.o 1415 :UBC 0 : inner_has_null = false;
1416 : :
1417 : : /* Merge the NULL partitions if any. */
2332 efujita@postgresql.o 1418 [ + + + + ]:CBC 365 : if (outer_has_null || inner_has_null)
1419 : 180 : merge_null_partitions(&outer_map, &inner_map,
1420 : : outer_has_null, inner_has_null,
1421 : : outer_bi->null_index, inner_bi->null_index,
1422 : : jointype, &next_index, &null_index);
1423 : : else
1424 [ - + ]: 185 : Assert(null_index == -1);
1425 : :
1426 : : /* Merge the default partitions if any. */
1427 [ + + + + ]: 365 : if (outer_has_default || inner_has_default)
1428 : 80 : merge_default_partitions(&outer_map, &inner_map,
1429 : : outer_has_default, inner_has_default,
1430 : : outer_default, inner_default,
1431 : : jointype, &next_index, &default_index);
1432 : : else
1433 [ - + ]: 285 : Assert(default_index == -1);
1434 : :
1435 : : /* If we have merged partitions, create the partition bounds. */
1436 [ + - ]: 365 : if (next_index > 0)
1437 : : {
1438 : : /* Fix the merged_indexes list if necessary. */
1439 [ + + - + ]: 365 : if (outer_map.did_remapping || inner_map.did_remapping)
1440 : : {
1441 [ - + ]: 40 : Assert(jointype == JOIN_FULL);
1442 : 40 : fix_merged_indexes(&outer_map, &inner_map,
1443 : : next_index, merged_indexes);
1444 : : }
1445 : :
1446 : : /* Use maps to match partitions from inputs. */
1447 : 365 : generate_matching_part_pairs(outer_rel, inner_rel,
1448 : : &outer_map, &inner_map,
1449 : : next_index,
1450 : : outer_parts, inner_parts);
1451 [ - + ]: 365 : Assert(*outer_parts != NIL);
1452 [ - + ]: 365 : Assert(*inner_parts != NIL);
1453 [ - + ]: 365 : Assert(list_length(*outer_parts) == list_length(*inner_parts));
1454 [ - + ]: 365 : Assert(list_length(*outer_parts) <= next_index);
1455 : :
1456 : : /* Make a PartitionBoundInfo struct to return. */
1457 : 365 : merged_bounds = build_merged_partition_bounds(outer_bi->strategy,
1458 : : merged_datums,
1459 : : NIL,
1460 : : merged_indexes,
1461 : : null_index,
1462 : : default_index);
1463 [ + - ]: 365 : Assert(merged_bounds);
1464 : : }
1465 : :
1466 : 365 : cleanup:
1467 : : /* Free local memory before returning. */
1468 : 405 : list_free(merged_datums);
1469 : 405 : list_free(merged_indexes);
1470 : 405 : free_partition_map(&outer_map);
1471 : 405 : free_partition_map(&inner_map);
1472 : :
1473 : 405 : return merged_bounds;
1474 : : }
1475 : :
1476 : : /*
1477 : : * merge_range_bounds
1478 : : * Create the partition bounds for a join relation between range
1479 : : * partitioned tables, if possible
1480 : : *
1481 : : * In this function we try to find sets of overlapping partitions from both
1482 : : * sides by comparing ranges stored in their partition bounds. Since the
1483 : : * ranges appear in the ascending order, an algorithm similar to merge join is
1484 : : * used for that. If a partition on one side doesn't have an overlapping
1485 : : * partition on the other side, the algorithm tries to match it with the
1486 : : * default partition on the other side if any; if not, the algorithm tries to
1487 : : * match it with a dummy partition on the other side if it's on the
1488 : : * non-nullable side of an outer join. Also, if both sides have the default
1489 : : * partitions, the algorithm tries to match them with each other. We give up
1490 : : * if the algorithm finds a partition overlapping multiple partitions on the
1491 : : * other side, which is the scenario the current implementation of partitioned
1492 : : * join can't handle.
1493 : : */
1494 : : static PartitionBoundInfo
1495 : 301 : merge_range_bounds(int partnatts, FmgrInfo *partsupfuncs,
1496 : : Oid *partcollations,
1497 : : RelOptInfo *outer_rel, RelOptInfo *inner_rel,
1498 : : JoinType jointype,
1499 : : List **outer_parts, List **inner_parts)
1500 : : {
1501 : 301 : PartitionBoundInfo merged_bounds = NULL;
1502 : 301 : PartitionBoundInfo outer_bi = outer_rel->boundinfo;
1503 : 301 : PartitionBoundInfo inner_bi = inner_rel->boundinfo;
1504 : 301 : bool outer_has_default = partition_bound_has_default(outer_bi);
1505 : 301 : bool inner_has_default = partition_bound_has_default(inner_bi);
1506 : 301 : int outer_default = outer_bi->default_index;
1507 : 301 : int inner_default = inner_bi->default_index;
1508 : : PartitionMap outer_map;
1509 : : PartitionMap inner_map;
1510 : : int outer_index;
1511 : : int inner_index;
1512 : : int outer_lb_pos;
1513 : : int inner_lb_pos;
1514 : : PartitionRangeBound outer_lb;
1515 : : PartitionRangeBound outer_ub;
1516 : : PartitionRangeBound inner_lb;
1517 : : PartitionRangeBound inner_ub;
1518 : 301 : int next_index = 0;
1519 : 301 : int default_index = -1;
1520 : 301 : List *merged_datums = NIL;
1521 : 301 : List *merged_kinds = NIL;
1522 : 301 : List *merged_indexes = NIL;
1523 : :
1524 [ - + ]: 301 : Assert(*outer_parts == NIL);
1525 [ - + ]: 301 : Assert(*inner_parts == NIL);
1526 [ + - - + ]: 301 : Assert(outer_bi->strategy == inner_bi->strategy &&
1527 : : outer_bi->strategy == PARTITION_STRATEGY_RANGE);
1528 : :
1529 : 301 : init_partition_map(outer_rel, &outer_map);
1530 : 301 : init_partition_map(inner_rel, &inner_map);
1531 : :
1532 : : /*
1533 : : * If the default partitions (if any) have been proven empty, deem them
1534 : : * non-existent.
1535 : : */
1536 [ + + + + ]: 301 : if (outer_has_default && is_dummy_partition(outer_rel, outer_default))
1537 : 10 : outer_has_default = false;
1538 [ + + - + ]: 301 : if (inner_has_default && is_dummy_partition(inner_rel, inner_default))
2332 efujita@postgresql.o 1539 :UBC 0 : inner_has_default = false;
1540 : :
1541 : : /*
1542 : : * Merge partitions from both sides. In each iteration we compare a pair
1543 : : * of ranges, one from each side, and decide whether the corresponding
1544 : : * partitions match or not. If the two ranges overlap, move to the next
1545 : : * pair of ranges, otherwise move to the next range on the side with a
1546 : : * lower range. outer_lb_pos/inner_lb_pos keep track of the positions of
1547 : : * lower bounds in the datums arrays in the outer/inner
1548 : : * PartitionBoundInfos respectively.
1549 : : */
2332 efujita@postgresql.o 1550 :CBC 301 : outer_lb_pos = inner_lb_pos = 0;
1551 : 301 : outer_index = get_range_partition(outer_rel, outer_bi, &outer_lb_pos,
1552 : : &outer_lb, &outer_ub);
1553 : 301 : inner_index = get_range_partition(inner_rel, inner_bi, &inner_lb_pos,
1554 : : &inner_lb, &inner_ub);
1555 [ + + + + ]: 1071 : while (outer_index >= 0 || inner_index >= 0)
1556 : : {
1557 : : bool overlap;
1558 : : int ub_cmpval;
1559 : : int lb_cmpval;
1560 : 826 : PartitionRangeBound merged_lb = {-1, NULL, NULL, true};
1561 : 826 : PartitionRangeBound merged_ub = {-1, NULL, NULL, false};
1562 : 826 : int merged_index = -1;
1563 : :
1564 : : /*
1565 : : * We run this loop till both sides finish. This allows us to avoid
1566 : : * duplicating code to handle the remaining ranges on the side which
1567 : : * finishes later. For that we set the comparison parameter cmpval in
1568 : : * such a way that it appears as if the side which finishes earlier
1569 : : * has an extra range higher than any other range on the unfinished
1570 : : * side. That way we advance the ranges on the unfinished side till
1571 : : * all of its ranges are exhausted.
1572 : : */
1573 [ + + ]: 826 : if (outer_index == -1)
1574 : : {
1575 : 75 : overlap = false;
1576 : 75 : lb_cmpval = 1;
1577 : 75 : ub_cmpval = 1;
1578 : : }
1579 [ + + ]: 751 : else if (inner_index == -1)
1580 : : {
1581 : 30 : overlap = false;
1582 : 30 : lb_cmpval = -1;
1583 : 30 : ub_cmpval = -1;
1584 : : }
1585 : : else
1586 : 721 : overlap = compare_range_partitions(partnatts, partsupfuncs,
1587 : : partcollations,
1588 : : &outer_lb, &outer_ub,
1589 : : &inner_lb, &inner_ub,
1590 : : &lb_cmpval, &ub_cmpval);
1591 : :
1592 [ + + ]: 826 : if (overlap)
1593 : : {
1594 : : /* Two ranges overlap; form a join pair. */
1595 : :
1596 : : PartitionRangeBound save_outer_ub;
1597 : : PartitionRangeBound save_inner_ub;
1598 : :
1599 : : /* Both partitions should not have been merged yet. */
1600 [ - + ]: 691 : Assert(outer_index >= 0);
1601 [ + - - + ]: 691 : Assert(outer_map.merged_indexes[outer_index] == -1 &&
1602 : : outer_map.merged[outer_index] == false);
1603 [ - + ]: 691 : Assert(inner_index >= 0);
1604 [ + - - + ]: 691 : Assert(inner_map.merged_indexes[inner_index] == -1 &&
1605 : : inner_map.merged[inner_index] == false);
1606 : :
1607 : : /*
1608 : : * Get the index of the merged partition. Both partitions aren't
1609 : : * merged yet, so the partitions should be merged successfully.
1610 : : */
1611 : 691 : merged_index = merge_matching_partitions(&outer_map, &inner_map,
1612 : : outer_index, inner_index,
1613 : : &next_index);
1614 [ - + ]: 691 : Assert(merged_index >= 0);
1615 : :
1616 : : /* Get the range bounds of the merged partition. */
1617 : 691 : get_merged_range_bounds(partnatts, partsupfuncs,
1618 : : partcollations, jointype,
1619 : : &outer_lb, &outer_ub,
1620 : : &inner_lb, &inner_ub,
1621 : : lb_cmpval, ub_cmpval,
1622 : : &merged_lb, &merged_ub);
1623 : :
1624 : : /* Save the upper bounds of both partitions for use below. */
1625 : 691 : save_outer_ub = outer_ub;
1626 : 691 : save_inner_ub = inner_ub;
1627 : :
1628 : : /* Move to the next pair of ranges. */
1629 : 691 : outer_index = get_range_partition(outer_rel, outer_bi, &outer_lb_pos,
1630 : : &outer_lb, &outer_ub);
1631 : 691 : inner_index = get_range_partition(inner_rel, inner_bi, &inner_lb_pos,
1632 : : &inner_lb, &inner_ub);
1633 : :
1634 : : /*
1635 : : * If the range of a partition on one side overlaps the range of
1636 : : * the next partition on the other side, that will cause the
1637 : : * partition on one side to match at least two partitions on the
1638 : : * other side, which is the case that we currently don't support
1639 : : * partitioned join for; give up.
1640 : : */
1641 [ + + + + : 861 : if (ub_cmpval > 0 && inner_index >= 0 &&
+ + ]
1642 : 170 : compare_range_bounds(partnatts, partsupfuncs, partcollations,
1643 : : &save_outer_ub, &inner_lb) > 0)
1644 : 51 : goto cleanup;
1645 [ + + + + : 717 : if (ub_cmpval < 0 && outer_index >= 0 &&
+ + ]
1646 : 56 : compare_range_bounds(partnatts, partsupfuncs, partcollations,
1647 : : &outer_lb, &save_inner_ub) < 0)
1648 : 16 : goto cleanup;
1649 : :
1650 : : /*
1651 : : * A row from a non-overlapping portion (if any) of a partition on
1652 : : * one side might find its join partner in the default partition
1653 : : * (if any) on the other side, causing the same situation as
1654 : : * above; give up in that case.
1655 : : */
1656 [ + + + - : 645 : if ((outer_has_default && (lb_cmpval > 0 || ub_cmpval < 0)) ||
+ + + + ]
1657 [ + - - + ]: 20 : (inner_has_default && (lb_cmpval < 0 || ub_cmpval > 0)))
1658 : 5 : goto cleanup;
1659 : : }
1660 [ + + ]: 135 : else if (ub_cmpval < 0)
1661 : : {
1662 : : /* A non-overlapping outer range. */
1663 : :
1664 : : /* The outer partition should not have been merged yet. */
1665 [ - + ]: 30 : Assert(outer_index >= 0);
1666 [ + - - + ]: 30 : Assert(outer_map.merged_indexes[outer_index] == -1 &&
1667 : : outer_map.merged[outer_index] == false);
1668 : :
1669 : : /*
1670 : : * If the inner side has the default partition, or this is an
1671 : : * outer join, try to assign a merged partition to the outer
1672 : : * partition (see process_outer_partition()). Otherwise, the
1673 : : * outer partition will not contribute to the result.
1674 : : */
1675 [ + - + + ]: 30 : if (inner_has_default || IS_OUTER_JOIN(jointype))
1676 : : {
1677 : 20 : merged_index = process_outer_partition(&outer_map,
1678 : : &inner_map,
1679 : : outer_has_default,
1680 : : inner_has_default,
1681 : : outer_index,
1682 : : inner_default,
1683 : : jointype,
1684 : : &next_index,
1685 : : &default_index);
1686 [ - + ]: 20 : if (merged_index == -1)
2332 efujita@postgresql.o 1687 :UBC 0 : goto cleanup;
2332 efujita@postgresql.o 1688 :CBC 20 : merged_lb = outer_lb;
1689 : 20 : merged_ub = outer_ub;
1690 : : }
1691 : :
1692 : : /* Move to the next range on the outer side. */
1693 : 30 : outer_index = get_range_partition(outer_rel, outer_bi, &outer_lb_pos,
1694 : : &outer_lb, &outer_ub);
1695 : : }
1696 : : else
1697 : : {
1698 : : /* A non-overlapping inner range. */
1699 [ - + ]: 105 : Assert(ub_cmpval > 0);
1700 : :
1701 : : /* The inner partition should not have been merged yet. */
1702 [ - + ]: 105 : Assert(inner_index >= 0);
1703 [ + - - + ]: 105 : Assert(inner_map.merged_indexes[inner_index] == -1 &&
1704 : : inner_map.merged[inner_index] == false);
1705 : :
1706 : : /*
1707 : : * If the outer side has the default partition, or this is a FULL
1708 : : * join, try to assign a merged partition to the inner partition
1709 : : * (see process_inner_partition()). Otherwise, the inner
1710 : : * partition will not contribute to the result.
1711 : : */
1712 [ + + + + ]: 105 : if (outer_has_default || jointype == JOIN_FULL)
1713 : : {
1714 : 55 : merged_index = process_inner_partition(&outer_map,
1715 : : &inner_map,
1716 : : outer_has_default,
1717 : : inner_has_default,
1718 : : inner_index,
1719 : : outer_default,
1720 : : jointype,
1721 : : &next_index,
1722 : : &default_index);
1723 [ + + ]: 55 : if (merged_index == -1)
1724 : 5 : goto cleanup;
1725 : 50 : merged_lb = inner_lb;
1726 : 50 : merged_ub = inner_ub;
1727 : : }
1728 : :
1729 : : /* Move to the next range on the inner side. */
1730 : 100 : inner_index = get_range_partition(inner_rel, inner_bi, &inner_lb_pos,
1731 : : &inner_lb, &inner_ub);
1732 : : }
1733 : :
1734 : : /*
1735 : : * If we assigned a merged partition, add the range bounds and index
1736 : : * of the merged partition if appropriate.
1737 : : */
1738 [ + + + + ]: 770 : if (merged_index >= 0 && merged_index != default_index)
1739 : 680 : add_merged_range_bounds(partnatts, partsupfuncs, partcollations,
1740 : : &merged_lb, &merged_ub, merged_index,
1741 : : &merged_datums, &merged_kinds,
1742 : : &merged_indexes);
1743 : : }
1744 : :
1745 : : /* Merge the default partitions if any. */
1746 [ + + + + ]: 245 : if (outer_has_default || inner_has_default)
1747 : 50 : merge_default_partitions(&outer_map, &inner_map,
1748 : : outer_has_default, inner_has_default,
1749 : : outer_default, inner_default,
1750 : : jointype, &next_index, &default_index);
1751 : : else
1752 [ - + ]: 195 : Assert(default_index == -1);
1753 : :
1754 : : /* If we have merged partitions, create the partition bounds. */
1755 [ + - ]: 245 : if (next_index > 0)
1756 : : {
1757 : : /*
1758 : : * Unlike the case of list partitioning, we wouldn't have re-merged
1759 : : * partitions, so did_remapping should be left alone.
1760 : : */
1761 [ - + ]: 245 : Assert(!outer_map.did_remapping);
1762 [ - + ]: 245 : Assert(!inner_map.did_remapping);
1763 : :
1764 : : /* Use maps to match partitions from inputs. */
1765 : 245 : generate_matching_part_pairs(outer_rel, inner_rel,
1766 : : &outer_map, &inner_map,
1767 : : next_index,
1768 : : outer_parts, inner_parts);
1769 [ - + ]: 245 : Assert(*outer_parts != NIL);
1770 [ - + ]: 245 : Assert(*inner_parts != NIL);
1771 [ - + ]: 245 : Assert(list_length(*outer_parts) == list_length(*inner_parts));
1772 [ - + ]: 245 : Assert(list_length(*outer_parts) == next_index);
1773 : :
1774 : : /* Make a PartitionBoundInfo struct to return. */
1775 : 245 : merged_bounds = build_merged_partition_bounds(outer_bi->strategy,
1776 : : merged_datums,
1777 : : merged_kinds,
1778 : : merged_indexes,
1779 : : -1,
1780 : : default_index);
1781 [ + - ]: 245 : Assert(merged_bounds);
1782 : : }
1783 : :
1784 : 245 : cleanup:
1785 : : /* Free local memory before returning. */
1786 : 301 : list_free(merged_datums);
1787 : 301 : list_free(merged_kinds);
1788 : 301 : list_free(merged_indexes);
1789 : 301 : free_partition_map(&outer_map);
1790 : 301 : free_partition_map(&inner_map);
1791 : :
1792 : 301 : return merged_bounds;
1793 : : }
1794 : :
1795 : : /*
1796 : : * init_partition_map
1797 : : * Initialize a PartitionMap struct for given relation
1798 : : */
1799 : : static void
1800 : 1412 : init_partition_map(RelOptInfo *rel, PartitionMap *map)
1801 : : {
1802 : 1412 : int nparts = rel->nparts;
1803 : : int i;
1804 : :
1805 : 1412 : map->nparts = nparts;
260 michael@paquier.xyz 1806 : 1412 : map->merged_indexes = palloc_array(int, nparts);
1807 : 1412 : map->merged = palloc_array(bool, nparts);
2332 efujita@postgresql.o 1808 : 1412 : map->did_remapping = false;
260 michael@paquier.xyz 1809 : 1412 : map->old_indexes = palloc_array(int, nparts);
2332 efujita@postgresql.o 1810 [ + + ]: 5732 : for (i = 0; i < nparts; i++)
1811 : : {
1812 : 4320 : map->merged_indexes[i] = map->old_indexes[i] = -1;
1813 : 4320 : map->merged[i] = false;
1814 : : }
1815 : 1412 : }
1816 : :
1817 : : /*
1818 : : * free_partition_map
1819 : : */
1820 : : static void
1821 : 1412 : free_partition_map(PartitionMap *map)
1822 : : {
1823 : 1412 : pfree(map->merged_indexes);
1824 : 1412 : pfree(map->merged);
1825 : 1412 : pfree(map->old_indexes);
1826 : 1412 : }
1827 : :
1828 : : /*
1829 : : * is_dummy_partition --- has partition been proven empty?
1830 : : */
1831 : : static bool
1832 : 7624 : is_dummy_partition(RelOptInfo *rel, int part_index)
1833 : : {
1834 : : RelOptInfo *part_rel;
1835 : :
1836 [ - + ]: 7624 : Assert(part_index >= 0);
1837 : 7624 : part_rel = rel->part_rels[part_index];
1838 [ + + - + ]: 7624 : if (part_rel == NULL || IS_DUMMY_REL(part_rel))
1839 : 210 : return true;
1840 : 7414 : return false;
1841 : : }
1842 : :
1843 : : /*
1844 : : * merge_matching_partitions
1845 : : * Try to merge given outer/inner partitions, and return the index of a
1846 : : * merged partition produced from them if successful, -1 otherwise
1847 : : *
1848 : : * If the merged partition is newly created, *next_index is incremented.
1849 : : */
1850 : : static int
1851 : 2266 : merge_matching_partitions(PartitionMap *outer_map, PartitionMap *inner_map,
1852 : : int outer_index, int inner_index, int *next_index)
1853 : : {
1854 : : int outer_merged_index;
1855 : : int inner_merged_index;
1856 : : bool outer_merged;
1857 : : bool inner_merged;
1858 : :
1859 [ + - - + ]: 2266 : Assert(outer_index >= 0 && outer_index < outer_map->nparts);
1860 : 2266 : outer_merged_index = outer_map->merged_indexes[outer_index];
1861 : 2266 : outer_merged = outer_map->merged[outer_index];
1862 [ + - - + ]: 2266 : Assert(inner_index >= 0 && inner_index < inner_map->nparts);
1863 : 2266 : inner_merged_index = inner_map->merged_indexes[inner_index];
1864 : 2266 : inner_merged = inner_map->merged[inner_index];
1865 : :
1866 : : /*
1867 : : * Handle cases where we have already assigned a merged partition to each
1868 : : * of the given partitions.
1869 : : */
1870 [ + + + + ]: 2266 : if (outer_merged_index >= 0 && inner_merged_index >= 0)
1871 : : {
1872 : : /*
1873 : : * If the merged partitions are the same, no need to do anything;
1874 : : * return the index of the merged partitions. Otherwise, if each of
1875 : : * the given partitions has been merged with a dummy partition on the
1876 : : * other side, re-map them to either of the two merged partitions.
1877 : : * Otherwise, they can't be merged, so return -1.
1878 : : */
1879 [ + + ]: 550 : if (outer_merged_index == inner_merged_index)
1880 : : {
1881 [ - + ]: 460 : Assert(outer_merged);
1882 [ - + ]: 460 : Assert(inner_merged);
1883 : 460 : return outer_merged_index;
1884 : : }
1885 [ + + + - ]: 90 : if (!outer_merged && !inner_merged)
1886 : : {
1887 : : /*
1888 : : * This can only happen for a list-partitioning case. We re-map
1889 : : * them to the merged partition with the smaller of the two merged
1890 : : * indexes to preserve the property that the canonical order of
1891 : : * list partitions is determined by the indexes assigned to the
1892 : : * smallest list value of each partition.
1893 : : */
1894 [ + + ]: 85 : if (outer_merged_index < inner_merged_index)
1895 : : {
1896 : 45 : outer_map->merged[outer_index] = true;
1897 : 45 : inner_map->merged_indexes[inner_index] = outer_merged_index;
1898 : 45 : inner_map->merged[inner_index] = true;
1899 : 45 : inner_map->did_remapping = true;
1900 : 45 : inner_map->old_indexes[inner_index] = inner_merged_index;
1901 : 45 : return outer_merged_index;
1902 : : }
1903 : : else
1904 : : {
1905 : 40 : inner_map->merged[inner_index] = true;
1906 : 40 : outer_map->merged_indexes[outer_index] = inner_merged_index;
1907 : 40 : outer_map->merged[outer_index] = true;
1908 : 40 : outer_map->did_remapping = true;
1909 : 40 : outer_map->old_indexes[outer_index] = outer_merged_index;
1910 : 40 : return inner_merged_index;
1911 : : }
1912 : : }
1913 : 5 : return -1;
1914 : : }
1915 : :
1916 : : /* At least one of the given partitions should not have yet been merged. */
1917 [ + + - + ]: 1716 : Assert(outer_merged_index == -1 || inner_merged_index == -1);
1918 : :
1919 : : /*
1920 : : * If neither of them has been merged, merge them. Otherwise, if one has
1921 : : * been merged with a dummy partition on the other side (and the other
1922 : : * hasn't yet been merged with anything), re-merge them. Otherwise, they
1923 : : * can't be merged, so return -1.
1924 : : */
1925 [ + + + - ]: 1716 : if (outer_merged_index == -1 && inner_merged_index == -1)
1926 : : {
2296 tgl@sss.pgh.pa.us 1927 : 1466 : int merged_index = *next_index;
1928 : :
2332 efujita@postgresql.o 1929 [ - + ]: 1466 : Assert(!outer_merged);
1930 [ - + ]: 1466 : Assert(!inner_merged);
1931 : 1466 : outer_map->merged_indexes[outer_index] = merged_index;
1932 : 1466 : outer_map->merged[outer_index] = true;
1933 : 1466 : inner_map->merged_indexes[inner_index] = merged_index;
1934 : 1466 : inner_map->merged[inner_index] = true;
1935 : 1466 : *next_index = *next_index + 1;
1936 : 1466 : return merged_index;
1937 : : }
1938 [ + - + + ]: 250 : if (outer_merged_index >= 0 && !outer_map->merged[outer_index])
1939 : : {
1940 [ - + ]: 220 : Assert(inner_merged_index == -1);
1941 [ - + ]: 220 : Assert(!inner_merged);
1942 : 220 : inner_map->merged_indexes[inner_index] = outer_merged_index;
1943 : 220 : inner_map->merged[inner_index] = true;
1944 : 220 : outer_map->merged[outer_index] = true;
1945 : 220 : return outer_merged_index;
1946 : : }
1947 [ - + - - ]: 30 : if (inner_merged_index >= 0 && !inner_map->merged[inner_index])
1948 : : {
2332 efujita@postgresql.o 1949 [ # # ]:UBC 0 : Assert(outer_merged_index == -1);
1950 [ # # ]: 0 : Assert(!outer_merged);
1951 : 0 : outer_map->merged_indexes[outer_index] = inner_merged_index;
1952 : 0 : outer_map->merged[outer_index] = true;
1953 : 0 : inner_map->merged[inner_index] = true;
1954 : 0 : return inner_merged_index;
1955 : : }
2332 efujita@postgresql.o 1956 :CBC 30 : return -1;
1957 : : }
1958 : :
1959 : : /*
1960 : : * process_outer_partition
1961 : : * Try to assign given outer partition a merged partition, and return the
1962 : : * index of the merged partition if successful, -1 otherwise
1963 : : *
1964 : : * If the partition is newly created, *next_index is incremented. Also, if it
1965 : : * is the default partition of the join relation, *default_index is set to the
1966 : : * index if not already done.
1967 : : */
1968 : : static int
1969 : 360 : process_outer_partition(PartitionMap *outer_map,
1970 : : PartitionMap *inner_map,
1971 : : bool outer_has_default,
1972 : : bool inner_has_default,
1973 : : int outer_index,
1974 : : int inner_default,
1975 : : JoinType jointype,
1976 : : int *next_index,
1977 : : int *default_index)
1978 : : {
2296 tgl@sss.pgh.pa.us 1979 : 360 : int merged_index = -1;
1980 : :
2332 efujita@postgresql.o 1981 [ - + ]: 360 : Assert(outer_index >= 0);
1982 : :
1983 : : /*
1984 : : * If the inner side has the default partition, a row from the outer
1985 : : * partition might find its join partner in the default partition; try
1986 : : * merging the outer partition with the default partition. Otherwise,
1987 : : * this should be an outer join, in which case the outer partition has to
1988 : : * be scanned all the way anyway; merge the outer partition with a dummy
1989 : : * partition on the other side.
1990 : : */
1991 [ + + ]: 360 : if (inner_has_default)
1992 : : {
1993 [ - + ]: 5 : Assert(inner_default >= 0);
1994 : :
1995 : : /*
1996 : : * If the outer side has the default partition as well, the default
1997 : : * partition on the inner side will have two matching partitions on
1998 : : * the other side: the outer partition and the default partition on
1999 : : * the outer side. Partitionwise join doesn't handle this scenario
2000 : : * yet.
2001 : : */
2002 [ - + ]: 5 : if (outer_has_default)
2332 efujita@postgresql.o 2003 :UBC 0 : return -1;
2004 : :
2332 efujita@postgresql.o 2005 :CBC 5 : merged_index = merge_matching_partitions(outer_map, inner_map,
2006 : : outer_index, inner_default,
2007 : : next_index);
2008 [ + - ]: 5 : if (merged_index == -1)
2009 : 5 : return -1;
2010 : :
2011 : : /*
2012 : : * If this is a FULL join, the default partition on the inner side has
2013 : : * to be scanned all the way anyway, so the resulting partition will
2014 : : * contain all key values from the default partition, which any other
2015 : : * partition of the join relation will not contain. Thus the
2016 : : * resulting partition will act as the default partition of the join
2017 : : * relation; record the index in *default_index if not already done.
2018 : : */
2332 efujita@postgresql.o 2019 [ # # ]:UBC 0 : if (jointype == JOIN_FULL)
2020 : : {
2021 [ # # ]: 0 : if (*default_index == -1)
2022 : 0 : *default_index = merged_index;
2023 : : else
2024 [ # # ]: 0 : Assert(*default_index == merged_index);
2025 : : }
2026 : : }
2027 : : else
2028 : : {
2332 efujita@postgresql.o 2029 [ - + ]:CBC 355 : Assert(IS_OUTER_JOIN(jointype));
2030 [ - + ]: 355 : Assert(jointype != JOIN_RIGHT);
2031 : :
2032 : : /* If we have already assigned a partition, no need to do anything. */
2033 : 355 : merged_index = outer_map->merged_indexes[outer_index];
2034 [ + + ]: 355 : if (merged_index == -1)
2035 : 335 : merged_index = merge_partition_with_dummy(outer_map, outer_index,
2036 : : next_index);
2037 : : }
2038 : 355 : return merged_index;
2039 : : }
2040 : :
2041 : : /*
2042 : : * process_inner_partition
2043 : : * Try to assign given inner partition a merged partition, and return the
2044 : : * index of the merged partition if successful, -1 otherwise
2045 : : *
2046 : : * If the partition is newly created, *next_index is incremented. Also, if it
2047 : : * is the default partition of the join relation, *default_index is set to the
2048 : : * index if not already done.
2049 : : */
2050 : : static int
2051 : 265 : process_inner_partition(PartitionMap *outer_map,
2052 : : PartitionMap *inner_map,
2053 : : bool outer_has_default,
2054 : : bool inner_has_default,
2055 : : int inner_index,
2056 : : int outer_default,
2057 : : JoinType jointype,
2058 : : int *next_index,
2059 : : int *default_index)
2060 : : {
2296 tgl@sss.pgh.pa.us 2061 : 265 : int merged_index = -1;
2062 : :
2332 efujita@postgresql.o 2063 [ - + ]: 265 : Assert(inner_index >= 0);
2064 : :
2065 : : /*
2066 : : * If the outer side has the default partition, a row from the inner
2067 : : * partition might find its join partner in the default partition; try
2068 : : * merging the inner partition with the default partition. Otherwise,
2069 : : * this should be a FULL join, in which case the inner partition has to be
2070 : : * scanned all the way anyway; merge the inner partition with a dummy
2071 : : * partition on the other side.
2072 : : */
2073 [ + + ]: 265 : if (outer_has_default)
2074 : : {
2075 [ - + ]: 170 : Assert(outer_default >= 0);
2076 : :
2077 : : /*
2078 : : * If the inner side has the default partition as well, the default
2079 : : * partition on the outer side will have two matching partitions on
2080 : : * the other side: the inner partition and the default partition on
2081 : : * the inner side. Partitionwise join doesn't handle this scenario
2082 : : * yet.
2083 : : */
2084 [ + + ]: 170 : if (inner_has_default)
2085 : 10 : return -1;
2086 : :
2087 : 160 : merged_index = merge_matching_partitions(outer_map, inner_map,
2088 : : outer_default, inner_index,
2089 : : next_index);
2090 [ + + ]: 160 : if (merged_index == -1)
2091 : 5 : return -1;
2092 : :
2093 : : /*
2094 : : * If this is an outer join, the default partition on the outer side
2095 : : * has to be scanned all the way anyway, so the resulting partition
2096 : : * will contain all key values from the default partition, which any
2097 : : * other partition of the join relation will not contain. Thus the
2098 : : * resulting partition will act as the default partition of the join
2099 : : * relation; record the index in *default_index if not already done.
2100 : : */
2101 [ + + ]: 155 : if (IS_OUTER_JOIN(jointype))
2102 : : {
2103 [ - + ]: 90 : Assert(jointype != JOIN_RIGHT);
2104 [ + + ]: 90 : if (*default_index == -1)
2105 : 60 : *default_index = merged_index;
2106 : : else
2107 [ - + ]: 30 : Assert(*default_index == merged_index);
2108 : : }
2109 : : }
2110 : : else
2111 : : {
2112 [ - + ]: 95 : Assert(jointype == JOIN_FULL);
2113 : :
2114 : : /* If we have already assigned a partition, no need to do anything. */
2115 : 95 : merged_index = inner_map->merged_indexes[inner_index];
2116 [ + - ]: 95 : if (merged_index == -1)
2117 : 95 : merged_index = merge_partition_with_dummy(inner_map, inner_index,
2118 : : next_index);
2119 : : }
2120 : 250 : return merged_index;
2121 : : }
2122 : :
2123 : : /*
2124 : : * merge_null_partitions
2125 : : * Merge the NULL partitions from a join's outer and inner sides.
2126 : : *
2127 : : * If the merged partition produced from them is the NULL partition of the join
2128 : : * relation, *null_index is set to the index of the merged partition.
2129 : : *
2130 : : * Note: We assume here that the join clause for a partitioned join is strict
2131 : : * because have_partkey_equi_join() requires that the corresponding operator
2132 : : * be mergejoinable, and we currently assume that mergejoinable operators are
2133 : : * strict (see MJEvalOuterValues()/MJEvalInnerValues()).
2134 : : */
2135 : : static void
2136 : 180 : merge_null_partitions(PartitionMap *outer_map,
2137 : : PartitionMap *inner_map,
2138 : : bool outer_has_null,
2139 : : bool inner_has_null,
2140 : : int outer_null,
2141 : : int inner_null,
2142 : : JoinType jointype,
2143 : : int *next_index,
2144 : : int *null_index)
2145 : : {
2296 tgl@sss.pgh.pa.us 2146 : 180 : bool consider_outer_null = false;
2147 : 180 : bool consider_inner_null = false;
2148 : :
2332 efujita@postgresql.o 2149 [ + + - + ]: 180 : Assert(outer_has_null || inner_has_null);
2150 [ - + ]: 180 : Assert(*null_index == -1);
2151 : :
2152 : : /*
2153 : : * Check whether the NULL partitions have already been merged and if so,
2154 : : * set the consider_outer_null/consider_inner_null flags.
2155 : : */
2156 [ + + ]: 180 : if (outer_has_null)
2157 : : {
2158 [ + - - + ]: 160 : Assert(outer_null >= 0 && outer_null < outer_map->nparts);
2159 [ + + ]: 160 : if (outer_map->merged_indexes[outer_null] == -1)
2160 : 70 : consider_outer_null = true;
2161 : : }
2162 [ + + ]: 180 : if (inner_has_null)
2163 : : {
2164 [ + - - + ]: 160 : Assert(inner_null >= 0 && inner_null < inner_map->nparts);
2165 [ + + ]: 160 : if (inner_map->merged_indexes[inner_null] == -1)
2166 : 100 : consider_inner_null = true;
2167 : : }
2168 : :
2169 : : /* If both flags are set false, we don't need to do anything. */
2170 [ + + + + ]: 180 : if (!consider_outer_null && !consider_inner_null)
2171 : 60 : return;
2172 : :
2173 [ + + + + ]: 120 : if (consider_outer_null && !consider_inner_null)
2174 : : {
2175 [ - + ]: 20 : Assert(outer_has_null);
2176 : :
2177 : : /*
2178 : : * If this is an outer join, the NULL partition on the outer side has
2179 : : * to be scanned all the way anyway; merge the NULL partition with a
2180 : : * dummy partition on the other side. In that case
2181 : : * consider_outer_null means that the NULL partition only contains
2182 : : * NULL values as the key values, so the merged partition will do so;
2183 : : * treat it as the NULL partition of the join relation.
2184 : : */
2185 [ + + ]: 20 : if (IS_OUTER_JOIN(jointype))
2186 : : {
2187 [ - + ]: 10 : Assert(jointype != JOIN_RIGHT);
2188 : 10 : *null_index = merge_partition_with_dummy(outer_map, outer_null,
2189 : : next_index);
2190 : : }
2191 : : }
2192 [ + + + - ]: 100 : else if (!consider_outer_null && consider_inner_null)
2193 : : {
2194 [ - + ]: 50 : Assert(inner_has_null);
2195 : :
2196 : : /*
2197 : : * If this is a FULL join, the NULL partition on the inner side has to
2198 : : * be scanned all the way anyway; merge the NULL partition with a
2199 : : * dummy partition on the other side. In that case
2200 : : * consider_inner_null means that the NULL partition only contains
2201 : : * NULL values as the key values, so the merged partition will do so;
2202 : : * treat it as the NULL partition of the join relation.
2203 : : */
2204 [ - + ]: 50 : if (jointype == JOIN_FULL)
2332 efujita@postgresql.o 2205 :UBC 0 : *null_index = merge_partition_with_dummy(inner_map, inner_null,
2206 : : next_index);
2207 : : }
2208 : : else
2209 : : {
2332 efujita@postgresql.o 2210 [ + - - + ]:CBC 50 : Assert(consider_outer_null && consider_inner_null);
2211 [ - + ]: 50 : Assert(outer_has_null);
2212 [ - + ]: 50 : Assert(inner_has_null);
2213 : :
2214 : : /*
2215 : : * If this is an outer join, the NULL partition on the outer side (and
2216 : : * that on the inner side if this is a FULL join) have to be scanned
2217 : : * all the way anyway, so merge them. Note that each of the NULL
2218 : : * partitions isn't merged yet, so they should be merged successfully.
2219 : : * Like the above, each of the NULL partitions only contains NULL
2220 : : * values as the key values, so the merged partition will do so; treat
2221 : : * it as the NULL partition of the join relation.
2222 : : *
2223 : : * Note: if this an INNER/SEMI join, the join clause will never be
2224 : : * satisfied by two NULL values (see comments above), so both the NULL
2225 : : * partitions can be eliminated.
2226 : : */
2227 [ + + ]: 50 : if (IS_OUTER_JOIN(jointype))
2228 : : {
2229 [ - + ]: 40 : Assert(jointype != JOIN_RIGHT);
2230 : 40 : *null_index = merge_matching_partitions(outer_map, inner_map,
2231 : : outer_null, inner_null,
2232 : : next_index);
2233 [ - + ]: 40 : Assert(*null_index >= 0);
2234 : : }
2235 : : }
2236 : : }
2237 : :
2238 : : /*
2239 : : * merge_default_partitions
2240 : : * Merge the default partitions from a join's outer and inner sides.
2241 : : *
2242 : : * If the merged partition produced from them is the default partition of the
2243 : : * join relation, *default_index is set to the index of the merged partition.
2244 : : */
2245 : : static void
2246 : 130 : merge_default_partitions(PartitionMap *outer_map,
2247 : : PartitionMap *inner_map,
2248 : : bool outer_has_default,
2249 : : bool inner_has_default,
2250 : : int outer_default,
2251 : : int inner_default,
2252 : : JoinType jointype,
2253 : : int *next_index,
2254 : : int *default_index)
2255 : : {
2296 tgl@sss.pgh.pa.us 2256 : 130 : int outer_merged_index = -1;
2257 : 130 : int inner_merged_index = -1;
2258 : :
2332 efujita@postgresql.o 2259 [ + + - + ]: 130 : Assert(outer_has_default || inner_has_default);
2260 : :
2261 : : /* Get the merged partition indexes for the default partitions. */
2262 [ + + ]: 130 : if (outer_has_default)
2263 : : {
2264 [ + - - + ]: 100 : Assert(outer_default >= 0 && outer_default < outer_map->nparts);
2265 : 100 : outer_merged_index = outer_map->merged_indexes[outer_default];
2266 : : }
2267 [ + + ]: 130 : if (inner_has_default)
2268 : : {
2269 [ + - - + ]: 30 : Assert(inner_default >= 0 && inner_default < inner_map->nparts);
2270 : 30 : inner_merged_index = inner_map->merged_indexes[inner_default];
2271 : : }
2272 : :
2273 [ + + + - ]: 130 : if (outer_has_default && !inner_has_default)
2274 : : {
2275 : : /*
2276 : : * If this is an outer join, the default partition on the outer side
2277 : : * has to be scanned all the way anyway; if we have not yet assigned a
2278 : : * partition, merge the default partition with a dummy partition on
2279 : : * the other side. The merged partition will act as the default
2280 : : * partition of the join relation (see comments in
2281 : : * process_inner_partition()).
2282 : : */
2283 [ + + ]: 100 : if (IS_OUTER_JOIN(jointype))
2284 : : {
2285 [ - + ]: 60 : Assert(jointype != JOIN_RIGHT);
2286 [ - + ]: 60 : if (outer_merged_index == -1)
2287 : : {
2332 efujita@postgresql.o 2288 [ # # ]:UBC 0 : Assert(*default_index == -1);
2289 : 0 : *default_index = merge_partition_with_dummy(outer_map,
2290 : : outer_default,
2291 : : next_index);
2292 : : }
2293 : : else
2332 efujita@postgresql.o 2294 [ - + ]:CBC 60 : Assert(*default_index == outer_merged_index);
2295 : : }
2296 : : else
2297 [ - + ]: 40 : Assert(*default_index == -1);
2298 : : }
2299 [ + - + - ]: 30 : else if (!outer_has_default && inner_has_default)
2300 : : {
2301 : : /*
2302 : : * If this is a FULL join, the default partition on the inner side has
2303 : : * to be scanned all the way anyway; if we have not yet assigned a
2304 : : * partition, merge the default partition with a dummy partition on
2305 : : * the other side. The merged partition will act as the default
2306 : : * partition of the join relation (see comments in
2307 : : * process_outer_partition()).
2308 : : */
2309 [ - + ]: 30 : if (jointype == JOIN_FULL)
2310 : : {
2332 efujita@postgresql.o 2311 [ # # ]:UBC 0 : if (inner_merged_index == -1)
2312 : : {
2313 [ # # ]: 0 : Assert(*default_index == -1);
2314 : 0 : *default_index = merge_partition_with_dummy(inner_map,
2315 : : inner_default,
2316 : : next_index);
2317 : : }
2318 : : else
2319 [ # # ]: 0 : Assert(*default_index == inner_merged_index);
2320 : : }
2321 : : else
2332 efujita@postgresql.o 2322 [ - + ]:CBC 30 : Assert(*default_index == -1);
2323 : : }
2324 : : else
2325 : : {
2332 efujita@postgresql.o 2326 [ # # # # ]:UBC 0 : Assert(outer_has_default && inner_has_default);
2327 : :
2328 : : /*
2329 : : * The default partitions have to be joined with each other, so merge
2330 : : * them. Note that each of the default partitions isn't merged yet
2331 : : * (see, process_outer_partition()/process_inner_partition()), so they
2332 : : * should be merged successfully. The merged partition will act as
2333 : : * the default partition of the join relation.
2334 : : */
2335 [ # # ]: 0 : Assert(outer_merged_index == -1);
2336 [ # # ]: 0 : Assert(inner_merged_index == -1);
2337 [ # # ]: 0 : Assert(*default_index == -1);
2338 : 0 : *default_index = merge_matching_partitions(outer_map,
2339 : : inner_map,
2340 : : outer_default,
2341 : : inner_default,
2342 : : next_index);
2343 [ # # ]: 0 : Assert(*default_index >= 0);
2344 : : }
2332 efujita@postgresql.o 2345 :CBC 130 : }
2346 : :
2347 : : /*
2348 : : * merge_partition_with_dummy
2349 : : * Assign given partition a new partition of a join relation
2350 : : *
2351 : : * Note: The caller assumes that the given partition doesn't have a non-dummy
2352 : : * matching partition on the other side, but if the given partition finds the
2353 : : * matching partition later, we will adjust the assignment.
2354 : : */
2355 : : static int
2356 : 440 : merge_partition_with_dummy(PartitionMap *map, int index, int *next_index)
2357 : : {
2296 tgl@sss.pgh.pa.us 2358 : 440 : int merged_index = *next_index;
2359 : :
2332 efujita@postgresql.o 2360 [ + - - + ]: 440 : Assert(index >= 0 && index < map->nparts);
2361 [ - + ]: 440 : Assert(map->merged_indexes[index] == -1);
2362 [ - + ]: 440 : Assert(!map->merged[index]);
2363 : 440 : map->merged_indexes[index] = merged_index;
2364 : : /* Leave the merged flag alone! */
2365 : 440 : *next_index = *next_index + 1;
2366 : 440 : return merged_index;
2367 : : }
2368 : :
2369 : : /*
2370 : : * fix_merged_indexes
2371 : : * Adjust merged indexes of re-merged partitions
2372 : : */
2373 : : static void
2374 : 40 : fix_merged_indexes(PartitionMap *outer_map, PartitionMap *inner_map,
2375 : : int nmerged, List *merged_indexes)
2376 : : {
2377 : : int *new_indexes;
2378 : : int merged_index;
2379 : : int i;
2380 : : ListCell *lc;
2381 : :
2382 [ - + ]: 40 : Assert(nmerged > 0);
2383 : :
260 michael@paquier.xyz 2384 : 40 : new_indexes = palloc_array(int, nmerged);
2332 efujita@postgresql.o 2385 [ + + ]: 260 : for (i = 0; i < nmerged; i++)
2386 : 220 : new_indexes[i] = -1;
2387 : :
2388 : : /* Build the mapping of old merged indexes to new merged indexes. */
2389 [ + - ]: 40 : if (outer_map->did_remapping)
2390 : : {
2391 [ + + ]: 175 : for (i = 0; i < outer_map->nparts; i++)
2392 : : {
2393 : 135 : merged_index = outer_map->old_indexes[i];
2394 [ + + ]: 135 : if (merged_index >= 0)
2395 : 40 : new_indexes[merged_index] = outer_map->merged_indexes[i];
2396 : : }
2397 : : }
2398 [ + - ]: 40 : if (inner_map->did_remapping)
2399 : : {
2400 [ + + ]: 175 : for (i = 0; i < inner_map->nparts; i++)
2401 : : {
2402 : 135 : merged_index = inner_map->old_indexes[i];
2403 [ + + ]: 135 : if (merged_index >= 0)
2404 : 40 : new_indexes[merged_index] = inner_map->merged_indexes[i];
2405 : : }
2406 : : }
2407 : :
2408 : : /* Fix the merged_indexes list using the mapping. */
2409 [ + - + + : 365 : foreach(lc, merged_indexes)
+ + ]
2410 : : {
2411 : 325 : merged_index = lfirst_int(lc);
2412 [ - + ]: 325 : Assert(merged_index >= 0);
2413 [ + + ]: 325 : if (new_indexes[merged_index] >= 0)
2414 : 80 : lfirst_int(lc) = new_indexes[merged_index];
2415 : : }
2416 : :
2417 : 40 : pfree(new_indexes);
2418 : 40 : }
2419 : :
2420 : : /*
2421 : : * generate_matching_part_pairs
2422 : : * Generate a pair of lists of partitions that produce merged partitions
2423 : : *
2424 : : * The lists of partitions are built in the order of merged partition indexes,
2425 : : * and returned in *outer_parts and *inner_parts.
2426 : : */
2427 : : static void
2428 : 610 : generate_matching_part_pairs(RelOptInfo *outer_rel, RelOptInfo *inner_rel,
2429 : : PartitionMap *outer_map, PartitionMap *inner_map,
2430 : : int nmerged,
2431 : : List **outer_parts, List **inner_parts)
2432 : : {
2433 : 610 : int outer_nparts = outer_map->nparts;
2434 : 610 : int inner_nparts = inner_map->nparts;
2435 : : int *outer_indexes;
2436 : : int *inner_indexes;
2437 : : int max_nparts;
2438 : : int i;
2439 : :
2440 [ - + ]: 610 : Assert(nmerged > 0);
2441 [ - + ]: 610 : Assert(*outer_parts == NIL);
2442 [ - + ]: 610 : Assert(*inner_parts == NIL);
2443 : :
260 michael@paquier.xyz 2444 : 610 : outer_indexes = palloc_array(int, nmerged);
2445 : 610 : inner_indexes = palloc_array(int, nmerged);
2332 efujita@postgresql.o 2446 [ + + ]: 2330 : for (i = 0; i < nmerged; i++)
2447 : 1720 : outer_indexes[i] = inner_indexes[i] = -1;
2448 : :
2449 : : /* Set pairs of matching partitions. */
2450 [ - + ]: 610 : Assert(outer_nparts == outer_rel->nparts);
2451 [ - + ]: 610 : Assert(inner_nparts == inner_rel->nparts);
2452 : 610 : max_nparts = Max(outer_nparts, inner_nparts);
2453 [ + + ]: 2550 : for (i = 0; i < max_nparts; i++)
2454 : : {
2455 [ + + ]: 1940 : if (i < outer_nparts)
2456 : : {
2296 tgl@sss.pgh.pa.us 2457 : 1850 : int merged_index = outer_map->merged_indexes[i];
2458 : :
2332 efujita@postgresql.o 2459 [ + + ]: 1850 : if (merged_index >= 0)
2460 : : {
2461 [ - + ]: 1630 : Assert(merged_index < nmerged);
2462 : 1630 : outer_indexes[merged_index] = i;
2463 : : }
2464 : : }
2465 [ + + ]: 1940 : if (i < inner_nparts)
2466 : : {
2296 tgl@sss.pgh.pa.us 2467 : 1870 : int merged_index = inner_map->merged_indexes[i];
2468 : :
2332 efujita@postgresql.o 2469 [ + + ]: 1870 : if (merged_index >= 0)
2470 : : {
2471 [ - + ]: 1600 : Assert(merged_index < nmerged);
2472 : 1600 : inner_indexes[merged_index] = i;
2473 : : }
2474 : : }
2475 : : }
2476 : :
2477 : : /* Build the list pairs. */
2478 [ + + ]: 2330 : for (i = 0; i < nmerged; i++)
2479 : : {
2480 : 1720 : int outer_index = outer_indexes[i];
2481 : 1720 : int inner_index = inner_indexes[i];
2482 : :
2483 : : /*
2484 : : * If both partitions are dummy, it means the merged partition that
2485 : : * had been assigned to the outer/inner partition was removed when
2486 : : * re-merging the outer/inner partition in
2487 : : * merge_matching_partitions(); ignore the merged partition.
2488 : : */
2489 [ + + + + ]: 1720 : if (outer_index == -1 && inner_index == -1)
2490 : 80 : continue;
2491 : :
2492 [ + + ]: 3270 : *outer_parts = lappend(*outer_parts, outer_index >= 0 ?
2493 : 1630 : outer_rel->part_rels[outer_index] : NULL);
2494 [ + + ]: 3240 : *inner_parts = lappend(*inner_parts, inner_index >= 0 ?
2495 : 1600 : inner_rel->part_rels[inner_index] : NULL);
2496 : : }
2497 : :
2498 : 610 : pfree(outer_indexes);
2499 : 610 : pfree(inner_indexes);
2500 : 610 : }
2501 : :
2502 : : /*
2503 : : * build_merged_partition_bounds
2504 : : * Create a PartitionBoundInfo struct from merged partition bounds
2505 : : */
2506 : : static PartitionBoundInfo
2507 : 610 : build_merged_partition_bounds(char strategy, List *merged_datums,
2508 : : List *merged_kinds, List *merged_indexes,
2509 : : int null_index, int default_index)
2510 : : {
2511 : : PartitionBoundInfo merged_bounds;
2512 : 610 : int ndatums = list_length(merged_datums);
2513 : : int pos;
2514 : : ListCell *lc;
2515 : :
260 michael@paquier.xyz 2516 : 610 : merged_bounds = palloc_object(PartitionBoundInfoData);
2332 efujita@postgresql.o 2517 : 610 : merged_bounds->strategy = strategy;
2518 : 610 : merged_bounds->ndatums = ndatums;
2519 : :
260 michael@paquier.xyz 2520 : 610 : merged_bounds->datums = palloc_array(Datum *, ndatums);
2332 efujita@postgresql.o 2521 : 610 : pos = 0;
2522 [ + - + + : 3370 : foreach(lc, merged_datums)
+ + ]
2523 : 2760 : merged_bounds->datums[pos++] = (Datum *) lfirst(lc);
2524 : :
2525 [ + + ]: 610 : if (strategy == PARTITION_STRATEGY_RANGE)
2526 : : {
2527 [ - + ]: 245 : Assert(list_length(merged_kinds) == ndatums);
260 michael@paquier.xyz 2528 : 245 : merged_bounds->kind = palloc_array(PartitionRangeDatumKind *, ndatums);
2332 efujita@postgresql.o 2529 : 245 : pos = 0;
2530 [ + - + + : 1300 : foreach(lc, merged_kinds)
+ + ]
2531 : 1055 : merged_bounds->kind[pos++] = (PartitionRangeDatumKind *) lfirst(lc);
2532 : :
2533 : : /* There are ndatums+1 indexes in the case of range partitioning. */
2534 : 245 : merged_indexes = lappend_int(merged_indexes, -1);
2535 : 245 : ndatums++;
2536 : : }
2537 : : else
2538 : : {
2539 [ - + ]: 365 : Assert(strategy == PARTITION_STRATEGY_LIST);
2540 [ - + ]: 365 : Assert(merged_kinds == NIL);
2541 : 365 : merged_bounds->kind = NULL;
2542 : : }
2543 : :
2544 : : /* interleaved_parts is always NULL for join relations. */
1791 drowley@postgresql.o 2545 : 610 : merged_bounds->interleaved_parts = NULL;
2546 : :
2332 efujita@postgresql.o 2547 [ - + ]: 610 : Assert(list_length(merged_indexes) == ndatums);
2037 tgl@sss.pgh.pa.us 2548 : 610 : merged_bounds->nindexes = ndatums;
260 michael@paquier.xyz 2549 : 610 : merged_bounds->indexes = palloc_array(int, ndatums);
2332 efujita@postgresql.o 2550 : 610 : pos = 0;
2551 [ + - + + : 3615 : foreach(lc, merged_indexes)
+ + ]
2552 : 3005 : merged_bounds->indexes[pos++] = lfirst_int(lc);
2553 : :
2554 : 610 : merged_bounds->null_index = null_index;
2555 : 610 : merged_bounds->default_index = default_index;
2556 : :
2557 : 610 : return merged_bounds;
2558 : : }
2559 : :
2560 : : /*
2561 : : * get_range_partition
2562 : : * Get the next non-dummy partition of a range-partitioned relation,
2563 : : * returning the index of that partition
2564 : : *
2565 : : * *lb and *ub are set to the lower and upper bounds of that partition
2566 : : * respectively, and *lb_pos is advanced to the next lower bound, if any.
2567 : : */
2568 : : static int
2569 : 2114 : get_range_partition(RelOptInfo *rel,
2570 : : PartitionBoundInfo bi,
2571 : : int *lb_pos,
2572 : : PartitionRangeBound *lb,
2573 : : PartitionRangeBound *ub)
2574 : : {
2575 : : int part_index;
2576 : :
2577 [ - + ]: 2114 : Assert(bi->strategy == PARTITION_STRATEGY_RANGE);
2578 : :
2579 : : do
2580 : : {
2581 : 2154 : part_index = get_range_partition_internal(bi, lb_pos, lb, ub);
2582 [ + + ]: 2154 : if (part_index == -1)
2583 : 525 : return -1;
2584 [ + + ]: 1629 : } while (is_dummy_partition(rel, part_index));
2585 : :
2586 : 1589 : return part_index;
2587 : : }
2588 : :
2589 : : static int
2590 : 2154 : get_range_partition_internal(PartitionBoundInfo bi,
2591 : : int *lb_pos,
2592 : : PartitionRangeBound *lb,
2593 : : PartitionRangeBound *ub)
2594 : : {
2595 : : /* Return the index as -1 if we've exhausted all lower bounds. */
2596 [ + + ]: 2154 : if (*lb_pos >= bi->ndatums)
2597 : 525 : return -1;
2598 : :
2599 : : /* A lower bound should have at least one more bound after it. */
2600 [ - + ]: 1629 : Assert(*lb_pos + 1 < bi->ndatums);
2601 : :
2602 : : /* Set the lower bound. */
2603 : 1629 : lb->index = bi->indexes[*lb_pos];
2604 : 1629 : lb->datums = bi->datums[*lb_pos];
2605 : 1629 : lb->kind = bi->kind[*lb_pos];
2606 : 1629 : lb->lower = true;
2607 : : /* Set the upper bound. */
2608 : 1629 : ub->index = bi->indexes[*lb_pos + 1];
2609 : 1629 : ub->datums = bi->datums[*lb_pos + 1];
2610 : 1629 : ub->kind = bi->kind[*lb_pos + 1];
2611 : 1629 : ub->lower = false;
2612 : :
2613 : : /* The index assigned to an upper bound should be valid. */
2614 [ - + ]: 1629 : Assert(ub->index >= 0);
2615 : :
2616 : : /*
2617 : : * Advance the position to the next lower bound. If there are no bounds
2618 : : * left beyond the upper bound, we have reached the last lower bound.
2619 : : */
2620 [ + + ]: 1629 : if (*lb_pos + 2 >= bi->ndatums)
2621 : 571 : *lb_pos = bi->ndatums;
2622 : : else
2623 : : {
2624 : : /*
2625 : : * If the index assigned to the bound next to the upper bound isn't
2626 : : * valid, that is the next lower bound; else, the upper bound is also
2627 : : * the lower bound of the next range partition.
2628 : : */
2629 [ + + ]: 1058 : if (bi->indexes[*lb_pos + 2] < 0)
2630 : 395 : *lb_pos = *lb_pos + 2;
2631 : : else
2632 : 663 : *lb_pos = *lb_pos + 1;
2633 : : }
2634 : :
2635 : 1629 : return ub->index;
2636 : : }
2637 : :
2638 : : /*
2639 : : * compare_range_partitions
2640 : : * Compare the bounds of two range partitions, and return true if the
2641 : : * two partitions overlap, false otherwise
2642 : : *
2643 : : * *lb_cmpval is set to -1, 0, or 1 if the outer partition's lower bound is
2644 : : * lower than, equal to, or higher than the inner partition's lower bound
2645 : : * respectively. Likewise, *ub_cmpval is set to -1, 0, or 1 if the outer
2646 : : * partition's upper bound is lower than, equal to, or higher than the inner
2647 : : * partition's upper bound respectively.
2648 : : */
2649 : : static bool
2650 : 721 : compare_range_partitions(int partnatts, FmgrInfo *partsupfuncs,
2651 : : Oid *partcollations,
2652 : : PartitionRangeBound *outer_lb,
2653 : : PartitionRangeBound *outer_ub,
2654 : : PartitionRangeBound *inner_lb,
2655 : : PartitionRangeBound *inner_ub,
2656 : : int *lb_cmpval, int *ub_cmpval)
2657 : : {
2658 : : /*
2659 : : * Check if the outer partition's upper bound is lower than the inner
2660 : : * partition's lower bound; if so the partitions aren't overlapping.
2661 : : */
2662 [ - + ]: 721 : if (compare_range_bounds(partnatts, partsupfuncs, partcollations,
2663 : : outer_ub, inner_lb) < 0)
2664 : : {
2332 efujita@postgresql.o 2665 :UBC 0 : *lb_cmpval = -1;
2666 : 0 : *ub_cmpval = -1;
2667 : 0 : return false;
2668 : : }
2669 : :
2670 : : /*
2671 : : * Check if the outer partition's lower bound is higher than the inner
2672 : : * partition's upper bound; if so the partitions aren't overlapping.
2673 : : */
2332 efujita@postgresql.o 2674 [ + + ]:CBC 721 : if (compare_range_bounds(partnatts, partsupfuncs, partcollations,
2675 : : outer_lb, inner_ub) > 0)
2676 : : {
2677 : 30 : *lb_cmpval = 1;
2678 : 30 : *ub_cmpval = 1;
2679 : 30 : return false;
2680 : : }
2681 : :
2682 : : /* All other cases indicate overlapping partitions. */
2683 : 691 : *lb_cmpval = compare_range_bounds(partnatts, partsupfuncs, partcollations,
2684 : : outer_lb, inner_lb);
2685 : 691 : *ub_cmpval = compare_range_bounds(partnatts, partsupfuncs, partcollations,
2686 : : outer_ub, inner_ub);
2687 : 691 : return true;
2688 : : }
2689 : :
2690 : : /*
2691 : : * get_merged_range_bounds
2692 : : * Given the bounds of range partitions to be joined, determine the bounds
2693 : : * of a merged partition produced from the range partitions
2694 : : *
2695 : : * *merged_lb and *merged_ub are set to the lower and upper bounds of the
2696 : : * merged partition.
2697 : : */
2698 : : static void
2699 : 691 : get_merged_range_bounds(int partnatts, FmgrInfo *partsupfuncs,
2700 : : Oid *partcollations, JoinType jointype,
2701 : : PartitionRangeBound *outer_lb,
2702 : : PartitionRangeBound *outer_ub,
2703 : : PartitionRangeBound *inner_lb,
2704 : : PartitionRangeBound *inner_ub,
2705 : : int lb_cmpval, int ub_cmpval,
2706 : : PartitionRangeBound *merged_lb,
2707 : : PartitionRangeBound *merged_ub)
2708 : : {
2709 [ - + ]: 691 : Assert(compare_range_bounds(partnatts, partsupfuncs, partcollations,
2710 : : outer_lb, inner_lb) == lb_cmpval);
2711 [ - + ]: 691 : Assert(compare_range_bounds(partnatts, partsupfuncs, partcollations,
2712 : : outer_ub, inner_ub) == ub_cmpval);
2713 : :
2714 [ + + + - ]: 691 : switch (jointype)
2715 : : {
2716 : 361 : case JOIN_INNER:
2717 : : case JOIN_SEMI:
2718 : :
2719 : : /*
2720 : : * An INNER/SEMI join will have the rows that fit both sides, so
2721 : : * the lower bound of the merged partition will be the higher of
2722 : : * the two lower bounds, and the upper bound of the merged
2723 : : * partition will be the lower of the two upper bounds.
2724 : : */
2725 [ + + ]: 361 : *merged_lb = (lb_cmpval > 0) ? *outer_lb : *inner_lb;
2726 [ + + ]: 361 : *merged_ub = (ub_cmpval < 0) ? *outer_ub : *inner_ub;
2727 : 361 : break;
2728 : :
2729 : 270 : case JOIN_LEFT:
2730 : : case JOIN_ANTI:
2731 : :
2732 : : /*
2733 : : * A LEFT/ANTI join will have all the rows from the outer side, so
2734 : : * the bounds of the merged partition will be the same as the
2735 : : * outer bounds.
2736 : : */
2737 : 270 : *merged_lb = *outer_lb;
2738 : 270 : *merged_ub = *outer_ub;
2739 : 270 : break;
2740 : :
2741 : 60 : case JOIN_FULL:
2742 : :
2743 : : /*
2744 : : * A FULL join will have all the rows from both sides, so the
2745 : : * lower bound of the merged partition will be the lower of the
2746 : : * two lower bounds, and the upper bound of the merged partition
2747 : : * will be the higher of the two upper bounds.
2748 : : */
2749 [ + + ]: 60 : *merged_lb = (lb_cmpval < 0) ? *outer_lb : *inner_lb;
2750 [ + + ]: 60 : *merged_ub = (ub_cmpval > 0) ? *outer_ub : *inner_ub;
2751 : 60 : break;
2752 : :
2332 efujita@postgresql.o 2753 :UBC 0 : default:
2754 [ # # ]: 0 : elog(ERROR, "unrecognized join type: %d", (int) jointype);
2755 : : }
2332 efujita@postgresql.o 2756 :CBC 691 : }
2757 : :
2758 : : /*
2759 : : * add_merged_range_bounds
2760 : : * Add the bounds of a merged partition to the lists of range bounds
2761 : : */
2762 : : static void
2763 : 680 : add_merged_range_bounds(int partnatts, FmgrInfo *partsupfuncs,
2764 : : Oid *partcollations,
2765 : : PartitionRangeBound *merged_lb,
2766 : : PartitionRangeBound *merged_ub,
2767 : : int merged_index,
2768 : : List **merged_datums,
2769 : : List **merged_kinds,
2770 : : List **merged_indexes)
2771 : : {
2772 : : int cmpval;
2773 : :
2774 [ + + ]: 680 : if (!*merged_datums)
2775 : : {
2776 : : /* First merged partition */
2777 [ - + ]: 275 : Assert(!*merged_kinds);
2778 [ - + ]: 275 : Assert(!*merged_indexes);
2779 : 275 : cmpval = 1;
2780 : : }
2781 : : else
2782 : : {
2783 : : PartitionRangeBound prev_ub;
2784 : :
2785 [ - + ]: 405 : Assert(*merged_datums);
2786 [ - + ]: 405 : Assert(*merged_kinds);
2787 [ - + ]: 405 : Assert(*merged_indexes);
2788 : :
2789 : : /* Get the last upper bound. */
2790 : 405 : prev_ub.index = llast_int(*merged_indexes);
2791 : 405 : prev_ub.datums = (Datum *) llast(*merged_datums);
2792 : 405 : prev_ub.kind = (PartitionRangeDatumKind *) llast(*merged_kinds);
2793 : 405 : prev_ub.lower = false;
2794 : :
2795 : : /*
2796 : : * We pass lower1 = false to partition_rbound_cmp() to prevent it from
2797 : : * considering the last upper bound to be smaller than the lower bound
2798 : : * of the merged partition when the values of the two range bounds
2799 : : * compare equal.
2800 : : */
2801 : 405 : cmpval = partition_rbound_cmp(partnatts, partsupfuncs, partcollations,
2802 : : merged_lb->datums, merged_lb->kind,
2803 : : false, &prev_ub);
2804 [ - + ]: 405 : Assert(cmpval >= 0);
2805 : : }
2806 : :
2807 : : /*
2808 : : * If the lower bound is higher than the last upper bound, add the lower
2809 : : * bound with the index as -1 indicating that that is a lower bound; else,
2810 : : * the last upper bound will be reused as the lower bound of the merged
2811 : : * partition, so skip this.
2812 : : */
2813 [ + + ]: 680 : if (cmpval > 0)
2814 : : {
2815 : 480 : *merged_datums = lappend(*merged_datums, merged_lb->datums);
2816 : 480 : *merged_kinds = lappend(*merged_kinds, merged_lb->kind);
2817 : 480 : *merged_indexes = lappend_int(*merged_indexes, -1);
2818 : : }
2819 : :
2820 : : /* Add the upper bound and index of the merged partition. */
2821 : 680 : *merged_datums = lappend(*merged_datums, merged_ub->datums);
2822 : 680 : *merged_kinds = lappend(*merged_kinds, merged_ub->kind);
2823 : 680 : *merged_indexes = lappend_int(*merged_indexes, merged_index);
2824 : 680 : }
2825 : :
2826 : : /*
2827 : : * partitions_are_ordered
2828 : : * Determine whether the partitions described by 'boundinfo' are ordered,
2829 : : * that is partitions appearing earlier in the PartitionDesc sequence
2830 : : * contain partition keys strictly less than those appearing later.
2831 : : * Also, if NULL values are possible, they must come in the last
2832 : : * partition defined in the PartitionDesc. 'live_parts' marks which
2833 : : * partitions we should include when checking the ordering. Partitions
2834 : : * that do not appear in 'live_parts' are ignored.
2835 : : *
2836 : : * If out of order, or there is insufficient info to know the order,
2837 : : * then we return false.
2838 : : */
2839 : : bool
1850 drowley@postgresql.o 2840 : 57966 : partitions_are_ordered(PartitionBoundInfo boundinfo, Bitmapset *live_parts)
2841 : : {
2701 tgl@sss.pgh.pa.us 2842 [ - + ]: 57966 : Assert(boundinfo != NULL);
2843 : :
2844 [ + + + - ]: 57966 : switch (boundinfo->strategy)
2845 : : {
2846 : 35215 : case PARTITION_STRATEGY_RANGE:
2847 : :
2848 : : /*
2849 : : * RANGE-type partitioning guarantees that the partitions can be
2850 : : * scanned in the order that they're defined in the PartitionDesc
2851 : : * to provide sequential, non-overlapping ranges of tuples.
2852 : : * However, if a DEFAULT partition exists and it's contained
2853 : : * within live_parts, then the partitions are not ordered.
2854 : : */
1850 drowley@postgresql.o 2855 [ + + ]: 35215 : if (!partition_bound_has_default(boundinfo) ||
2856 [ + + ]: 2578 : !bms_is_member(boundinfo->default_index, live_parts))
2701 tgl@sss.pgh.pa.us 2857 : 34107 : return true;
2858 : 1108 : break;
2859 : :
2860 : 21980 : case PARTITION_STRATEGY_LIST:
2861 : :
2862 : : /*
2863 : : * LIST partitioned are ordered providing none of live_parts
2864 : : * overlap with the partitioned table's interleaved partitions.
2865 : : */
1850 drowley@postgresql.o 2866 [ + + ]: 21980 : if (!bms_overlap(live_parts, boundinfo->interleaved_parts))
2701 tgl@sss.pgh.pa.us 2867 : 20049 : return true;
2868 : :
1850 drowley@postgresql.o 2869 : 1931 : break;
1393 alvherre@alvh.no-ip. 2870 : 771 : case PARTITION_STRATEGY_HASH:
2701 tgl@sss.pgh.pa.us 2871 : 771 : break;
2872 : : }
2873 : :
2874 : 3810 : return false;
2875 : : }
2876 : :
2877 : : /*
2878 : : * check_new_partition_bound
2879 : : *
2880 : : * Checks if the new partition's bound overlaps any of the existing partitions
2881 : : * of parent. Also performs additional checks as necessary per strategy.
2882 : : */
2883 : : void
3057 alvherre@alvh.no-ip. 2884 : 7163 : check_new_partition_bound(char *relname, Relation parent,
2885 : : PartitionBoundSpec *spec, ParseState *pstate)
2886 : : {
2887 : 7163 : PartitionKey key = RelationGetPartitionKey(parent);
1953 2888 : 7163 : PartitionDesc partdesc = RelationGetPartitionDesc(parent, false);
3057 2889 : 7163 : PartitionBoundInfo boundinfo = partdesc->boundinfo;
2890 : 7163 : int with = -1;
2891 : 7163 : bool overlap = false;
2164 tgl@sss.pgh.pa.us 2892 : 7163 : int overlap_location = -1;
2893 : :
3057 alvherre@alvh.no-ip. 2894 [ + + ]: 7163 : if (spec->is_default)
2895 : : {
2896 : : /*
2897 : : * The default partition bound never conflicts with any other
2898 : : * partition's; if that's what we're attaching, the only possible
2899 : : * problem is that one already exists, so check for that and we're
2900 : : * done.
2901 : : */
2902 [ + + + + ]: 370 : if (boundinfo == NULL || !partition_bound_has_default(boundinfo))
2903 : 354 : return;
2904 : :
2905 : : /* Default partition already exists, error out. */
2906 [ + - ]: 16 : ereport(ERROR,
2907 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
2908 : : errmsg("partition \"%s\" conflicts with existing default partition \"%s\"",
2909 : : relname, get_rel_name(partdesc->oids[boundinfo->default_index])),
2910 : : parser_errposition(pstate, spec->location)));
2911 : : }
2912 : :
2913 [ + + + - ]: 6793 : switch (key->strategy)
2914 : : {
2915 : 440 : case PARTITION_STRATEGY_HASH:
2916 : : {
2917 [ - + ]: 440 : Assert(spec->strategy == PARTITION_STRATEGY_HASH);
2918 [ + - - + ]: 440 : Assert(spec->remainder >= 0 && spec->remainder < spec->modulus);
2919 : :
2920 [ + + ]: 440 : if (partdesc->nparts > 0)
2921 : : {
2922 : : int greatest_modulus;
2923 : : int remainder;
2924 : : int offset;
2925 : :
2926 : : /*
2927 : : * Check rule that every modulus must be a factor of the
2928 : : * next larger modulus. (For example, if you have a bunch
2929 : : * of partitions that all have modulus 5, you can add a
2930 : : * new partition with modulus 10 or a new partition with
2931 : : * modulus 15, but you cannot add both a partition with
2932 : : * modulus 10 and a partition with modulus 15, because 10
2933 : : * is not a factor of 15.) We need only check the next
2934 : : * smaller and next larger existing moduli, relying on
2935 : : * previous enforcement of this rule to be sure that the
2936 : : * rest are in line.
2937 : : */
2938 : :
2939 : : /*
2940 : : * Get the greatest (modulus, remainder) pair contained in
2941 : : * boundinfo->datums that is less than or equal to the
2942 : : * (spec->modulus, spec->remainder) pair.
2943 : : */
2944 : 281 : offset = partition_hash_bsearch(boundinfo,
2945 : : spec->modulus,
2946 : : spec->remainder);
2947 [ + + ]: 281 : if (offset < 0)
2948 : : {
2949 : : int next_modulus;
2950 : :
2951 : : /*
2952 : : * All existing moduli are greater or equal, so the
2953 : : * new one must be a factor of the smallest one, which
2954 : : * is first in the boundinfo.
2955 : : */
2012 peter@eisentraut.org 2956 : 9 : next_modulus = DatumGetInt32(boundinfo->datums[0][0]);
2957 [ + + ]: 9 : if (next_modulus % spec->modulus != 0)
2958 [ + - ]: 4 : ereport(ERROR,
2959 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
2960 : : errmsg("every hash partition modulus must be a factor of the next larger modulus"),
2961 : : errdetail("The new modulus %d is not a factor of %d, the modulus of existing partition \"%s\".",
2962 : : spec->modulus, next_modulus,
2963 : : get_rel_name(partdesc->oids[0]))));
2964 : : }
2965 : : else
2966 : : {
2967 : : int prev_modulus;
2968 : :
2969 : : /*
2970 : : * We found the largest (modulus, remainder) pair less
2971 : : * than or equal to the new one. That modulus must be
2972 : : * a divisor of, or equal to, the new modulus.
2973 : : */
2974 : 272 : prev_modulus = DatumGetInt32(boundinfo->datums[offset][0]);
2975 : :
2976 [ + + ]: 272 : if (spec->modulus % prev_modulus != 0)
2977 [ + - ]: 4 : ereport(ERROR,
2978 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
2979 : : errmsg("every hash partition modulus must be a factor of the next larger modulus"),
2980 : : errdetail("The new modulus %d is not divisible by %d, the modulus of existing partition \"%s\".",
2981 : : spec->modulus,
2982 : : prev_modulus,
2983 : : get_rel_name(partdesc->oids[offset]))));
2984 : :
2985 [ + + ]: 268 : if (offset + 1 < boundinfo->ndatums)
2986 : : {
2987 : : int next_modulus;
2988 : :
2989 : : /*
2990 : : * Look at the next higher (modulus, remainder)
2991 : : * pair. That could have the same modulus and a
2992 : : * larger remainder than the new pair, in which
2993 : : * case we're good. If it has a larger modulus,
2994 : : * the new modulus must divide that one.
2995 : : */
2996 : 20 : next_modulus = DatumGetInt32(boundinfo->datums[offset + 1][0]);
2997 : :
2998 [ + + ]: 20 : if (next_modulus % spec->modulus != 0)
2999 [ + - ]: 4 : ereport(ERROR,
3000 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
3001 : : errmsg("every hash partition modulus must be a factor of the next larger modulus"),
3002 : : errdetail("The new modulus %d is not a factor of %d, the modulus of existing partition \"%s\".",
3003 : : spec->modulus, next_modulus,
3004 : : get_rel_name(partdesc->oids[offset + 1]))));
3005 : : }
3006 : : }
3007 : :
2037 tgl@sss.pgh.pa.us 3008 : 269 : greatest_modulus = boundinfo->nindexes;
3057 alvherre@alvh.no-ip. 3009 : 269 : remainder = spec->remainder;
3010 : :
3011 : : /*
3012 : : * Normally, the lowest remainder that could conflict with
3013 : : * the new partition is equal to the remainder specified
3014 : : * for the new partition, but when the new partition has a
3015 : : * modulus higher than any used so far, we need to adjust.
3016 : : */
3017 [ + + ]: 269 : if (remainder >= greatest_modulus)
3018 : 8 : remainder = remainder % greatest_modulus;
3019 : :
3020 : : /* Check every potentially-conflicting remainder. */
3021 : : do
3022 : : {
3023 [ + + ]: 353 : if (boundinfo->indexes[remainder] != -1)
3024 : : {
3025 : 16 : overlap = true;
2164 tgl@sss.pgh.pa.us 3026 : 16 : overlap_location = spec->location;
3057 alvherre@alvh.no-ip. 3027 : 16 : with = boundinfo->indexes[remainder];
3028 : 16 : break;
3029 : : }
3030 : 337 : remainder += spec->modulus;
3031 [ + + ]: 337 : } while (remainder < greatest_modulus);
3032 : : }
3033 : :
3034 : 428 : break;
3035 : : }
3036 : :
3037 : 3037 : case PARTITION_STRATEGY_LIST:
3038 : : {
3039 [ - + ]: 3037 : Assert(spec->strategy == PARTITION_STRATEGY_LIST);
3040 : :
3041 [ + + ]: 3037 : if (partdesc->nparts > 0)
3042 : : {
3043 : : ListCell *cell;
3044 : :
3045 [ + - + - : 1571 : Assert(boundinfo &&
+ + + + -
+ ]
3046 : : boundinfo->strategy == PARTITION_STRATEGY_LIST &&
3047 : : (boundinfo->ndatums > 0 ||
3048 : : partition_bound_accepts_nulls(boundinfo) ||
3049 : : partition_bound_has_default(boundinfo)));
3050 : :
3051 [ + - + + : 3978 : foreach(cell, spec->listdatums)
+ + ]
3052 : : {
1865 peter@eisentraut.org 3053 : 2423 : Const *val = lfirst_node(Const, cell);
3054 : :
2164 tgl@sss.pgh.pa.us 3055 : 2423 : overlap_location = val->location;
3057 alvherre@alvh.no-ip. 3056 [ + + ]: 2423 : if (!val->constisnull)
3057 : : {
3058 : : int offset;
3059 : : bool equal;
3060 : :
3061 : 2326 : offset = partition_list_bsearch(&key->partsupfunc[0],
3062 : : key->partcollation,
3063 : : boundinfo,
3064 : : val->constvalue,
3065 : : &equal);
3066 [ + + + + ]: 2326 : if (offset >= 0 && equal)
3067 : : {
3068 : 12 : overlap = true;
3069 : 12 : with = boundinfo->indexes[offset];
3070 : 12 : break;
3071 : : }
3072 : : }
3073 [ + + ]: 97 : else if (partition_bound_accepts_nulls(boundinfo))
3074 : : {
3075 : 4 : overlap = true;
3076 : 4 : with = boundinfo->null_index;
3077 : 4 : break;
3078 : : }
3079 : : }
3080 : : }
3081 : :
3082 : 3037 : break;
3083 : : }
3084 : :
3085 : 3316 : case PARTITION_STRATEGY_RANGE:
3086 : : {
3087 : : PartitionRangeBound *lower,
3088 : : *upper;
3089 : : int cmpval;
3090 : :
3091 [ - + ]: 3316 : Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
2997 tgl@sss.pgh.pa.us 3092 : 3316 : lower = make_one_partition_rbound(key, -1, spec->lowerdatums, true);
3093 : 3316 : upper = make_one_partition_rbound(key, -1, spec->upperdatums, false);
3094 : :
3095 : : /*
3096 : : * First check if the resulting range would be empty with
3097 : : * specified lower and upper bounds. partition_rbound_cmp
3098 : : * cannot return zero here, since the lower-bound flags are
3099 : : * different.
3100 : : */
2164 3101 : 3316 : cmpval = partition_rbound_cmp(key->partnatts,
3102 : : key->partsupfunc,
3103 : : key->partcollation,
3104 : : lower->datums, lower->kind,
3105 : : true, upper);
2127 3106 [ - + ]: 3316 : Assert(cmpval != 0);
3107 [ + + ]: 3316 : if (cmpval > 0)
3108 : : {
3109 : : /* Point to problematic key in the lower datums list. */
2164 3110 : 8 : PartitionRangeDatum *datum = list_nth(spec->lowerdatums,
3111 : : cmpval - 1);
3112 : :
3057 alvherre@alvh.no-ip. 3113 [ + - ]: 8 : ereport(ERROR,
3114 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
3115 : : errmsg("empty range bound specified for partition \"%s\"",
3116 : : relname),
3117 : : errdetail("Specified lower bound %s is greater than or equal to upper bound %s.",
3118 : : get_range_partbound_string(spec->lowerdatums),
3119 : : get_range_partbound_string(spec->upperdatums)),
3120 : : parser_errposition(pstate, datum->location)));
3121 : : }
3122 : :
3123 [ + + ]: 3308 : if (partdesc->nparts > 0)
3124 : : {
3125 : : int offset;
3126 : :
3127 [ + - + - : 2041 : Assert(boundinfo &&
+ + - + ]
3128 : : boundinfo->strategy == PARTITION_STRATEGY_RANGE &&
3129 : : (boundinfo->ndatums > 0 ||
3130 : : partition_bound_has_default(boundinfo)));
3131 : :
3132 : : /*
3133 : : * Test whether the new lower bound (which is treated
3134 : : * inclusively as part of the new partition) lies inside
3135 : : * an existing partition, or in a gap.
3136 : : *
3137 : : * If it's inside an existing partition, the bound at
3138 : : * offset + 1 will be the upper bound of that partition,
3139 : : * and its index will be >= 0.
3140 : : *
3141 : : * If it's in a gap, the bound at offset + 1 will be the
3142 : : * lower bound of the next partition, and its index will
3143 : : * be -1. This is also true if there is no next partition,
3144 : : * since the index array is initialised with an extra -1
3145 : : * at the end.
3146 : : */
3147 : 2041 : offset = partition_range_bsearch(key->partnatts,
3148 : : key->partsupfunc,
3149 : : key->partcollation,
3150 : : boundinfo, lower,
3151 : : &cmpval);
3152 : :
3153 [ + + ]: 2041 : if (boundinfo->indexes[offset + 1] < 0)
3154 : : {
3155 : : /*
3156 : : * Check that the new partition will fit in the gap.
3157 : : * For it to fit, the new upper bound must be less
3158 : : * than or equal to the lower bound of the next
3159 : : * partition, if there is one.
3160 : : */
3161 [ + + ]: 2017 : if (offset + 1 < boundinfo->ndatums)
3162 : : {
3163 : : Datum *datums;
3164 : : PartitionRangeDatumKind *kind;
3165 : : bool is_lower;
3166 : :
3167 : 60 : datums = boundinfo->datums[offset + 1];
3168 : 60 : kind = boundinfo->kind[offset + 1];
3169 : 60 : is_lower = (boundinfo->indexes[offset + 1] == -1);
3170 : :
3171 : 60 : cmpval = partition_rbound_cmp(key->partnatts,
3172 : : key->partsupfunc,
3173 : : key->partcollation,
3174 : : datums, kind,
3175 : : is_lower, upper);
3176 [ + + ]: 60 : if (cmpval < 0)
3177 : : {
3178 : : /*
3179 : : * Point to problematic key in the upper
3180 : : * datums list.
3181 : : */
3182 : : PartitionRangeDatum *datum =
1196 tgl@sss.pgh.pa.us 3183 : 8 : list_nth(spec->upperdatums, abs(cmpval) - 1);
3184 : :
3185 : : /*
3186 : : * The new partition overlaps with the
3187 : : * existing partition between offset + 1 and
3188 : : * offset + 2.
3189 : : */
3057 alvherre@alvh.no-ip. 3190 : 8 : overlap = true;
2164 tgl@sss.pgh.pa.us 3191 : 8 : overlap_location = datum->location;
3057 alvherre@alvh.no-ip. 3192 : 8 : with = boundinfo->indexes[offset + 2];
3193 : : }
3194 : : }
3195 : : }
3196 : : else
3197 : : {
3198 : : /*
3199 : : * The new partition overlaps with the existing
3200 : : * partition between offset and offset + 1.
3201 : : */
3202 : : PartitionRangeDatum *datum;
3203 : :
3204 : : /*
3205 : : * Point to problematic key in the lower datums list;
3206 : : * if we have equality, point to the first one.
3207 : : */
2164 tgl@sss.pgh.pa.us 3208 [ + + ]: 24 : datum = cmpval == 0 ? linitial(spec->lowerdatums) :
1420 peter@eisentraut.org 3209 : 12 : list_nth(spec->lowerdatums, abs(cmpval) - 1);
3057 alvherre@alvh.no-ip. 3210 : 24 : overlap = true;
2164 tgl@sss.pgh.pa.us 3211 : 24 : overlap_location = datum->location;
3057 alvherre@alvh.no-ip. 3212 : 24 : with = boundinfo->indexes[offset + 1];
3213 : : }
3214 : : }
3215 : :
3216 : 3308 : break;
3217 : : }
3218 : : }
3219 : :
3220 [ + + ]: 6773 : if (overlap)
3221 : : {
3222 [ - + ]: 64 : Assert(with >= 0);
3223 [ + - ]: 64 : ereport(ERROR,
3224 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
3225 : : errmsg("partition \"%s\" would overlap partition \"%s\"",
3226 : : relname, get_rel_name(partdesc->oids[with])),
3227 : : parser_errposition(pstate, overlap_location)));
3228 : : }
3229 : : }
3230 : :
3231 : : /*
3232 : : * check_default_partition_contents
3233 : : *
3234 : : * This function checks if there exists a row in the default partition that
3235 : : * would properly belong to the new partition being added. If it finds one,
3236 : : * it throws an error.
3237 : : */
3238 : : void
2997 tgl@sss.pgh.pa.us 3239 : 231 : check_default_partition_contents(Relation parent, Relation default_rel,
3240 : : PartitionBoundSpec *new_spec)
3241 : : {
3242 : : List *new_part_constraints;
3243 : : List *def_part_constraints;
3244 : : List *all_parts;
3245 : : ListCell *lc;
3246 : :
3057 alvherre@alvh.no-ip. 3247 : 462 : new_part_constraints = (new_spec->strategy == PARTITION_STRATEGY_LIST)
3248 : 107 : ? get_qual_for_list(parent, new_spec)
3249 [ + + ]: 231 : : get_qual_for_range(parent, new_spec, false);
3250 : : def_part_constraints =
3251 : 231 : get_proposed_default_constraint(new_part_constraints);
3252 : :
3253 : : /*
3254 : : * Map the Vars in the constraint expression from parent's attnos to
3255 : : * default_rel's.
3256 : : */
3257 : : def_part_constraints =
2614 tgl@sss.pgh.pa.us 3258 : 231 : map_partition_varattnos(def_part_constraints, 1, default_rel,
3259 : : parent);
3260 : :
3261 : : /*
3262 : : * If the existing constraints on the default partition imply that it will
3263 : : * not contain any row that would belong to the new partition, we can
3264 : : * avoid scanning the default partition.
3265 : : */
3057 alvherre@alvh.no-ip. 3266 [ + + ]: 231 : if (PartConstraintImpliedByRelConstraint(default_rel, def_part_constraints))
3267 : : {
2546 tgl@sss.pgh.pa.us 3268 [ + + ]: 9 : ereport(DEBUG1,
3269 : : (errmsg_internal("updated partition constraint for default partition \"%s\" is implied by existing constraints",
3270 : : RelationGetRelationName(default_rel))));
3057 alvherre@alvh.no-ip. 3271 : 9 : return;
3272 : : }
3273 : :
3274 : : /*
3275 : : * Scan the default partition and its subpartitions, and check for rows
3276 : : * that do not satisfy the revised partition constraints.
3277 : : */
3278 [ + + ]: 222 : if (default_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
3279 : 34 : all_parts = find_all_inheritors(RelationGetRelid(default_rel),
3280 : : AccessExclusiveLock, NULL);
3281 : : else
3282 : 188 : all_parts = list_make1_oid(RelationGetRelid(default_rel));
3283 : :
3284 [ + - + + : 526 : foreach(lc, all_parts)
+ + ]
3285 : : {
3286 : 316 : Oid part_relid = lfirst_oid(lc);
3287 : : Relation part_rel;
3288 : : Expr *partition_constraint;
3289 : : EState *estate;
3290 : 316 : ExprState *partqualstate = NULL;
3291 : : Snapshot snapshot;
3292 : : ExprContext *econtext;
3293 : : TableScanDesc scan;
3294 : : MemoryContext oldCxt;
3295 : : TupleTableSlot *tupslot;
3296 : :
3297 : : /* Lock already taken above. */
3298 [ + + ]: 316 : if (part_relid != RelationGetRelid(default_rel))
3299 : : {
2775 andres@anarazel.de 3300 : 94 : part_rel = table_open(part_relid, NoLock);
3301 : :
3302 : : /*
3303 : : * Map the Vars in the constraint expression from default_rel's
3304 : : * the sub-partition's.
3305 : : */
2617 alvherre@alvh.no-ip. 3306 : 94 : partition_constraint = make_ands_explicit(def_part_constraints);
3307 : : partition_constraint = (Expr *)
3308 : 94 : map_partition_varattnos((List *) partition_constraint, 1,
3309 : : part_rel, default_rel);
3310 : :
3311 : : /*
3312 : : * If the partition constraints on default partition child imply
3313 : : * that it will not contain any row that would belong to the new
3314 : : * partition, we can avoid scanning the child table.
3315 : : */
3057 3316 [ + + ]: 94 : if (PartConstraintImpliedByRelConstraint(part_rel,
3317 : : def_part_constraints))
3318 : : {
2546 tgl@sss.pgh.pa.us 3319 [ + + ]: 5 : ereport(DEBUG1,
3320 : : (errmsg_internal("updated partition constraint for default partition \"%s\" is implied by existing constraints",
3321 : : RelationGetRelationName(part_rel))));
3322 : :
2775 andres@anarazel.de 3323 : 5 : table_close(part_rel, NoLock);
3057 alvherre@alvh.no-ip. 3324 : 5 : continue;
3325 : : }
3326 : : }
3327 : : else
3328 : : {
3329 : 222 : part_rel = default_rel;
2617 3330 : 222 : partition_constraint = make_ands_explicit(def_part_constraints);
3331 : : }
3332 : :
3333 : : /*
3334 : : * Only RELKIND_RELATION relations (i.e. leaf partitions) need to be
3335 : : * scanned.
3336 : : */
3057 3337 [ + + ]: 311 : if (part_rel->rd_rel->relkind != RELKIND_RELATION)
3338 : : {
3339 [ - + ]: 34 : if (part_rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
3057 alvherre@alvh.no-ip. 3340 [ # # ]:UBC 0 : ereport(WARNING,
3341 : : (errcode(ERRCODE_CHECK_VIOLATION),
3342 : : errmsg("skipped scanning foreign table \"%s\" which is a partition of default partition \"%s\"",
3343 : : RelationGetRelationName(part_rel),
3344 : : RelationGetRelationName(default_rel))));
3345 : :
3057 alvherre@alvh.no-ip. 3346 [ - + ]:CBC 34 : if (RelationGetRelid(default_rel) != RelationGetRelid(part_rel))
2775 andres@anarazel.de 3347 :UBC 0 : table_close(part_rel, NoLock);
3348 : :
3057 alvherre@alvh.no-ip. 3349 :CBC 34 : continue;
3350 : : }
3351 : :
3352 : 277 : estate = CreateExecutorState();
3353 : :
3354 : : /* Build expression execution states for partition check quals */
3355 : 277 : partqualstate = ExecPrepareExpr(partition_constraint, estate);
3356 : :
3357 [ - + ]: 277 : econtext = GetPerTupleExprContext(estate);
3358 : 277 : snapshot = RegisterSnapshot(GetLatestSnapshot());
2726 andres@anarazel.de 3359 : 277 : tupslot = table_slot_create(part_rel, &estate->es_tupleTable);
150 melanieplageman@gmai 3360 : 277 : scan = table_beginscan(part_rel, snapshot, 0, NULL,
3361 : : SO_NONE);
3362 : :
3363 : : /*
3364 : : * Switch to per-tuple memory context and reset it for each tuple
3365 : : * produced, so we don't leak memory.
3366 : : */
3057 alvherre@alvh.no-ip. 3367 [ + - ]: 277 : oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
3368 : :
2726 andres@anarazel.de 3369 [ + + ]: 586 : while (table_scan_getnextslot(scan, ForwardScanDirection, tupslot))
3370 : : {
3057 alvherre@alvh.no-ip. 3371 : 44 : econtext->ecxt_scantuple = tupslot;
3372 : :
3373 [ + + ]: 44 : if (!ExecCheck(partqualstate, econtext))
3374 [ + - ]: 12 : ereport(ERROR,
3375 : : (errcode(ERRCODE_CHECK_VIOLATION),
3376 : : errmsg("updated partition constraint for default partition \"%s\" would be violated by some row",
3377 : : RelationGetRelationName(default_rel)),
3378 : : errtable(default_rel)));
3379 : :
3380 : 32 : ResetExprContext(econtext);
3381 [ - + ]: 32 : CHECK_FOR_INTERRUPTS();
3382 : : }
3383 : :
3384 : 265 : MemoryContextSwitchTo(oldCxt);
2726 andres@anarazel.de 3385 : 265 : table_endscan(scan);
3057 alvherre@alvh.no-ip. 3386 : 265 : UnregisterSnapshot(snapshot);
3387 : 265 : ExecDropSingleTupleTableSlot(tupslot);
3388 : 265 : FreeExecutorState(estate);
3389 : :
3390 [ + + ]: 265 : if (RelationGetRelid(default_rel) != RelationGetRelid(part_rel))
2775 andres@anarazel.de 3391 : 89 : table_close(part_rel, NoLock); /* keep the lock until commit */
3392 : : }
3393 : : }
3394 : :
3395 : : /*
3396 : : * get_hash_partition_greatest_modulus
3397 : : *
3398 : : * Returns the greatest modulus of the hash partition bound.
3399 : : * This is no longer used in the core code, but we keep it around
3400 : : * in case external modules are using it.
3401 : : */
3402 : : int
3057 alvherre@alvh.no-ip. 3403 :UBC 0 : get_hash_partition_greatest_modulus(PartitionBoundInfo bound)
3404 : : {
3405 [ # # # # ]: 0 : Assert(bound && bound->strategy == PARTITION_STRATEGY_HASH);
2037 tgl@sss.pgh.pa.us 3406 : 0 : return bound->nindexes;
3407 : : }
3408 : :
3409 : : /*
3410 : : * make_one_partition_rbound
3411 : : *
3412 : : * Return a PartitionRangeBound given a list of PartitionRangeDatum elements
3413 : : * and a flag telling whether the bound is lower or not. Made into a function
3414 : : * because there are multiple sites that want to use this facility.
3415 : : */
3416 : : static PartitionRangeBound *
2997 tgl@sss.pgh.pa.us 3417 :CBC 102432 : make_one_partition_rbound(PartitionKey key, int index, List *datums, bool lower)
3418 : : {
3419 : : PartitionRangeBound *bound;
3420 : : ListCell *lc;
3421 : : int i;
3422 : :
3057 alvherre@alvh.no-ip. 3423 [ - + ]: 102432 : Assert(datums != NIL);
3424 : :
260 michael@paquier.xyz 3425 : 102432 : bound = palloc0_object(PartitionRangeBound);
3057 alvherre@alvh.no-ip. 3426 : 102432 : bound->index = index;
260 michael@paquier.xyz 3427 : 102432 : bound->datums = palloc0_array(Datum, key->partnatts);
3428 : 102432 : bound->kind = palloc0_array(PartitionRangeDatumKind, key->partnatts);
3057 alvherre@alvh.no-ip. 3429 : 102432 : bound->lower = lower;
3430 : :
3431 : 102432 : i = 0;
3432 [ + - + + : 211388 : foreach(lc, datums)
+ + ]
3433 : : {
1865 peter@eisentraut.org 3434 : 108956 : PartitionRangeDatum *datum = lfirst_node(PartitionRangeDatum, lc);
3435 : :
3436 : : /* What's contained in this range datum? */
3057 alvherre@alvh.no-ip. 3437 : 108956 : bound->kind[i] = datum->kind;
3438 : :
3439 [ + + ]: 108956 : if (datum->kind == PARTITION_RANGE_DATUM_VALUE)
3440 : : {
3441 : 106727 : Const *val = castNode(Const, datum->value);
3442 : :
3443 [ - + ]: 106727 : if (val->constisnull)
3057 alvherre@alvh.no-ip. 3444 [ # # ]:UBC 0 : elog(ERROR, "invalid range bound datum");
3057 alvherre@alvh.no-ip. 3445 :CBC 106727 : bound->datums[i] = val->constvalue;
3446 : : }
3447 : :
3448 : 108956 : i++;
3449 : : }
3450 : :
3451 : 102432 : return bound;
3452 : : }
3453 : :
3454 : : /*
3455 : : * partition_rbound_cmp
3456 : : *
3457 : : * For two range bounds this decides whether the 1st one (specified by
3458 : : * datums1, kind1, and lower1) is <, =, or > the bound specified in *b2.
3459 : : *
3460 : : * 0 is returned if they are equal, otherwise a non-zero integer whose sign
3461 : : * indicates the ordering, and whose absolute value gives the 1-based
3462 : : * partition key number of the first mismatching column.
3463 : : *
3464 : : * partnatts, partsupfunc and partcollation give the number of attributes in the
3465 : : * bounds to be compared, comparison function to be used and the collations of
3466 : : * attributes, respectively.
3467 : : *
3468 : : * Note that if the values of the two range bounds compare equal, then we take
3469 : : * into account whether they are upper or lower bounds, and an upper bound is
3470 : : * considered to be smaller than a lower bound. This is important to the way
3471 : : * that RelationBuildPartitionDesc() builds the PartitionBoundInfoData
3472 : : * structure, which only stores the upper bound of a common boundary between
3473 : : * two contiguous partitions.
3474 : : */
3475 : : static int32
3476 : 106226 : partition_rbound_cmp(int partnatts, FmgrInfo *partsupfunc,
3477 : : Oid *partcollation,
3478 : : Datum *datums1, PartitionRangeDatumKind *kind1,
3479 : : bool lower1, PartitionRangeBound *b2)
3480 : : {
2164 tgl@sss.pgh.pa.us 3481 : 106226 : int32 colnum = 0;
3057 alvherre@alvh.no-ip. 3482 : 106226 : int32 cmpval = 0; /* placate compiler */
3483 : : int i;
3484 : 106226 : Datum *datums2 = b2->datums;
3485 : 106226 : PartitionRangeDatumKind *kind2 = b2->kind;
3486 : 106226 : bool lower2 = b2->lower;
3487 : :
3488 [ + + ]: 155696 : for (i = 0; i < partnatts; i++)
3489 : : {
3490 : : /* Track column number in case we need it for result */
2164 tgl@sss.pgh.pa.us 3491 : 110213 : colnum++;
3492 : :
3493 : : /*
3494 : : * First, handle cases where the column is unbounded, which should not
3495 : : * invoke the comparison procedure, and should not consider any later
3496 : : * columns. Note that the PartitionRangeDatumKind enum elements
3497 : : * compare the same way as the values they represent.
3498 : : */
3057 alvherre@alvh.no-ip. 3499 [ + + ]: 110213 : if (kind1[i] < kind2[i])
2164 tgl@sss.pgh.pa.us 3500 : 1276 : return -colnum;
3057 alvherre@alvh.no-ip. 3501 [ + + ]: 108937 : else if (kind1[i] > kind2[i])
2164 tgl@sss.pgh.pa.us 3502 : 4 : return colnum;
3057 alvherre@alvh.no-ip. 3503 [ + + ]: 108933 : else if (kind1[i] != PARTITION_RANGE_DATUM_VALUE)
3504 : : {
3505 : : /*
3506 : : * The column bounds are both MINVALUE or both MAXVALUE. No later
3507 : : * columns should be considered, but we still need to compare
3508 : : * whether they are upper or lower bounds.
3509 : : */
3510 : 172 : break;
3511 : : }
3512 : :
3513 : 108761 : cmpval = DatumGetInt32(FunctionCall2Coll(&partsupfunc[i],
3514 : 108761 : partcollation[i],
3515 : 108761 : datums1[i],
3516 : 108761 : datums2[i]));
3517 [ + + ]: 108761 : if (cmpval != 0)
3518 : 59291 : break;
3519 : : }
3520 : :
3521 : : /*
3522 : : * If the comparison is anything other than equal, we're done. If they
3523 : : * compare equal though, we still have to consider whether the boundaries
3524 : : * are inclusive or exclusive. Exclusive one is considered smaller of the
3525 : : * two.
3526 : : */
3527 [ + + + + ]: 104946 : if (cmpval == 0 && lower1 != lower2)
3528 [ + + ]: 43881 : cmpval = lower1 ? 1 : -1;
3529 : :
2164 tgl@sss.pgh.pa.us 3530 [ + + + + ]: 104946 : return cmpval == 0 ? 0 : (cmpval < 0 ? -colnum : colnum);
3531 : : }
3532 : :
3533 : : /*
3534 : : * partition_rbound_datum_cmp
3535 : : *
3536 : : * Return whether range bound (specified in rb_datums and rb_kind)
3537 : : * is <, =, or > partition key of tuple (tuple_datums)
3538 : : *
3539 : : * n_tuple_datums, partsupfunc and partcollation give number of attributes in
3540 : : * the bounds to be compared, comparison function to be used and the collations
3541 : : * of attributes resp.
3542 : : */
3543 : : int32
3057 alvherre@alvh.no-ip. 3544 : 1045630 : partition_rbound_datum_cmp(FmgrInfo *partsupfunc, Oid *partcollation,
3545 : : const Datum *rb_datums, PartitionRangeDatumKind *rb_kind,
3546 : : const Datum *tuple_datums, int n_tuple_datums)
3547 : : {
3548 : : int i;
3549 : 1045630 : int32 cmpval = -1;
3550 : :
3551 [ + + ]: 1096417 : for (i = 0; i < n_tuple_datums; i++)
3552 : : {
3553 [ + + ]: 1049620 : if (rb_kind[i] == PARTITION_RANGE_DATUM_MINVALUE)
3554 : 34399 : return -1;
3555 [ + + ]: 1015221 : else if (rb_kind[i] == PARTITION_RANGE_DATUM_MAXVALUE)
3556 : 34529 : return 1;
3557 : :
3558 : 980692 : cmpval = DatumGetInt32(FunctionCall2Coll(&partsupfunc[i],
3559 : 980692 : partcollation[i],
3560 : 980692 : rb_datums[i],
3561 : 980692 : tuple_datums[i]));
3562 [ + + ]: 980692 : if (cmpval != 0)
3563 : 929905 : break;
3564 : : }
3565 : :
3566 : 976702 : return cmpval;
3567 : : }
3568 : :
3569 : : /*
3570 : : * partition_hbound_cmp
3571 : : *
3572 : : * Compares modulus first, then remainder if modulus is equal.
3573 : : */
3574 : : static int32
3575 : 1071 : partition_hbound_cmp(int modulus1, int remainder1, int modulus2, int remainder2)
3576 : : {
3577 [ + + ]: 1071 : if (modulus1 < modulus2)
3578 : 116 : return -1;
3579 [ + + ]: 955 : if (modulus1 > modulus2)
3580 : 40 : return 1;
3581 [ + - + - ]: 915 : if (modulus1 == modulus2 && remainder1 != remainder2)
3582 [ + + ]: 915 : return (remainder1 > remainder2) ? 1 : -1;
3057 alvherre@alvh.no-ip. 3583 :UBC 0 : return 0;
3584 : : }
3585 : :
3586 : : /*
3587 : : * partition_list_bsearch
3588 : : * Returns the index of the greatest bound datum that is less than equal
3589 : : * to the given value or -1 if all of the bound datums are greater
3590 : : *
3591 : : * *is_equal is set to true if the bound datum at the returned index is equal
3592 : : * to the input value.
3593 : : */
3594 : : int
3057 alvherre@alvh.no-ip. 3595 :CBC 105682 : partition_list_bsearch(FmgrInfo *partsupfunc, Oid *partcollation,
3596 : : PartitionBoundInfo boundinfo,
3597 : : Datum value, bool *is_equal)
3598 : : {
3599 : : int lo,
3600 : : hi,
3601 : : mid;
3602 : :
3603 : 105682 : lo = -1;
3604 : 105682 : hi = boundinfo->ndatums - 1;
3605 [ + + ]: 214015 : while (lo < hi)
3606 : : {
3607 : : int32 cmpval;
3608 : :
3609 : 208726 : mid = (lo + hi + 1) / 2;
3610 : 208726 : cmpval = DatumGetInt32(FunctionCall2Coll(&partsupfunc[0],
3611 : : partcollation[0],
3612 : 208726 : boundinfo->datums[mid][0],
3613 : : value));
3614 [ + + ]: 208726 : if (cmpval <= 0)
3615 : : {
3616 : 177381 : lo = mid;
3617 : 177381 : *is_equal = (cmpval == 0);
3618 [ + + ]: 177381 : if (*is_equal)
3619 : 100393 : break;
3620 : : }
3621 : : else
3622 : 31345 : hi = mid - 1;
3623 : : }
3624 : :
3625 : 105682 : return lo;
3626 : : }
3627 : :
3628 : : /*
3629 : : * partition_range_bsearch
3630 : : * Returns the index of the greatest range bound that is less than or
3631 : : * equal to the given range bound or -1 if all of the range bounds are
3632 : : * greater
3633 : : *
3634 : : * Upon return from this function, *cmpval is set to 0 if the bound at the
3635 : : * returned index matches the input range bound exactly, otherwise a
3636 : : * non-zero integer whose sign indicates the ordering, and whose absolute
3637 : : * value gives the 1-based partition key number of the first mismatching
3638 : : * column.
3639 : : */
3640 : : static int
3641 : 2041 : partition_range_bsearch(int partnatts, FmgrInfo *partsupfunc,
3642 : : Oid *partcollation,
3643 : : PartitionBoundInfo boundinfo,
3644 : : PartitionRangeBound *probe, int32 *cmpval)
3645 : : {
3646 : : int lo,
3647 : : hi,
3648 : : mid;
3649 : :
3650 : 2041 : lo = -1;
3651 : 2041 : hi = boundinfo->ndatums - 1;
3652 [ + + ]: 8632 : while (lo < hi)
3653 : : {
3654 : 6603 : mid = (lo + hi + 1) / 2;
2164 tgl@sss.pgh.pa.us 3655 : 13206 : *cmpval = partition_rbound_cmp(partnatts, partsupfunc,
3656 : : partcollation,
3657 : 6603 : boundinfo->datums[mid],
3658 : 6603 : boundinfo->kind[mid],
3659 : 6603 : (boundinfo->indexes[mid] == -1),
3660 : : probe);
3661 [ + + ]: 6603 : if (*cmpval <= 0)
3662 : : {
3057 alvherre@alvh.no-ip. 3663 : 6511 : lo = mid;
2164 tgl@sss.pgh.pa.us 3664 [ + + ]: 6511 : if (*cmpval == 0)
3057 alvherre@alvh.no-ip. 3665 : 12 : break;
3666 : : }
3667 : : else
3668 : 92 : hi = mid - 1;
3669 : : }
3670 : :
3671 : 2041 : return lo;
3672 : : }
3673 : :
3674 : : /*
3675 : : * partition_range_datum_bsearch
3676 : : * Returns the index of the greatest range bound that is less than or
3677 : : * equal to the given tuple or -1 if all of the range bounds are greater
3678 : : *
3679 : : * *is_equal is set to true if the range bound at the returned index is equal
3680 : : * to the input tuple.
3681 : : */
3682 : : int
3683 : 349464 : partition_range_datum_bsearch(FmgrInfo *partsupfunc, Oid *partcollation,
3684 : : PartitionBoundInfo boundinfo,
3685 : : int nvalues, const Datum *values, bool *is_equal)
3686 : : {
3687 : : int lo,
3688 : : hi,
3689 : : mid;
3690 : :
3691 : 349464 : lo = -1;
3692 : 349464 : hi = boundinfo->ndatums - 1;
3693 [ + + ]: 1072694 : while (lo < hi)
3694 : : {
3695 : : int32 cmpval;
3696 : :
3697 : 764588 : mid = (lo + hi + 1) / 2;
3698 : 764588 : cmpval = partition_rbound_datum_cmp(partsupfunc,
3699 : : partcollation,
3700 : 764588 : boundinfo->datums[mid],
3701 : 764588 : boundinfo->kind[mid],
3702 : : values,
3703 : : nvalues);
3704 [ + + ]: 764588 : if (cmpval <= 0)
3705 : : {
3706 : 440556 : lo = mid;
3707 : 440556 : *is_equal = (cmpval == 0);
3708 : :
3709 [ + + ]: 440556 : if (*is_equal)
3710 : 41358 : break;
3711 : : }
3712 : : else
3713 : 324032 : hi = mid - 1;
3714 : : }
3715 : :
3716 : 349464 : return lo;
3717 : : }
3718 : :
3719 : : /*
3720 : : * partition_hash_bsearch
3721 : : * Returns the index of the greatest (modulus, remainder) pair that is
3722 : : * less than or equal to the given (modulus, remainder) pair or -1 if
3723 : : * all of them are greater
3724 : : */
3725 : : int
3726 : 281 : partition_hash_bsearch(PartitionBoundInfo boundinfo,
3727 : : int modulus, int remainder)
3728 : : {
3729 : : int lo,
3730 : : hi,
3731 : : mid;
3732 : :
3733 : 281 : lo = -1;
3734 : 281 : hi = boundinfo->ndatums - 1;
3735 [ + + ]: 732 : while (lo < hi)
3736 : : {
3737 : : int32 cmpval,
3738 : : bound_modulus,
3739 : : bound_remainder;
3740 : :
3741 : 451 : mid = (lo + hi + 1) / 2;
3742 : 451 : bound_modulus = DatumGetInt32(boundinfo->datums[mid][0]);
3743 : 451 : bound_remainder = DatumGetInt32(boundinfo->datums[mid][1]);
3744 : 451 : cmpval = partition_hbound_cmp(bound_modulus, bound_remainder,
3745 : : modulus, remainder);
3746 [ + + ]: 451 : if (cmpval <= 0)
3747 : : {
3748 : 410 : lo = mid;
3749 : :
3750 [ - + ]: 410 : if (cmpval == 0)
3057 alvherre@alvh.no-ip. 3751 :UBC 0 : break;
3752 : : }
3753 : : else
3057 alvherre@alvh.no-ip. 3754 :CBC 41 : hi = mid - 1;
3755 : : }
3756 : :
3757 : 281 : return lo;
3758 : : }
3759 : :
3760 : : /*
3761 : : * qsort_partition_hbound_cmp
3762 : : *
3763 : : * Hash bounds are sorted by modulus, then by remainder.
3764 : : */
3765 : : static int32
2843 michael@paquier.xyz 3766 : 620 : qsort_partition_hbound_cmp(const void *a, const void *b)
3767 : : {
1656 tgl@sss.pgh.pa.us 3768 : 620 : const PartitionHashBound *h1 = (const PartitionHashBound *) a;
3769 : 620 : const PartitionHashBound *h2 = (const PartitionHashBound *) b;
3770 : :
2843 michael@paquier.xyz 3771 : 1240 : return partition_hbound_cmp(h1->modulus, h1->remainder,
3772 : 620 : h2->modulus, h2->remainder);
3773 : : }
3774 : :
3775 : : /*
3776 : : * qsort_partition_list_value_cmp
3777 : : *
3778 : : * Compare two list partition bound datums.
3779 : : */
3780 : : static int32
3781 : 17848 : qsort_partition_list_value_cmp(const void *a, const void *b, void *arg)
3782 : : {
1656 tgl@sss.pgh.pa.us 3783 : 17848 : Datum val1 = ((const PartitionListValue *) a)->value,
3784 : 17848 : val2 = ((const PartitionListValue *) b)->value;
2843 michael@paquier.xyz 3785 : 17848 : PartitionKey key = (PartitionKey) arg;
3786 : :
3787 : 17848 : return DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
3788 : 17848 : key->partcollation[0],
3789 : : val1, val2));
3790 : : }
3791 : :
3792 : : /*
3793 : : * qsort_partition_rbound_cmp
3794 : : *
3795 : : * Used when sorting range bounds across all range partitions.
3796 : : */
3797 : : static int32
3798 : 91410 : qsort_partition_rbound_cmp(const void *a, const void *b, void *arg)
3799 : : {
3800 : 91410 : PartitionRangeBound *b1 = (*(PartitionRangeBound *const *) a);
3801 : 91410 : PartitionRangeBound *b2 = (*(PartitionRangeBound *const *) b);
3802 : 91410 : PartitionKey key = (PartitionKey) arg;
3803 : :
2127 tgl@sss.pgh.pa.us 3804 : 91410 : return compare_range_bounds(key->partnatts, key->partsupfunc,
3805 : : key->partcollation,
3806 : : b1, b2);
3807 : : }
3808 : :
3809 : : /*
3810 : : * get_partition_operator
3811 : : *
3812 : : * Return oid of the operator of the given strategy for the given partition
3813 : : * key column. It is assumed that the partitioning key is of the same type as
3814 : : * the chosen partitioning opclass, or at least binary-compatible. In the
3815 : : * latter case, *need_relabel is set to true if the opclass is not of a
3816 : : * polymorphic type (indicating a RelabelType node needed on top), otherwise
3817 : : * false.
3818 : : */
3819 : : static Oid
3057 alvherre@alvh.no-ip. 3820 : 9168 : get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
3821 : : bool *need_relabel)
3822 : : {
3823 : : Oid operoid;
3824 : :
3825 : : /*
3826 : : * Get the operator in the partitioning opfamily using the opclass'
3827 : : * declared input type as both left- and righttype.
3828 : : */
3829 : 9168 : operoid = get_opfamily_member(key->partopfamily[col],
2970 3830 : 9168 : key->partopcintype[col],
3831 : 9168 : key->partopcintype[col],
3832 : : strategy);
3833 [ - + ]: 9168 : if (!OidIsValid(operoid))
2970 alvherre@alvh.no-ip. 3834 [ # # ]:UBC 0 : elog(ERROR, "missing operator %d(%u,%u) in partition opfamily %u",
3835 : : strategy, key->partopcintype[col], key->partopcintype[col],
3836 : : key->partopfamily[col]);
3837 : :
3838 : : /*
3839 : : * If the partition key column is not of the same type as the operator
3840 : : * class and not polymorphic, tell caller to wrap the non-Const expression
3841 : : * in a RelabelType. This matches what parse_coerce.c does.
3842 : : */
2970 alvherre@alvh.no-ip. 3843 :CBC 18439 : *need_relabel = (key->parttypid[col] != key->partopcintype[col] &&
3844 [ + + + + ]: 9267 : key->partopcintype[col] != RECORDOID &&
3845 [ + - + + : 99 : !IsPolymorphicType(key->partopcintype[col]));
+ - + - +
+ + - + -
+ - + - +
- + - ]
3846 : :
3057 3847 : 9168 : return operoid;
3848 : : }
3849 : :
3850 : : /*
3851 : : * make_partition_op_expr
3852 : : * Returns an Expr for the given partition key column with arg1 and
3853 : : * arg2 as its leftop and rightop, respectively
3854 : : */
3855 : : static Expr *
3856 : 9168 : make_partition_op_expr(PartitionKey key, int keynum,
3857 : : uint16 strategy, Expr *arg1, Expr *arg2)
3858 : : {
3859 : : Oid operoid;
3860 : 9168 : bool need_relabel = false;
3861 : 9168 : Expr *result = NULL;
3862 : :
3863 : : /* Get the correct btree operator for this partitioning column */
3864 : 9168 : operoid = get_partition_operator(key, keynum, strategy, &need_relabel);
3865 : :
3866 : : /*
3867 : : * Chosen operator may be such that the non-Const operand needs to be
3868 : : * coerced, so apply the same; see the comment in
3869 : : * get_partition_operator().
3870 : : */
3871 [ + + ]: 9168 : if (!IsA(arg1, Const) &&
3872 [ + + ]: 6929 : (need_relabel ||
3873 [ - + ]: 6901 : key->partcollation[keynum] != key->parttypcoll[keynum]))
3874 : 28 : arg1 = (Expr *) makeRelabelType(arg1,
3875 : 28 : key->partopcintype[keynum],
3876 : : -1,
3877 : 28 : key->partcollation[keynum],
3878 : : COERCE_EXPLICIT_CAST);
3879 : :
3880 : : /* Generate the actual expression */
3881 [ + + - - ]: 9168 : switch (key->strategy)
3882 : : {
3883 : 1774 : case PARTITION_STRATEGY_LIST:
3884 : : {
3885 : 1774 : List *elems = (List *) arg2;
3886 : 1774 : int nelems = list_length(elems);
3887 : :
3888 [ - + ]: 1774 : Assert(nelems >= 1);
3889 [ - + ]: 1774 : Assert(keynum == 0);
3890 : :
3891 [ + + + + ]: 2431 : if (nelems > 1 &&
3892 : 657 : !type_is_array(key->parttypid[keynum]))
3893 : 653 : {
3894 : : ArrayExpr *arrexpr;
3895 : : ScalarArrayOpExpr *saopexpr;
3896 : :
3897 : : /* Construct an ArrayExpr for the right-hand inputs */
3898 : 653 : arrexpr = makeNode(ArrayExpr);
3899 : 653 : arrexpr->array_typeid =
3900 : 653 : get_array_type(key->parttypid[keynum]);
3901 : 653 : arrexpr->array_collid = key->parttypcoll[keynum];
3902 : 653 : arrexpr->element_typeid = key->parttypid[keynum];
3903 : 653 : arrexpr->elements = elems;
3904 : 653 : arrexpr->multidims = false;
3905 : 653 : arrexpr->location = -1;
3906 : :
3907 : : /* Build leftop = ANY (rightop) */
3908 : 653 : saopexpr = makeNode(ScalarArrayOpExpr);
3909 : 653 : saopexpr->opno = operoid;
3910 : 653 : saopexpr->opfuncid = get_opcode(operoid);
1967 drowley@postgresql.o 3911 : 653 : saopexpr->hashfuncid = InvalidOid;
1877 3912 : 653 : saopexpr->negfuncid = InvalidOid;
3057 alvherre@alvh.no-ip. 3913 : 653 : saopexpr->useOr = true;
3914 : 653 : saopexpr->inputcollid = key->partcollation[keynum];
3915 : 653 : saopexpr->args = list_make2(arg1, arrexpr);
3916 : 653 : saopexpr->location = -1;
3917 : :
3918 : 653 : result = (Expr *) saopexpr;
3919 : : }
3920 : : else
3921 : : {
3922 : 1121 : List *elemops = NIL;
3923 : : ListCell *lc;
3924 : :
3925 [ + - + + : 2246 : foreach(lc, elems)
+ + ]
3926 : : {
3927 : 1125 : Expr *elem = lfirst(lc),
3928 : : *elemop;
3929 : :
3930 : 1125 : elemop = make_opclause(operoid,
3931 : : BOOLOID,
3932 : : false,
3933 : : arg1, elem,
3934 : : InvalidOid,
3935 : 1125 : key->partcollation[keynum]);
3936 : 1125 : elemops = lappend(elemops, elemop);
3937 : : }
3938 : :
3939 [ + + ]: 1121 : result = nelems > 1 ? makeBoolExpr(OR_EXPR, elemops, -1) : linitial(elemops);
3940 : : }
3941 : 1774 : break;
3942 : : }
3943 : :
3944 : 7394 : case PARTITION_STRATEGY_RANGE:
3945 : 7394 : result = make_opclause(operoid,
3946 : : BOOLOID,
3947 : : false,
3948 : : arg1, arg2,
3949 : : InvalidOid,
3950 : 7394 : key->partcollation[keynum]);
3951 : 7394 : break;
3952 : :
1393 alvherre@alvh.no-ip. 3953 :UBC 0 : case PARTITION_STRATEGY_HASH:
3954 : 0 : Assert(false);
3955 : : break;
3956 : : }
3957 : :
3057 alvherre@alvh.no-ip. 3958 :CBC 9168 : return result;
3959 : : }
3960 : :
3961 : : /*
3962 : : * get_qual_for_hash
3963 : : *
3964 : : * Returns a CHECK constraint expression to use as a hash partition's
3965 : : * constraint, given the parent relation and partition bound structure.
3966 : : *
3967 : : * The partition constraint for a hash partition is always a call to the
3968 : : * built-in function satisfies_hash_partition().
3969 : : */
3970 : : static List *
3971 : 93 : get_qual_for_hash(Relation parent, PartitionBoundSpec *spec)
3972 : : {
3973 : 93 : PartitionKey key = RelationGetPartitionKey(parent);
3974 : : FuncExpr *fexpr;
3975 : : Node *relidConst;
3976 : : Node *modulusConst;
3977 : : Node *remainderConst;
3978 : : List *args;
3979 : : ListCell *partexprs_item;
3980 : : int i;
3981 : :
3982 : : /* Fixed arguments. */
3983 : 93 : relidConst = (Node *) makeConst(OIDOID,
3984 : : -1,
3985 : : InvalidOid,
3986 : : sizeof(Oid),
3987 : : ObjectIdGetDatum(RelationGetRelid(parent)),
3988 : : false,
3989 : : true);
3990 : :
3991 : 93 : modulusConst = (Node *) makeConst(INT4OID,
3992 : : -1,
3993 : : InvalidOid,
3994 : : sizeof(int32),
3995 : : Int32GetDatum(spec->modulus),
3996 : : false,
3997 : : true);
3998 : :
3999 : 93 : remainderConst = (Node *) makeConst(INT4OID,
4000 : : -1,
4001 : : InvalidOid,
4002 : : sizeof(int32),
4003 : : Int32GetDatum(spec->remainder),
4004 : : false,
4005 : : true);
4006 : :
4007 : 93 : args = list_make3(relidConst, modulusConst, remainderConst);
4008 : 93 : partexprs_item = list_head(key->partexprs);
4009 : :
4010 : : /* Add an argument for each key column. */
4011 [ + + ]: 202 : for (i = 0; i < key->partnatts; i++)
4012 : : {
4013 : : Node *keyCol;
4014 : :
4015 : : /* Left operand */
4016 [ + + ]: 109 : if (key->partattrs[i] != 0)
4017 : : {
4018 : 106 : keyCol = (Node *) makeVar(1,
4019 : 106 : key->partattrs[i],
4020 : 106 : key->parttypid[i],
4021 : 106 : key->parttypmod[i],
4022 : 106 : key->parttypcoll[i],
4023 : : 0);
4024 : : }
4025 : : else
4026 : : {
4027 : 3 : keyCol = (Node *) copyObject(lfirst(partexprs_item));
2600 tgl@sss.pgh.pa.us 4028 : 3 : partexprs_item = lnext(key->partexprs, partexprs_item);
4029 : : }
4030 : :
3057 alvherre@alvh.no-ip. 4031 : 109 : args = lappend(args, keyCol);
4032 : : }
4033 : :
4034 : 93 : fexpr = makeFuncExpr(F_SATISFIES_HASH_PARTITION,
4035 : : BOOLOID,
4036 : : args,
4037 : : InvalidOid,
4038 : : InvalidOid,
4039 : : COERCE_EXPLICIT_CALL);
4040 : :
4041 : 93 : return list_make1(fexpr);
4042 : : }
4043 : :
4044 : : /*
4045 : : * get_qual_for_list
4046 : : *
4047 : : * Returns an implicit-AND list of expressions to use as a list partition's
4048 : : * constraint, given the parent relation and partition bound structure.
4049 : : *
4050 : : * The function returns NIL for a default partition when it's the only
4051 : : * partition since in that case there is no constraint.
4052 : : */
4053 : : static List *
4054 : 1821 : get_qual_for_list(Relation parent, PartitionBoundSpec *spec)
4055 : : {
4056 : 1821 : PartitionKey key = RelationGetPartitionKey(parent);
4057 : : List *result;
4058 : : Expr *keyCol;
4059 : : Expr *opexpr;
4060 : : NullTest *nulltest;
4061 : : ListCell *cell;
4062 : 1821 : List *elems = NIL;
4063 : 1821 : bool list_has_null = false;
4064 : :
4065 : : /*
4066 : : * Only single-column list partitioning is supported, so we are worried
4067 : : * only about the partition key with index 0.
4068 : : */
4069 [ - + ]: 1821 : Assert(key->partnatts == 1);
4070 : :
4071 : : /* Construct Var or expression representing the partition column */
4072 [ + + ]: 1821 : if (key->partattrs[0] != 0)
4073 : 1746 : keyCol = (Expr *) makeVar(1,
4074 : 1746 : key->partattrs[0],
4075 : 1746 : key->parttypid[0],
4076 : 1746 : key->parttypmod[0],
4077 : 1746 : key->parttypcoll[0],
4078 : : 0);
4079 : : else
4080 : 75 : keyCol = (Expr *) copyObject(linitial(key->partexprs));
4081 : :
4082 : : /*
4083 : : * For default list partition, collect datums for all the partitions. The
4084 : : * default partition constraint should check that the partition key is
4085 : : * equal to none of those.
4086 : : */
4087 [ + + ]: 1821 : if (spec->is_default)
4088 : : {
4089 : : int i;
4090 : 178 : int ndatums = 0;
1953 4091 : 178 : PartitionDesc pdesc = RelationGetPartitionDesc(parent, false);
3057 4092 : 178 : PartitionBoundInfo boundinfo = pdesc->boundinfo;
4093 : :
4094 [ + - ]: 178 : if (boundinfo)
4095 : : {
4096 : 178 : ndatums = boundinfo->ndatums;
4097 : :
4098 [ + + ]: 178 : if (partition_bound_accepts_nulls(boundinfo))
4099 : 32 : list_has_null = true;
4100 : : }
4101 : :
4102 : : /*
4103 : : * If default is the only partition, there need not be any partition
4104 : : * constraint on it.
4105 : : */
4106 [ + + + + ]: 178 : if (ndatums == 0 && !list_has_null)
4107 : 23 : return NIL;
4108 : :
4109 [ + + ]: 811 : for (i = 0; i < ndatums; i++)
4110 : : {
4111 : : Const *val;
4112 : :
4113 : : /*
4114 : : * Construct Const from known-not-null datum. We must be careful
4115 : : * to copy the value, because our result has to be able to outlive
4116 : : * the relcache entry we're copying from.
4117 : : */
4118 : 1312 : val = makeConst(key->parttypid[0],
4119 : 656 : key->parttypmod[0],
4120 : 656 : key->parttypcoll[0],
4121 : 656 : key->parttyplen[0],
4122 : 656 : datumCopy(*boundinfo->datums[i],
4123 : 656 : key->parttypbyval[0],
4124 : 656 : key->parttyplen[0]),
4125 : : false, /* isnull */
4126 : 656 : key->parttypbyval[0]);
4127 : :
4128 : 656 : elems = lappend(elems, val);
4129 : : }
4130 : : }
4131 : : else
4132 : : {
4133 : : /*
4134 : : * Create list of Consts for the allowed values, excluding any nulls.
4135 : : */
4136 [ + - + + : 4312 : foreach(cell, spec->listdatums)
+ + ]
4137 : : {
1865 peter@eisentraut.org 4138 : 2669 : Const *val = lfirst_node(Const, cell);
4139 : :
3057 alvherre@alvh.no-ip. 4140 [ + + ]: 2669 : if (val->constisnull)
4141 : 60 : list_has_null = true;
4142 : : else
4143 : 2609 : elems = lappend(elems, copyObject(val));
4144 : : }
4145 : : }
4146 : :
4147 [ + + ]: 1798 : if (elems)
4148 : : {
4149 : : /*
4150 : : * Generate the operator expression from the non-null partition
4151 : : * values.
4152 : : */
4153 : 1774 : opexpr = make_partition_op_expr(key, 0, BTEqualStrategyNumber,
4154 : : keyCol, (Expr *) elems);
4155 : : }
4156 : : else
4157 : : {
4158 : : /*
4159 : : * If there are no partition values, we don't need an operator
4160 : : * expression.
4161 : : */
4162 : 24 : opexpr = NULL;
4163 : : }
4164 : :
4165 [ + + ]: 1798 : if (!list_has_null)
4166 : : {
4167 : : /*
4168 : : * Gin up a "col IS NOT NULL" test that will be ANDed with the main
4169 : : * expression. This might seem redundant, but the partition routing
4170 : : * machinery needs it.
4171 : : */
4172 : 1706 : nulltest = makeNode(NullTest);
4173 : 1706 : nulltest->arg = keyCol;
4174 : 1706 : nulltest->nulltesttype = IS_NOT_NULL;
4175 : 1706 : nulltest->argisrow = false;
4176 : 1706 : nulltest->location = -1;
4177 : :
4178 [ + - ]: 1706 : result = opexpr ? list_make2(nulltest, opexpr) : list_make1(nulltest);
4179 : : }
4180 : : else
4181 : : {
4182 : : /*
4183 : : * Gin up a "col IS NULL" test that will be OR'd with the main
4184 : : * expression.
4185 : : */
4186 : 92 : nulltest = makeNode(NullTest);
4187 : 92 : nulltest->arg = keyCol;
4188 : 92 : nulltest->nulltesttype = IS_NULL;
4189 : 92 : nulltest->argisrow = false;
4190 : 92 : nulltest->location = -1;
4191 : :
4192 [ + + ]: 92 : if (opexpr)
4193 : : {
4194 : : Expr *or;
4195 : :
4196 : 68 : or = makeBoolExpr(OR_EXPR, list_make2(nulltest, opexpr), -1);
4197 : 68 : result = list_make1(or);
4198 : : }
4199 : : else
4200 : 24 : result = list_make1(nulltest);
4201 : : }
4202 : :
4203 : : /*
4204 : : * Note that, in general, applying NOT to a constraint expression doesn't
4205 : : * necessarily invert the set of rows it accepts, because NOT (NULL) is
4206 : : * NULL. However, the partition constraints we construct here never
4207 : : * evaluate to NULL, so applying NOT works as intended.
4208 : : */
4209 [ + + ]: 1798 : if (spec->is_default)
4210 : : {
4211 : 155 : result = list_make1(make_ands_explicit(result));
4212 : 155 : result = list_make1(makeBoolExpr(NOT_EXPR, result, -1));
4213 : : }
4214 : :
4215 : 1798 : return result;
4216 : : }
4217 : :
4218 : : /*
4219 : : * get_qual_for_range
4220 : : *
4221 : : * Returns an implicit-AND list of expressions to use as a range partition's
4222 : : * constraint, given the parent relation and partition bound structure.
4223 : : *
4224 : : * For a multi-column range partition key, say (a, b, c), with (al, bl, cl)
4225 : : * as the lower bound tuple and (au, bu, cu) as the upper bound tuple, we
4226 : : * generate an expression tree of the following form:
4227 : : *
4228 : : * (a IS NOT NULL) and (b IS NOT NULL) and (c IS NOT NULL)
4229 : : * AND
4230 : : * (a > al OR (a = al AND b > bl) OR (a = al AND b = bl AND c >= cl))
4231 : : * AND
4232 : : * (a < au OR (a = au AND b < bu) OR (a = au AND b = bu AND c < cu))
4233 : : *
4234 : : * It is often the case that a prefix of lower and upper bound tuples contains
4235 : : * the same values, for example, (al = au), in which case, we will emit an
4236 : : * expression tree of the following form:
4237 : : *
4238 : : * (a IS NOT NULL) and (b IS NOT NULL) and (c IS NOT NULL)
4239 : : * AND
4240 : : * (a = al)
4241 : : * AND
4242 : : * (b > bl OR (b = bl AND c >= cl))
4243 : : * AND
4244 : : * (b < bu OR (b = bu AND c < cu))
4245 : : *
4246 : : * If a bound datum is either MINVALUE or MAXVALUE, these expressions are
4247 : : * simplified using the fact that any value is greater than MINVALUE and less
4248 : : * than MAXVALUE. So, for example, if cu = MAXVALUE, c < cu is automatically
4249 : : * true, and we need not emit any expression for it, and the last line becomes
4250 : : *
4251 : : * (b < bu) OR (b = bu), which is simplified to (b <= bu)
4252 : : *
4253 : : * In most common cases with only one partition column, say a, the following
4254 : : * expression tree will be generated: a IS NOT NULL AND a >= al AND a < au
4255 : : *
4256 : : * For default partition, it returns the negation of the constraints of all
4257 : : * the other partitions.
4258 : : *
4259 : : * External callers should pass for_default as false; we set it to true only
4260 : : * when recursing.
4261 : : */
4262 : : static List *
4263 : 2285 : get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
4264 : : bool for_default)
4265 : : {
4266 : 2285 : List *result = NIL;
4267 : : ListCell *cell1,
4268 : : *cell2,
4269 : : *partexprs_item,
4270 : : *partexprs_item_saved;
4271 : : int i,
4272 : : j;
4273 : : PartitionRangeDatum *ldatum,
4274 : : *udatum;
4275 : 2285 : PartitionKey key = RelationGetPartitionKey(parent);
4276 : : Expr *keyCol;
4277 : : Const *lower_val,
4278 : : *upper_val;
4279 : : List *lower_or_arms,
4280 : : *upper_or_arms;
4281 : : int num_or_arms,
4282 : : current_or_arm;
4283 : : ListCell *lower_or_start_datum,
4284 : : *upper_or_start_datum;
4285 : : bool need_next_lower_arm,
4286 : : need_next_upper_arm;
4287 : :
4288 [ + + ]: 2285 : if (spec->is_default)
4289 : : {
4290 : 157 : List *or_expr_args = NIL;
1953 4291 : 157 : PartitionDesc pdesc = RelationGetPartitionDesc(parent, false);
3057 4292 : 157 : Oid *inhoids = pdesc->oids;
4293 : 157 : int nparts = pdesc->nparts,
4294 : : k;
4295 : :
1422 drowley@postgresql.o 4296 [ + + ]: 593 : for (k = 0; k < nparts; k++)
4297 : : {
4298 : 436 : Oid inhrelid = inhoids[k];
4299 : : HeapTuple tuple;
4300 : : Datum datum;
4301 : : PartitionBoundSpec *bspec;
4302 : :
1134 michael@paquier.xyz 4303 : 436 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(inhrelid));
3057 alvherre@alvh.no-ip. 4304 [ - + ]: 436 : if (!HeapTupleIsValid(tuple))
3057 alvherre@alvh.no-ip. 4305 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u", inhrelid);
4306 : :
1251 dgustafsson@postgres 4307 :CBC 436 : datum = SysCacheGetAttrNotNull(RELOID, tuple,
4308 : : Anum_pg_class_relpartbound);
4309 : : bspec = (PartitionBoundSpec *)
3057 alvherre@alvh.no-ip. 4310 : 436 : stringToNode(TextDatumGetCString(datum));
4311 [ - + ]: 436 : if (!IsA(bspec, PartitionBoundSpec))
3057 alvherre@alvh.no-ip. 4312 [ # # ]:UBC 0 : elog(ERROR, "expected PartitionBoundSpec");
4313 : :
3057 alvherre@alvh.no-ip. 4314 [ + + ]:CBC 436 : if (!bspec->is_default)
4315 : : {
4316 : : List *part_qual;
4317 : :
4318 : 279 : part_qual = get_qual_for_range(parent, bspec, true);
4319 : :
4320 : : /*
4321 : : * AND the constraints of the partition and add to
4322 : : * or_expr_args
4323 : : */
4324 [ + + ]: 558 : or_expr_args = lappend(or_expr_args, list_length(part_qual) > 1
4325 : 267 : ? makeBoolExpr(AND_EXPR, part_qual, -1)
4326 : 12 : : linitial(part_qual));
4327 : : }
4328 : 436 : ReleaseSysCache(tuple);
4329 : : }
4330 : :
4331 [ + + ]: 157 : if (or_expr_args != NIL)
4332 : : {
4333 : : Expr *other_parts_constr;
4334 : :
4335 : : /*
4336 : : * Combine the constraints obtained for non-default partitions
4337 : : * using OR. As requested, each of the OR's args doesn't include
4338 : : * the NOT NULL test for partition keys (which is to avoid its
4339 : : * useless repetition). Add the same now.
4340 : : */
4341 : : other_parts_constr =
4342 [ + + ]: 234 : makeBoolExpr(AND_EXPR,
4343 : : lappend(get_range_nulltest(key),
4344 : 117 : list_length(or_expr_args) > 1
4345 : 74 : ? makeBoolExpr(OR_EXPR, or_expr_args,
4346 : : -1)
4347 : 43 : : linitial(or_expr_args)),
4348 : : -1);
4349 : :
4350 : : /*
4351 : : * Finally, the default partition contains everything *NOT*
4352 : : * contained in the non-default partitions.
4353 : : */
4354 : 117 : result = list_make1(makeBoolExpr(NOT_EXPR,
4355 : : list_make1(other_parts_constr), -1));
4356 : : }
4357 : :
4358 : 157 : return result;
4359 : : }
4360 : :
4361 : : /*
4362 : : * If it is the recursive call for default, we skip the get_range_nulltest
4363 : : * to avoid accumulating the NullTest on the same keys for each partition.
4364 : : */
4365 [ + + ]: 2128 : if (!for_default)
4366 : 1849 : result = get_range_nulltest(key);
4367 : :
4368 : : /*
4369 : : * Iterate over the key columns and check if the corresponding lower and
4370 : : * upper datums are equal using the btree equality operator for the
4371 : : * column's type. If equal, we emit single keyCol = common_value
4372 : : * expression. Starting from the first column for which the corresponding
4373 : : * lower and upper bound datums are not equal, we generate OR expressions
4374 : : * as shown in the function's header comment.
4375 : : */
4376 : 2128 : i = 0;
4377 : 2128 : partexprs_item = list_head(key->partexprs);
4378 : 2128 : partexprs_item_saved = partexprs_item; /* placate compiler */
4379 [ + - + - : 2480 : forboth(cell1, spec->lowerdatums, cell2, spec->upperdatums)
+ - + - +
- + - +
- ]
4380 : : {
4381 : : EState *estate;
4382 : : MemoryContext oldcxt;
4383 : : Expr *test_expr;
4384 : : ExprState *test_exprstate;
4385 : : Datum test_result;
4386 : : bool isNull;
4387 : :
1865 peter@eisentraut.org 4388 : 2480 : ldatum = lfirst_node(PartitionRangeDatum, cell1);
4389 : 2480 : udatum = lfirst_node(PartitionRangeDatum, cell2);
4390 : :
4391 : : /*
4392 : : * Since get_range_key_properties() modifies partexprs_item, and we
4393 : : * might need to start over from the previous expression in the later
4394 : : * part of this function, save away the current value.
4395 : : */
3057 alvherre@alvh.no-ip. 4396 : 2480 : partexprs_item_saved = partexprs_item;
4397 : :
4398 : 2480 : get_range_key_properties(key, i, ldatum, udatum,
4399 : : &partexprs_item,
4400 : : &keyCol,
4401 : : &lower_val, &upper_val);
4402 : :
4403 : : /*
4404 : : * If either value is NULL, the corresponding partition bound is
4405 : : * either MINVALUE or MAXVALUE, and we treat them as unequal, because
4406 : : * even if they're the same, there is no common value to equate the
4407 : : * key column with.
4408 : : */
4409 [ + + + + ]: 2480 : if (!lower_val || !upper_val)
4410 : : break;
4411 : :
4412 : : /* Create the test expression */
4413 : 2239 : estate = CreateExecutorState();
4414 : 2239 : oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
4415 : 2239 : test_expr = make_partition_op_expr(key, i, BTEqualStrategyNumber,
4416 : : (Expr *) lower_val,
4417 : : (Expr *) upper_val);
4418 : 2239 : fix_opfuncids((Node *) test_expr);
4419 : 2239 : test_exprstate = ExecInitExpr(test_expr, NULL);
4420 : 2239 : test_result = ExecEvalExprSwitchContext(test_exprstate,
4421 [ - + ]: 2239 : GetPerTupleExprContext(estate),
4422 : : &isNull);
4423 : 2239 : MemoryContextSwitchTo(oldcxt);
4424 : 2239 : FreeExecutorState(estate);
4425 : :
4426 : : /* If not equal, go generate the OR expressions */
4427 [ + + ]: 2239 : if (!DatumGetBool(test_result))
4428 : 1887 : break;
4429 : :
4430 : : /*
4431 : : * The bounds for the last key column can't be equal, because such a
4432 : : * range partition would never be allowed to be defined (it would have
4433 : : * an empty range otherwise).
4434 : : */
4435 [ - + ]: 352 : if (i == key->partnatts - 1)
3057 alvherre@alvh.no-ip. 4436 [ # # ]:UBC 0 : elog(ERROR, "invalid range bound specification");
4437 : :
4438 : : /* Equal, so generate keyCol = lower_val expression */
3057 alvherre@alvh.no-ip. 4439 :CBC 352 : result = lappend(result,
4440 : 352 : make_partition_op_expr(key, i, BTEqualStrategyNumber,
4441 : : keyCol, (Expr *) lower_val));
4442 : :
4443 : 352 : i++;
4444 : : }
4445 : :
4446 : : /* First pair of lower_val and upper_val that are not equal. */
4447 : 2128 : lower_or_start_datum = cell1;
4448 : 2128 : upper_or_start_datum = cell2;
4449 : :
4450 : : /* OR will have as many arms as there are key columns left. */
4451 : 2128 : num_or_arms = key->partnatts - i;
4452 : 2128 : current_or_arm = 0;
4453 : 2128 : lower_or_arms = upper_or_arms = NIL;
4454 : 2128 : need_next_lower_arm = need_next_upper_arm = true;
4455 [ + - ]: 2347 : while (current_or_arm < num_or_arms)
4456 : : {
4457 : 2347 : List *lower_or_arm_args = NIL,
4458 : 2347 : *upper_or_arm_args = NIL;
4459 : :
4460 : : /* Restart scan of columns from the i'th one */
4461 : 2347 : j = i;
4462 : 2347 : partexprs_item = partexprs_item_saved;
4463 : :
2600 tgl@sss.pgh.pa.us 4464 [ + - + - : 2622 : for_both_cell(cell1, spec->lowerdatums, lower_or_start_datum,
+ - + - +
- + - +
- ]
4465 : : cell2, spec->upperdatums, upper_or_start_datum)
4466 : : {
3057 alvherre@alvh.no-ip. 4467 : 2622 : PartitionRangeDatum *ldatum_next = NULL,
4468 : 2622 : *udatum_next = NULL;
4469 : :
1865 peter@eisentraut.org 4470 : 2622 : ldatum = lfirst_node(PartitionRangeDatum, cell1);
2600 tgl@sss.pgh.pa.us 4471 [ + + ]: 2622 : if (lnext(spec->lowerdatums, cell1))
3057 alvherre@alvh.no-ip. 4472 : 546 : ldatum_next = castNode(PartitionRangeDatum,
4473 : : lfirst(lnext(spec->lowerdatums, cell1)));
1865 peter@eisentraut.org 4474 : 2622 : udatum = lfirst_node(PartitionRangeDatum, cell2);
2600 tgl@sss.pgh.pa.us 4475 [ + + ]: 2622 : if (lnext(spec->upperdatums, cell2))
3057 alvherre@alvh.no-ip. 4476 : 546 : udatum_next = castNode(PartitionRangeDatum,
4477 : : lfirst(lnext(spec->upperdatums, cell2)));
4478 : 2622 : get_range_key_properties(key, j, ldatum, udatum,
4479 : : &partexprs_item,
4480 : : &keyCol,
4481 : : &lower_val, &upper_val);
4482 : :
4483 [ + + + + ]: 2622 : if (need_next_lower_arm && lower_val)
4484 : : {
4485 : : uint16 strategy;
4486 : :
4487 : : /*
4488 : : * For the non-last columns of this arm, use the EQ operator.
4489 : : * For the last column of this arm, use GT, unless this is the
4490 : : * last column of the whole bound check, or the next bound
4491 : : * datum is MINVALUE, in which case use GE.
4492 : : */
4493 [ + + ]: 2430 : if (j - i < current_or_arm)
4494 : 239 : strategy = BTEqualStrategyNumber;
4495 [ + + + - ]: 2191 : else if (j == key->partnatts - 1 ||
4496 : 231 : (ldatum_next &&
4497 [ + + ]: 231 : ldatum_next->kind == PARTITION_RANGE_DATUM_MINVALUE))
4498 : 1988 : strategy = BTGreaterEqualStrategyNumber;
4499 : : else
4500 : 203 : strategy = BTGreaterStrategyNumber;
4501 : :
4502 : 2430 : lower_or_arm_args = lappend(lower_or_arm_args,
4503 : 2430 : make_partition_op_expr(key, j,
4504 : : strategy,
4505 : : keyCol,
4506 : : (Expr *) lower_val));
4507 : : }
4508 : :
4509 [ + + + + ]: 2622 : if (need_next_upper_arm && upper_val)
4510 : : {
4511 : : uint16 strategy;
4512 : :
4513 : : /*
4514 : : * For the non-last columns of this arm, use the EQ operator.
4515 : : * For the last column of this arm, use LT, unless the next
4516 : : * bound datum is MAXVALUE, in which case use LE.
4517 : : */
4518 [ + + ]: 2373 : if (j - i < current_or_arm)
4519 : 195 : strategy = BTEqualStrategyNumber;
4520 [ + + ]: 2178 : else if (udatum_next &&
4521 [ + + ]: 207 : udatum_next->kind == PARTITION_RANGE_DATUM_MAXVALUE)
4522 : 20 : strategy = BTLessEqualStrategyNumber;
4523 : : else
4524 : 2158 : strategy = BTLessStrategyNumber;
4525 : :
4526 : 2373 : upper_or_arm_args = lappend(upper_or_arm_args,
4527 : 2373 : make_partition_op_expr(key, j,
4528 : : strategy,
4529 : : keyCol,
4530 : : (Expr *) upper_val));
4531 : : }
4532 : :
4533 : : /*
4534 : : * Did we generate enough of OR's arguments? First arm considers
4535 : : * the first of the remaining columns, second arm considers first
4536 : : * two of the remaining columns, and so on.
4537 : : */
4538 : 2622 : ++j;
4539 [ + + ]: 2622 : if (j - i > current_or_arm)
4540 : : {
4541 : : /*
4542 : : * We must not emit any more arms if the new column that will
4543 : : * be considered is unbounded, or this one was.
4544 : : */
4545 [ + + + + ]: 2347 : if (!lower_val || !ldatum_next ||
4546 [ + + ]: 231 : ldatum_next->kind != PARTITION_RANGE_DATUM_VALUE)
4547 : 2152 : need_next_lower_arm = false;
4548 [ + + + + ]: 2347 : if (!upper_val || !udatum_next ||
4549 [ + + ]: 207 : udatum_next->kind != PARTITION_RANGE_DATUM_VALUE)
4550 : 2184 : need_next_upper_arm = false;
4551 : 2347 : break;
4552 : : }
4553 : : }
4554 : :
4555 [ + + ]: 2347 : if (lower_or_arm_args != NIL)
4556 [ + + ]: 4382 : lower_or_arms = lappend(lower_or_arms,
4557 : 2191 : list_length(lower_or_arm_args) > 1
4558 : 195 : ? makeBoolExpr(AND_EXPR, lower_or_arm_args, -1)
4559 : 1996 : : linitial(lower_or_arm_args));
4560 : :
4561 [ + + ]: 2347 : if (upper_or_arm_args != NIL)
4562 [ + + ]: 4356 : upper_or_arms = lappend(upper_or_arms,
4563 : 2178 : list_length(upper_or_arm_args) > 1
4564 : 163 : ? makeBoolExpr(AND_EXPR, upper_or_arm_args, -1)
4565 : 2015 : : linitial(upper_or_arm_args));
4566 : :
4567 : : /* If no work to do in the next iteration, break away. */
4568 [ + + + + ]: 2347 : if (!need_next_lower_arm && !need_next_upper_arm)
4569 : 2128 : break;
4570 : :
4571 : 219 : ++current_or_arm;
4572 : : }
4573 : :
4574 : : /*
4575 : : * Generate the OR expressions for each of lower and upper bounds (if
4576 : : * required), and append to the list of implicitly ANDed list of
4577 : : * expressions.
4578 : : */
4579 [ + + ]: 2128 : if (lower_or_arms != NIL)
4580 [ + + ]: 3992 : result = lappend(result,
4581 : 1996 : list_length(lower_or_arms) > 1
4582 : 151 : ? makeBoolExpr(OR_EXPR, lower_or_arms, -1)
4583 : 1845 : : linitial(lower_or_arms));
4584 [ + + ]: 2128 : if (upper_or_arms != NIL)
4585 [ + + ]: 4030 : result = lappend(result,
4586 : 2015 : list_length(upper_or_arms) > 1
4587 : 131 : ? makeBoolExpr(OR_EXPR, upper_or_arms, -1)
4588 : 1884 : : linitial(upper_or_arms));
4589 : :
4590 : : /*
4591 : : * As noted above, for non-default, we return list with constant TRUE. If
4592 : : * the result is NIL during the recursive call for default, it implies
4593 : : * this is the only other partition which can hold every value of the key
4594 : : * except NULL. Hence we return the NullTest result skipped earlier.
4595 : : */
4596 [ - + ]: 2128 : if (result == NIL)
3057 alvherre@alvh.no-ip. 4597 :UBC 0 : result = for_default
4598 : 0 : ? get_range_nulltest(key)
4599 [ # # ]: 0 : : list_make1(makeBoolConst(true, false));
4600 : :
3057 alvherre@alvh.no-ip. 4601 :CBC 2128 : return result;
4602 : : }
4603 : :
4604 : : /*
4605 : : * get_range_key_properties
4606 : : * Returns range partition key information for a given column
4607 : : *
4608 : : * This is a subroutine for get_qual_for_range, and its API is pretty
4609 : : * specialized to that caller.
4610 : : *
4611 : : * Constructs an Expr for the key column (returned in *keyCol) and Consts
4612 : : * for the lower and upper range limits (returned in *lower_val and
4613 : : * *upper_val). For MINVALUE/MAXVALUE limits, NULL is returned instead of
4614 : : * a Const. All of these structures are freshly palloc'd.
4615 : : *
4616 : : * *partexprs_item points to the cell containing the next expression in
4617 : : * the key->partexprs list, or NULL. It may be advanced upon return.
4618 : : */
4619 : : static void
4620 : 5102 : get_range_key_properties(PartitionKey key, int keynum,
4621 : : PartitionRangeDatum *ldatum,
4622 : : PartitionRangeDatum *udatum,
4623 : : ListCell **partexprs_item,
4624 : : Expr **keyCol,
4625 : : Const **lower_val, Const **upper_val)
4626 : : {
4627 : : /* Get partition key expression for this column */
4628 [ + + ]: 5102 : if (key->partattrs[keynum] != 0)
4629 : : {
4630 : 4627 : *keyCol = (Expr *) makeVar(1,
4631 : 4627 : key->partattrs[keynum],
4632 : 4627 : key->parttypid[keynum],
4633 : 4627 : key->parttypmod[keynum],
4634 : 4627 : key->parttypcoll[keynum],
4635 : : 0);
4636 : : }
4637 : : else
4638 : : {
4639 [ - + ]: 475 : if (*partexprs_item == NULL)
3057 alvherre@alvh.no-ip. 4640 [ # # ]:UBC 0 : elog(ERROR, "wrong number of partition key expressions");
3057 alvherre@alvh.no-ip. 4641 :CBC 475 : *keyCol = copyObject(lfirst(*partexprs_item));
2600 tgl@sss.pgh.pa.us 4642 : 475 : *partexprs_item = lnext(key->partexprs, *partexprs_item);
4643 : : }
4644 : :
4645 : : /* Get appropriate Const nodes for the bounds */
3057 alvherre@alvh.no-ip. 4646 [ + + ]: 5102 : if (ldatum->kind == PARTITION_RANGE_DATUM_VALUE)
4647 : 4790 : *lower_val = castNode(Const, copyObject(ldatum->value));
4648 : : else
4649 : 312 : *lower_val = NULL;
4650 : :
4651 [ + + ]: 5102 : if (udatum->kind == PARTITION_RANGE_DATUM_VALUE)
4652 : 4756 : *upper_val = castNode(Const, copyObject(udatum->value));
4653 : : else
4654 : 346 : *upper_val = NULL;
4655 : 5102 : }
4656 : :
4657 : : /*
4658 : : * get_range_nulltest
4659 : : *
4660 : : * A non-default range partition table does not currently allow partition
4661 : : * keys to be null, so emit an IS NOT NULL expression for each key column.
4662 : : */
4663 : : static List *
4664 : 1966 : get_range_nulltest(PartitionKey key)
4665 : : {
4666 : 1966 : List *result = NIL;
4667 : : NullTest *nulltest;
4668 : : ListCell *partexprs_item;
4669 : : int i;
4670 : :
4671 : 1966 : partexprs_item = list_head(key->partexprs);
4672 [ + + ]: 4467 : for (i = 0; i < key->partnatts; i++)
4673 : : {
4674 : : Expr *keyCol;
4675 : :
4676 [ + + ]: 2501 : if (key->partattrs[i] != 0)
4677 : : {
4678 : 2272 : keyCol = (Expr *) makeVar(1,
4679 : 2272 : key->partattrs[i],
4680 : 2272 : key->parttypid[i],
4681 : 2272 : key->parttypmod[i],
4682 : 2272 : key->parttypcoll[i],
4683 : : 0);
4684 : : }
4685 : : else
4686 : : {
4687 [ - + ]: 229 : if (partexprs_item == NULL)
3057 alvherre@alvh.no-ip. 4688 [ # # ]:UBC 0 : elog(ERROR, "wrong number of partition key expressions");
3057 alvherre@alvh.no-ip. 4689 :CBC 229 : keyCol = copyObject(lfirst(partexprs_item));
2600 tgl@sss.pgh.pa.us 4690 : 229 : partexprs_item = lnext(key->partexprs, partexprs_item);
4691 : : }
4692 : :
3057 alvherre@alvh.no-ip. 4693 : 2501 : nulltest = makeNode(NullTest);
4694 : 2501 : nulltest->arg = keyCol;
4695 : 2501 : nulltest->nulltesttype = IS_NOT_NULL;
4696 : 2501 : nulltest->argisrow = false;
4697 : 2501 : nulltest->location = -1;
4698 : 2501 : result = lappend(result, nulltest);
4699 : : }
4700 : :
4701 : 1966 : return result;
4702 : : }
4703 : :
4704 : : /*
4705 : : * compute_partition_hash_value
4706 : : *
4707 : : * Compute the hash value for given partition key values.
4708 : : */
4709 : : uint64
1052 peter@eisentraut.org 4710 : 107354 : compute_partition_hash_value(int partnatts, FmgrInfo *partsupfunc, const Oid *partcollation,
4711 : : const Datum *values, const bool *isnull)
4712 : : {
4713 : : int i;
3057 alvherre@alvh.no-ip. 4714 : 107354 : uint64 rowHash = 0;
4715 : 107354 : Datum seed = UInt64GetDatum(HASH_PARTITION_SEED);
4716 : :
4717 [ + + ]: 215464 : for (i = 0; i < partnatts; i++)
4718 : : {
4719 : : /* Nulls are just ignored */
4720 [ + + ]: 108118 : if (!isnull[i])
4721 : : {
4722 : : Datum hash;
4723 : :
4724 [ - + ]: 107620 : Assert(OidIsValid(partsupfunc[i].fn_oid));
4725 : :
4726 : : /*
4727 : : * Compute hash for each datum value by calling respective
4728 : : * datatype-specific hash functions of each partition key
4729 : : * attribute.
4730 : : */
2691 tgl@sss.pgh.pa.us 4731 : 107620 : hash = FunctionCall2Coll(&partsupfunc[i], partcollation[i],
4732 : 107620 : values[i], seed);
4733 : :
4734 : : /* Form a single 64-bit hash value */
3057 alvherre@alvh.no-ip. 4735 : 107612 : rowHash = hash_combine64(rowHash, DatumGetUInt64(hash));
4736 : : }
4737 : : }
4738 : :
4739 : 107346 : return rowHash;
4740 : : }
4741 : :
4742 : : /*
4743 : : * satisfies_hash_partition
4744 : : *
4745 : : * This is an SQL-callable function for use in hash partition constraints.
4746 : : * The first three arguments are the parent table OID, modulus, and remainder.
4747 : : * The remaining arguments are the value of the partitioning columns (or
4748 : : * expressions); these are hashed and the results are combined into a single
4749 : : * hash value by calling hash_combine64.
4750 : : *
4751 : : * Returns true if remainder produced when this computed single hash value is
4752 : : * divided by the given modulus is equal to given remainder, otherwise false.
4753 : : * NB: it's important that this never return null, as the constraint machinery
4754 : : * would consider that to be a "pass".
4755 : : *
4756 : : * See get_qual_for_hash() for usage.
4757 : : */
4758 : : Datum
4759 : 2160 : satisfies_hash_partition(PG_FUNCTION_ARGS)
4760 : : {
4761 : : typedef struct ColumnsHashData
4762 : : {
4763 : : Oid relid;
4764 : : int nkeys;
4765 : : Oid variadic_type;
4766 : : int16 variadic_typlen;
4767 : : bool variadic_typbyval;
4768 : : char variadic_typalign;
4769 : : Oid partcollid[PARTITION_MAX_KEYS];
4770 : : FmgrInfo partsupfunc[FLEXIBLE_ARRAY_MEMBER];
4771 : : } ColumnsHashData;
4772 : : Oid parentId;
4773 : : int modulus;
4774 : : int remainder;
4775 : 2160 : Datum seed = UInt64GetDatum(HASH_PARTITION_SEED);
4776 : : ColumnsHashData *my_extra;
4777 : 2160 : uint64 rowHash = 0;
4778 : :
4779 : : /* Return false if the parent OID, modulus, or remainder is NULL. */
4780 [ + - + + : 2160 : if (PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2))
+ + ]
2110 tgl@sss.pgh.pa.us 4781 : 10 : PG_RETURN_BOOL(false);
3057 alvherre@alvh.no-ip. 4782 : 2150 : parentId = PG_GETARG_OID(0);
4783 : 2150 : modulus = PG_GETARG_INT32(1);
4784 : 2150 : remainder = PG_GETARG_INT32(2);
4785 : :
4786 : : /* Sanity check modulus and remainder. */
4787 [ + + ]: 2150 : if (modulus <= 0)
4788 [ + - ]: 4 : ereport(ERROR,
4789 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
4790 : : errmsg("modulus for hash partition must be an integer value greater than zero")));
4791 [ + + ]: 2146 : if (remainder < 0)
4792 [ + - ]: 4 : ereport(ERROR,
4793 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
4794 : : errmsg("remainder for hash partition must be an integer value greater than or equal to zero")));
4795 [ + + ]: 2142 : if (remainder >= modulus)
4796 [ + - ]: 4 : ereport(ERROR,
4797 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
4798 : : errmsg("remainder for hash partition must be less than modulus")));
4799 : :
4800 : : /*
4801 : : * Cache hash function information.
4802 : : */
4803 : 2138 : my_extra = (ColumnsHashData *) fcinfo->flinfo->fn_extra;
4804 [ + + - + ]: 2138 : if (my_extra == NULL || my_extra->relid != parentId)
4805 : : {
4806 : : Relation parent;
4807 : : PartitionKey key;
4808 : : int j;
4809 : :
4810 : : /* Open parent relation and fetch partition key info */
2110 tgl@sss.pgh.pa.us 4811 : 1136 : parent = relation_open(parentId, AccessShareLock);
3057 alvherre@alvh.no-ip. 4812 : 1132 : key = RelationGetPartitionKey(parent);
4813 : :
4814 : : /* Reject parent table that is not hash-partitioned. */
2110 tgl@sss.pgh.pa.us 4815 [ + + - + ]: 1132 : if (key == NULL || key->strategy != PARTITION_STRATEGY_HASH)
3057 alvherre@alvh.no-ip. 4816 [ + - ]: 8 : ereport(ERROR,
4817 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
4818 : : errmsg("\"%s\" is not a hash partitioned table",
4819 : : get_rel_name(parentId))));
4820 : :
4821 [ + + ]: 1124 : if (!get_fn_expr_variadic(fcinfo->flinfo))
4822 : : {
4823 : 1097 : int nargs = PG_NARGS() - 3;
4824 : :
4825 : : /* complain if wrong number of column values */
4826 [ + + ]: 1097 : if (key->partnatts != nargs)
4827 [ + - ]: 8 : ereport(ERROR,
4828 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
4829 : : errmsg("number of partitioning columns (%d) does not match number of partition keys provided (%d)",
4830 : : key->partnatts, nargs)));
4831 : :
4832 : : /* allocate space for our cache */
4833 : 2178 : fcinfo->flinfo->fn_extra =
4834 : 1089 : MemoryContextAllocZero(fcinfo->flinfo->fn_mcxt,
4835 : : offsetof(ColumnsHashData, partsupfunc) +
4836 : 1089 : sizeof(FmgrInfo) * nargs);
4837 : 1089 : my_extra = (ColumnsHashData *) fcinfo->flinfo->fn_extra;
4838 : 1089 : my_extra->relid = parentId;
4839 : 1089 : my_extra->nkeys = key->partnatts;
2691 tgl@sss.pgh.pa.us 4840 : 1089 : memcpy(my_extra->partcollid, key->partcollation,
4841 : 1089 : key->partnatts * sizeof(Oid));
4842 : :
4843 : : /* check argument types and save fmgr_infos */
3057 alvherre@alvh.no-ip. 4844 [ + + ]: 2208 : for (j = 0; j < key->partnatts; ++j)
4845 : : {
4846 : 1123 : Oid argtype = get_fn_expr_argtype(fcinfo->flinfo, j + 3);
4847 : :
4848 [ + + + - ]: 1123 : if (argtype != key->parttypid[j] && !IsBinaryCoercible(argtype, key->parttypid[j]))
4849 [ + - ]: 4 : ereport(ERROR,
4850 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
4851 : : errmsg("column %d of the partition key has type %s, but supplied value is of type %s",
4852 : : j + 1, format_type_be(key->parttypid[j]), format_type_be(argtype))));
4853 : :
4854 : 1119 : fmgr_info_copy(&my_extra->partsupfunc[j],
4855 : 1119 : &key->partsupfunc[j],
4856 : 1119 : fcinfo->flinfo->fn_mcxt);
4857 : : }
4858 : : }
52 rhaas@postgresql.org 4859 [ + + ]: 27 : else if (PG_ARGISNULL(3))
4860 : : {
4861 : : /* Special case for VARIADIC NULL::sometype[] */
4862 : 5 : relation_close(parent, NoLock);
4863 : 5 : PG_RETURN_BOOL(false);
4864 : : }
4865 : : else
4866 : : {
3057 alvherre@alvh.no-ip. 4867 : 22 : ArrayType *variadic_array = PG_GETARG_ARRAYTYPE_P(3);
4868 : :
4869 : : /* allocate space for our cache -- just one FmgrInfo in this case */
4870 : 44 : fcinfo->flinfo->fn_extra =
4871 : 22 : MemoryContextAllocZero(fcinfo->flinfo->fn_mcxt,
4872 : : offsetof(ColumnsHashData, partsupfunc) +
4873 : : sizeof(FmgrInfo));
4874 : 22 : my_extra = (ColumnsHashData *) fcinfo->flinfo->fn_extra;
4875 : 22 : my_extra->relid = parentId;
4876 : 22 : my_extra->nkeys = key->partnatts;
4877 : 22 : my_extra->variadic_type = ARR_ELEMTYPE(variadic_array);
4878 : 22 : get_typlenbyvalalign(my_extra->variadic_type,
4879 : : &my_extra->variadic_typlen,
4880 : : &my_extra->variadic_typbyval,
4881 : : &my_extra->variadic_typalign);
2691 tgl@sss.pgh.pa.us 4882 : 22 : my_extra->partcollid[0] = key->partcollation[0];
4883 : :
4884 : : /* check argument types */
3057 alvherre@alvh.no-ip. 4885 [ + + ]: 54 : for (j = 0; j < key->partnatts; ++j)
4886 [ + + ]: 40 : if (key->parttypid[j] != my_extra->variadic_type)
4887 [ + - ]: 8 : ereport(ERROR,
4888 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
4889 : : errmsg("column %d of the partition key has type \"%s\", but supplied value is of type \"%s\"",
4890 : : j + 1,
4891 : : format_type_be(key->parttypid[j]),
4892 : : format_type_be(my_extra->variadic_type))));
4893 : :
4894 : 14 : fmgr_info_copy(&my_extra->partsupfunc[0],
4895 : : &key->partsupfunc[0],
4896 : 14 : fcinfo->flinfo->fn_mcxt);
4897 : : }
4898 : :
4899 : : /* Hold lock until commit */
4900 : 1099 : relation_close(parent, NoLock);
4901 : : }
4902 : :
4903 [ + + ]: 2101 : if (!OidIsValid(my_extra->variadic_type))
4904 : : {
4905 : 2087 : int nkeys = my_extra->nkeys;
4906 : : int i;
4907 : :
4908 : : /*
4909 : : * For a non-variadic call, neither the number of arguments nor their
4910 : : * types can change across calls, so avoid the expense of rechecking
4911 : : * here.
4912 : : */
4913 : :
4914 [ + + ]: 4204 : for (i = 0; i < nkeys; i++)
4915 : : {
4916 : : Datum hash;
4917 : :
4918 : : /* keys start from fourth argument of function. */
4919 : 2117 : int argno = i + 3;
4920 : :
4921 [ - + ]: 2117 : if (PG_ARGISNULL(argno))
3057 alvherre@alvh.no-ip. 4922 :UBC 0 : continue;
4923 : :
2691 tgl@sss.pgh.pa.us 4924 :CBC 2117 : hash = FunctionCall2Coll(&my_extra->partsupfunc[i],
4925 : : my_extra->partcollid[i],
4926 : : PG_GETARG_DATUM(argno),
4927 : : seed);
4928 : :
4929 : : /* Form a single 64-bit hash value */
3057 alvherre@alvh.no-ip. 4930 : 2117 : rowHash = hash_combine64(rowHash, DatumGetUInt64(hash));
4931 : : }
4932 : : }
4933 : : else
4934 : : {
4935 : : ArrayType *variadic_array;
4936 : : int i;
4937 : : int nelems;
4938 : : Datum *datum;
4939 : : bool *isnull;
4940 : :
4941 : : /* Special case for VARIADIC NULL::sometype[] */
52 rhaas@postgresql.org 4942 [ - + ]: 14 : if (PG_ARGISNULL(3))
52 rhaas@postgresql.org 4943 :UBC 0 : PG_RETURN_BOOL(false);
4944 : :
52 rhaas@postgresql.org 4945 :CBC 14 : variadic_array = PG_GETARG_ARRAYTYPE_P(3);
4946 : :
3057 alvherre@alvh.no-ip. 4947 : 14 : deconstruct_array(variadic_array,
4948 : : my_extra->variadic_type,
4949 : 14 : my_extra->variadic_typlen,
4950 : 14 : my_extra->variadic_typbyval,
4951 : 14 : my_extra->variadic_typalign,
4952 : : &datum, &isnull, &nelems);
4953 : :
4954 : : /* complain if wrong number of column values */
4955 [ + + ]: 14 : if (nelems != my_extra->nkeys)
4956 [ + - ]: 4 : ereport(ERROR,
4957 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
4958 : : errmsg("number of partitioning columns (%d) does not match number of partition keys provided (%d)",
4959 : : my_extra->nkeys, nelems)));
4960 : :
4961 [ + + ]: 30 : for (i = 0; i < nelems; i++)
4962 : : {
4963 : : Datum hash;
4964 : :
4965 [ - + ]: 20 : if (isnull[i])
3057 alvherre@alvh.no-ip. 4966 :UBC 0 : continue;
4967 : :
2691 tgl@sss.pgh.pa.us 4968 :CBC 20 : hash = FunctionCall2Coll(&my_extra->partsupfunc[0],
4969 : : my_extra->partcollid[0],
4970 : 20 : datum[i],
4971 : : seed);
4972 : :
4973 : : /* Form a single 64-bit hash value */
3057 alvherre@alvh.no-ip. 4974 : 20 : rowHash = hash_combine64(rowHash, DatumGetUInt64(hash));
4975 : : }
4976 : : }
4977 : :
4978 : 2097 : PG_RETURN_BOOL(rowHash % modulus == remainder);
4979 : : }
|