26
26
#include "utils/array.h"
27
27
#include "utils/builtins.h"
28
28
#include "utils/lsyscache.h"
29
+ #include "utils/numeric.h"
29
30
#include "utils/ruleutils.h"
31
+ #include "utils/syscache.h"
30
32
31
33
32
34
static char * deparse_constraint (Oid relid , Node * expr );
@@ -48,7 +50,9 @@ static void modify_range_constraint(Oid child_relid,
48
50
const Bound * upper );
49
51
static char * get_qualified_rel_name (Oid relid );
50
52
static void drop_table_by_oid (Oid relid );
51
-
53
+ static bool interval_is_trivial (Oid atttype ,
54
+ Datum interval ,
55
+ Oid interval_type );
52
56
53
57
/* Function declarations */
54
58
@@ -620,10 +624,9 @@ validate_interval_value(PG_FUNCTION_ARGS)
620
624
Oid partrel = PG_GETARG_OID (0 );
621
625
text * attname = PG_GETARG_TEXT_P (1 );
622
626
PartType parttype = DatumGetPartType (PG_GETARG_DATUM (2 ));
623
- Datum range_interval = PG_GETARG_DATUM (3 );
624
-
625
- char * attname_cstr ;
626
- Oid atttype ; /* type of partitioned attribute */
627
+ Datum interval_text = PG_GETARG_DATUM (3 );
628
+ Datum interval_value ;
629
+ Oid interval_type ;
627
630
628
631
if (PG_ARGISNULL (0 ))
629
632
elog (ERROR , "'partrel' should not be NULL" );
@@ -634,21 +637,130 @@ validate_interval_value(PG_FUNCTION_ARGS)
634
637
if (PG_ARGISNULL (2 ))
635
638
elog (ERROR , "'parttype' should not be NULL" );
636
639
637
- /* it's OK if interval is NULL and table is HASH-partitioned */
638
- if (PG_ARGISNULL (3 ))
639
- PG_RETURN_BOOL (parttype == PT_HASH );
640
+ /*
641
+ * NULL interval is fine for both HASH and RANGE. But for RANGE we need
642
+ * to make some additional checks
643
+ */
644
+ if (!PG_ARGISNULL (3 ))
645
+ {
646
+ char * attname_cstr ;
647
+ Oid atttype ; /* type of partitioned attribute */
648
+
649
+ if (parttype == PT_HASH )
650
+ elog (ERROR , "interval must be NULL for HASH partitioned table" );
640
651
641
- /* Convert attname to CSTRING and fetch column's type */
642
- attname_cstr = text_to_cstring (attname );
643
- atttype = get_attribute_type (partrel , attname_cstr , false);
652
+ /* Convert attname to CSTRING and fetch column's type */
653
+ attname_cstr = text_to_cstring (attname );
654
+ atttype = get_attribute_type (partrel , attname_cstr , false);
644
655
645
- /* Try converting textual representation */
646
- extract_binary_interval_from_text (range_interval , atttype , NULL );
656
+ /* Try converting textual representation */
657
+ interval_value = extract_binary_interval_from_text (interval_text ,
658
+ atttype ,
659
+ & interval_type );
660
+
661
+ /* Check that interval isn't trivial */
662
+ if (interval_is_trivial (atttype , interval_value , interval_type ))
663
+ elog (ERROR , "interval must not be trivial" );
664
+ }
647
665
648
666
PG_RETURN_BOOL (true);
649
667
}
650
668
651
669
670
+ /*
671
+ * Check if interval is insignificant to avoid infinite loops while adding
672
+ * new partitions
673
+ *
674
+ * The main idea behind this function is to add specified interval to some
675
+ * default value (zero for numeric types and current date/timestamp for datetime
676
+ * types) and look if it is changed. If it is then return true.
677
+ */
678
+ static bool
679
+ interval_is_trivial (Oid atttype , Datum interval , Oid interval_type )
680
+ {
681
+ Datum default_value ;
682
+ Datum op_result ;
683
+ Oid op_result_type ;
684
+ Operator op ;
685
+ Oid op_func ;
686
+ FmgrInfo cmp_func ;
687
+
688
+ /* Generate default value */
689
+ switch (atttype )
690
+ {
691
+ case INT2OID :
692
+ case INT4OID :
693
+ case INT8OID :
694
+ default_value = Int16GetDatum (0 );
695
+ break ;
696
+ case FLOAT4OID :
697
+ default_value = Float4GetDatum (0 );
698
+ break ;
699
+ case FLOAT8OID :
700
+ default_value = Float8GetDatum (0 );
701
+ break ;
702
+ case NUMERICOID :
703
+ default_value = NumericGetDatum (0 );
704
+ break ;
705
+ case TIMESTAMPOID :
706
+ case TIMESTAMPTZOID :
707
+ default_value = TimestampGetDatum (GetCurrentTimestamp ());
708
+ break ;
709
+ case DATEOID :
710
+ {
711
+ Datum ts = TimestampGetDatum (GetCurrentTimestamp ());
712
+
713
+ default_value = perform_type_cast (ts , TIMESTAMPTZOID , DATEOID , NULL );
714
+ }
715
+ break ;
716
+ default :
717
+ return false;
718
+ }
719
+
720
+ /* Find suitable addition operator for default value and interval */
721
+ op = get_binary_operator ("+" , atttype , interval_type );
722
+ if (!op )
723
+ elog (ERROR , "missing \"+\" operator for types %s and %s" ,
724
+ format_type_be (atttype ),
725
+ format_type_be (interval_type ));
726
+
727
+ op_func = oprfuncid (op );
728
+ op_result_type = get_operator_ret_type (op );
729
+ ReleaseSysCache (op );
730
+
731
+ /* Invoke addition operator and get a result*/
732
+ op_result = OidFunctionCall2 (op_func , default_value , interval );
733
+
734
+ /*
735
+ * If operator result type isn't the same as original value then
736
+ * convert it. We need this to make sure that specified interval would
737
+ * change the _origianal_ value somehow. For example, if we add one second
738
+ * to a date then we'll get a timestamp which is one second later than
739
+ * original date (obviously). But when we convert it back to a date we will
740
+ * get the same original value meaning that one second interval wouldn't
741
+ * change original value anyhow. We should consider such interval
742
+ * as trivial
743
+ */
744
+ if (op_result_type != atttype )
745
+ {
746
+ op_result = perform_type_cast (op_result , op_result_type , atttype , NULL );
747
+ op_result_type = atttype ;
748
+ }
749
+
750
+ /*
751
+ * Compare it to the default_value. If they are the same then obviously
752
+ * interval is trivial
753
+ */
754
+ fill_type_cmp_fmgr_info (& cmp_func ,
755
+ getBaseType (atttype ),
756
+ getBaseType (op_result_type ));
757
+ if (DatumGetInt32 (FunctionCall2 (& cmp_func , default_value , op_result )) == 0 )
758
+ return true;
759
+
760
+ return false;
761
+ }
762
+
763
+
652
764
/*
653
765
* ------------------
654
766
* Helper functions
0 commit comments