modify-partbounds-changes-2.patch
application/octet-stream
Filename: modify-partbounds-changes-2.patch
Type: application/octet-stream
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: unified
| File | + | − |
|---|---|---|
| src/backend/partitioning/partbounds.c | 737 | 598 |
| src/test/regress/expected/partition_join.out | 179 | 0 |
| src/test/regress/sql/partition_join.sql | 44 | 0 |
diff --git a/src/backend/partitioning/partbounds.c b/src/backend/partitioning/partbounds.c
index 3b2008e0f1..d35c9817cd 100644
--- a/src/backend/partitioning/partbounds.c
+++ b/src/backend/partitioning/partbounds.c
@@ -72,8 +72,13 @@ typedef struct PartitionRangeBound
typedef struct PartitionMap
{
- int from;
- int to;
+ int nparts; /* number of partitions */
+ int *merged_indexes; /* indexes of merged partitions */
+ bool *merged; /* flags to indicate whether partitions are
+ * merged with non-dummy partitions */
+ bool did_remapping; /* did we remap partitions? */
+ int *old_indexes; /* old indexes of merged partitions if
+ * did_remapping */
} PartitionMap;
static int32 qsort_partition_hbound_cmp(const void *a, const void *b);
@@ -115,20 +120,49 @@ static void get_range_key_properties(PartitionKey key, int keynum,
Expr **keyCol,
Const **lower_val, Const **upper_val);
static List *get_range_nulltest(PartitionKey key);
-static PartitionBoundInfo partition_range_bounds_merge(
+static PartitionBoundInfo partition_range_bounds_merge(int partnatts, FmgrInfo *partsupfuncs,
+ Oid *partcollations,
RelOptInfo *outer_rel, RelOptInfo *inner_rel,
- List **outer_parts, List **inner_parts,
- JoinType jointype, int partnatts,
- FmgrInfo *supfuncs, Oid *collations);
+ JoinType jointype,
+ List **outer_parts, List **inner_parts);
static PartitionBoundInfo partition_list_bounds_merge(FmgrInfo *partsupfunc, Oid *collations,
RelOptInfo *outer_rel, RelOptInfo *inner_rel,
- List **outer_parts, List **inner_parts,
- JoinType jointype);
+ JoinType jointype,
+ List **outer_parts, List **inner_parts);
+static void init_partition_map(RelOptInfo *rel, PartitionMap *map);
+static void free_partition_map(PartitionMap *map);
+static int map_and_merge_partitions(PartitionMap *outer_map, PartitionMap *inner_map,
+ int outer_part, int inner_part, int *next_index);
+static int merge_partition_with_dummy(PartitionMap *map, int index,
+ int *next_index);
+static bool process_outer_partition(PartitionMap *outer_map,
+ PartitionMap *inner_map,
+ bool outer_has_default,
+ bool inner_has_default,
+ int outer_index,
+ int inner_default,
+ JoinType jointype,
+ char strategy,
+ int *next_index,
+ int *default_index,
+ int *merged_index);
+static bool process_inner_partition(PartitionMap *outer_map,
+ PartitionMap *inner_map,
+ bool outer_has_default,
+ bool inner_has_default,
+ int inner_index,
+ int outer_default,
+ JoinType jointype,
+ char strategy,
+ int *next_index,
+ int *default_index,
+ int *merged_index);
+static void fix_merged_indexes(PartitionMap *outer_map, PartitionMap *inner_map,
+ int nmerged, List *merged_indexes);
static void generate_matching_part_pairs(RelOptInfo *rel1,
RelOptInfo *rel2,
- PartitionMap *partmaps1,
- PartitionMap *partmaps2,
- int nparts1, int nparts2,
+ PartitionMap *map1,
+ PartitionMap *map2,
int nparts,
List **matched_parts1,
List **matched_parts2);
@@ -136,9 +170,6 @@ static PartitionBoundInfo build_merged_partition_bounds(char strategy,
List *merged_datums, List *merged_indexes,
List *merged_contents, int null_index,
int default_index);
-static int map_and_merge_partitions(PartitionMap *outer_maps,
- PartitionMap *inner_maps,
- int index1, int index2, int *next_index);
static int32 partition_range_bound_cmp(int partnatts, FmgrInfo *partsupfunc,
Oid *collations, PartitionRangeBound *bound1,
PartitionRangeBound *bound2);
@@ -153,19 +184,21 @@ static bool partition_range_merge_next_lb(int partnatts, FmgrInfo *supfuncs,
PartitionRangeDatumKind *next_lb_kind,
List **merged_datums, List **merged_kinds,
List **merged_indexes);
-static bool merge_default_partitions(PartitionBoundInfo outer_bi,
- PartitionBoundInfo inner_bi,
- PartitionMap *outer_maps,
- PartitionMap *inner_maps,
+static bool merge_default_partitions(PartitionMap *outer_map,
+ PartitionMap *inner_map,
+ bool outer_has_default,
+ int outer_default,
+ bool inner_has_default,
+ int inner_default,
JoinType jointype,
- int *next_index, int *default_index);
-static bool merge_null_partitions(PartitionBoundInfo outer_bi,
- PartitionBoundInfo inner_bi,
- PartitionMap *outer_maps,
- PartitionMap *inner_maps,
- JoinType jointype,
- int *next_index, int *null_index,
- int *default_index);
+ int *next_index,
+ int *default_index);
+static bool merge_null_partitions(PartitionBoundInfo outer_bi, PartitionBoundInfo inner_bi,
+ PartitionMap *outer_map, PartitionMap *inner_map,
+ bool outer_has_default, bool inner_has_default,
+ bool outer_has_null, bool inner_has_null,
+ JoinType jointype, int *next_index,
+ int *default_index, int *null_index);
/*
* get_qual_from_partbound
@@ -3081,8 +3114,8 @@ partition_bounds_merge(int partnatts,
int16 *parttyplen, bool *parttypbyval,
FmgrInfo *partsupfunc, Oid *partcollation,
RelOptInfo *outer_rel, RelOptInfo *inner_rel,
- JoinType jointype,
- List **outer_parts, List **inner_parts)
+ JoinType jointype, List **outer_parts,
+ List **inner_parts)
{
PartitionBoundInfo merged_bounds;
PartitionBoundInfo outer_binfo = outer_rel->boundinfo,
@@ -3110,17 +3143,22 @@ partition_bounds_merge(int partnatts,
case PARTITION_STRATEGY_LIST:
merged_bounds = partition_list_bounds_merge(partsupfunc,
partcollation,
- outer_rel, inner_rel,
- outer_parts, inner_parts,
- jointype);
+ outer_rel,
+ inner_rel,
+ jointype,
+ outer_parts,
+ inner_parts);
break;
case PARTITION_STRATEGY_RANGE:
- merged_bounds = partition_range_bounds_merge(outer_rel, inner_rel,
- outer_parts, inner_parts,
- jointype, partnatts,
+ merged_bounds = partition_range_bounds_merge(partnatts,
partsupfunc,
- partcollation);
+ partcollation,
+ outer_rel,
+ inner_rel,
+ jointype,
+ outer_parts,
+ inner_parts);
break;
default:
@@ -3335,8 +3373,6 @@ partition_range_merge(int partnatts, FmgrInfo *partsupfuncs,
default:
elog(ERROR, "unexpected join type %d", jointype);
}
-
- return;
}
/*
@@ -3396,157 +3432,34 @@ partition_range_merge_next_lb(int partnatts, FmgrInfo *partsupfuncs,
return true;
}
-/*
- * handle_missing_partition
- *
- * If a range appears in one of the joining relations but not the other, a row
- * in the corresponding partition will not have any join partner in the other
- * relation, unless the other relation has a default partition. If a given list
- * value is present in one joining relation but not the other, the default
- * partition on the other side may contain that value.
- *
- * In both these cases, such an extra partition forms a joining pair with the
- * default partition, if any, on the other side.
- *
- * If the default partition happens to be on the outer side of the join, the
- * resultant partition will act as the default partition of the join relation.
- * Otherwise the resultant partition will be associated with the range.
- *
- * When the default partition is not present in the other relation, the rows in
- * the extra partition will be included in the bounds of the join result, if it
- * appears on the outer side of the join, since all rows from the outer side
- * are included in the join result.
- *
- * This function handles all these cases.
- *
- * maps_with_missing and missing_side_default are the partition maps (See
- * partition_range/list_bounds_merge() for details) and the index of default
- * partition respectively corresponding the side with missing partition.
- *
- * maps_with_extra and extra_part are the partition maps (See
- * partition_range/list_bounds_merge() for details) and the index of extra
- * partition respectively corresponding to the side with the extra partition.
- *
- * It returns true if the matching succeeds, otherwise returns false.
- */
-static bool
-handle_missing_partition(PartitionMap *maps_with_missing,
- PartitionMap *maps_with_extra,
- int missing_side_default,
- int extra_part,
- bool missing_side_outer,
- bool missing_side_inner,
- int *next_index, int *default_index,
- int *merged_index)
-{
- bool missing_has_default = (missing_side_default != -1);
-
- if (missing_has_default)
- {
- *merged_index = map_and_merge_partitions(maps_with_missing,
- maps_with_extra,
- missing_side_default,
- extra_part,
- next_index);
- if (*merged_index < 0)
- return false;
-
- if (missing_side_outer)
- {
- /*
- * Default partition on the outer side forms the default
- * partition of the join result.
- */
- if (*default_index < 0)
- *default_index = *merged_index;
- else if(*default_index != *merged_index)
- {
- /*
- * Ended up with default partition on the outer side
- * being joined with multiple partitions on the inner
- * side. We don't support this case.
- */
- return false;
- }
-
- /*
- * Since the merged partition acts as a default partition, it
- * doesn't need a separate index.
- */
- *merged_index = -1;
- }
- }
- else if (missing_side_inner)
- {
- /*
- * If this partition has already been mapped (say because we
- * found an overlapping range earlier), we know where does it
- * fit in the join result. Nothing to do in that case. Else
- * create a new merged partition.
- */
- PartitionMap *extra_map = &maps_with_extra[extra_part];
- if (extra_map->to < 0)
- {
- extra_map->to = *next_index;
- *next_index = *next_index + 1;
- *merged_index = extra_map->to;
- }
- }
- else
- *merged_index = -1;
-
- return true;
-}
-
-static PartitionMap*
-init_partition_map(RelOptInfo *rel)
-{
- int i, nparts = rel->nparts;
- PartitionMap *map;
-
- map = (PartitionMap *) palloc(sizeof(PartitionMap) * nparts);
-
- for (i = 0; i < nparts; i++)
- {
- map[i].from = -1;
- map[i].to = -1;
- }
-
- return map;
-}
-
/*
* partition_range_bounds_merge
*
* partition_bounds_merge()'s arm for range partitioned tables.
*/
static PartitionBoundInfo
-partition_range_bounds_merge(RelOptInfo *outer_rel, RelOptInfo *inner_rel,
- List **outer_parts, List **inner_parts,
- JoinType jointype, int partnatts,
- FmgrInfo *partsupfuncs, Oid *partcollations)
-
+partition_range_bounds_merge(int partnatts, FmgrInfo *partsupfuncs,
+ Oid *partcollations,
+ RelOptInfo *outer_rel, RelOptInfo *inner_rel,
+ JoinType jointype,
+ List **outer_parts, List **inner_parts)
{
- PartitionMap *outer_maps = NULL;
- PartitionMap *inner_maps = NULL;
- int outer_part = 0;
- int inner_part = 0;
PartitionBoundInfo merged_bounds = NULL;
- int outer_lb_index;
- int inner_lb_index;
- int next_index;
+ PartitionBoundInfo outer_bi = outer_rel->boundinfo;
+ PartitionBoundInfo inner_bi = inner_rel->boundinfo;
+ bool outer_has_default = partition_bound_has_default(outer_bi);
+ int outer_default = outer_bi->default_index;
+ bool inner_has_default = partition_bound_has_default(inner_bi);
+ int inner_default = inner_bi->default_index;
+ PartitionMap outer_map;
+ PartitionMap inner_map;
+ int next_index = 0;
int default_index = -1;
List *merged_datums = NIL;
- List *merged_indexes = NIL;
List *merged_kinds = NIL;
- PartitionBoundInfo outer_bi = outer_rel->boundinfo,
- inner_bi = inner_rel->boundinfo;
- int inner_default = inner_bi->default_index;
- int outer_default = outer_bi->default_index;
- bool inner_has_default = partition_bound_has_default(inner_bi);
- bool outer_has_default = partition_bound_has_default(outer_bi);
- int outer_nparts = outer_rel->nparts,
- inner_nparts = inner_rel->nparts;
+ List *merged_indexes = NIL;
+ int outer_lb_index;
+ int inner_lb_index;
Assert(outer_bi->strategy == inner_bi->strategy &&
outer_bi->strategy == PARTITION_STRATEGY_RANGE);
@@ -3554,8 +3467,8 @@ partition_range_bounds_merge(RelOptInfo *outer_rel, RelOptInfo *inner_rel,
Assert(*outer_parts == NIL);
Assert(*inner_parts == NIL);
- outer_maps = init_partition_map(outer_rel);
- inner_maps = init_partition_map(inner_rel);
+ init_partition_map(outer_rel, &outer_map);
+ init_partition_map(inner_rel, &inner_map);
/*
* Merge the ranges (partitions) from both sides. Every iteration compares
@@ -3566,40 +3479,30 @@ partition_range_bounds_merge(RelOptInfo *outer_rel, RelOptInfo *inner_rel,
* lb_index, for inner or outer side, keeps track of the index of lower bound
* datum in PartitionBoundInfo::datums of that side.
*/
- outer_lb_index = 0;
- inner_lb_index = 0;
- next_index = 0;
+ outer_lb_index = inner_lb_index = 0;
while (outer_lb_index < outer_bi->ndatums ||
inner_lb_index < inner_bi->ndatums)
{
- PartitionRangeBound outer_lb, outer_ub,
- inner_lb, inner_ub,
- *merged_lb = NULL,
- *merged_ub = NULL;
-
+ PartitionRangeBound *merged_lb = NULL;
+ PartitionRangeBound *merged_ub = NULL;
int merged_index = -1;
+ PartitionRangeBound outer_lb;
+ PartitionRangeBound outer_ub;
+ PartitionRangeBound inner_lb;
+ PartitionRangeBound inner_ub;
+ int outer_part = -1;
+ int inner_part = -1;
bool overlap;
- bool finished_outer = false;
- bool finished_inner = false;
-
- /* Result of bounds comparison per partition_rbound_cmp(). */
- int ub_cmpval; /* Upper bounds comparison result. */
- int lb_cmpval; /* Lower bounds comparison result. */
+ int ub_cmpval;
+ int lb_cmpval;
/* Get the range bounds of the next pair of partitions. */
if (outer_lb_index < outer_bi->ndatums)
outer_part = partition_get_range_bounds(outer_bi, outer_lb_index,
- &outer_lb, &outer_ub);
- else
- finished_outer = true;
-
+ &outer_lb, &outer_ub);
if (inner_lb_index < inner_bi->ndatums)
inner_part = partition_get_range_bounds(inner_bi, inner_lb_index,
- &inner_lb, &inner_ub);
- else
- finished_inner = true;
-
- Assert(!finished_outer || !finished_inner);
+ &inner_lb, &inner_ub);
/*
* We run this loop till both the sides finish. This allows to avoid
@@ -3611,13 +3514,13 @@ partition_range_bounds_merge(RelOptInfo *outer_rel, RelOptInfo *inner_rel,
* side. That way we advance the partitions on that side till all of
* them are exhausted.
*/
- if (finished_outer)
+ if (outer_lb_index >= outer_bi->ndatums)
{
overlap = false;
ub_cmpval = 1;
lb_cmpval = 1;
}
- else if (finished_inner)
+ else if (inner_lb_index >= inner_bi->ndatums)
{
overlap = false;
ub_cmpval = -1;
@@ -3641,7 +3544,7 @@ partition_range_bounds_merge(RelOptInfo *outer_rel, RelOptInfo *inner_rel,
jointype, &outer_lb, &outer_ub, &inner_lb,
&inner_ub, &merged_lb, &merged_ub);
- merged_index = map_and_merge_partitions(outer_maps, inner_maps,
+ merged_index = map_and_merge_partitions(&outer_map, &inner_map,
outer_part, inner_part,
&next_index);
@@ -3680,6 +3583,8 @@ partition_range_bounds_merge(RelOptInfo *outer_rel, RelOptInfo *inner_rel,
Assert(overlap);
/* Move to the next pair of partitions. */
+ Assert(outer_lb_index < outer_bi->ndatums);
+ Assert(inner_lb_index < inner_bi->ndatums);
outer_lb_index = partition_range_get_next_lb_index(outer_bi,
outer_lb_index);
inner_lb_index = partition_range_get_next_lb_index(inner_bi,
@@ -3695,37 +3600,28 @@ partition_range_bounds_merge(RelOptInfo *outer_rel, RelOptInfo *inner_rel,
}
else
{
- /* A range missing from the inner side. */
- bool missing_side_outer;
- bool missing_side_inner;
+ if (inner_has_default || IS_OUTER_JOIN(jointype))
+ {
+ if (!process_outer_partition(&outer_map,
+ &inner_map,
+ outer_has_default,
+ inner_has_default,
+ outer_part,
+ inner_default,
+ jointype,
+ outer_bi->strategy,
+ &next_index,
+ &default_index,
+ &merged_index))
+ return NULL;
+ }
merged_lb = &outer_lb;
merged_ub = &outer_ub;
-
- /*
- * For a FULL join, inner relation acts as both OUTER and INNER
- * relation. For LEFT and ANTI join the inner relation acts as
- * INNER relation. For INNER and SEMI join OUTER and INNER
- * differentiation is immaterial.
- */
- missing_side_inner = (jointype == JOIN_FULL ||
- jointype == JOIN_LEFT ||
- jointype == JOIN_ANTI);
- missing_side_outer = (jointype == JOIN_FULL);
- if (!handle_missing_partition(inner_maps,
- outer_maps,
- inner_default,
- outer_part,
- missing_side_outer,
- missing_side_inner,
- &next_index,
- &default_index,
- &merged_index))
- return NULL;
}
/* Move to the next partition on the outer side. */
- Assert(!finished_outer);
+ Assert(outer_lb_index < outer_bi->ndatums);
outer_lb_index = partition_range_get_next_lb_index(outer_bi,
outer_lb_index);
}
@@ -3740,38 +3636,28 @@ partition_range_bounds_merge(RelOptInfo *outer_rel, RelOptInfo *inner_rel,
}
else
{
- /* A range missing from the outer side. */
- bool missing_side_outer;
- bool missing_side_inner;
+ if (outer_has_default || jointype == JOIN_FULL)
+ {
+ if (!process_inner_partition(&outer_map,
+ &inner_map,
+ outer_has_default,
+ inner_has_default,
+ inner_part,
+ outer_default,
+ jointype,
+ outer_bi->strategy,
+ &next_index,
+ &default_index,
+ &merged_index))
+ return NULL;
+ }
merged_lb = &inner_lb;
merged_ub = &inner_ub;
-
- /*
- * For a FULL join, outer relation acts as both OUTER and INNER
- * relation. For LEFT and ANTI join the outer relation acts as
- * OUTER relation. For INNER and SEMI join OUTER and INNER
- * differentiation is immaterial.
- */
- missing_side_outer = (jointype == JOIN_FULL ||
- jointype == JOIN_LEFT ||
- jointype == JOIN_ANTI);
- missing_side_inner = (jointype == JOIN_FULL);
-
- if (!handle_missing_partition(outer_maps,
- inner_maps,
- outer_default,
- inner_part,
- missing_side_outer,
- missing_side_inner,
- &next_index,
- &default_index,
- &merged_index))
- return NULL;
}
/* Move to the next partition on the inner side. */
- Assert (!finished_inner);
+ Assert(inner_lb_index < inner_bi->ndatums);
inner_lb_index = partition_range_get_next_lb_index(inner_bi,
inner_lb_index);
}
@@ -3802,16 +3688,28 @@ partition_range_bounds_merge(RelOptInfo *outer_rel, RelOptInfo *inner_rel,
merged_indexes = lappend_int(merged_indexes, merged_index);
}
- if (!merge_default_partitions(outer_bi, inner_bi,
- outer_maps, inner_maps,
- jointype, &next_index,
- &default_index))
- return NULL;
+ /* Merge default partitions if any. */
+ if (outer_has_default || inner_has_default)
+ {
+ if (!merge_default_partitions(&outer_map,
+ &inner_map,
+ outer_has_default,
+ outer_default,
+ inner_has_default,
+ inner_default,
+ jointype,
+ &next_index,
+ &default_index))
+ return NULL;
+ }
+ else
+ Assert(default_index == -1);
+
+ Assert(!outer_map.did_remapping && !inner_map.did_remapping);
/* Use maps to match partition from the joining relations. */
generate_matching_part_pairs(outer_rel, inner_rel,
- outer_maps, inner_maps,
- outer_nparts, inner_nparts,
+ &outer_map, &inner_map,
next_index,
outer_parts, inner_parts);
@@ -3828,6 +3726,8 @@ partition_range_bounds_merge(RelOptInfo *outer_rel, RelOptInfo *inner_rel,
}
/* Free any memory we used in this function. */
+ free_partition_map(&outer_map);
+ free_partition_map(&inner_map);
list_free(merged_datums);
list_free(merged_indexes);
list_free(merged_kinds);
@@ -3844,27 +3744,27 @@ partition_range_bounds_merge(RelOptInfo *outer_rel, RelOptInfo *inner_rel,
static PartitionBoundInfo
partition_list_bounds_merge(FmgrInfo *partsupfunc, Oid *partcollation,
RelOptInfo *outer_rel, RelOptInfo *inner_rel,
- List **outer_parts, List **inner_parts,
- JoinType jointype)
+ JoinType jointype,
+ List **outer_parts, List **inner_parts)
{
- PartitionMap *outer_maps = NULL;
- PartitionMap *inner_maps = NULL;
- int cnto;
- int cnti;
- List *merged_datums = NIL;
- List *merged_indexes = NIL;
+ PartitionBoundInfo merged_bounds = NULL;
+ PartitionBoundInfo outer_bi = outer_rel->boundinfo;
+ PartitionBoundInfo inner_bi = inner_rel->boundinfo;
+ bool outer_has_default = partition_bound_has_default(outer_bi);
+ int outer_default = outer_bi->default_index;
+ bool inner_has_default = partition_bound_has_default(inner_bi);
+ int inner_default = inner_bi->default_index;
+ bool outer_has_null = partition_bound_accepts_nulls(outer_bi);
+ bool inner_has_null = partition_bound_accepts_nulls(inner_bi);
+ PartitionMap outer_map;
+ PartitionMap inner_map;
int next_index = 0;
int null_index = -1;
int default_index = -1;
- PartitionBoundInfo merged_bounds = NULL;
- PartitionBoundInfo outer_bi = outer_rel->boundinfo,
- inner_bi = inner_rel->boundinfo;
- int *outer_indexes = outer_bi->indexes;
- int *inner_indexes = inner_bi->indexes;
- int outer_default = outer_bi->default_index;
- int inner_default = inner_bi->default_index;
- int outer_nparts = outer_rel->nparts,
- inner_nparts = inner_rel->nparts;
+ List *merged_datums = NIL;
+ List *merged_indexes = NIL;
+ int cnto;
+ int cnti;
Assert(*outer_parts == NIL);
Assert(*inner_parts == NIL);
@@ -3875,8 +3775,8 @@ partition_list_bounds_merge(FmgrInfo *partsupfunc, Oid *partcollation,
/* List partitions do not require unbounded ranges. */
Assert(!outer_bi->kind && !inner_bi->kind);
- outer_maps = init_partition_map(outer_rel);
- inner_maps = init_partition_map(inner_rel);
+ init_partition_map(outer_rel, &outer_map);
+ init_partition_map(inner_rel, &inner_map);
/*
* Merge the list value datums from both sides. Every iteration compares a
@@ -3888,15 +3788,15 @@ partition_list_bounds_merge(FmgrInfo *partsupfunc, Oid *partcollation,
cnto = cnti = 0;
while (cnto < outer_bi->ndatums || cnti < inner_bi->ndatums)
{
+ Datum *merged_datum = NULL;
+ int merged_index = -1;
Datum *odatums;
Datum *idatums;
- int o_index;
- int i_index;
int cmpval;
- int merged_index = -1;
- Datum *merged_datum;
- bool finished_inner;
- bool finished_outer;
+
+ /* Get the list datums of the next pair of partitions. */
+ odatums = cnto < outer_bi->ndatums ? outer_bi->datums[cnto] : NULL;
+ idatums = cnti < inner_bi->ndatums ? inner_bi->datums[cnti] : NULL;
/*
* We run this loop till both the sides finish. This allows to avoid
@@ -3908,51 +3808,23 @@ partition_list_bounds_merge(FmgrInfo *partsupfunc, Oid *partcollation,
* its datums are exhausted.
*/
if (cnto >= outer_bi->ndatums)
- {
- finished_outer = true;
- odatums = NULL;
- o_index = -1;
- }
- else
- {
- finished_outer = false;
- odatums = outer_bi->datums[cnto];
- o_index = outer_indexes[cnto];
- }
-
- if (cnti >= inner_bi->ndatums)
- {
- finished_inner = true;
- idatums = NULL;
- i_index = -1;
- }
- else
- {
- finished_inner = false;
- idatums = inner_bi->datums[cnti];
- i_index = inner_indexes[cnti];
- }
-
- /* If we exhausted both the sides, we won't enter the loop. */
- Assert(!finished_inner || !finished_outer);
-
- if (finished_outer)
cmpval = 1;
- else if (finished_inner)
+ else if (cnti >= inner_bi->ndatums)
cmpval = -1;
else
{
- /* Every list datum should map to a valid partition index. */
- Assert(o_index >= 0 && i_index >= 0 &&
- odatums != NULL && idatums != NULL);
-
+ Assert(odatums != NULL && idatums != NULL);
cmpval = DatumGetInt32(FunctionCall2Coll(&partsupfunc[0],
partcollation[0],
- odatums[0], idatums[0]));
+ odatums[0],
+ idatums[0]));
}
if (cmpval == 0)
{
+ int o_index = outer_bi->indexes[cnto];
+ int i_index = inner_bi->indexes[cnti];
+
/*
* Datums match. Rows on either side with these datums as partition
* key value will join and will be part of the partition of the
@@ -3962,7 +3834,8 @@ partition_list_bounds_merge(FmgrInfo *partsupfunc, Oid *partcollation,
* partition containing it.
*/
merged_datum = odatums;
- merged_index = map_and_merge_partitions(outer_maps, inner_maps,
+ Assert(o_index >= 0 && i_index >= 0);
+ merged_index = map_and_merge_partitions(&outer_map, &inner_map,
o_index, i_index,
&next_index);
@@ -3975,74 +3848,61 @@ partition_list_bounds_merge(FmgrInfo *partsupfunc, Oid *partcollation,
}
else if (cmpval < 0)
{
- bool missing_side_outer;
- bool missing_side_inner;
+ Assert(cnto < outer_bi->ndatums);
/* A datum missing from the inner side. */
- merged_index = -1;
merged_datum = odatums;
- /*
- * For a FULL join, inner relation acts as both OUTER and INNER
- * relation. For LEFT and ANTI join the inner relation acts as
- * INNER relation. For INNER and SEMI join OUTER and INNER
- * differentiation is immaterial.
- */
- missing_side_inner = (jointype == JOIN_FULL ||
- jointype == JOIN_LEFT ||
- jointype == JOIN_ANTI);
- missing_side_outer = (jointype == JOIN_FULL);
-
- if (!handle_missing_partition(inner_maps,
- outer_maps,
- inner_default,
- o_index,
- missing_side_outer,
- missing_side_inner,
- &next_index,
- &default_index,
- &merged_index))
- return NULL;
+ if (inner_has_default || IS_OUTER_JOIN(jointype))
+ {
+ int o_index = outer_bi->indexes[cnto];
+
+ Assert(o_index >= 0);
+ if (!process_outer_partition(&outer_map,
+ &inner_map,
+ outer_has_default,
+ inner_has_default,
+ o_index,
+ inner_default,
+ jointype,
+ outer_bi->strategy,
+ &next_index,
+ &default_index,
+ &merged_index))
+ return NULL;
+ }
/* Move to the next datum on the outer side. */
- Assert(!finished_outer);
cnto++;
}
else
{
- bool missing_side_outer;
- bool missing_side_inner;
-
Assert(cmpval > 0);
+ Assert(cnti < inner_bi->ndatums);
/* A datum missing from the outer side. */
- merged_index = -1;
merged_datum = idatums;
- /*
- * For a FULL join, outer relation acts as both OUTER and INNER
- * relation. For LEFT and ANTI join the outer relation acts as
- * OUTER relation. For INNER and SEMI join OUTER and INNER
- * differentiation is immaterial.
- */
- missing_side_outer = (jointype == JOIN_FULL ||
- jointype == JOIN_LEFT ||
- jointype == JOIN_ANTI);
- missing_side_inner = (jointype == JOIN_FULL);
-
- if (!handle_missing_partition(outer_maps,
- inner_maps,
- outer_default,
- i_index,
- missing_side_outer,
- missing_side_inner,
- &next_index,
- &default_index,
- &merged_index))
- return NULL;
+ if (outer_has_default || jointype == JOIN_FULL)
+ {
+ int i_index = inner_bi->indexes[cnti];
+
+ Assert(i_index >= 0);
+ if (!process_inner_partition(&outer_map,
+ &inner_map,
+ outer_has_default,
+ inner_has_default,
+ i_index,
+ outer_default,
+ jointype,
+ outer_bi->strategy,
+ &next_index,
+ &default_index,
+ &merged_index))
+ return NULL;
+ }
- /* Move to the next datum on the right side. */
- Assert(!finished_inner);
+ /* Move to the next datum on the inner side. */
cnti++;
}
@@ -4057,22 +3917,43 @@ partition_list_bounds_merge(FmgrInfo *partsupfunc, Oid *partcollation,
}
}
- if (!merge_null_partitions(outer_bi, inner_bi,
- outer_maps, inner_maps,
- jointype, &next_index, &null_index,
- &default_index))
- return NULL;
+ /* Merge null partitions if any. */
+ if (outer_has_null || inner_has_null)
+ {
+ if (!merge_null_partitions(outer_bi, inner_bi,
+ &outer_map, &inner_map,
+ outer_has_default, inner_has_default,
+ outer_has_null, inner_has_null,
+ jointype, &next_index, &default_index,
+ &null_index))
+ return NULL;
+ }
+ else
+ Assert(null_index == -1);
- if (!merge_default_partitions(outer_bi, inner_bi,
- outer_maps, inner_maps,
- jointype, &next_index,
- &default_index))
- return NULL;
+ /* Merge default partitions if any. */
+ if (outer_has_default || inner_has_default)
+ {
+ if (!merge_default_partitions(&outer_map, &inner_map,
+ outer_has_default, outer_default,
+ inner_has_default, inner_default,
+ jointype, &next_index, &default_index))
+ return NULL;
+ }
+ else
+ Assert(default_index == -1);
+
+ /* Fix the merged_indexes list if necessary. */
+ if (outer_map.did_remapping || inner_map.did_remapping)
+ {
+ Assert(jointype == JOIN_FULL);
+ fix_merged_indexes(&outer_map, &inner_map,
+ next_index, merged_indexes);
+ }
/* Use maps to match partition from the joining relations. */
generate_matching_part_pairs(outer_rel, inner_rel,
- outer_maps, inner_maps,
- outer_nparts, inner_nparts,
+ &outer_map, &inner_map,
next_index,
outer_parts, inner_parts);
@@ -4080,7 +3961,7 @@ partition_list_bounds_merge(FmgrInfo *partsupfunc, Oid *partcollation,
if (*outer_parts && *inner_parts)
{
Assert(list_length(*outer_parts) == list_length(*inner_parts));
- Assert(list_length(*outer_parts) == next_index);
+ Assert(list_length(*outer_parts) <= next_index);
merged_bounds = build_merged_partition_bounds(outer_bi->strategy,
merged_datums,
merged_indexes, NIL,
@@ -4088,6 +3969,8 @@ partition_list_bounds_merge(FmgrInfo *partsupfunc, Oid *partcollation,
}
/* Free up all extra memory before returning from this function. */
+ free_partition_map(&outer_map);
+ free_partition_map(&inner_map);
list_free(merged_datums);
list_free(merged_indexes);
@@ -4095,132 +3978,408 @@ partition_list_bounds_merge(FmgrInfo *partsupfunc, Oid *partcollation,
}
/*
- * map_and_merge_partitions
- *
- * If the two given partitions (given by index1 and index2 resp.) are
- * already mapped to each other return the index of corresponding partition in
- * the merged set of partitions. If they do not have a merged partition
- * associated with them, assign a new merged partition index. If the
- * partitions are already mapped and their mapped partitions are different from
- * each other, they can not be merged, so return -1.
- *
- * partmaps1[i] gives the mapping of partitions for both relations. It
- * describes which partition of relation 2 matches ith partition of relation 1,
- * and which partition in the merged set matches ith partition of relation 1
- * maps to. Similarly for partmap2.
+ * init_partition_map
*
- * index1 and index2 are the indexes of matching partition from respective
- * relations.
- *
- * *next_index is used and incremented when the given partitions require a new
- * merged partition.
+ * Initialize a PartitionMap struct for given relation.
*/
-
-static int
-map_and_merge_partitions(PartitionMap *partmaps1, PartitionMap *partmaps2,
- int index1, int index2, int *next_index)
+static void
+init_partition_map(RelOptInfo *rel, PartitionMap *map)
{
- PartitionMap *partmap1 = &partmaps1[index1];
- PartitionMap *partmap2 = &partmaps2[index2];
- int merged_index;
+ int nparts = rel->nparts;
+ int i;
- /*
- * If both the partitions are not mapped to each other, update the
- * maps.
- */
- if (partmap1->from < 0 && partmap2->from < 0)
+ map->nparts = nparts;
+ map->merged_indexes = (int *) palloc(sizeof(int) * nparts);
+ map->merged = (bool *) palloc(sizeof(bool) * nparts);
+ map->did_remapping = false;
+ map->old_indexes = (int *) palloc(sizeof(int) * nparts);
+ for (i = 0; i < nparts; i++)
{
- partmap1->from = index2;
- partmap2->from = index1;
+ map->merged_indexes[i] = map->old_indexes[i] = -1;
+ map->merged[i] = false;
}
+}
+
+/*
+ * free_partition_map
+ */
+static void
+free_partition_map(PartitionMap *map)
+{
+ pfree(map->merged_indexes);
+ pfree(map->merged);
+ pfree(map->old_indexes);
+}
+
+/*
+ * map_and_merge_partitions
+ *
+ * *next_index is incremented when creating a new merged partition associated
+ * with the given partitions.
+ */
+static int
+map_and_merge_partitions(PartitionMap *outer_map, PartitionMap *inner_map,
+ int outer_index, int inner_index, int *next_index)
+{
+ int outer_merged_index;
+ bool outer_merged;
+ int inner_merged_index;
+ bool inner_merged;
+
+ Assert(outer_index >= 0 && outer_index < outer_map->nparts);
+ outer_merged_index = outer_map->merged_indexes[outer_index];
+ outer_merged = outer_map->merged[outer_index];
+ Assert(inner_index >= 0 && inner_index < inner_map->nparts);
+ inner_merged_index = inner_map->merged_indexes[inner_index];
+ inner_merged = inner_map->merged[inner_index];
/*
- * If the given to partitions map to each other, find the corresponding
- * merged partition index .
+ * Handle cases where both partitions are mapped to merged partitions.
*/
- if (partmap1->from == index2 && partmap2->from == index1)
+ if (outer_merged_index >= 0 && inner_merged_index >= 0)
{
/*
- * If both the partitions are mapped to the same merged partition, get
- * the index of merged partition.
+ * If the mereged partitions are the same, no need to do anything;
+ * return the index of the merged partition. Otherwise, if both
+ * partitions are merged with dummy partitions, re-merge them; map
+ * them to the merged partition with the smaller of the two merged
+ * indexes and return the smaller index. Otherwise they can't be
+ * merged, so return -1.
*/
- if (partmap1->to == partmap2->to)
+ if (outer_merged_index == inner_merged_index)
+ {
+ Assert(outer_merged);
+ Assert(inner_merged);
+ return outer_merged_index;
+ }
+ if (!outer_merged && !inner_merged)
{
- merged_index = partmap1->to;
-
/*
- * If the given two partitions do not have a merged partition
- * associated with them, allocate a new merged partition.
+ * Note that we will fix the larger index that have been added to
+ * the merged_indexes list so far in fix_merged_indexes().
*/
- if (merged_index < 0)
+ if (outer_merged_index < inner_merged_index)
+ {
+ outer_map->merged[outer_index] = true;
+ inner_map->merged_indexes[inner_index] = outer_merged_index;
+ inner_map->merged[inner_index] = true;
+ inner_map->did_remapping = true;
+ inner_map->old_indexes[inner_index] = inner_merged_index;
+ return outer_merged_index;
+ }
+ else
{
- merged_index = *next_index;
- *next_index = *next_index + 1;
- partmap1->to = merged_index;
- partmap2->to = merged_index;
+ inner_map->merged[inner_index] = true;
+ outer_map->merged_indexes[outer_index] = inner_merged_index;
+ outer_map->merged[outer_index] = true;
+ outer_map->did_remapping = true;
+ outer_map->old_indexes[outer_index] = outer_merged_index;
+ return inner_merged_index;
}
}
+ return -1;
+ }
+
+ /* At least one partition isn't mapped to a merged partition. */
+ Assert(outer_merged_index == -1 || inner_merged_index == -1);
+
+ /*
+ * If neither of partitions isn't mapped, assign them a new merged
+ * partition and return the index of the merged partition. Otherwise, if
+ * one of partitions is merged with a dummy relation (and the other isn't
+ * merged), re-merge it with the other, with the same index, and return
+ * the index. Otherwise they can't be merged, so return -1.
+ */
+ if (outer_merged_index == -1 && inner_merged_index == -1)
+ {
+ int merged_index = *next_index;
+
+ Assert(!outer_merged);
+ Assert(!inner_merged);
+ outer_map->merged_indexes[outer_index] = merged_index;
+ outer_map->merged[outer_index] = true;
+ inner_map->merged_indexes[inner_index] = merged_index;
+ inner_map->merged[inner_index] = true;
+ *next_index = *next_index + 1;
+ return merged_index;
+ }
+ if (outer_merged_index >= 0 && !outer_map->merged[outer_index])
+ {
+ Assert(inner_merged_index == -1);
+ Assert(!inner_merged);
+ inner_map->merged_indexes[inner_index] = outer_merged_index;
+ inner_map->merged[inner_index] = true;
+ outer_map->merged[outer_index] = true;
+ return outer_merged_index;
+ }
+ if (inner_merged_index >= 0 && !inner_map->merged[inner_index])
+ {
+ Assert(outer_merged_index == -1);
+ Assert(!outer_merged);
+ outer_map->merged_indexes[outer_index] = inner_merged_index;
+ outer_map->merged[outer_index] = true;
+ inner_map->merged[inner_index] = true;
+ return inner_merged_index;
+ }
+ return -1;
+}
+
+/*
+ * merge_partition_with_dummy
+ *
+ * *next_index is incremented.
+ */
+static int
+merge_partition_with_dummy(PartitionMap *map, int index, int *next_index)
+{
+ int merged_index = *next_index;
+
+ Assert(index >= 0 && index < map->nparts);
+ Assert(map->merged_indexes[index] == -1);
+ Assert(!map->merged[index]);
+ map->merged_indexes[index] = merged_index;
+ /* Leave the merged flag alone! */
+ *next_index = *next_index + 1;
+ return merged_index;
+}
+
+/*
+ * process_outer_partition
+ *
+ * Determine the merged partition associated with the given outer partition.
+ *
+ * *next_index is incremented when creating a new merged partition associated
+ * with the given outer partition.
+ */
+static bool
+process_outer_partition(PartitionMap *outer_map,
+ PartitionMap *inner_map,
+ bool outer_has_default,
+ bool inner_has_default,
+ int outer_index,
+ int inner_default,
+ JoinType jointype,
+ char strategy,
+ int *next_index,
+ int *default_index,
+ int *merged_index)
+{
+ Assert(outer_index >= 0);
+
+ /*
+ * If the inner side has the default partition, the outer partition has to
+ * be joined with the default partition; try merging them. Otherwise, we
+ * should in an outer join, in which case the outer partition has to be
+ * scanned all the way anyway; if the outer partition is already mapped to
+ * a merged partition, get it, otherwise create a new merged partition by
+ * merging the outer partition with a dummy partition.
+ */
+ if (inner_has_default)
+ {
+ Assert(inner_default >= 0);
+
+ /*
+ * If the outer side has the default partition as well, we need to
+ * merge the default partitions (see merge_default_partitions()); give
+ * up on it.
+ */
+ if (outer_has_default)
+ return false;
+
+ *merged_index = map_and_merge_partitions(outer_map, inner_map,
+ outer_index, inner_default,
+ next_index);
+ if (*merged_index == -1)
+ return false;
/*
- * If partition from one relation was mapped to a merged partition but
- * not the partition from the other relation, map the same merged
- * partition to the partition from other relation, since matching
- * partitions map to the same merged partition.
+ * If this is a FULL join, the merged partition would act as the
+ * default partition of the join; record the index in *default_index
+ * if not done yet.
*/
- else if (partmap1->to >= 0 && partmap2->to < 0)
+ if (jointype == JOIN_FULL)
{
- partmap2->to = partmap1->to;
- merged_index = partmap1->to;
+ if (*default_index == -1)
+ *default_index = *merged_index;
+ else
+ Assert(*merged_index == *default_index);
+ /* Don't add this index to the list of merged indexes. */
+ *merged_index = -1;
}
- else if (partmap1->to < 0 && partmap2->to >= 0)
+ }
+ else
+ {
+ Assert(IS_OUTER_JOIN(jointype));
+ Assert(jointype != JOIN_RIGHT);
+
+ /*
+ * In range partitioning, if the given outer partition is already
+ * merged (eg, because we found an overlapping range earlier), we know
+ * where it fits in the join result; nothing to do in that case. Else
+ * create a new merged partition.
+ */
+ if (outer_map->merged_indexes[outer_index] >= 0)
{
- partmap1->to = partmap2->to;
- merged_index = partmap2->to;
+ if (strategy == PARTITION_STRATEGY_LIST)
+ *merged_index = outer_map->merged_indexes[outer_index];
+ else
+ {
+ Assert(strategy == PARTITION_STRATEGY_RANGE);
+ *merged_index = -1;
+ }
}
else
- {
- Assert(partmap1->to != partmap2->to &&
- partmap1->to >= 0 && partmap2->to >= 0);
+ *merged_index = merge_partition_with_dummy(outer_map, outer_index,
+ next_index);
+ }
+ return true;
+}
- /*
- * Both the partitions map to different merged partitions. This
- * means that multiple partitions from one relation matches to one
- * partition from the other relation. Partition-wise join does not
- * handle this case right now, since it requires ganging multiple
- * partitions together (into one RelOptInfo).
- */
- merged_index = -1;
+/*
+ * process_inner_partition
+ *
+ * Determine the merged partition associated with the given inner partition.
+ *
+ * *next_index is incremented when creating a new merged partition associated
+ * with the given inner partition.
+ */
+static bool
+process_inner_partition(PartitionMap *outer_map,
+ PartitionMap *inner_map,
+ bool outer_has_default,
+ bool inner_has_default,
+ int inner_index,
+ int outer_default,
+ JoinType jointype,
+ char strategy,
+ int *next_index,
+ int *default_index,
+ int *merged_index)
+{
+ Assert(inner_index >= 0);
+
+ /*
+ * If the outer side has the default partition, the inner partition has to
+ * be joined with the default partition; try merging them. Otherwise, we
+ * should in an FULL join, in which case the inner partition has to be
+ * scanned all the way anyway; if the inner partition is already mapped to
+ * a merged partition, get it, otherwise create a new merged partition by
+ * merging the inner partition with a dummy partition.
+ */
+ if (outer_has_default)
+ {
+ Assert(outer_default >= 0);
+
+ /*
+ * If the inner side has the default partition as well, we need to
+ * merge the default partitions (see merge_default_partitions()); give
+ * up on it.
+ */
+ if (inner_has_default)
+ return false;
+
+ *merged_index = map_and_merge_partitions(outer_map, inner_map,
+ outer_default, inner_index,
+ next_index);
+ if (*merged_index == -1)
+ return false;
+
+ /*
+ * If this is an outer join, the merged partition would act as the
+ * default partition of the join; record the index in *default_index
+ * if not done yet.
+ */
+ if (jointype == JOIN_LEFT || jointype == JOIN_ANTI ||
+ jointype == JOIN_FULL)
+ {
+ if (*default_index == -1)
+ *default_index = *merged_index;
+ else
+ Assert(*merged_index == *default_index);
+ /* Don't add this index to the list of merged indexes. */
+ *merged_index = -1;
}
}
else
{
+ Assert(jointype == JOIN_FULL);
+
/*
- * Multiple partitions from one relation map to one partition from the
- * other relation. Partition-wise join does not handle this case right
- * now, since it requires ganging multiple partitions together (into
- * one RelOptInfo).
+ * In range partitioning, if the given inner partition is already
+ * merged (eg, because we found an overlapping range earlier), we know
+ * where it fits in the join result; nothing to do in that case. Else
+ * create a new merged partition.
*/
- merged_index = -1;
+ if (inner_map->merged_indexes[inner_index] >= 0)
+ {
+ if (strategy == PARTITION_STRATEGY_LIST)
+ *merged_index = inner_map->merged_indexes[inner_index];
+ else
+ {
+ Assert(strategy == PARTITION_STRATEGY_RANGE);
+ *merged_index = -1;
+ }
+ }
+ else
+ *merged_index = merge_partition_with_dummy(inner_map, inner_index,
+ next_index);
}
+ return true;
+}
- return merged_index;
+/*
+ * fix_merged_indexes
+ */
+static void
+fix_merged_indexes(PartitionMap *outer_map, PartitionMap *inner_map,
+ int nmerged, List *merged_indexes)
+{
+ int *new_indexes;
+ int merged_index;
+ int i;
+ ListCell *lc;
+
+ new_indexes = (int *) palloc(sizeof(int) * nmerged);
+ for (i = 0; i < nmerged; i++)
+ new_indexes[i] = -1;
+
+ /* Build the mapping of old merged indexes to new merged indexes. */
+ if (outer_map->did_remapping)
+ {
+ for (i = 0; i < outer_map->nparts; i++)
+ {
+ merged_index = outer_map->old_indexes[i];
+ if (merged_index >= 0)
+ new_indexes[merged_index] = outer_map->merged_indexes[i];
+ }
+ }
+ if (inner_map->did_remapping)
+ {
+ for (i = 0; i < inner_map->nparts; i++)
+ {
+ merged_index = inner_map->old_indexes[i];
+ if (merged_index >= 0)
+ new_indexes[merged_index] = inner_map->merged_indexes[i];
+ }
+ }
+
+ /* Fix the merged_indexes list using the mapping. */
+ foreach(lc, merged_indexes)
+ {
+ merged_index = lfirst_int(lc);
+ Assert(merged_index >= 0);
+ if (new_indexes[merged_index] >= 0)
+ lfirst_int(lc) = new_indexes[merged_index];
+ }
+
+ pfree(new_indexes);
}
/*
* generate_matching_part_pairs
*
- * partmaps1 map each partition from either side of the join to a merged
- * partition resp. E.g. partmaps1[i].to gives the merged partition to which ith
- * partition of first relation maps. Similarly for partmap2. If
- * partmaps1[i].to == partmaps2[j].to, i and j form the matching pair of
- * partitions.
- *
- * Given these maps this function produces the list pairs of partitions which
- * when joined produce the merged partitions in the order of merged partition
- * indexes.
- *
- * nparts1 and nparts2 are the number of partitions of the joining relations
- * resp.
+ * This function produces the list pairs of partitions which produce merged
+ * partitions in the order of merged partition indexes.
*
* nparts is the number of merged partitions.
*
@@ -4230,15 +4389,20 @@ map_and_merge_partitions(PartitionMap *partmaps1, PartitionMap *partmaps2,
*/
static void
generate_matching_part_pairs(RelOptInfo *rel1, RelOptInfo *rel2,
- PartitionMap *partmaps1, PartitionMap *partmaps2,
- int nparts1, int nparts2, int nparts,
+ PartitionMap *map1, PartitionMap *map2,
+ int nparts,
List **matched_parts1, List **matched_parts2)
{
+ int nparts1 = map1->nparts;
+ int nparts2 = map2->nparts;
int *matching1,
*matching2;
int i;
int max_nparts;
+ Assert(map1->nparts == rel1->nparts);
+ Assert(map2->nparts == rel2->nparts);
+
*matched_parts1 = NIL;
*matched_parts2 = NIL;
@@ -4253,23 +4417,22 @@ generate_matching_part_pairs(RelOptInfo *rel1, RelOptInfo *rel2,
{
if (i < nparts1)
{
- PartitionMap outer_map = partmaps1[i];
+ int merged_index = map1->merged_indexes[i];
- if (outer_map.to >= 0)
+ if (merged_index >= 0)
{
- Assert(outer_map.to < nparts);
- matching1[outer_map.to] = i;
+ Assert(merged_index < nparts);
+ matching1[merged_index] = i;
}
}
-
if (i < nparts2)
{
- PartitionMap inner_map = partmaps2[i];
+ int merged_index = map2->merged_indexes[i];
- if (inner_map.to >= 0)
+ if (merged_index >= 0)
{
- Assert(inner_map.to < nparts);
- matching2[inner_map.to] = i;
+ Assert(merged_index < nparts);
+ matching2[merged_index] = i;
}
}
}
@@ -4279,8 +4442,8 @@ generate_matching_part_pairs(RelOptInfo *rel1, RelOptInfo *rel2,
int part1 = matching1[i];
int part2 = matching2[i];
- /* At least one of the partitions should exist. */
- Assert(part1 >= 0 || part2 >= 0);
+ if (part1 == -1 && part2 == -1)
+ continue;
*matched_parts1 = lappend(*matched_parts1,
part1 >= 0 ? rel1->part_rels[part1] : NULL);
@@ -4356,57 +4519,53 @@ build_merged_partition_bounds(char strategy, List *merged_datums,
* appear in the join result, so create a default merged partition.
*/
static bool
-merge_default_partitions(PartitionBoundInfo outer_bi, PartitionBoundInfo inner_bi,
- PartitionMap *outer_maps, PartitionMap *inner_maps,
- JoinType jointype, int *next_index, int *default_index)
+merge_default_partitions(PartitionMap *outer_map, PartitionMap *inner_map,
+ bool outer_has_default, int outer_default,
+ bool inner_has_default, int inner_default,
+ JoinType jointype, int *next_index,
+ int *default_index)
{
- int outer_default = outer_bi->default_index;
- int inner_default = inner_bi->default_index;
- bool outer_has_default = partition_bound_has_default(outer_bi);
- bool inner_has_default = partition_bound_has_default(inner_bi);
- bool merged = true;
- PartitionMap *outer_default_map = NULL;
- PartitionMap *inner_default_map = NULL;
+ Assert(outer_has_default || inner_has_default);
- if (outer_has_default)
- outer_default_map = &outer_maps[outer_default];
-
- if (inner_has_default)
- inner_default_map = &inner_maps[inner_default];
-
- if (!outer_has_default && !inner_has_default)
- Assert(*default_index < 0);
- else if (outer_default_map != NULL && inner_default_map == NULL)
+ if (outer_has_default && !inner_has_default)
{
if (jointype == JOIN_LEFT || jointype == JOIN_FULL ||
jointype == JOIN_ANTI)
{
- if (outer_default_map->to < 0)
+ int merged_index;
+
+ Assert(outer_default >= 0 && outer_default < outer_map->nparts);
+ merged_index = outer_map->merged_indexes[outer_default];
+ if (merged_index == -1)
{
- outer_default_map->to = *next_index;
- *next_index = *next_index + 1;
- Assert(*default_index < 0);
- *default_index = outer_default_map->to;
+ Assert(*default_index == -1);
+ *default_index = merge_partition_with_dummy(outer_map,
+ outer_default,
+ next_index);
}
else
- Assert(*default_index == outer_default_map->to);
+ Assert(*default_index == merged_index);
}
else
Assert(*default_index < 0);
}
- else if (outer_default_map == NULL && inner_default_map != NULL)
+ else if (!outer_has_default && inner_has_default)
{
if (jointype == JOIN_FULL)
{
- if (inner_default_map->to < 0)
+ int merged_index;
+
+ Assert(inner_default >= 0 && inner_default < inner_map->nparts);
+ merged_index = inner_map->merged_indexes[inner_default];
+ if (merged_index == -1)
{
- inner_default_map->to = *next_index;
- *next_index = *next_index + 1;
- Assert(*default_index < 0);
- *default_index = inner_default_map->to;
+ Assert(*default_index == -1);
+ *default_index = merge_partition_with_dummy(inner_map,
+ inner_default,
+ next_index);
}
else
- Assert(*default_index == inner_default_map->to);
+ Assert(*default_index == merged_index);
}
else
Assert(*default_index < 0);
@@ -4415,15 +4574,16 @@ merge_default_partitions(PartitionBoundInfo outer_bi, PartitionBoundInfo inner_b
{
Assert(outer_has_default && inner_has_default);
- *default_index = map_and_merge_partitions(outer_maps, inner_maps,
- outer_default, inner_default,
+ *default_index = map_and_merge_partitions(outer_map,
+ inner_map,
+ outer_default,
+ inner_default,
next_index);
-
- if (*default_index < 0)
- merged = false;
+ if (*default_index == -1)
+ return false;
}
- return merged;
+ return true;
}
/*
@@ -4443,101 +4603,80 @@ merge_default_partitions(PartitionBoundInfo outer_bi, PartitionBoundInfo inner_b
*/
static bool
merge_null_partitions(PartitionBoundInfo outer_bi, PartitionBoundInfo inner_bi,
- PartitionMap *outer_maps, PartitionMap *inner_maps,
+ PartitionMap *outer_map, PartitionMap *inner_map,
+ bool outer_has_default, bool inner_has_default,
+ bool outer_has_null, bool inner_has_null,
JoinType jointype, int *next_index,
- int *null_index, int *default_index)
+ int *default_index, int *null_index)
{
- bool outer_has_null = partition_bound_accepts_nulls(outer_bi);
- bool inner_has_null = partition_bound_accepts_nulls(inner_bi);
- int outer_ni = outer_bi->null_index;
- int inner_ni = inner_bi->null_index;
- int outer_default = outer_bi->default_index;
- int inner_default = inner_bi->default_index;
- bool merged = true;
+ Assert(outer_has_null || inner_has_null);
+ Assert(*null_index == -1);
- if (!outer_has_null && !inner_has_null)
- Assert(*null_index < 0);
- else if (outer_has_null && !inner_has_null)
+ if (outer_has_null && !inner_has_null)
{
int merged_index = -1;
- bool missing_side_outer;
- bool missing_side_inner;
-
- /*
- * For a FULL join, inner relation acts as both OUTER and INNER
- * relation. For LEFT and ANTI join the inner relation acts as
- * INNER relation. For INNER and SEMI join OUTER and INNER
- * differentiation is immaterial.
- */
- missing_side_inner = (jointype == JOIN_FULL ||
- jointype == JOIN_LEFT ||
- jointype == JOIN_ANTI);
- missing_side_outer = (jointype == JOIN_FULL);
-
- merged = handle_missing_partition(inner_maps,
- outer_maps,
- inner_default,
- outer_ni,
- missing_side_outer,
- missing_side_inner, next_index,
- default_index, &merged_index);
- *null_index = merged_index;
/*
* If the NULL partition was missing from the inner side of the join,
- * the partition of the join to which the outer null partition maps
- * will contain the NULL values and thus becomes the NULL partition of
+ * the partition of the join to which the NULL partition matches will
+ * contain the NULL values and thus become the NULL partition of the
* the join.
*/
- if (missing_side_inner)
- *null_index = outer_maps[outer_ni].to;
+ if (inner_has_default || IS_OUTER_JOIN(jointype))
+ {
+ if (!process_outer_partition(outer_map,
+ inner_map,
+ outer_has_default,
+ inner_has_default,
+ outer_bi->null_index,
+ inner_bi->default_index,
+ jointype,
+ outer_bi->strategy,
+ next_index,
+ default_index,
+ &merged_index))
+ return false;
+ }
+ *null_index = merged_index;
}
else if (!outer_has_null && inner_has_null)
{
int merged_index = -1;
- bool missing_side_outer;
- bool missing_side_inner;
/*
- * For a FULL join, outer relation acts as both OUTER and INNER
- * relation. For LEFT and ANTI join the outer relation acts as OUTER
- * relation. For INNER and SEMI join OUTER and INNER differentiation is
- * immaterial.
- */
- missing_side_outer = (jointype == JOIN_FULL ||
- jointype == JOIN_LEFT ||
- jointype == JOIN_ANTI);
- missing_side_inner = (jointype == JOIN_FULL);
- merged = handle_missing_partition(outer_maps,
- inner_maps,
- outer_default,
- inner_ni,
- missing_side_outer,
- missing_side_inner,
- next_index, default_index,
- &merged_index);
- *null_index = merged_index;
-
- /*
- * If the NULL partition was missing from the inner side of the join,
- * the partition of the join, to which the outer side null partition maps,
- * will contain the NULL values and thus becomes the NULL partition of
+ * If the NULL partition was missing from the outer side of the join,
+ * the partition of the join to which the NULL partition matches will
+ * contain the NULL values and thus become the NULL partition of the
* the join.
*/
- if (missing_side_inner)
- *null_index = inner_maps[inner_ni].to;
+ if (outer_has_default || jointype == JOIN_FULL)
+ {
+ if (!process_inner_partition(outer_map,
+ inner_map,
+ outer_has_default,
+ inner_has_default,
+ inner_bi->null_index,
+ outer_bi->default_index,
+ jointype,
+ outer_bi->strategy,
+ next_index,
+ default_index,
+ &merged_index))
+ return false;
+ }
+ *null_index = merged_index;
}
else
{
/* Both the relations have NULL partitions, try merging them. */
- *null_index = map_and_merge_partitions(outer_maps,
- inner_maps,
- outer_ni,
- inner_ni,
+ *null_index = map_and_merge_partitions(outer_map,
+ inner_map,
+ outer_bi->null_index,
+ inner_bi->null_index,
next_index);
- if (*null_index < 0)
- merged = false;
+ if (*null_index == -1)
+ return false;
}
- return merged;
+ return true;
}
diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out
index b5b5c8a260..89f0c6a9ee 100644
--- a/src/test/regress/expected/partition_join.out
+++ b/src/test/regress/expected/partition_join.out
@@ -4769,3 +4769,182 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b =
Filter: (b = 0)
(23 rows)
+DROP TABLE plt1;
+DROP TABLE plt2;
+CREATE TABLE plt1 (a int, b int, c text) PARTITION BY LIST(c);
+CREATE TABLE plt1_p1 PARTITION OF plt1 FOR VALUES IN ('0000', '0002');
+CREATE TABLE plt1_p2 PARTITION OF plt1 FOR VALUES IN ('0003', '0004');
+INSERT INTO plt1 SELECT i, i, to_char(i % 5, 'FM0000') FROM generate_series(0, 24) i WHERE i % 5 IN (0, 2, 3, 4);
+ANALYZE plt1;
+CREATE TABLE plt2 (a int, b int, c text) PARTITION BY LIST(c);
+CREATE TABLE plt2_p1 PARTITION OF plt2 FOR VALUES IN ('0001', '0002');
+CREATE TABLE plt2_p2 PARTITION OF plt2 FOR VALUES IN ('0003', '0004');
+INSERT INTO plt2 SELECT i, i, to_char(i % 5, 'FM0000') FROM generate_series(0, 24) i WHERE i % 5 IN (1, 2, 3, 4);
+ANALYZE plt2;
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 FULL JOIN plt2 t2 ON (t1.c = t2.c) WHERE COALESCE(t1.a, 0) % 5 != 3 AND COALESCE(t1.a, 0) % 5 != 4 ORDER BY t1.c, t1.a, t2.a;
+ QUERY PLAN
+-----------------------------------------------------------------------------------------------
+ Sort
+ Sort Key: t1.c, t1.a, t2.a
+ -> Append
+ -> Hash Full Join
+ Hash Cond: (t1.c = t2.c)
+ Filter: (((COALESCE(t1.a, 0) % 5) <> 3) AND ((COALESCE(t1.a, 0) % 5) <> 4))
+ -> Seq Scan on plt1_p1 t1
+ -> Hash
+ -> Seq Scan on plt2_p1 t2
+ -> Hash Full Join
+ Hash Cond: (t1_1.c = t2_1.c)
+ Filter: (((COALESCE(t1_1.a, 0) % 5) <> 3) AND ((COALESCE(t1_1.a, 0) % 5) <> 4))
+ -> Seq Scan on plt1_p2 t1_1
+ -> Hash
+ -> Seq Scan on plt2_p2 t2_1
+(15 rows)
+
+SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 FULL JOIN plt2 t2 ON (t1.c = t2.c) WHERE COALESCE(t1.a, 0) % 5 != 3 AND COALESCE(t1.a, 0) % 5 != 4 ORDER BY t1.c, t1.a, t2.a;
+ a | c | a | c
+----+------+----+------
+ 0 | 0000 | |
+ 5 | 0000 | |
+ 10 | 0000 | |
+ 15 | 0000 | |
+ 20 | 0000 | |
+ 2 | 0002 | 2 | 0002
+ 2 | 0002 | 7 | 0002
+ 2 | 0002 | 12 | 0002
+ 2 | 0002 | 17 | 0002
+ 2 | 0002 | 22 | 0002
+ 7 | 0002 | 2 | 0002
+ 7 | 0002 | 7 | 0002
+ 7 | 0002 | 12 | 0002
+ 7 | 0002 | 17 | 0002
+ 7 | 0002 | 22 | 0002
+ 12 | 0002 | 2 | 0002
+ 12 | 0002 | 7 | 0002
+ 12 | 0002 | 12 | 0002
+ 12 | 0002 | 17 | 0002
+ 12 | 0002 | 22 | 0002
+ 17 | 0002 | 2 | 0002
+ 17 | 0002 | 7 | 0002
+ 17 | 0002 | 12 | 0002
+ 17 | 0002 | 17 | 0002
+ 17 | 0002 | 22 | 0002
+ 22 | 0002 | 2 | 0002
+ 22 | 0002 | 7 | 0002
+ 22 | 0002 | 12 | 0002
+ 22 | 0002 | 17 | 0002
+ 22 | 0002 | 22 | 0002
+ | | 1 | 0001
+ | | 6 | 0001
+ | | 11 | 0001
+ | | 16 | 0001
+ | | 21 | 0001
+(35 rows)
+
+DROP TABLE plt1;
+DROP TABLE plt2;
+CREATE TABLE plt1 (a int, b int, c text) PARTITION BY LIST(c);
+CREATE TABLE plt1_p1 PARTITION OF plt1 FOR VALUES IN ('0000', '0001', '0002');
+CREATE TABLE plt1_p2 PARTITION OF plt1 FOR VALUES IN ('0003', '0004');
+INSERT INTO plt1 SELECT i, i, to_char(i % 5, 'FM0000') FROM generate_series(0, 24) i;
+ANALYZE plt1;
+CREATE TABLE plt2 (a int, b int, c text) PARTITION BY LIST(c);
+CREATE TABLE plt2_p1 PARTITION OF plt2 FOR VALUES IN ('0002');
+CREATE TABLE plt2_p2 PARTITION OF plt2 FOR VALUES IN ('0003', '0004');
+INSERT INTO plt2 SELECT i, i, to_char(i % 5, 'FM0000') FROM generate_series(0, 24) i WHERE i % 5 IN (2, 3, 4);
+ANALYZE plt2;
+CREATE TABLE plt3 (a int, b int, c text) PARTITION BY LIST(c);
+CREATE TABLE plt3_p1 PARTITION OF plt3 FOR VALUES IN ('0001');
+CREATE TABLE plt3_p2 PARTITION OF plt3 FOR VALUES IN ('0003', '0004');
+INSERT INTO plt3 SELECT i, i, to_char(i % 5, 'FM0000') FROM generate_series(0, 24) i WHERE i % 5 IN (1, 3, 4);
+ANALYZE plt3;
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM (plt1 t1 LEFT JOIN plt2 t2 ON (t1.c = t2.c)) FULL JOIN plt3 t3 ON (t1.c = t3.c) WHERE COALESCE(t1.a, 0) % 5 != 3 AND coalesce(t1.a, 0) % 5 != 4 ORDER BY t1.c, t1.a, t2.a, t3.a;
+ QUERY PLAN
+-----------------------------------------------------------------------------------------------
+ Sort
+ Sort Key: t1.c, t1.a, t2.a, t3.a
+ -> Append
+ -> Hash Full Join
+ Hash Cond: (t1.c = t3.c)
+ Filter: (((COALESCE(t1.a, 0) % 5) <> 3) AND ((COALESCE(t1.a, 0) % 5) <> 4))
+ -> Hash Left Join
+ Hash Cond: (t1.c = t2.c)
+ -> Seq Scan on plt1_p1 t1
+ -> Hash
+ -> Seq Scan on plt2_p1 t2
+ -> Hash
+ -> Seq Scan on plt3_p1 t3
+ -> Hash Full Join
+ Hash Cond: (t1_1.c = t3_1.c)
+ Filter: (((COALESCE(t1_1.a, 0) % 5) <> 3) AND ((COALESCE(t1_1.a, 0) % 5) <> 4))
+ -> Hash Left Join
+ Hash Cond: (t1_1.c = t2_1.c)
+ -> Seq Scan on plt1_p2 t1_1
+ -> Hash
+ -> Seq Scan on plt2_p2 t2_1
+ -> Hash
+ -> Seq Scan on plt3_p2 t3_1
+(23 rows)
+
+SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM (plt1 t1 LEFT JOIN plt2 t2 ON (t1.c = t2.c)) FULL JOIN plt3 t3 ON (t1.c = t3.c) WHERE COALESCE(t1.a, 0) % 5 != 3 AND coalesce(t1.a, 0) % 5 != 4 ORDER BY t1.c, t1.a, t2.a, t3.a;
+ a | c | a | c | a | c
+----+------+----+------+----+------
+ 0 | 0000 | | | |
+ 5 | 0000 | | | |
+ 10 | 0000 | | | |
+ 15 | 0000 | | | |
+ 20 | 0000 | | | |
+ 1 | 0001 | | | 1 | 0001
+ 1 | 0001 | | | 6 | 0001
+ 1 | 0001 | | | 11 | 0001
+ 1 | 0001 | | | 16 | 0001
+ 1 | 0001 | | | 21 | 0001
+ 6 | 0001 | | | 1 | 0001
+ 6 | 0001 | | | 6 | 0001
+ 6 | 0001 | | | 11 | 0001
+ 6 | 0001 | | | 16 | 0001
+ 6 | 0001 | | | 21 | 0001
+ 11 | 0001 | | | 1 | 0001
+ 11 | 0001 | | | 6 | 0001
+ 11 | 0001 | | | 11 | 0001
+ 11 | 0001 | | | 16 | 0001
+ 11 | 0001 | | | 21 | 0001
+ 16 | 0001 | | | 1 | 0001
+ 16 | 0001 | | | 6 | 0001
+ 16 | 0001 | | | 11 | 0001
+ 16 | 0001 | | | 16 | 0001
+ 16 | 0001 | | | 21 | 0001
+ 21 | 0001 | | | 1 | 0001
+ 21 | 0001 | | | 6 | 0001
+ 21 | 0001 | | | 11 | 0001
+ 21 | 0001 | | | 16 | 0001
+ 21 | 0001 | | | 21 | 0001
+ 2 | 0002 | 2 | 0002 | |
+ 2 | 0002 | 7 | 0002 | |
+ 2 | 0002 | 12 | 0002 | |
+ 2 | 0002 | 17 | 0002 | |
+ 2 | 0002 | 22 | 0002 | |
+ 7 | 0002 | 2 | 0002 | |
+ 7 | 0002 | 7 | 0002 | |
+ 7 | 0002 | 12 | 0002 | |
+ 7 | 0002 | 17 | 0002 | |
+ 7 | 0002 | 22 | 0002 | |
+ 12 | 0002 | 2 | 0002 | |
+ 12 | 0002 | 7 | 0002 | |
+ 12 | 0002 | 12 | 0002 | |
+ 12 | 0002 | 17 | 0002 | |
+ 12 | 0002 | 22 | 0002 | |
+ 17 | 0002 | 2 | 0002 | |
+ 17 | 0002 | 7 | 0002 | |
+ 17 | 0002 | 12 | 0002 | |
+ 17 | 0002 | 17 | 0002 | |
+ 17 | 0002 | 22 | 0002 | |
+ 22 | 0002 | 2 | 0002 | |
+ 22 | 0002 | 7 | 0002 | |
+ 22 | 0002 | 12 | 0002 | |
+ 22 | 0002 | 17 | 0002 | |
+ 22 | 0002 | 22 | 0002 | |
+(55 rows)
+
diff --git a/src/test/regress/sql/partition_join.sql b/src/test/regress/sql/partition_join.sql
index 34ae92135f..b0ab002eee 100644
--- a/src/test/regress/sql/partition_join.sql
+++ b/src/test/regress/sql/partition_join.sql
@@ -800,3 +800,47 @@ ANALYZE prt2;
EXPLAIN (COSTS OFF)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b;
+
+DROP TABLE plt1;
+DROP TABLE plt2;
+
+CREATE TABLE plt1 (a int, b int, c text) PARTITION BY LIST(c);
+CREATE TABLE plt1_p1 PARTITION OF plt1 FOR VALUES IN ('0000', '0002');
+CREATE TABLE plt1_p2 PARTITION OF plt1 FOR VALUES IN ('0003', '0004');
+INSERT INTO plt1 SELECT i, i, to_char(i % 5, 'FM0000') FROM generate_series(0, 24) i WHERE i % 5 IN (0, 2, 3, 4);
+ANALYZE plt1;
+
+CREATE TABLE plt2 (a int, b int, c text) PARTITION BY LIST(c);
+CREATE TABLE plt2_p1 PARTITION OF plt2 FOR VALUES IN ('0001', '0002');
+CREATE TABLE plt2_p2 PARTITION OF plt2 FOR VALUES IN ('0003', '0004');
+INSERT INTO plt2 SELECT i, i, to_char(i % 5, 'FM0000') FROM generate_series(0, 24) i WHERE i % 5 IN (1, 2, 3, 4);
+ANALYZE plt2;
+
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 FULL JOIN plt2 t2 ON (t1.c = t2.c) WHERE COALESCE(t1.a, 0) % 5 != 3 AND COALESCE(t1.a, 0) % 5 != 4 ORDER BY t1.c, t1.a, t2.a;
+SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 FULL JOIN plt2 t2 ON (t1.c = t2.c) WHERE COALESCE(t1.a, 0) % 5 != 3 AND COALESCE(t1.a, 0) % 5 != 4 ORDER BY t1.c, t1.a, t2.a;
+
+DROP TABLE plt1;
+DROP TABLE plt2;
+
+CREATE TABLE plt1 (a int, b int, c text) PARTITION BY LIST(c);
+CREATE TABLE plt1_p1 PARTITION OF plt1 FOR VALUES IN ('0000', '0001', '0002');
+CREATE TABLE plt1_p2 PARTITION OF plt1 FOR VALUES IN ('0003', '0004');
+INSERT INTO plt1 SELECT i, i, to_char(i % 5, 'FM0000') FROM generate_series(0, 24) i;
+ANALYZE plt1;
+
+CREATE TABLE plt2 (a int, b int, c text) PARTITION BY LIST(c);
+CREATE TABLE plt2_p1 PARTITION OF plt2 FOR VALUES IN ('0002');
+CREATE TABLE plt2_p2 PARTITION OF plt2 FOR VALUES IN ('0003', '0004');
+INSERT INTO plt2 SELECT i, i, to_char(i % 5, 'FM0000') FROM generate_series(0, 24) i WHERE i % 5 IN (2, 3, 4);
+ANALYZE plt2;
+
+CREATE TABLE plt3 (a int, b int, c text) PARTITION BY LIST(c);
+CREATE TABLE plt3_p1 PARTITION OF plt3 FOR VALUES IN ('0001');
+CREATE TABLE plt3_p2 PARTITION OF plt3 FOR VALUES IN ('0003', '0004');
+INSERT INTO plt3 SELECT i, i, to_char(i % 5, 'FM0000') FROM generate_series(0, 24) i WHERE i % 5 IN (1, 3, 4);
+ANALYZE plt3;
+
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM (plt1 t1 LEFT JOIN plt2 t2 ON (t1.c = t2.c)) FULL JOIN plt3 t3 ON (t1.c = t3.c) WHERE COALESCE(t1.a, 0) % 5 != 3 AND coalesce(t1.a, 0) % 5 != 4 ORDER BY t1.c, t1.a, t2.a, t3.a;
+SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM (plt1 t1 LEFT JOIN plt2 t2 ON (t1.c = t2.c)) FULL JOIN plt3 t3 ON (t1.c = t3.c) WHERE COALESCE(t1.a, 0) % 5 != 3 AND coalesce(t1.a, 0) % 5 != 4 ORDER BY t1.c, t1.a, t2.a, t3.a;