Skip to content

Commit 280c26a

Browse files
committed
Add more checks and fixes for subpartitions
1 parent 1ec8525 commit 280c26a

File tree

6 files changed

+250
-6
lines changed

6 files changed

+250
-6
lines changed

init.sql

+30
Original file line numberDiff line numberDiff line change
@@ -862,6 +862,36 @@ CREATE OR REPLACE FUNCTION @extschema@.invoke_on_partition_created_callback(
862862
RETURNS VOID AS 'pg_pathman', 'invoke_on_partition_created_callback'
863863
LANGUAGE C;
864864

865+
/*
866+
* Get parent of pg_pathman's partition.
867+
*/
868+
CREATE OR REPLACE FUNCTION @extschema@.is_equal_to_partitioning_expression(
869+
parent_relid REGCLASS,
870+
expression TEXT,
871+
value_type OID)
872+
RETURNS BOOL AS 'pg_pathman', 'is_equal_to_partitioning_expression_pl'
873+
LANGUAGE C STRICT;
874+
875+
/*
876+
* Get lower bound of a partitioned relation
877+
* bound_value is used to determine the type of bound
878+
*/
879+
CREATE OR REPLACE FUNCTION @extschema@.get_lower_bound(
880+
relid REGCLASS,
881+
bound_value ANYELEMENT
882+
)
883+
RETURNS ANYELEMENT AS 'pg_pathman', 'get_lower_bound_pl'
884+
LANGUAGE C STRICT;
885+
886+
/*
887+
* Get upper bound of a partition
888+
*/
889+
CREATE OR REPLACE FUNCTION @extschema@.get_upper_bound(
890+
relid REGCLASS,
891+
bound_value ANYELEMENT
892+
)
893+
RETURNS ANYELEMENT AS 'pg_pathman', 'get_upper_bound_pl'
894+
LANGUAGE C STRICT;
865895

866896
/*
867897
* DEBUG: Place this inside some plpgsql fuction and set breakpoint.

range.sql

+81-3
Original file line numberDiff line numberDiff line change
@@ -158,24 +158,47 @@ CREATE OR REPLACE FUNCTION @extschema@.create_range_partitions(
158158
partition_data BOOLEAN DEFAULT TRUE)
159159
RETURNS INTEGER AS $$
160160
DECLARE
161+
relid REGCLASS;
161162
rows_count BIGINT;
162163
max_value start_value%TYPE;
163164
cur_value start_value%TYPE := start_value;
164165
end_value start_value%TYPE;
166+
lower_bound start_value%TYPE = NULL;
167+
upper_bound start_value%TYPE = NULL;
165168
part_count INTEGER := 0;
166169
i INTEGER;
167-
170+
part_type INT4;
168171
BEGIN
169172
PERFORM @extschema@.prepare_for_partitioning(parent_relid,
170173
expression,
171174
partition_data);
172175

176+
/*
177+
* Check that we're trying to make subpartitions.
178+
* If expressions are same then we set and use upper bound.
179+
* We change start_value if it's greater than lower bound.
180+
*/
181+
relid := @extschema@.get_parent_of_partition(parent_relid, false);
182+
IF relid IS NOT NULL THEN
183+
part_type := get_partition_type(relid);
184+
IF (part_type = 2) AND @extschema@.is_equal_to_partitioning_expression(
185+
relid, expression, pg_typeof(start_value))
186+
THEN
187+
lower_bound := @extschema@.get_lower_bound(parent_relid, start_value);
188+
upper_bound := @extschema@.get_upper_bound(parent_relid, start_value);
189+
IF lower_bound != start_value THEN
190+
start_value := lower_bound;
191+
RAISE NOTICE '"start_value" was set to %', start_value;
192+
END IF;
193+
END IF;
194+
END IF;
195+
173196
IF p_count < 0 THEN
174197
RAISE EXCEPTION 'partitions count must not be less than zero';
175198
END IF;
176199

177200
/* Try to determine partitions count if not set */
178-
IF p_count IS NULL THEN
201+
IF p_count IS NULL OR (relid IS NOT NULL AND p_count = 0) THEN
179202
EXECUTE format('SELECT count(*), max(%s) FROM %s', expression, parent_relid)
180203
INTO rows_count, max_value;
181204

@@ -189,6 +212,7 @@ BEGIN
189212

190213
p_count := 0;
191214
WHILE cur_value <= max_value
215+
OR (upper_bound IS NOT NULL AND cur_value < upper_bound)
192216
LOOP
193217
cur_value := cur_value + p_interval;
194218
p_count := p_count + 1;
@@ -205,6 +229,20 @@ BEGIN
205229
FOR i IN 1..p_count
206230
LOOP
207231
end_value := end_value + p_interval;
232+
IF upper_bound IS NOT NULL AND end_value >= upper_bound THEN
233+
part_count := i;
234+
IF end_value > upper_bound THEN
235+
RAISE WARNING '"p_interval" is not multiple of range (%, %)',
236+
start_value, end_value;
237+
END IF;
238+
IF p_count != part_count THEN
239+
p_count := part_count;
240+
RAISE NOTICE '"p_count" was set %', p_count;
241+
END IF;
242+
243+
/* we got our partitions count */
244+
EXIT;
245+
END IF;
208246
END LOOP;
209247

210248
/* check boundaries */
@@ -460,6 +498,26 @@ BEGIN
460498
END
461499
$$ LANGUAGE plpgsql;
462500

501+
502+
/*
503+
* NOTE: we need this function just to determine the type
504+
* of "upper_bound" var
505+
*/
506+
CREATE OR REPLACE FUNCTION @extschema@.check_against_upper_bound_internal(
507+
relid REGCLASS,
508+
bound_value ANYELEMENT,
509+
error_message TEXT)
510+
RETURNS VOID AS $$
511+
DECLARE
512+
upper_bound bound_value%TYPE;
513+
BEGIN
514+
upper_bound := get_upper_bound(relid, bound_value);
515+
IF bound_value > upper_bound THEN
516+
RAISE EXCEPTION '%', error_message;
517+
END IF;
518+
END
519+
$$ LANGUAGE plpgsql;
520+
463521
/*
464522
* Spawn logic for append_partition(). We have to
465523
* separate this in order to pass the 'p_range'.
@@ -475,10 +533,12 @@ CREATE OR REPLACE FUNCTION @extschema@.append_partition_internal(
475533
tablespace TEXT DEFAULT NULL)
476534
RETURNS TEXT AS $$
477535
DECLARE
536+
relid REGCLASS;
478537
part_expr_type REGTYPE;
479538
part_name TEXT;
480539
v_args_format TEXT;
481-
540+
part_expr TEXT;
541+
part_type INTEGER;
482542
BEGIN
483543
IF @extschema@.get_number_of_partitions(parent_relid) = 0 THEN
484544
RAISE EXCEPTION 'cannot append to empty partitions set';
@@ -496,6 +556,24 @@ BEGIN
496556
RAISE EXCEPTION 'Cannot append partition because last partition''s range is half open';
497557
END IF;
498558

559+
/*
560+
* In case a user has used same expression on two levels, we need to check
561+
* that we've not reached upper bound of higher partitioned table
562+
*/
563+
relid := @extschema@.get_parent_of_partition(parent_relid, false);
564+
IF relid IS NOT NULL THEN
565+
SELECT expr FROM @extschema@.pathman_config WHERE partrel = parent_relid
566+
INTO part_expr;
567+
568+
part_type := get_partition_type(relid);
569+
IF (part_type = 2) AND @extschema@.is_equal_to_partitioning_expression(
570+
relid, part_expr, part_expr_type)
571+
THEN
572+
PERFORM @extschema@.check_against_upper_bound_internal(parent_relid,
573+
p_range[2], 'reached upper bound in the current level of subpartitions');
574+
END IF;
575+
END IF;
576+
499577
IF @extschema@.is_date_type(p_atttype) THEN
500578
v_args_format := format('$1, $2, ($2 + $3::interval)::%s, $4, $5', part_expr_type::TEXT);
501579
ELSE

sql/pathman_subpartitions.sql

+6-3
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,8 @@ SET pg_pathman.enable_partitionrouter = ON;
8282

8383
CREATE TABLE subpartitions.abc(a INTEGER NOT NULL, b INTEGER NOT NULL);
8484
SELECT create_range_partitions('subpartitions.abc', 'a', 0, 100, 2);
85-
SELECT create_range_partitions('subpartitions.abc_1', 'b', 0, 50, 2);
86-
SELECT create_range_partitions('subpartitions.abc_2', 'b', 0, 50, 2);
85+
SELECT create_range_partitions('subpartitions.abc_1', 'b', 0, 50, 2); /* 0 - 100 */
86+
SELECT create_range_partitions('subpartitions.abc_2', 'b', 0, 50, 2); /* 100 - 200 */
8787

8888
INSERT INTO subpartitions.abc SELECT 25, 25 FROM generate_series(1, 10);
8989
SELECT tableoid::regclass, * FROM subpartitions.abc; /* Should be in subpartitions.abc_1_1 */
@@ -103,10 +103,13 @@ SELECT split_range_partition('subpartitions.abc_2_2', 75);
103103
SELECT subpartitions.partitions_tree('subpartitions.abc');
104104

105105
/* merge_range_partitions */
106-
SELECT append_range_partition('subpartitions.abc', 'subpartitions.abc_3');
106+
SELECT append_range_partition('subpartitions.abc', 'subpartitions.abc_3'); /* 200 - 300 */
107107
select merge_range_partitions('subpartitions.abc_2', 'subpartitions.abc_3');
108108
select merge_range_partitions('subpartitions.abc_2_1', 'subpartitions.abc_2_2');
109109

110+
/* create subpartitions but use same expression */
111+
SELECT create_range_partitions('subpartitions.abc_3', 'a', 150, 50, 2);
112+
110113
DROP TABLE subpartitions.abc CASCADE;
111114
DROP SCHEMA subpartitions CASCADE;
112115
DROP EXTENSION pg_pathman;

src/include/relation_info.h

+4
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,8 @@ Datum cook_partitioning_expression(const Oid relid,
296296

297297
char *canonicalize_partitioning_expression(const Oid relid,
298298
const char *expr_cstr);
299+
bool is_equal_to_partitioning_expression(Oid relid, char *expression,
300+
Oid value_type);
299301

300302
/* Global invalidation routines */
301303
void delay_pathman_shutdown(void);
@@ -312,6 +314,8 @@ Oid get_parent_of_partition(Oid partition, PartParentSearch *status);
312314
void forget_bounds_of_partition(Oid partition);
313315
PartBoundInfo *get_bounds_of_partition(Oid partition,
314316
const PartRelationInfo *prel);
317+
Datum get_lower_bound(Oid parent_relid, Oid value_type);
318+
Datum get_upper_bound(Oid relid, Oid value_type);
315319

316320
/* PartType wrappers */
317321

src/pl_funcs.c

+44
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ PG_FUNCTION_INFO_V1( check_security_policy );
7272
PG_FUNCTION_INFO_V1( debug_capture );
7373
PG_FUNCTION_INFO_V1( pathman_version );
7474

75+
PG_FUNCTION_INFO_V1( get_lower_bound_pl );
76+
PG_FUNCTION_INFO_V1( get_upper_bound_pl );
77+
PG_FUNCTION_INFO_V1( is_equal_to_partitioning_expression_pl );
78+
7579

7680
/* User context for function show_partition_list_internal() */
7781
typedef struct
@@ -145,6 +149,46 @@ get_parent_of_partition_pl(PG_FUNCTION_ARGS)
145149
PG_RETURN_NULL();
146150
}
147151

152+
/*
153+
* Get parent of a specified partition.
154+
*/
155+
Datum
156+
is_equal_to_partitioning_expression_pl(PG_FUNCTION_ARGS)
157+
{
158+
bool result;
159+
Oid parent_relid = PG_GETARG_OID(0);
160+
char *expr = TextDatumGetCString(PG_GETARG_TEXT_P(1));
161+
Oid value_type = PG_GETARG_OID(2);
162+
163+
result = is_equal_to_partitioning_expression(parent_relid, expr,
164+
value_type);
165+
PG_RETURN_BOOL(result);
166+
}
167+
168+
/*
169+
* Get min bound value for parent relation
170+
*/
171+
Datum
172+
get_lower_bound_pl(PG_FUNCTION_ARGS)
173+
{
174+
Oid relid = PG_GETARG_OID(0);
175+
Oid value_type = get_fn_expr_argtype(fcinfo->flinfo, 1);
176+
177+
PG_RETURN_POINTER(get_lower_bound(relid, value_type));
178+
}
179+
180+
/*
181+
* Get min bound value for parent relation
182+
*/
183+
Datum
184+
get_upper_bound_pl(PG_FUNCTION_ARGS)
185+
{
186+
Oid relid = PG_GETARG_OID(0);
187+
Oid value_type = get_fn_expr_argtype(fcinfo->flinfo, 1);
188+
189+
PG_RETURN_POINTER(get_upper_bound(relid, value_type));
190+
}
191+
148192
/*
149193
* Extract basic type of a domain.
150194
*/

src/relation_info.c

+85
Original file line numberDiff line numberDiff line change
@@ -1040,6 +1040,91 @@ get_parent_of_partition(Oid partition, PartParentSearch *status)
10401040
return get_parent_of_partition_internal(partition, status, HASH_FIND);
10411041
}
10421042

1043+
/* Check that expression is equal to expression of some partitioned table */
1044+
bool
1045+
is_equal_to_partitioning_expression(Oid relid, char *expression,
1046+
Oid value_type)
1047+
{
1048+
const PartRelationInfo *prel;
1049+
char *cexpr;
1050+
Oid expr_type;
1051+
1052+
/*
1053+
* Cook and get a canonicalized expression,
1054+
* we don't need a result of the cooking
1055+
*/
1056+
cook_partitioning_expression(relid, expression, &expr_type);
1057+
cexpr = canonicalize_partitioning_expression(relid, expression);
1058+
1059+
prel = get_pathman_relation_info(relid);
1060+
1061+
/* caller should have been check it already */
1062+
Assert(prel != NULL);
1063+
1064+
return (expr_type == value_type) &&
1065+
(strcmp(cexpr, prel->expr_cstr) == 0);
1066+
}
1067+
1068+
/* Get lower bound of a partition */
1069+
Datum
1070+
get_lower_bound(Oid relid, Oid value_type)
1071+
{
1072+
Oid parent_relid;
1073+
Datum result;
1074+
const PartRelationInfo *prel;
1075+
PartBoundInfo *pbin;
1076+
PartParentSearch parent_search;
1077+
1078+
parent_relid = get_parent_of_partition(relid, &parent_search);
1079+
if (parent_search != PPS_ENTRY_PART_PARENT)
1080+
elog(ERROR, "relation \"%s\" is not a partition",
1081+
get_rel_name_or_relid(relid));
1082+
1083+
prel = get_pathman_relation_info(parent_relid);
1084+
Assert(prel && prel->parttype == PT_RANGE);
1085+
pbin = get_bounds_of_partition(relid, prel);
1086+
Assert(prel != NULL);
1087+
1088+
if (IsInfinite(&pbin->range_min))
1089+
return PointerGetDatum(NULL);
1090+
1091+
result = BoundGetValue(&pbin->range_min);
1092+
if (value_type != prel->ev_type)
1093+
result = perform_type_cast(result, prel->ev_type, value_type, NULL);
1094+
1095+
return result;
1096+
}
1097+
1098+
/* Get upper bound of a partition */
1099+
Datum
1100+
get_upper_bound(Oid relid, Oid value_type)
1101+
{
1102+
Oid parent_relid;
1103+
Datum result;
1104+
const PartRelationInfo *prel;
1105+
PartBoundInfo *pbin;
1106+
PartParentSearch parent_search;
1107+
1108+
parent_relid = get_parent_of_partition(relid, &parent_search);
1109+
if (parent_search != PPS_ENTRY_PART_PARENT)
1110+
elog(ERROR, "relation \"%s\" is not a partition",
1111+
get_rel_name_or_relid(relid));
1112+
1113+
prel = get_pathman_relation_info(parent_relid);
1114+
Assert(prel && prel->parttype == PT_RANGE);
1115+
pbin = get_bounds_of_partition(relid, prel);
1116+
Assert(prel != NULL);
1117+
1118+
if (IsInfinite(&pbin->range_max))
1119+
return PointerGetDatum(NULL);
1120+
1121+
result = BoundGetValue(&pbin->range_max);
1122+
if (value_type != prel->ev_type)
1123+
result = perform_type_cast(result, prel->ev_type, value_type, NULL);
1124+
1125+
return result;
1126+
}
1127+
10431128
/*
10441129
* Get [and remove] "partition+parent" pair from cache,
10451130
* also check syscache if 'status' is provided.

0 commit comments

Comments
 (0)