/* ------------------------------------------------------------------------ * * pl_range_funcs.c * Utility C functions for stored RANGE procedures * * Copyright (c) 2016, Postgres Professional * * ------------------------------------------------------------------------ */ #include "pathman.h" #include "relation_info.h" #include "utils.h" #include "utils/array.h" #include "utils/builtins.h" #include "utils/lsyscache.h" /* Function declarations */ PG_FUNCTION_INFO_V1( find_or_create_range_partition); PG_FUNCTION_INFO_V1( check_overlap ); PG_FUNCTION_INFO_V1( get_part_range_by_oid ); PG_FUNCTION_INFO_V1( get_part_range_by_idx ); PG_FUNCTION_INFO_V1( build_range_condition ); /* * ----------------------------- * Partition creation & checks * ----------------------------- */ /* * Returns partition oid for specified parent relid and value. * In case when partition doesn't exist try to create one. */ Datum find_or_create_range_partition(PG_FUNCTION_ARGS) { Oid parent_oid = PG_GETARG_OID(0); Datum value = PG_GETARG_DATUM(1); Oid value_type = get_fn_expr_argtype(fcinfo->flinfo, 1); const PartRelationInfo *prel; FmgrInfo cmp_func; RangeEntry found_rentry; search_rangerel_result search_state; prel = get_pathman_relation_info(parent_oid); shout_if_prel_is_invalid(parent_oid, prel, PT_RANGE); fill_type_cmp_fmgr_info(&cmp_func, getBaseType(value_type), getBaseType(prel->atttype)); /* Use available PartRelationInfo to find partition */ search_state = search_range_partition_eq(value, &cmp_func, prel, &found_rentry); /* * If found then just return oid, else create new partitions */ if (search_state == SEARCH_RANGEREL_FOUND) PG_RETURN_OID(found_rentry.child_oid); /* * If not found and value is between first and last partitions */ else if (search_state == SEARCH_RANGEREL_GAP) PG_RETURN_NULL(); else { Oid child_oid = create_partitions(parent_oid, value, value_type); /* get_pathman_relation_info() will refresh this entry */ invalidate_pathman_relation_info(parent_oid, NULL); PG_RETURN_OID(child_oid); } } /* * Checks if range overlaps with existing partitions. * Returns TRUE if overlaps and FALSE otherwise. */ Datum check_overlap(PG_FUNCTION_ARGS) { Oid parent_oid = PG_GETARG_OID(0); Datum p1 = PG_GETARG_DATUM(1), p2 = PG_GETARG_DATUM(2); Oid p1_type = get_fn_expr_argtype(fcinfo->flinfo, 1), p2_type = get_fn_expr_argtype(fcinfo->flinfo, 2), part_type; FmgrInfo cmp_func_1, cmp_func_2; uint32 i; RangeEntry *ranges; const PartRelationInfo *prel; /* Try fetching the PartRelationInfo structure */ prel = get_pathman_relation_info(parent_oid); /* If there's no prel, return FALSE (overlap is not possible) */ if (!prel) PG_RETURN_BOOL(false); /* Emit an error if it is not partitioned by RANGE */ shout_if_prel_is_invalid(parent_oid, prel, PT_RANGE); /* Get base type of partitioned column */ part_type = getBaseType(prel->atttype); /* Fetch comparison functions */ fill_type_cmp_fmgr_info(&cmp_func_1, getBaseType(p1_type), part_type); fill_type_cmp_fmgr_info(&cmp_func_2, getBaseType(p2_type), part_type); ranges = PrelGetRangesArray(prel); for (i = 0; i < PrelChildrenCount(prel); i++) { int c1 = FunctionCall2(&cmp_func_1, p1, ranges[i].max); int c2 = FunctionCall2(&cmp_func_2, p2, ranges[i].min); if (c1 < 0 && c2 > 0) PG_RETURN_BOOL(true); } PG_RETURN_BOOL(false); } /* * ------------------------ * Various useful getters * ------------------------ */ /* * Returns range entry (min, max) (in form of array). * * arg #1 is the parent's Oid. * arg #2 is the partition's Oid. */ Datum get_part_range_by_oid(PG_FUNCTION_ARGS) { Oid partition_relid = InvalidOid, parent_relid; PartParentSearch parent_search; uint32 i; RangeEntry *ranges; const PartRelationInfo *prel; if (PG_ARGISNULL(0)) elog(ERROR, "'partition_relid' should not be NULL"); else partition_relid = PG_GETARG_OID(0); parent_relid = get_parent_of_partition(partition_relid, &parent_search); if (parent_search != PPS_ENTRY_PART_PARENT) elog(ERROR, "relation \"%s\" is not a partition", get_rel_name_or_relid(partition_relid)); prel = get_pathman_relation_info(parent_relid); shout_if_prel_is_invalid(parent_relid, prel, PT_RANGE); ranges = PrelGetRangesArray(prel); /* Look for the specified partition */ for (i = 0; i < PrelChildrenCount(prel); i++) if (ranges[i].child_oid == partition_relid) { ArrayType *arr; Datum elems[2] = { ranges[i].min, ranges[i].max }; arr = construct_array(elems, 2, prel->atttype, prel->attlen, prel->attbyval, prel->attalign); PG_RETURN_ARRAYTYPE_P(arr); } /* No partition found, report error */ elog(ERROR, "relation \"%s\" has no partition \"%s\"", get_rel_name_or_relid(parent_relid), get_rel_name_or_relid(partition_relid)); PG_RETURN_NULL(); /* keep compiler happy */ } /* * Returns N-th range entry (min, max) (in form of array). * * arg #1 is the parent's Oid. * arg #2 is the index of the range * (if it is negative then the last range will be returned). */ Datum get_part_range_by_idx(PG_FUNCTION_ARGS) { Oid parent_relid = InvalidOid; int partition_idx = 0; Datum elems[2]; RangeEntry *ranges; const PartRelationInfo *prel; if (PG_ARGISNULL(0)) elog(ERROR, "'parent_relid' should not be NULL"); else parent_relid = PG_GETARG_OID(0); if (PG_ARGISNULL(1)) elog(ERROR, "'partition_idx' should not be NULL"); else partition_idx = PG_GETARG_INT32(1); prel = get_pathman_relation_info(parent_relid); shout_if_prel_is_invalid(parent_relid, prel, PT_RANGE); /* Now we have to deal with 'idx' */ if (partition_idx < -1) { elog(ERROR, "negative indices other than -1 (last partition) are not allowed"); } else if (partition_idx == -1) { partition_idx = PrelLastChild(prel); } else if (((uint32) abs(partition_idx)) >= PrelChildrenCount(prel)) { elog(ERROR, "partition #%d does not exist (total amount is %u)", partition_idx, PrelChildrenCount(prel)); } ranges = PrelGetRangesArray(prel); elems[0] = ranges[partition_idx].min; elems[1] = ranges[partition_idx].max; PG_RETURN_ARRAYTYPE_P(construct_array(elems, 2, prel->atttype, prel->attlen, prel->attbyval, prel->attalign)); } /* * ------------------------ * Useful string builders * ------------------------ */ /* Build range condition for a CHECK CONSTRAINT. */ Datum build_range_condition(PG_FUNCTION_ARGS) { text *attname = PG_GETARG_TEXT_P(0); Datum min_bound = PG_GETARG_DATUM(1), max_bound = PG_GETARG_DATUM(2); Oid min_bound_type = get_fn_expr_argtype(fcinfo->flinfo, 1), max_bound_type = get_fn_expr_argtype(fcinfo->flinfo, 2); char *result; /* This is not going to trigger (not now, at least), just for the safety */ if (min_bound_type != max_bound_type) elog(ERROR, "cannot build range condition: " "boundaries should be of the same type"); /* Create range condition CSTRING */ result = psprintf("%1$s >= '%2$s' AND %1$s < '%3$s'", text_to_cstring(attname), datum_to_cstring(min_bound, min_bound_type), datum_to_cstring(max_bound, max_bound_type)); PG_RETURN_TEXT_P(cstring_to_text(result)); }