From 4429f6a9e3e12bb4af6e3677fbc78cd80f160252 Mon Sep 17 00:00:00 2001 From: Heikki Linnakangas Date: Thu, 3 Nov 2011 13:16:28 +0200 Subject: [PATCH] Support range data types. Selectivity estimation functions are missing for some range type operators, which is a TODO. Jeff Davis --- doc/src/sgml/catalogs.sgml | 77 + doc/src/sgml/datatype.sgml | 16 +- doc/src/sgml/extend.sgml | 22 +- doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/func.sgml | 304 +++ doc/src/sgml/plpgsql.sgml | 6 +- doc/src/sgml/rangetypes.sgml | 373 +++ doc/src/sgml/ref/create_type.sgml | 61 +- doc/src/sgml/xfunc.sgml | 6 +- src/backend/catalog/Makefile | 6 +- src/backend/catalog/pg_proc.c | 63 +- src/backend/catalog/pg_range.c | 136 ++ src/backend/commands/typecmds.c | 445 +++- src/backend/executor/functions.c | 1 + src/backend/nodes/copyfuncs.c | 14 + src/backend/nodes/equalfuncs.c | 12 + src/backend/parser/gram.y | 7 + src/backend/parser/parse_coerce.c | 180 +- src/backend/tcop/utility.c | 13 + src/backend/utils/adt/Makefile | 4 +- src/backend/utils/adt/date.c | 1 - src/backend/utils/adt/pseudotypes.c | 24 + src/backend/utils/adt/rangetypes.c | 2153 +++++++++++++++++ src/backend/utils/adt/rangetypes_gist.c | 587 +++++ src/backend/utils/cache/lsyscache.c | 30 + src/backend/utils/cache/syscache.c | 12 + src/backend/utils/fmgr/funcapi.c | 115 +- src/bin/pg_dump/pg_dump.c | 184 +- src/include/catalog/catversion.h | 3 +- src/include/catalog/indexing.h | 3 + src/include/catalog/pg_amop.h | 30 + src/include/catalog/pg_amproc.h | 9 + src/include/catalog/pg_opclass.h | 3 + src/include/catalog/pg_operator.h | 39 + src/include/catalog/pg_opfamily.h | 3 + src/include/catalog/pg_proc.h | 147 ++ src/include/catalog/pg_range.h | 84 + src/include/catalog/pg_type.h | 29 +- src/include/commands/typecmds.h | 1 + src/include/nodes/nodes.h | 1 + src/include/nodes/parsenodes.h | 11 + src/include/utils/lsyscache.h | 2 + src/include/utils/rangetypes.h | 159 ++ src/include/utils/syscache.h | 1 + src/pl/plpgsql/src/pl_comp.c | 10 +- .../regress/expected/collate.linux.utf8.out | 17 + src/test/regress/expected/opr_sanity.out | 37 +- src/test/regress/expected/plpgsql.out | 14 + src/test/regress/expected/rangetypes.out | 951 ++++++++ src/test/regress/expected/sanity_check.out | 3 +- src/test/regress/expected/type_sanity.out | 2 +- src/test/regress/parallel_schedule | 2 +- src/test/regress/serial_schedule | 1 + src/test/regress/sql/collate.linux.utf8.sql | 11 + src/test/regress/sql/opr_sanity.sql | 12 +- src/test/regress/sql/plpgsql.sql | 10 + src/test/regress/sql/rangetypes.sql | 371 +++ src/test/regress/sql/type_sanity.sql | 2 +- 58 files changed, 6718 insertions(+), 103 deletions(-) create mode 100644 doc/src/sgml/rangetypes.sgml create mode 100644 src/backend/catalog/pg_range.c create mode 100644 src/backend/utils/adt/rangetypes.c create mode 100644 src/backend/utils/adt/rangetypes_gist.c create mode 100644 src/include/catalog/pg_range.h create mode 100644 src/include/utils/rangetypes.h create mode 100644 src/test/regress/expected/rangetypes.out create mode 100644 src/test/regress/sql/rangetypes.sql diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index cfecaa6931..2063812942 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -218,6 +218,11 @@ functions and procedures + + pg_range + information about range types + + pg_rewrite query rewrite rules @@ -4594,6 +4599,78 @@ + + <structname>pg_range</structname> + + + pg_range + + + + The catalog pg_range stores information about range types. + + + + <structname>pg_range</> Columns + + + + + Name + Type + References + Description + + + + + + rngtypid + oid + pg_type.oid + The type that is a range type + + + + rngsubtype + oid + pg_type.oid + Subtype of this range type, e.g. integer is the subtype of int4range + + + + rngcollation + oid + pg_collation.oid + The collation used when comparing range boundaries + + + + rngsubopc + oid + pg_opclass.oid + The operator class used when comparing range boundaries + + + + rngcanonical + regproc + pg_proc.oid + A function to convert a range into its canonical form + + + + rngsubdiff + regproc + pg_proc.oid + A function to return the distance between two lower and upper bound, as a double precision. Used for GiST support + + + +
+ +
+ <structname>pg_rewrite</structname> diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml index e7b3098f28..fe59a1c776 100644 --- a/doc/src/sgml/datatype.sgml +++ b/doc/src/sgml/datatype.sgml @@ -4173,6 +4173,8 @@ SET xmloption TO { DOCUMENT | CONTENT }; &rowtypes; + &rangetypes; + Object Identifier Types @@ -4443,6 +4445,10 @@ SELECT * FROM pg_attribute anyenum + + anyrange + + void @@ -4519,6 +4525,13 @@ SELECT * FROM pg_attribute ).
+ + anyrange + Indicates that a function accepts any range data type + (see and + ). + + anynonarray Indicates that a function accepts any non-array data type @@ -4583,7 +4596,8 @@ SELECT * FROM pg_attribute only void and record as a result type (plus trigger when the function is used as a trigger). Some also support polymorphic functions using the types anyarray, - anyelement, anyenum, and anynonarray. + anyelement, anyenum, anyrange, and + anynonarray. diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml index 7079db3ed3..f3850b391e 100644 --- a/doc/src/sgml/extend.sgml +++ b/doc/src/sgml/extend.sgml @@ -198,14 +198,15 @@ - Four pseudo-types of special interest are anyelement, - anyarray, anynonarray, and anyenum, - which are collectively called polymorphic types. - Any function declared using these types is said to be - a polymorphic function. A polymorphic function can - operate on many different data types, with the specific data type(s) - being determined by the data types actually passed to it in a particular - call. + Five pseudo-types of special interest are anyelement, + anyarray, anynonarray, anyenum, + and anyrange, which are collectively + called polymorphic types. Any function declared + using these types is said to be a polymorphic + function. A polymorphic function can operate on many + different data types, with the specific data type(s) being + determined by the data types actually passed to it in a + particular call. @@ -221,6 +222,11 @@ anyelement, the actual array type in the anyarray positions must be an array whose elements are the same type appearing in the anyelement positions. + Similarly, if there are positions declared anyrange + and others declared + anyelement, the actual range type in the + anyrange positions must be a range whose subtype is + the same type appearing in the anyelement positions. anynonarray is treated exactly the same as anyelement, but adds the additional constraint that the actual type must not be an array type. diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index ed39e0b661..fb69415f80 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -25,6 +25,7 @@ + diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 2b8298c3e0..f81bb9db97 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -10457,6 +10457,310 @@ SELECT NULLIF(value, '(none)') ... + + Range Functions and Operators + + + shows the operators + available for range types. + + + + Range Operators + + + + Operator + Description + Example + Result + + + + + = + equal + int4range(1,5) = '[1,4]'::int4range + t + + + + <> + not equal + numrange(1.1,2.2) <> numrange(1.1,2.3) + t + + + + < + less than + int4range(1,10) < int4range(2,3) + t + + + + > + greater than + int4range(1,10) > int4range(1,5) + t + + + + <= + less than or equal + numrange(1.1,2.2) <= numrange(1.1,2.2) + t + + + + >= + greater than or equal + numrange(1.1,2.2) >= numrange(1.1,2.0) + t + + + + @> + contains + '[2011-01-01,2011-03-01)'::tsrange @> '2011-01-10'::timestamp + t + + + + <@ + is contained by + int4range(2,4) <@ int4range(1,7) + t + + + + && + overlap (have points in common) + int8range(3,7) && int8range(4,12) + t + + + + << + strictly left of + int8range(1,10) << int8range(100,110) + t + + + + >> + strictly right of + int8range(50,60) >> int8range(20,30) + t + + + + &< + Does not extend to the right of? + int8range(1,20) &< int8range(18,20) + t + + + + &> + Does not extend to the left of? + int8range(7,20) &> int8range(5,10) + t + + + + -|- + adjacent? + numrange(1.1,2.2) -|- numrange(2.2,3.3) + t + + + + + + Union + numrange(5,15) + numrange(10,20) + [5,20) + + + + - + Difference + int8range(5,15) - int8range(10,20) + [5,10) + + + + * + Intersection + int8range(5,15) * int8range(10,20) + [10,15) + + + + !? + Is empty? + 'empty'::int4range !? + t + + + + ? + Is non-empty? + numrange(1.0,2.0)? + t + + + +
+ + + Range comparisons compare the lower bounds first, and only if + equal, compare the upper bounds. This is generally most useful for + B-tree indexes, rather than being useful comparisons by themselves. + + + + See for more details about range operator + behavior. + + + + shows the functions + available for use with range types. See + for more information and examples of the use of these functions. + + + + lower + + + upper + + + empty + + + non_empty + + + lower_inc + + + upper_inc + + + lower_inf + + + upper_inf + + + + Range Functions + + + + Function + Return Type + Description + Example + Result + + + + + + + lower(anyrange) + + + anyrange + lower bound of range + lower(numrange(1.1,2.2)) + 1.1 + + + + + upper(anyrange) + + + anyrange + upper bound of range + upper(numrange(1.1,2.2)) + 2.2 + + + + + empty(anyrange) + + + anyrange + is the range empty? + empty(numrange(1.1,2.2)) + false + + + + + non_empty(anyrange) + + + anyrange + is the range non-empty? + non_empty(numrange(1.1,2.2)) + true + + + + + lower_inc(anyrange) + + + anyrange + is the lower bound of the range inclusive? + lower_inc(numrange(1.1,2.2)) + true + + + + + upper_inc(anyrange) + + + anyrange + is the upper bound of the range inclusive? + upper_inc(numrange(1.1,2.2)) + false + + + + + lower_inf(anyrange) + + + anyrange + is the lower bound of the range infinite? + lower_inf('(,)'::daterange) + true + + + + + upper_inf(anyrange) + + + anyrange + is the upper bound of the range infinite? + upper_inf('(,)'::daterange) + true + + + +
+
+ Aggregate Functions diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml index 19c15ad26f..f33cef55ed 100644 --- a/doc/src/sgml/plpgsql.sgml +++ b/doc/src/sgml/plpgsql.sgml @@ -139,7 +139,7 @@ PL/pgSQL functions can also be declared to accept and return the polymorphic types anyelement, anyarray, anynonarray, - and anyenum. The actual + anyenum, and anyrange. The actual data types handled by a polymorphic function can vary from call to call, as discussed in . An example is shown in . @@ -500,8 +500,8 @@ $$ LANGUAGE plpgsql; When the return type of a PL/pgSQL function is declared as a polymorphic type (anyelement, - anyarray, anynonarray, or anyenum), - a special parameter $0 + anyarray, anynonarray, anyenum, + or anyrange), a special parameter $0 is created. Its data type is the actual return type of the function, as deduced from the actual input types (see ). diff --git a/doc/src/sgml/rangetypes.sgml b/doc/src/sgml/rangetypes.sgml new file mode 100644 index 0000000000..fc5896d8f4 --- /dev/null +++ b/doc/src/sgml/rangetypes.sgml @@ -0,0 +1,373 @@ + + + + Range Types + + + range type + + + + Range types are data types representing a range of values over some + sub-type with a total order. For instance, ranges + of timestamp might be used to represent the ranges of + time that a meeting room is reserved. In this case the data type + is tsrange (short for "timestamp range"), + and timestamp is the sub-type with a total order. + + + + Range types are useful because they represent many points in a + single value. The use of time and date ranges for scheduling + purposes is the clearest example; but price ranges, measurement + ranges from an instrument, etc., are also useful. + + + + Built-in Range Types + + PostgreSQL comes with the following built-in range types: + + + + INT4RANGE -- Range of INTEGER. This is a discrete range type, see . + + + + + INT8RANGE -- Range of BIGINT. This is a discrete range type, see . + + + + + NUMRANGE -- Range of NUMERIC. + + + + + TSRANGE -- Range of TIMESTAMP WITHOUT TIME ZONE. + + + + + TSTZRANGE -- Range of TIMESTAMP WITH TIME ZONE. + + + + + DATERANGE -- Range of DATE. This is a discrete range type, see . + + + + In addition, you can define your own; see for more information. + + + + + Examples + + +CREATE TABLE reservation ( during TSRANGE ); +INSERT INTO reservation VALUES + ( '[2010-01-01 14:30, 2010-01-01 15:30)' ); + +-- Containment +SELECT int4range(10, 20) @> 3; + +-- Overlaps +SELECT numrange(11.1, 22.2) && numrange(20.0, 30.0); + +-- Find the upper bound: +SELECT upper(int8range(15, 25)); + +-- Compute the intersection: +SELECT int4range(10, 20) * int4range(15, 25); + +-- Is the range non-empty? +SELECT numrange(1, 5)? ; + + + + See + and for complete lists of + functions and operators on range types. + + + + + Inclusive and Exclusive Bounds + + Every range has two bounds, the lower bound and the upper bound. All + points in between those values are included in the range. An + inclusive bound means that the boundary point itself is included in + the range as well, while an exclusive bound means that the boundary + point is not included in the range. + + + An inclusive lower bound is represented by [ + while an exclusive lower bound is represented + by ( (see + and below). Likewise, an inclusive + upper bound is represented by ], while an + exclusive upper bound is represented by ). + + + Functions lower_inc + and upper_inc test the inclusivity of the lower + and upper bounds of a range, respectively. + + + + + Infinite (unbounded) Ranges + + The lower bound of a range can be omitted, meaning that all points + less (or equal to, if inclusive) than the upper bound are included + in the range. Likewise, if the upper bound of the range is omitted, + then all points greater than (or equal to, if omitted) the lower + bound are included in the range. If both lower and upper bounds are + omitted, all points are considered to be in the range. + + + Functions lower_inf + and upper_inf test the range for infinite lower + and upper bounds of a range, respectively. + + + + + Input/Output + + The input follows one of the following patterns: + +(lower-bound,upper-bound) +(lower-bound,upper-bound] +[lower-bound,upper-bound) +[lower-bound,upper-bound] +empty + + Notice that the final pattern is empty, which + represents an empty range (a range that contains no points). + + + The lower-bound may be either a string + that is valid input for the sub-type, or omitted (to indicate no + lower bound); and upper-bound may be + either a string that is valid input for the sub-type, or omitted (to + indicate no upper bound). + + + Either the lower-bound or + the upper-bound may be quoted + using "" (double quotation marks), which will allow + special characters such as ",". Within quotation + marks, "\" (backslash) serves as an escape + character. + + + The choice between the other input formats affects the inclusivity + of the bounds. See . + + + Examples: + +-- includes point 3, does not include point 7, and does include all points in between +select '[3,7)' + +-- does not include either 3 or 7, but includes all points in between +select '(3,7)' + +-- includes only the single point 4 +select '[4,4]' + + + + + + Constructing Ranges + + Each range type has a constructor by the same name. The constructor + accepts from zero to three arguments. The zero-argument form + constructs an empty range; the one-argument form constructs a + singleton range; the two-argument form constructs a range + in [ ) form; and the three-argument form + constructs a range in a form specified by the third argument. For + example: + +-- Three-argument form: lower bound, upper bound, and third argument indicating +-- inclusivity/exclusivity of bounds (if omitted, defaults to '[)'). +SELECT numrange(1.0, 14.0, '(]'); + +-- The int4range input will exclude the lower bound and include the upper bound; but the +-- resulting output will appear in the canonical form; see . +SELECT int8range(1, 14, '(]'); + +-- Single argument form constructs a singleton range; that is a range consisting of just +-- one point. +SELECT numrange(11.1); + +-- Zero-argument form constructs and empty range. +SELECT numrange(); + +-- Using NULL for a bound causes the range to be unbounded on that side; that is, negative +-- infinity for the lower bound or positive infinity for the upper bound. +SELECT numrange(NULL,2.2); + + + + + + Discrete Range Types + + Discrete ranges are those that have a + defined canonical function. Loosely speaking, a + discrete range has a sub-type with a well-defined "step"; + e.g. INTEGER or DATE. + + + The canonical function should take an input range + value, and return an equal range value that may have a different + formatting. For instance, the integer range [1, + 7] could be represented by the equal integer + range [1, 8). The two values are equal because + there are no points within the integer domain + between 7 and 8, so not + including the end point 8 is the same as + including the end point 7. The canonical output + for two values that are equal, like [1, 7] + and [1, 8), must be equal. It doesn't matter + which representation you choose to be the canonical one, as long as + two equal values with different formattings are always mapped to the + same value with the same formatting. If the canonical function is + not specified, then ranges with different formatting + (e.g. [1, 7] and [1, 8)) will + always be treated as unequal. + + + For types such as NUMRANGE, this is not possible, + because there are always points in between two + distinct NUMERIC values. + + + The built-in range + types INT4RANGE, INT8RANGE, + and DATERNAGE all use a canonical form that includes + the lower bound and excludes the upper bound; that is, [ + ). User-defined ranges can use other conventions, however. + + + + + Defining New Range Types + + Users can define their own range types. The most common reason to do + this is to use ranges where the subtype is not among the built-in + range types, e.g. a range of type FLOAT (or, if the + subtype itself is a user-defined type). + + + For example: to define a new range type of sub-type DOUBLE PRECISION: + +CREATE TYPE FLOATRANGE AS RANGE ( + SUBTYPE = DOUBLE PRECISION +); + +SELECT '[1.234, 5.678]'::floatrange; + + Because DOUBLE PRECISION has no meaningful "step", we + do not define a canonical + function. See for more + information. + + + Defining your own range type also allows you to specify a different + operator class or collation to use (which affects the points that + fall between the range boundaries), or a different canonicalization + function. + + + + + + range type + gist + + Indexing + + GiST indexes can be applied to a table containing a range type. For instance: + +CREATE INDEX reservation_idx ON reservation USING gist (during); + + This index may speed up queries + involving && + (overlaps), @> (contains), and all the boolean + operators found in this + table: . + + + + + + range type + exclude + + Constraints on Ranges + + While UNIQUE is a natural constraint for scalar + values, it is usually unsuitable for range types. Instead, an + exclusion constraint is often more appropriate + (see CREATE TABLE + ... CONSTRAINT ... EXCLUDE). Exclusion constraints allow the + specification of constraints such as "non-overlapping" on a range + type. For example: + +ALTER TABLE reservation + ADD EXCLUDE USING gist (during WITH &&); + + That constraint will prevent any overlapping values from existing + in the table at the same time: + +INSERT INTO reservation VALUES + ( '[2010-01-01 11:30, 2010-01-01 13:00)' ); +-- Result: INSERT 0 1 +INSERT INTO reservation VALUES + ( '[2010-01-01 14:45, 2010-01-01 15:45)' ); +-- Result: +-- ERROR: conflicting key value violates exclusion constraint "reservation_during_excl" +-- DETAIL: Key (during)=([ 2010-01-01 14:45:00, 2010-01-01 15:45:00 )) conflicts with +-- existing key (during)=([ 2010-01-01 14:30:00, 2010-01-01 15:30:00 )). + + + + Combine range types and exclusion constraints + with btree_gist for maximum + flexibility defining + constraints. After btree_gist is installed, the + following constraint will prevent overlapping ranges only if the + meeting room numbers are equal: + + +CREATE TABLE room_reservation +( + room TEXT, + during TSRANGE, + EXCLUDE USING gist (room WITH =, during WITH &&) +); + +INSERT INTO room_reservation VALUES + ( '123A', '[2010-01-01 14:00, 2010-01-01 15:00)' ); +-- Result: INSERT 0 1 +INSERT INTO room_reservation VALUES + ( '123A', '[2010-01-01 14:30, 2010-01-01 15:30)' ); +-- Result: +-- ERROR: conflicting key value violates exclusion constraint "room_reservation_room_during_excl" +-- DETAIL: Key (room, during)=(123A, [ 2010-01-01 14:30:00, 2010-01-01 15:30:00 )) conflicts with +-- existing key (room, during)=(123A, [ 2010-01-01 14:00:00, 2010-01-01 15:00:00 )). +INSERT INTO room_reservation VALUES + ( '123B', '[2010-01-01 14:30, 2010-01-01 15:30)' ); +-- Result: INSERT 0 1 + + + + + diff --git a/doc/src/sgml/ref/create_type.sgml b/doc/src/sgml/ref/create_type.sgml index ea45fadae6..ebcd461bd9 100644 --- a/doc/src/sgml/ref/create_type.sgml +++ b/doc/src/sgml/ref/create_type.sgml @@ -27,6 +27,15 @@ CREATE TYPE name AS CREATE TYPE name AS ENUM ( [ 'label' [, ... ] ] ) +CREATE TYPE name AS RANGE ( + SUBTYPE = subtype, + [ , SUBTYPE_OPCLASS = subtype_operator_class ] + [ , SUBTYPE_DIFF = subtype_diff_function ] + [ , CANONICAL = canonical_function ] + [ , ANALYZE = analyze_function ] + [ , COLLATION = collation ] +) + CREATE TYPE name ( INPUT = input_function, OUTPUT = output_function @@ -98,11 +107,61 @@ CREATE TYPE name + + Range Types + + + The third form of CREATE TYPE creates a new + range type, as described in . + + + + The subtype parameter + can be any type with an associated btree opclass (uses the type's + default btree operator class unless specified with + subtype_operator_class). + + + + The subtype_diff + function takes two values of type + subtype as argument, and + returns the distance between the two values as + double precision. This function is used for GiST indexing + (see for more information), and should be provided + for efficiency. + + + + The canonical + function takes an argument and returns a value, both of the same + type being defined. This is used to convert the range value to a + canonical form, when applicable. See + for more information. To define + a canonical function, + you must first create a shell type, which is a + placeholder type that has no properties except a name and an + owner. This is done by issuing the command CREATE TYPE + name, with no additional parameters. + + + + The analyze + function is the same as for creating a base type. + + + + The collation option + specifies the collation used when determining the total order for + the range. + + + Base Types - The third form of CREATE TYPE creates a new base type + The fourth form of CREATE TYPE creates a new base type (scalar type). To create a new base type, you must be a superuser. (This restriction is made because an erroneous type definition could confuse or even crash the server.) diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml index 34e2cc2915..7064312204 100644 --- a/doc/src/sgml/xfunc.sgml +++ b/doc/src/sgml/xfunc.sgml @@ -997,8 +997,8 @@ $$ LANGUAGE SQL; SQL functions can be declared to accept and return the polymorphic types anyelement, - anyarray, anynonarray, and - anyenum. See anyarray, anynonarray, + anyenum, and anyrange. See for a more detailed explanation of polymorphic functions. Here is a polymorphic function make_array that builds up an array @@ -3046,7 +3046,7 @@ CREATE OR REPLACE FUNCTION retcomposite(IN integer, IN integer, C-language functions can be declared to accept and return the polymorphic types anyelement, anyarray, anynonarray, - and anyenum. + anyenum, and anyrange. See for a more detailed explanation of polymorphic functions. When function arguments or return types are defined as polymorphic types, the function author cannot know diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile index 7e0b7d65a8..5a4419d3a8 100644 --- a/src/backend/catalog/Makefile +++ b/src/backend/catalog/Makefile @@ -13,8 +13,8 @@ include $(top_builddir)/src/Makefile.global OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \ objectaddress.o pg_aggregate.o pg_collation.o pg_constraint.o pg_conversion.o \ pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \ - pg_operator.o pg_proc.o pg_db_role_setting.o pg_shdepend.o pg_type.o \ - storage.o toasting.o + pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \ + pg_type.o storage.o toasting.o BKIFILES = postgres.bki postgres.description postgres.shdescription @@ -39,7 +39,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\ pg_ts_parser.h pg_ts_template.h pg_extension.h \ pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \ pg_foreign_table.h \ - pg_default_acl.h pg_seclabel.h pg_shseclabel.h pg_collation.h \ + pg_default_acl.h pg_seclabel.h pg_shseclabel.h pg_collation.h pg_range.h \ toasting.h indexing.h \ ) diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c index 5f7d7f6b68..8378c360b9 100644 --- a/src/backend/catalog/pg_proc.c +++ b/src/backend/catalog/pg_proc.c @@ -91,8 +91,11 @@ ProcedureCreate(const char *procedureName, int parameterCount; int allParamCount; Oid *allParams; + char *modes = NULL; bool genericInParam = false; bool genericOutParam = false; + bool anyrangeInParam = false; + bool anyrangeOutParam = false; bool internalInParam = false; bool internalOutParam = false; Oid variadicType = InvalidOid; @@ -152,6 +155,24 @@ ProcedureCreate(const char *procedureName, allParams = parameterTypes->values; } + if (parameterModes != PointerGetDatum(NULL)) + { + /* + * We expect the array to be a 1-D CHAR array; verify that. We don't + * need to use deconstruct_array() since the array data is just going + * to look like a C array of char values. + */ + ArrayType *modesArray = (ArrayType *) DatumGetPointer(parameterModes); + + if (ARR_NDIM(modesArray) != 1 || + ARR_DIMS(modesArray)[0] != allParamCount || + ARR_HASNULL(modesArray) || + ARR_ELEMTYPE(modesArray) != CHAROID) + elog(ERROR, "parameterModes is not a 1-D char array"); + modes = (char *) ARR_DATA_PTR(modesArray); + } + + /* * Do not allow polymorphic return type unless at least one input argument * is polymorphic. Also, do not allow return type INTERNAL unless at @@ -161,6 +182,9 @@ ProcedureCreate(const char *procedureName, { switch (parameterTypes->values[i]) { + case ANYRANGEOID: + anyrangeInParam = true; + /* FALL THROUGH */ case ANYARRAYOID: case ANYELEMENTOID: case ANYNONARRAYOID: @@ -177,14 +201,17 @@ ProcedureCreate(const char *procedureName, { for (i = 0; i < allParamCount; i++) { - /* - * We don't bother to distinguish input and output params here, so - * if there is, say, just an input INTERNAL param then we will - * still set internalOutParam. This is OK since we don't really - * care. - */ + if (modes == NULL || + (modes[i] != PROARGMODE_OUT && + modes[i] != PROARGMODE_INOUT && + modes[i] != PROARGMODE_TABLE)) + continue; + switch (allParams[i]) { + case ANYRANGEOID: + anyrangeOutParam = true; + /* FALL THROUGH */ case ANYARRAYOID: case ANYELEMENTOID: case ANYNONARRAYOID: @@ -205,6 +232,13 @@ ProcedureCreate(const char *procedureName, errmsg("cannot determine result data type"), errdetail("A function returning a polymorphic type must have at least one polymorphic argument."))); + if ((returnType == ANYRANGEOID || anyrangeOutParam) && + !anyrangeInParam) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("cannot determine result data type"), + errdetail("A function returning ANYRANGE must have at least one ANYRANGE argument."))); + if ((returnType == INTERNALOID || internalOutParam) && !internalInParam) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), @@ -225,23 +259,8 @@ ProcedureCreate(const char *procedureName, procedureName, format_type_be(parameterTypes->values[0])))); - if (parameterModes != PointerGetDatum(NULL)) + if (modes != NULL) { - /* - * We expect the array to be a 1-D CHAR array; verify that. We don't - * need to use deconstruct_array() since the array data is just going - * to look like a C array of char values. - */ - ArrayType *modesArray = (ArrayType *) DatumGetPointer(parameterModes); - char *modes; - - if (ARR_NDIM(modesArray) != 1 || - ARR_DIMS(modesArray)[0] != allParamCount || - ARR_HASNULL(modesArray) || - ARR_ELEMTYPE(modesArray) != CHAROID) - elog(ERROR, "parameterModes is not a 1-D char array"); - modes = (char *) ARR_DATA_PTR(modesArray); - /* * Only the last input parameter can be variadic; if it is, save its * element type. Errors here are just elog since caller should have diff --git a/src/backend/catalog/pg_range.c b/src/backend/catalog/pg_range.c new file mode 100644 index 0000000000..3b9003395e --- /dev/null +++ b/src/backend/catalog/pg_range.c @@ -0,0 +1,136 @@ +/*------------------------------------------------------------------------- + * + * pg_range.c + * routines to support manipulation of the pg_range relation + * + * Copyright (c) 2006-2010, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/catalog/pg_range.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/genam.h" +#include "access/heapam.h" +#include "catalog/dependency.h" +#include "catalog/indexing.h" +#include "catalog/pg_collation.h" +#include "catalog/pg_opclass.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_range.h" +#include "catalog/pg_type.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/tqual.h" +#include "utils/rel.h" + +/* + * RangeCreate + * Create an entry in pg_range. + */ +void +RangeCreate(Oid rangeTypeOid, Oid rangeSubType, Oid rangeCollation, + Oid rangeSubOpclass, RegProcedure rangeCanonical, + RegProcedure rangeSubDiff) +{ + Relation pg_range; + Datum values[Natts_pg_range]; + bool nulls[Natts_pg_range]; + HeapTuple tup; + ObjectAddress myself; + ObjectAddress referenced; + + pg_range = heap_open(RangeRelationId, RowExclusiveLock); + + memset(nulls, 0, Natts_pg_range * sizeof(bool)); + + values[Anum_pg_range_rngtypid - 1] = ObjectIdGetDatum(rangeTypeOid); + values[Anum_pg_range_rngsubtype - 1] = ObjectIdGetDatum(rangeSubType); + values[Anum_pg_range_rngcollation - 1] = ObjectIdGetDatum(rangeCollation); + values[Anum_pg_range_rngsubopc - 1] = ObjectIdGetDatum(rangeSubOpclass); + values[Anum_pg_range_rngcanonical - 1] = ObjectIdGetDatum(rangeCanonical); + values[Anum_pg_range_rngsubdiff - 1] = ObjectIdGetDatum(rangeSubDiff); + + tup = heap_form_tuple(RelationGetDescr(pg_range), values, nulls); + simple_heap_insert(pg_range, tup); + CatalogUpdateIndexes(pg_range, tup); + heap_freetuple(tup); + + /* record dependencies */ + + myself.classId = TypeRelationId; + myself.objectId = rangeTypeOid; + myself.objectSubId = 0; + + referenced.classId = TypeRelationId; + referenced.objectId = rangeSubType; + referenced.objectSubId = 0; + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + + referenced.classId = OperatorClassRelationId; + referenced.objectId = rangeSubOpclass; + referenced.objectSubId = 0; + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + + if (OidIsValid(rangeCollation)) + { + referenced.classId = CollationRelationId; + referenced.objectId = rangeCollation; + referenced.objectSubId = 0; + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + } + + if (OidIsValid(rangeCanonical)) + { + referenced.classId = ProcedureRelationId; + referenced.objectId = rangeCanonical; + referenced.objectSubId = 0; + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + } + + if (OidIsValid(rangeSubDiff)) + { + referenced.classId = ProcedureRelationId; + referenced.objectId = rangeSubDiff; + referenced.objectSubId = 0; + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + } + + heap_close(pg_range, RowExclusiveLock); +} + + +/* + * RangeDelete + * Remove the pg_range entry. + */ +void +RangeDelete(Oid rangeTypeOid) +{ + Relation pg_range; + ScanKeyData key[1]; + SysScanDesc scan; + HeapTuple tup; + + pg_range = heap_open(RangeRelationId, RowExclusiveLock); + + ScanKeyInit(&key[0], + Anum_pg_range_rngtypid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(rangeTypeOid)); + + scan = systable_beginscan(pg_range, RangeTypidIndexId, true, + SnapshotNow, 1, key); + + while (HeapTupleIsValid(tup = systable_getnext(scan))) + { + simple_heap_delete(pg_range, &tup->t_self); + } + + systable_endscan(scan); + + heap_close(pg_range, RowExclusiveLock); +} diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index 5069c5759e..91488bbbf5 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -42,7 +42,11 @@ #include "catalog/pg_constraint.h" #include "catalog/pg_depend.h" #include "catalog/pg_enum.h" +#include "catalog/pg_language.h" #include "catalog/pg_namespace.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_proc_fn.h" +#include "catalog/pg_range.h" #include "catalog/pg_type.h" #include "catalog/pg_type_fn.h" #include "commands/defrem.h" @@ -63,6 +67,7 @@ #include "utils/fmgroids.h" #include "utils/lsyscache.h" #include "utils/memutils.h" +#include "utils/rangetypes.h" #include "utils/rel.h" #include "utils/syscache.h" #include "utils/tqual.h" @@ -87,6 +92,9 @@ static Oid findTypeSendFunction(List *procname, Oid typeOid); static Oid findTypeTypmodinFunction(List *procname); static Oid findTypeTypmodoutFunction(List *procname); static Oid findTypeAnalyzeFunction(List *procname, Oid typeOid); +static Oid findRangeCanonicalFunction(List *procname, Oid typeOid); +static Oid findRangeSubOpclass(List *procname, Oid typeOid); +static Oid findRangeSubtypeDiffFunction(List *procname, Oid typeOid); static void validateDomainConstraint(Oid domainoid, char *ccbin); static List *get_rels_with_domain(Oid domainOid, LOCKMODE lockmode); static void checkDomainOwner(HeapTuple tup); @@ -95,6 +103,8 @@ static char *domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid, int typMod, Constraint *constr, char *domainName); +static void makeRangeConstructor(char *name, Oid namespace, Oid rettype, + Oid subtype); /* @@ -643,6 +653,14 @@ RemoveTypeById(Oid typeOid) if (((Form_pg_type) GETSTRUCT(tup))->typtype == TYPTYPE_ENUM) EnumValuesDelete(typeOid); + /* + * If it is a range type, delete the pg_range entries too; we + * don't bother with making dependency entries for those, so it + * has to be done "by hand" here. + */ + if (((Form_pg_type) GETSTRUCT(tup))->typtype == TYPTYPE_RANGE) + RangeDelete(typeOid); + ReleaseSysCache(tup); heap_close(relation, RowExclusiveLock); @@ -724,14 +742,15 @@ DefineDomain(CreateDomainStmt *stmt) basetypeoid = HeapTupleGetOid(typeTup); /* - * Base type must be a plain base type, another domain or an enum. Domains - * over pseudotypes would create a security hole. Domains over composite - * types might be made to work in the future, but not today. + * Base type must be a plain base type, another domain, an enum or a range + * type. Domains over pseudotypes would create a security hole. Domains + * over composite types might be made to work in the future, but not today. */ typtype = baseType->typtype; if (typtype != TYPTYPE_BASE && typtype != TYPTYPE_DOMAIN && - typtype != TYPTYPE_ENUM) + typtype != TYPTYPE_ENUM && + typtype != TYPTYPE_RANGE) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("\"%s\" is not a valid base type for a domain", @@ -1134,6 +1153,327 @@ DefineEnum(CreateEnumStmt *stmt) pfree(enumArrayName); } +/* + * DefineRange + * Registers a new range type. + */ +void +DefineRange(CreateRangeStmt *stmt) +{ + char *typeName; + char *rangeArrayName; + Oid typeNamespace; + Oid typoid; + Oid rangeArrayOid; + List *parameters = stmt->params; + + ListCell *lc; + List *rangeSubOpclassName = NIL; + List *rangeSubtypeDiffName = NIL; + List *rangeCollationName = NIL; + Oid rangeCollation = InvalidOid; + regproc rangeAnalyze = InvalidOid; + Oid rangeSubtype = InvalidOid; + regproc rangeSubOpclass = InvalidOid; + regproc rangeCanonical = InvalidOid; + regproc rangeSubtypeDiff = InvalidOid; + + AclResult aclresult; + + /* Convert list of names to a name and namespace */ + typeNamespace = QualifiedNameGetCreationNamespace(stmt->typeName, + &typeName); + + /* Check we have creation rights in target namespace */ + aclresult = pg_namespace_aclcheck(typeNamespace, GetUserId(), ACL_CREATE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, ACL_KIND_NAMESPACE, + get_namespace_name(typeNamespace)); + + /* + * Look to see if type already exists (presumably as a shell; if not, + * TypeCreate will complain). + */ + typoid = GetSysCacheOid2(TYPENAMENSP, + CStringGetDatum(typeName), + ObjectIdGetDatum(typeNamespace)); + + /* + * If it's not a shell, see if it's an autogenerated array type, and if so + * rename it out of the way. + */ + if (OidIsValid(typoid) && get_typisdefined(typoid)) + { + if (moveArrayTypeName(typoid, typeName, typeNamespace)) + typoid = InvalidOid; + } + + /* + * If it doesn't exist, create it as a shell, so that the OID is known for + * use in the I/O function definitions. + */ + if (!OidIsValid(typoid)) + { + typoid = TypeShellMake(typeName, typeNamespace, GetUserId()); + /* Make new shell type visible for modification below */ + CommandCounterIncrement(); + + /* + * If the command was a parameterless CREATE TYPE, we're done --- + * creating the shell type was all we're supposed to do. + */ + if (parameters == NIL) + return; + } + else + { + /* Complain if dummy CREATE TYPE and entry already exists */ + if (parameters == NIL) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("type \"%s\" already exists", typeName))); + } + + foreach(lc, stmt->params) + { + DefElem *defel = lfirst(lc); + + if (pg_strcasecmp(defel->defname, "subtype") == 0) + { + if (OidIsValid(rangeSubtype)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + rangeSubtype = typenameTypeId(NULL, defGetTypeName(defel)); + } + else if (pg_strcasecmp(defel->defname, "canonical") == 0) + { + if (OidIsValid(rangeCanonical)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + rangeCanonical = findRangeCanonicalFunction( + defGetQualifiedName(defel), typoid); + } + else if (pg_strcasecmp(defel->defname, "collation") == 0) + { + if (rangeCollationName != NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + rangeCollationName = defGetQualifiedName(defel); + } + else if (pg_strcasecmp(defel->defname, "analyze") == 0) + { + if (OidIsValid(rangeAnalyze)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + rangeAnalyze = findTypeAnalyzeFunction(defGetQualifiedName(defel), + typoid); + } + else if (pg_strcasecmp(defel->defname, "subtype_opclass") == 0) + { + if (rangeSubOpclassName != NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + rangeSubOpclassName = defGetQualifiedName(defel); + } + else if (pg_strcasecmp(defel->defname, "subtype_diff") == 0) + { + if (rangeSubtypeDiffName != NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + rangeSubtypeDiffName = defGetQualifiedName(defel); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("type attribute \"%s\" not recognized", + defel->defname))); + continue; + } + } + + if (!OidIsValid(rangeSubtype)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("type attribute \"subtype\" is required"))); + + if (type_is_collatable(rangeSubtype)) + { + if (rangeCollationName == NIL) + rangeCollation = get_typcollation(rangeSubtype); + else + rangeCollation = get_collation_oid(rangeCollationName, false); + } + else if (rangeCollationName != NIL) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("range collation provided but subtype does not support collation"))); + + rangeSubOpclass = findRangeSubOpclass(rangeSubOpclassName, rangeSubtype); + + if (rangeSubtypeDiffName != NIL) + rangeSubtypeDiff = findRangeSubtypeDiffFunction( + rangeSubtypeDiffName, rangeSubtype); + + rangeArrayOid = AssignTypeArrayOid(); + + /* Create the pg_type entry */ + typoid = + TypeCreate(InvalidOid, /* no predetermined type OID */ + typeName, /* type name */ + typeNamespace, /* namespace */ + InvalidOid, /* relation oid (n/a here) */ + 0, /* relation kind (ditto) */ + GetUserId(), /* owner's ID */ + -1, /* internal size */ + TYPTYPE_RANGE, /* type-type (range type) */ + TYPCATEGORY_RANGE, /* type-category (range type) */ + false, /* range types are never preferred */ + DEFAULT_TYPDELIM, /* array element delimiter */ + F_RANGE_IN, /* input procedure */ + F_RANGE_OUT, /* output procedure */ + F_RANGE_RECV, /* receive procedure */ + F_RANGE_SEND, /* send procedure */ + InvalidOid, /* typmodin procedure - none */ + InvalidOid, /* typmodout procedure - none */ + rangeAnalyze, /* analyze procedure - default */ + InvalidOid, /* element type ID */ + false, /* this is not an array type */ + rangeArrayOid, /* array type we are about to create */ + InvalidOid, /* base type ID (only for domains) */ + NULL, /* never a default type value */ + NULL, /* binary default isn't sent either */ + false, /* never passed by value */ + 'i', /* int alignment */ + 'x', /* TOAST strategy always plain */ + -1, /* typMod (Domains only) */ + 0, /* Array dimensions of typbasetype */ + false, /* Type NOT NULL */ + InvalidOid); /* typcollation */ + + /* create the entry in pg_range */ + RangeCreate(typoid, rangeSubtype, rangeCollation, rangeSubOpclass, + rangeCanonical, rangeSubtypeDiff); + + /* + * Create the array type that goes with it. + */ + rangeArrayName = makeArrayTypeName(typeName, typeNamespace); + + TypeCreate(rangeArrayOid, /* force assignment of this type OID */ + rangeArrayName, /* type name */ + typeNamespace, /* namespace */ + InvalidOid, /* relation oid (n/a here) */ + 0, /* relation kind (ditto) */ + GetUserId(), /* owner's ID */ + -1, /* internal size (always varlena) */ + TYPTYPE_BASE, /* type-type (base type) */ + TYPCATEGORY_ARRAY, /* type-category (array) */ + false, /* array types are never preferred */ + DEFAULT_TYPDELIM, /* array element delimiter */ + F_ARRAY_IN, /* input procedure */ + F_ARRAY_OUT, /* output procedure */ + F_ARRAY_RECV, /* receive procedure */ + F_ARRAY_SEND, /* send procedure */ + InvalidOid, /* typmodin procedure - none */ + InvalidOid, /* typmodout procedure - none */ + InvalidOid, /* analyze procedure - default */ + typoid, /* element type ID */ + true, /* yes this is an array type */ + InvalidOid, /* no further array type */ + InvalidOid, /* base type ID */ + NULL, /* never a default type value */ + NULL, /* binary default isn't sent either */ + false, /* never passed by value */ + 'i', /* align 'i' */ + 'x', /* ARRAY is always toastable */ + -1, /* typMod (Domains only) */ + 0, /* Array dimensions of typbasetype */ + false, /* Type NOT NULL */ + InvalidOid); /* typcollation */ + + pfree(rangeArrayName); + + makeRangeConstructor(typeName, typeNamespace, typoid, rangeSubtype); +} + +/* + * Because there may exist several range types over one subtype, the range type + * can't be determined from the subtype. This means that constructors can't be + * polymorphic, and so we must generate a new constructor for every range type + * defined. + * + * We actually define 4 functions with 0 through 3 arguments. This is just to + * offer more convenience for the user. + */ +static void +makeRangeConstructor(char *name, Oid namespace, Oid rangeOid, Oid subtype) +{ + ObjectAddress referenced; + Oid constructorArgTypes[3]; + int i; + + referenced.classId = TypeRelationId; + referenced.objectId = rangeOid; + referenced.objectSubId = 0; + + constructorArgTypes[0] = subtype; + constructorArgTypes[1] = subtype; + constructorArgTypes[2] = TEXTOID; + + for (i = 0; i < 4; i++) + { + oidvector *constructorArgTypesVector; + ObjectAddress myself; + Oid procOid; + char *prosrc[4] = { "range_constructor0", + "range_constructor1", + "range_constructor2", + "range_constructor3"}; + + constructorArgTypesVector = buildoidvector(constructorArgTypes, i); + + procOid = ProcedureCreate( + name, /* name */ + namespace, /* namespace */ + false, /* replace */ + false, /* return set */ + rangeOid, /* return type */ + INTERNALlanguageId, /* language */ + F_FMGR_INTERNAL_VALIDATOR, /* language validator */ + prosrc[i], /* prosrc */ + NULL, /* probin */ + false, /* agg */ + false, /* window */ + false, /* security definer */ + false, /* strict */ + PROVOLATILE_IMMUTABLE, /* volatility */ + constructorArgTypesVector, /* param types */ + PointerGetDatum(NULL), /* allParameterTypes */ + PointerGetDatum(NULL), /* parameterModes */ + PointerGetDatum(NULL), /* parameterNames */ + NIL, /* parameterDefaults */ + PointerGetDatum(NULL), /* proconfig */ + 1.0, /* procost */ + 0.0); /* prorows */ + + /* + * Make the constructor internally-dependent on the range type so that + * the user doesn't have to treat them as separate objects. + */ + myself.classId = ProcedureRelationId; + myself.objectId = procOid; + myself.objectSubId = 0; + recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL); + } +} + /* * AlterEnum * Adds a new label to an existing enum. @@ -1449,6 +1789,103 @@ findTypeAnalyzeFunction(List *procname, Oid typeOid) return procOid; } +/* + * Find named btree opclass for subtype, or default btree opclass if + * opcname is NIL. This will be used for comparing values of subtype. + */ +static Oid +findRangeSubOpclass(List *opcname, Oid subtype) +{ + Oid opcid; + + if (opcname == NIL) + { + opcid = GetDefaultOpClass(subtype, BTREE_AM_OID); + if (!OidIsValid(opcid)) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("data type %s has no default operator class for access method \"btree\"", + format_type_be(subtype)), + errhint("You must specify an operator class for the data type or define a default operator class for the data type."))); + } + return opcid; + } + + opcid = get_opclass_oid(BTREE_AM_OID, opcname, false); + + return opcid; +} + +/* + * Used to find a range's 'canonical' function. + */ +static Oid +findRangeSubtypeDiffFunction(List *procname, Oid typeOid) +{ + Oid argList[2]; + Oid procOid; + + argList[0] = typeOid; + argList[1] = typeOid; + + procOid = LookupFuncName(procname, 2, argList, true); + + if (!OidIsValid(procOid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("function %s does not exist", + func_signature_string(procname, 2, NIL, argList)))); + + if (get_func_rettype(procOid) != FLOAT8OID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("range subtype diff function %s must return type \"float8\"", + func_signature_string(procname, 2, NIL, argList)))); + + if (func_volatile(procOid) != PROVOLATILE_IMMUTABLE) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("range subtype diff function %s must be immutable", + func_signature_string(procname, 2, NIL, argList)))); + + return procOid; +} + +/* + * Used to find a range's 'canonical' function. + */ +static Oid +findRangeCanonicalFunction(List *procname, Oid typeOid) +{ + Oid argList[1]; + Oid procOid; + + argList[0] = typeOid; + + procOid = LookupFuncName(procname, 1, argList, true); + + if (!OidIsValid(procOid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("function %s does not exist", + func_signature_string(procname, 1, NIL, argList)))); + + if (get_func_rettype(procOid) != typeOid) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("range canonical function %s must return range type", + NameListToString(procname)))); + + if (func_volatile(procOid) != PROVOLATILE_IMMUTABLE) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("range canonical function %s must be immutable", + func_signature_string(procname, 1, NIL, argList)))); + + return procOid; +} + /* * AssignTypeArrayOid * diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c index 398bc40c49..45ca5ec4c7 100644 --- a/src/backend/executor/functions.c +++ b/src/backend/executor/functions.c @@ -1352,6 +1352,7 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList, if (fn_typtype == TYPTYPE_BASE || fn_typtype == TYPTYPE_DOMAIN || fn_typtype == TYPTYPE_ENUM || + fn_typtype == TYPTYPE_RANGE || rettype == VOIDOID) { /* diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 24ac5295f6..63958c3afc 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -3055,6 +3055,17 @@ _copyCreateEnumStmt(CreateEnumStmt *from) return newnode; } +static CreateRangeStmt * +_copyCreateRangeStmt(CreateRangeStmt *from) +{ + CreateRangeStmt *newnode = makeNode(CreateRangeStmt); + + COPY_NODE_FIELD(typeName); + COPY_NODE_FIELD(params); + + return newnode; +} + static AlterEnumStmt * _copyAlterEnumStmt(AlterEnumStmt *from) { @@ -4297,6 +4308,9 @@ copyObject(void *from) case T_CreateEnumStmt: retval = _copyCreateEnumStmt(from); break; + case T_CreateRangeStmt: + retval = _copyCreateRangeStmt(from); + break; case T_AlterEnumStmt: retval = _copyAlterEnumStmt(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 4052a9a1fc..f3a34a12e2 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1438,6 +1438,15 @@ _equalCreateEnumStmt(CreateEnumStmt *a, CreateEnumStmt *b) return true; } +static bool +_equalCreateRangeStmt(CreateRangeStmt *a, CreateRangeStmt *b) +{ + COMPARE_NODE_FIELD(typeName); + COMPARE_NODE_FIELD(params); + + return true; +} + static bool _equalAlterEnumStmt(AlterEnumStmt *a, AlterEnumStmt *b) { @@ -2826,6 +2835,9 @@ equal(void *a, void *b) case T_CreateEnumStmt: retval = _equalCreateEnumStmt(a, b); break; + case T_CreateRangeStmt: + retval = _equalCreateRangeStmt(a, b); + break; case T_AlterEnumStmt: retval = _equalAlterEnumStmt(a, b); break; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index f0a4b0efa4..c135465fbd 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -4336,6 +4336,13 @@ DefineStmt: n->vals = $7; $$ = (Node *)n; } + | CREATE TYPE_P any_name AS RANGE definition + { + CreateRangeStmt *n = makeNode(CreateRangeStmt); + n->typeName = $3; + n->params = $6; + $$ = (Node *)n; + } | CREATE TEXT_P SEARCH PARSER any_name definition { DefineStmt *n = makeNode(DefineStmt); diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c index 127818abde..a461ac9aef 100644 --- a/src/backend/parser/parse_coerce.c +++ b/src/backend/parser/parse_coerce.c @@ -158,7 +158,8 @@ coerce_type(ParseState *pstate, Node *node, return node; } if (targetTypeId == ANYARRAYOID || - targetTypeId == ANYENUMOID) + targetTypeId == ANYENUMOID || + targetTypeId == ANYRANGEOID) { /* * Assume can_coerce_type verified that implicit coercion is okay. @@ -1275,9 +1276,11 @@ coerce_to_common_type(ParseState *pstate, Node *node, * 1) All arguments declared ANYARRAY must have matching datatypes, * and must in fact be varlena arrays. * 2) All arguments declared ANYELEMENT must have matching datatypes. - * 3) If there are arguments of both ANYELEMENT and ANYARRAY, make sure - * the actual ANYELEMENT datatype is in fact the element type for - * the actual ANYARRAY datatype. + * 3) If there are arguments of both ANYELEMENT and ANYARRAY, make sure the + * actual ANYELEMENT datatype is in fact the element type for the actual + * ANYARRAY datatype. Similarly, if there are arguments of both ANYELEMENT + * and ANYRANGE, make sure the actual ANYELEMENT datatype is in fact the + * subtype for the actual ANYRANGE type. * 4) ANYENUM is treated the same as ANYELEMENT except that if it is used * (alone or in combination with plain ANYELEMENT), we add the extra * condition that the ANYELEMENT type must be an enum. @@ -1311,6 +1314,8 @@ check_generic_type_consistency(Oid *actual_arg_types, Oid elem_typeid = InvalidOid; Oid array_typeid = InvalidOid; Oid array_typelem; + Oid range_typeid = InvalidOid; + Oid range_typelem; bool have_anyelement = false; bool have_anynonarray = false; bool have_anyenum = false; @@ -1348,6 +1353,15 @@ check_generic_type_consistency(Oid *actual_arg_types, return false; array_typeid = actual_type; } + else if (decl_type == ANYRANGEOID) + { + if (actual_type == UNKNOWNOID) + continue; + actual_type = getBaseType(actual_type); + if (OidIsValid(range_typeid) && actual_type != range_typeid) + return false; + range_typeid = actual_type; + } } /* Get the element type based on the array type, if we have one */ @@ -1393,6 +1407,27 @@ check_generic_type_consistency(Oid *actual_arg_types, return false; } + /* Get the element type based on the range type, if we have one */ + if (OidIsValid(range_typeid)) + { + range_typelem = get_range_subtype(range_typeid); + if (!OidIsValid(range_typelem)) + return false; /* should be a range, but isn't */ + + if (!OidIsValid(elem_typeid)) + { + /* + * if we don't have an element type yet, use the one we just got + */ + elem_typeid = range_typelem; + } + else if (range_typelem != elem_typeid) + { + /* otherwise, they better match */ + return false; + } + } + /* Looks valid */ return true; } @@ -1416,23 +1451,28 @@ check_generic_type_consistency(Oid *actual_arg_types, * if it is declared as a polymorphic type: * * 1) If return type is ANYARRAY, and any argument is ANYARRAY, use the - * argument's actual type as the function's return type. - * 2) If return type is ANYARRAY, no argument is ANYARRAY, but any argument - * is ANYELEMENT, use the actual type of the argument to determine - * the function's return type, i.e. the element type's corresponding - * array type. + * argument's actual type as the function's return type. Similarly, if + * return type is ANYRANGE, and any argument is ANYRANGE, use the argument's + * actual type as the function's return type. + * 2) If return type is ANYARRAY, no argument is ANYARRAY, but any argument is + * ANYELEMENT, use the actual type of the argument to determine the + * function's return type, i.e. the element type's corresponding array + * type. Note: similar behavior does not exist for ANYRANGE, because it's + * impossble to determine the range type from the subtype alone.\ * 3) If return type is ANYARRAY, no argument is ANYARRAY or ANYELEMENT, - * generate an ERROR. This condition is prevented by CREATE FUNCTION - * and is therefore not expected here. + * generate an ERROR. Similarly, if the return type is ANYRANGE, and no + * argument is ANYRANGE or ANYELEMENT, generate an error. These conditions + * are prevented by CREATE FUNCTION and is therefore not expected here. * 4) If return type is ANYELEMENT, and any argument is ANYELEMENT, use the * argument's actual type as the function's return type. - * 5) If return type is ANYELEMENT, no argument is ANYELEMENT, but any - * argument is ANYARRAY, use the actual type of the argument to determine - * the function's return type, i.e. the array type's corresponding - * element type. - * 6) If return type is ANYELEMENT, no argument is ANYARRAY or ANYELEMENT, - * generate an ERROR. This condition is prevented by CREATE FUNCTION - * and is therefore not expected here. + * 5) If return type is ANYELEMENT, no argument is ANYELEMENT, but any argument + * is ANYARRAY or ANYRANGE, use the actual type of the argument to determine + * the function's return type; i.e. the array type's corresponding element + * type or the range type's corresponding subtype (or both, in which case + * they must match). + * 6) If return type is ANYELEMENT, no argument is ANYARRAY, ANYRANGE, or + * ANYELEMENT, generate an ERROR. This condition is prevented by CREATE + * FUNCTION and is therefore not expected here. * 7) ANYENUM is treated the same as ANYELEMENT except that if it is used * (alone or in combination with plain ANYELEMENT), we add the extra * condition that the ANYELEMENT type must be an enum. @@ -1441,9 +1481,10 @@ check_generic_type_consistency(Oid *actual_arg_types, * (This is a no-op if used in combination with ANYARRAY or ANYENUM, but * is an extra restriction if not.) * - * Domains over arrays match ANYARRAY arguments, and are immediately flattened - * to their base type. (In particular, if the return type is also ANYARRAY, - * we'll set it to the base type not the domain type.) + * Domains over arrays or ranges match ANYARRAY or ANYRANGE arguments, + * respectively, and are immediately flattened to their base type. (In + * particular, if the return type is also ANYARRAY or ANYRANGE, we'll set it to + * the base type not the domain type.) * * When allow_poly is false, we are not expecting any of the actual_arg_types * to be polymorphic, and we should not return a polymorphic result type @@ -1473,7 +1514,9 @@ enforce_generic_type_consistency(Oid *actual_arg_types, bool have_unknowns = false; Oid elem_typeid = InvalidOid; Oid array_typeid = InvalidOid; + Oid range_typeid = InvalidOid; Oid array_typelem; + Oid range_typelem; bool have_anyelement = (rettype == ANYELEMENTOID || rettype == ANYNONARRAYOID || rettype == ANYENUMOID); @@ -1534,6 +1577,26 @@ enforce_generic_type_consistency(Oid *actual_arg_types, format_type_be(actual_type)))); array_typeid = actual_type; } + else if (decl_type == ANYRANGEOID) + { + have_generics = true; + if (actual_type == UNKNOWNOID) + { + have_unknowns = true; + continue; + } + if (allow_poly && decl_type == actual_type) + continue; /* no new information here */ + actual_type = getBaseType(actual_type); /* flatten domains */ + if (OidIsValid(range_typeid) && actual_type != range_typeid) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("arguments declared \"anyrange\" are not all alike"), + errdetail("%s versus %s", + format_type_be(range_typeid), + format_type_be(actual_type)))); + range_typeid = actual_type; + } } /* @@ -1579,6 +1642,34 @@ enforce_generic_type_consistency(Oid *actual_arg_types, format_type_be(elem_typeid)))); } } + /* Get the element type based on the range type, if we have one */ + else if (OidIsValid(range_typeid)) + { + range_typelem = get_range_subtype(range_typeid); + if (!OidIsValid(range_typelem)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("argument declared \"anyrange\" is not a range but type %s", + format_type_be(range_typeid)))); + + if (!OidIsValid(elem_typeid)) + { + /* + * if we don't have an element type yet, use the one we just got + */ + elem_typeid = range_typelem; + } + else if (range_typelem != elem_typeid) + { + /* otherwise, they better match */ + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("argument declared \"anyrange\" is not consistent with argument declared \"anyelement\""), + errdetail("%s versus %s", + format_type_be(range_typeid), + format_type_be(elem_typeid)))); + } + } else if (!OidIsValid(elem_typeid)) { if (allow_poly) @@ -1645,6 +1736,17 @@ enforce_generic_type_consistency(Oid *actual_arg_types, } declared_arg_types[j] = array_typeid; } + else if (decl_type == ANYRANGEOID) + { + if (!OidIsValid(range_typeid)) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("could not find range type for data type %s", + format_type_be(elem_typeid)))); + } + declared_arg_types[j] = range_typeid; + } } } @@ -1663,6 +1765,19 @@ enforce_generic_type_consistency(Oid *actual_arg_types, return array_typeid; } + /* if we return ANYRANGE use the appropriate argument type */ + if (rettype == ANYRANGEOID) + { + if (!OidIsValid(range_typeid)) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("could not find range type for data type %s", + format_type_be(elem_typeid)))); + } + return range_typeid; + } + /* if we return ANYELEMENT use the appropriate argument type */ if (rettype == ANYELEMENTOID || rettype == ANYNONARRAYOID || @@ -1711,7 +1826,8 @@ resolve_generic_type(Oid declared_type, } else if (context_declared_type == ANYELEMENTOID || context_declared_type == ANYNONARRAYOID || - context_declared_type == ANYENUMOID) + context_declared_type == ANYENUMOID || + context_declared_type == ANYRANGEOID) { /* Use the array type corresponding to actual type */ Oid array_typeid = get_array_type(context_actual_type); @@ -1726,7 +1842,8 @@ resolve_generic_type(Oid declared_type, } else if (declared_type == ANYELEMENTOID || declared_type == ANYNONARRAYOID || - declared_type == ANYENUMOID) + declared_type == ANYENUMOID || + declared_type == ANYRANGEOID) { if (context_declared_type == ANYARRAYOID) { @@ -1741,6 +1858,18 @@ resolve_generic_type(Oid declared_type, format_type_be(context_base_type)))); return array_typelem; } + else if (context_declared_type == ANYRANGEOID) + { + /* Use the element type corresponding to actual type */ + Oid range_typelem = get_range_subtype(context_actual_type); + + if (!OidIsValid(range_typelem)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("argument declared \"anyrange\" is not a range but type %s", + format_type_be(context_actual_type)))); + return range_typelem; + } else if (context_declared_type == ANYELEMENTOID || context_declared_type == ANYNONARRAYOID || context_declared_type == ANYENUMOID) @@ -1854,6 +1983,11 @@ IsBinaryCoercible(Oid srctype, Oid targettype) if (type_is_enum(srctype)) return true; + /* Also accept any range type as coercible to ANYRANGE */ + if (targettype == ANYRANGEOID) + if (type_is_range(srctype)) + return true; + /* Also accept any composite type as coercible to RECORD */ if (targettype == RECORDOID) if (ISCOMPLEX(srctype)) diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index a74019a0d6..5b0633398c 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -202,6 +202,7 @@ check_xact_readonly(Node *parsetree) case T_CreateTrigStmt: case T_CompositeTypeStmt: case T_CreateEnumStmt: + case T_CreateRangeStmt: case T_AlterEnumStmt: case T_ViewStmt: case T_DropCastStmt: @@ -870,6 +871,10 @@ standard_ProcessUtility(Node *parsetree, DefineEnum((CreateEnumStmt *) parsetree); break; + case T_CreateRangeStmt: + DefineRange((CreateRangeStmt *) parsetree); + break; + case T_AlterEnumStmt: /* ALTER TYPE (enum) */ /* @@ -1854,6 +1859,10 @@ CreateCommandTag(Node *parsetree) tag = "CREATE TYPE"; break; + case T_CreateRangeStmt: + tag = "CREATE TYPE"; + break; + case T_AlterEnumStmt: tag = "ALTER TYPE"; break; @@ -2401,6 +2410,10 @@ GetCommandLogLevel(Node *parsetree) lev = LOGSTMT_DDL; break; + case T_CreateRangeStmt: + lev = LOGSTMT_DDL; + break; + case T_AlterEnumStmt: lev = LOGSTMT_DDL; break; diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile index ce28abd9fe..5f968b011f 100644 --- a/src/backend/utils/adt/Makefile +++ b/src/backend/utils/adt/Makefile @@ -20,8 +20,8 @@ OBJS = acl.o arrayfuncs.o array_userfuncs.o arrayutils.o bool.o \ enum.o float.o format_type.o \ geo_ops.o geo_selfuncs.o int.o int8.o like.o lockfuncs.o \ misc.o nabstime.o name.o numeric.o numutils.o \ - oid.o oracle_compat.o pseudotypes.o rowtypes.o \ - regexp.o regproc.o ruleutils.o selfuncs.o \ + oid.o oracle_compat.o pseudotypes.o rangetypes.o rangetypes_gist.o \ + rowtypes.o regexp.o regproc.o ruleutils.o selfuncs.o \ tid.o timestamp.o varbit.o varchar.o varlena.o version.o xid.o \ network.o mac.o inet_cidr_ntop.o inet_net_pton.o \ ri_triggers.o pg_lzcompress.o pg_locale.o formatting.o \ diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c index b06faf0c72..271b57a830 100644 --- a/src/backend/utils/adt/date.c +++ b/src/backend/utils/adt/date.c @@ -889,7 +889,6 @@ date_timestamp(PG_FUNCTION_ARGS) PG_RETURN_TIMESTAMP(result); } - /* timestamp_date() * Convert timestamp to date data type. */ diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c index ddb1bd2b71..3b78c36a7e 100644 --- a/src/backend/utils/adt/pseudotypes.c +++ b/src/backend/utils/adt/pseudotypes.c @@ -25,6 +25,7 @@ #include "libpq/pqformat.h" #include "utils/array.h" #include "utils/builtins.h" +#include "utils/rangetypes.h" /* @@ -187,6 +188,29 @@ anyenum_out(PG_FUNCTION_ARGS) return enum_out(fcinfo); } +/* + * anyrange_in - input routine for pseudo-type ANYRANGE. + */ +Datum +anyrange_in(PG_FUNCTION_ARGS) +{ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot accept a value of type anyrange"))); + + PG_RETURN_VOID(); /* keep compiler quiet */ +} + +/* + * anyrange_out - output routine for pseudo-type ANYRANGE. + * + * We may as well allow this, since range_out will in fact work. + */ +Datum +anyrange_out(PG_FUNCTION_ARGS) +{ + return range_out(fcinfo); +} /* * void_in - input routine for pseudo-type VOID. diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c new file mode 100644 index 0000000000..6327d33f3a --- /dev/null +++ b/src/backend/utils/adt/rangetypes.c @@ -0,0 +1,2153 @@ +/*------------------------------------------------------------------------- + * + * rangetypes.c + * I/O functions, operators, and support functions for range types + * + * Copyright (c) 2006-2011, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/utils/adt/rangetypes.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/hash.h" +#include "access/nbtree.h" +#include "catalog/pg_opclass.h" +#include "catalog/pg_range.h" +#include "catalog/pg_type.h" +#include "fmgr.h" +#include "lib/stringinfo.h" +#include "libpq/pqformat.h" +#include "utils/builtins.h" +#include "utils/date.h" +#include "utils/fmgroids.h" +#include "utils/int8.h" +#include "utils/lsyscache.h" +#include "utils/rangetypes.h" +#include "utils/syscache.h" +#include "utils/timestamp.h" +#include "utils/typcache.h" + +#define TYPE_IS_PACKABLE(typlen, typstorage) \ + (typlen == -1 && typstorage != 'p') + +/* flags */ +#define RANGE_EMPTY 0x01 +#define RANGE_LB_INC 0x02 +#define RANGE_LB_NULL 0x04 /* NOT USED */ +#define RANGE_LB_INF 0x08 +#define RANGE_UB_INC 0x10 +#define RANGE_UB_NULL 0x20 /* NOT USED */ +#define RANGE_UB_INF 0x40 + +#define RANGE_HAS_LBOUND(flags) (!(flags & (RANGE_EMPTY | \ + RANGE_LB_NULL | \ + RANGE_LB_INF))) + +#define RANGE_HAS_UBOUND(flags) (!(flags & (RANGE_EMPTY | \ + RANGE_UB_NULL | \ + RANGE_UB_INF))) + +#define RANGE_EMPTY_LITERAL "empty" + +static void range_parse(char *input_str, char *flags, char **lbound_str, + char **ubound_str); +static char *range_parse_bound(char *string, char *ptr, char **bound_str, + bool *infinite); +static char *range_deparse(char flags, char *lbound_str, char *ubound_str); +static char *range_bound_escape(char *in_str); +static bool range_contains_internal(FunctionCallInfo fcinfo, RangeType *r1, + RangeType *r2); +static Size datum_compute_size(Size sz, Datum datum, bool typbyval, + char typalign, int16 typlen, char typstorage); +static Pointer datum_write(Pointer ptr, Datum datum, bool typbyval, + char typalign, int16 typlen, char typstorage); + +/* + *---------------------------------------------------------- + * I/O FUNCTIONS + *---------------------------------------------------------- + */ + +Datum +range_in(PG_FUNCTION_ARGS) +{ + char *input_str = PG_GETARG_CSTRING(0); + Oid rngtypoid = PG_GETARG_OID(1); + Oid typmod = PG_GETARG_INT32(2); + + char flags; + Datum range; + char *lbound_str; + char *ubound_str; + + regproc subInput; + FmgrInfo subInputFn; + Oid ioParam; + + RangeTypeInfo rngtypinfo; + RangeBound lower; + RangeBound upper; + + if (rngtypoid == ANYRANGEOID) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot accept a value of type anyrange"))); + + range_gettypinfo(fcinfo, rngtypoid, &rngtypinfo); + + /* parse */ + range_parse(input_str, &flags, &lbound_str, &ubound_str); + + /* input */ + getTypeInputInfo(rngtypinfo.subtype, &subInput, &ioParam); + fmgr_info(subInput, &subInputFn); + + lower.rngtypid = rngtypoid; + lower.infinite = (flags & RANGE_LB_INF) != 0; + lower.inclusive = (flags & RANGE_LB_INC) != 0; + lower.lower = true; + upper.rngtypid = rngtypoid; + upper.infinite = (flags & RANGE_UB_INF) != 0; + upper.inclusive = (flags & RANGE_UB_INC) != 0; + upper.lower = false; + + if (RANGE_HAS_LBOUND(flags)) + lower.val = InputFunctionCall(&subInputFn, lbound_str, + ioParam, typmod); + if (RANGE_HAS_UBOUND(flags)) + upper.val = InputFunctionCall(&subInputFn, ubound_str, + ioParam, typmod); + + /* serialize and canonicalize */ + range = make_range(fcinfo, &lower, &upper, flags & RANGE_EMPTY); + + PG_RETURN_RANGE(range); +} + +Datum +range_out(PG_FUNCTION_ARGS) +{ + RangeType *range = PG_GETARG_RANGE(0); + + regproc subOutput; + FmgrInfo subOutputFn; + bool isVarlena; + + char flags = 0; + char *lbound_str = NULL; + char *ubound_str = NULL; + char *output_str; + + bool empty; + + RangeTypeInfo rngtypinfo; + RangeBound lower; + RangeBound upper; + + /* deserialize */ + range_deserialize(fcinfo, range, &lower, &upper, &empty); + + if (lower.rngtypid != upper.rngtypid) + elog(ERROR, "range types do not match"); + + range_gettypinfo(fcinfo, lower.rngtypid, &rngtypinfo); + + if (empty) + flags |= RANGE_EMPTY; + + flags |= (lower.inclusive) ? RANGE_LB_INC : 0; + flags |= (lower.infinite) ? RANGE_LB_INF : 0; + flags |= (upper.inclusive) ? RANGE_UB_INC : 0; + flags |= (upper.infinite) ? RANGE_UB_INF : 0; + + /* output */ + getTypeOutputInfo(rngtypinfo.subtype, &subOutput, &isVarlena); + fmgr_info(subOutput, &subOutputFn); + + if (RANGE_HAS_LBOUND(flags)) + lbound_str = OutputFunctionCall(&subOutputFn, lower.val); + if (RANGE_HAS_UBOUND(flags)) + ubound_str = OutputFunctionCall(&subOutputFn, upper.val); + + /* deparse */ + output_str = range_deparse(flags, lbound_str, ubound_str); + + PG_RETURN_CSTRING(output_str); +} + +/* + * Binary representation: The first byte is the flags, then 4 bytes are the + * range type Oid, then the lower bound (if present) then the upper bound (if + * present). Each bound is represented by a 4-byte length header and the binary + * representation of that bound (as returned by a call to the send function for + * the subtype). + */ + +Datum +range_recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + Oid rngtypid = PG_GETARG_OID(1); + int32 typmod = PG_GETARG_INT32(2); + Oid subrecv; + Oid ioparam; + Datum range; + char flags; + RangeBound lower; + RangeBound upper; + + RangeTypeInfo rngtypinfo; + + flags = (unsigned char) pq_getmsgbyte(buf); + + range_gettypinfo(fcinfo, rngtypid, &rngtypinfo); + + getTypeBinaryInputInfo(rngtypinfo.subtype, &subrecv, &ioparam); + + if (RANGE_HAS_LBOUND(flags)) + { + uint32 bound_len = pq_getmsgint(buf, 4); + const char *bound_data = pq_getmsgbytes(buf, bound_len); + StringInfoData bound_buf; + + initStringInfo(&bound_buf); + appendBinaryStringInfo(&bound_buf, bound_data, bound_len); + + lower.val = OidReceiveFunctionCall(subrecv, + &bound_buf, + ioparam, + typmod); + pfree(bound_buf.data); + } + else + lower.val = (Datum) 0; + + if (RANGE_HAS_UBOUND(flags)) + { + uint32 bound_len = pq_getmsgint(buf, 4); + const char *bound_data = pq_getmsgbytes(buf, bound_len); + StringInfoData bound_buf; + + initStringInfo(&bound_buf); + appendBinaryStringInfo(&bound_buf, bound_data, bound_len); + + upper.val = OidReceiveFunctionCall(subrecv, + &bound_buf, + ioparam, + typmod); + pfree(bound_buf.data); + } + else + upper.val = (Datum) 0; + + pq_getmsgend(buf); + + lower.rngtypid = rngtypid; + lower.infinite = (flags & RANGE_LB_INF) != 0; + lower.inclusive = (flags & RANGE_LB_INC) != 0; + lower.lower = true; + upper.rngtypid = rngtypid; + upper.infinite = (flags & RANGE_UB_INF) != 0; + upper.inclusive = (flags & RANGE_UB_INC) != 0; + upper.lower = false; + + /* serialize and canonicalize */ + range = make_range(fcinfo, &lower, &upper, flags & RANGE_EMPTY); + + /* + * XXX if the subtype is pass-by-val, we should pfree the upper and + * lower bounds here. + */ + + PG_RETURN_RANGE(range); +} + +Datum +range_send(PG_FUNCTION_ARGS) +{ + RangeType *range = PG_GETARG_RANGE(0); + StringInfo buf = makeStringInfo(); + char flags = 0; + RangeBound lower; + RangeBound upper; + bool empty; + Oid subsend; + bool typIsVarlena; + + RangeTypeInfo rngtypinfo; + + pq_begintypsend(buf); + + range_deserialize(fcinfo, range, &lower, &upper, &empty); + + if (empty) + flags |= RANGE_EMPTY; + + flags |= (lower.inclusive) ? RANGE_LB_INC : 0; + flags |= (lower.infinite) ? RANGE_LB_INF : 0; + flags |= (upper.inclusive) ? RANGE_UB_INC : 0; + flags |= (upper.infinite) ? RANGE_UB_INF : 0; + + range_gettypinfo(fcinfo, lower.rngtypid, &rngtypinfo); + + getTypeBinaryOutputInfo(rngtypinfo.subtype, + &subsend, &typIsVarlena); + + pq_sendbyte(buf, flags); + + if (RANGE_HAS_LBOUND(flags)) + { + Datum bound = PointerGetDatum( + OidSendFunctionCall(subsend, lower.val)); + uint32 bound_len = VARSIZE(bound) - VARHDRSZ; + char *bound_data = VARDATA(bound); + + pq_sendint(buf, bound_len, 4); + pq_sendbytes(buf, bound_data, bound_len); + } + + if (RANGE_HAS_UBOUND(flags)) + { + Datum bound = PointerGetDatum( + OidSendFunctionCall(subsend, upper.val)); + uint32 bound_len = VARSIZE(bound) - VARHDRSZ; + char *bound_data = VARDATA(bound); + + pq_sendint(buf, bound_len, 4); + pq_sendbytes(buf, bound_data, bound_len); + } + + PG_RETURN_BYTEA_P(pq_endtypsend(buf)); +} + + +/* + *---------------------------------------------------------- + * GENERIC FUNCTIONS + *---------------------------------------------------------- + */ + +Datum +range_constructor0(PG_FUNCTION_ARGS) +{ + Oid rngtypid = get_fn_expr_rettype(fcinfo->flinfo); + RangeType *range; + RangeBound lower; + RangeBound upper; + + lower.rngtypid = rngtypid; + lower.val = (Datum) 0; + lower.inclusive = false; + lower.infinite = false; + lower.lower = true; + + upper.rngtypid = rngtypid; + upper.val = (Datum) 0; + upper.inclusive = false; + upper.infinite = false; + upper.lower = false; + + range = DatumGetRangeType(make_range(fcinfo, &lower, &upper, true)); + + PG_RETURN_RANGE(range); +} + +Datum +range_constructor1(PG_FUNCTION_ARGS) +{ + Datum arg1 = PG_GETARG_DATUM(0); + Oid rngtypid = get_fn_expr_rettype(fcinfo->flinfo); + RangeType *range; + RangeBound lower; + RangeBound upper; + + if (PG_ARGISNULL(0)) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("argument must not be NULL"))); + + lower.rngtypid = rngtypid; + lower.val = arg1; + lower.inclusive = true; + lower.infinite = false; + lower.lower = true; + + upper.rngtypid = rngtypid; + upper.val = arg1; + upper.inclusive = true; + upper.infinite = false; + upper.lower = false; + + range = DatumGetRangeType(make_range(fcinfo, &lower, &upper, false)); + + PG_RETURN_RANGE(range); +} + +Datum +range_constructor2(PG_FUNCTION_ARGS) +{ + Datum arg1 = PG_GETARG_DATUM(0); + Datum arg2 = PG_GETARG_DATUM(1); + Oid rngtypid = get_fn_expr_rettype(fcinfo->flinfo); + RangeType *range; + RangeBound lower; + RangeBound upper; + char flags; + + flags = range_parse_flags(RANGE_DEFAULT_FLAGS); + + lower.rngtypid = rngtypid; + lower.val = PG_ARGISNULL(0) ? (Datum)0 : arg1; + lower.inclusive = flags & RANGE_LB_INC; + lower.infinite = PG_ARGISNULL(0); + lower.lower = true; + + upper.rngtypid = rngtypid; + upper.val = PG_ARGISNULL(1) ? (Datum)0 : arg2; + upper.inclusive = flags & RANGE_UB_INC; + upper.infinite = PG_ARGISNULL(1); + upper.lower = false; + + range = DatumGetRangeType(make_range(fcinfo, &lower, &upper, false)); + + PG_RETURN_RANGE(range); +} + +Datum +range_constructor3(PG_FUNCTION_ARGS) +{ + Datum arg1 = PG_GETARG_DATUM(0); + Datum arg2 = PG_GETARG_DATUM(1); + Oid rngtypid = get_fn_expr_rettype(fcinfo->flinfo); + RangeType *range; + RangeBound lower; + RangeBound upper; + char flags; + + if (PG_ARGISNULL(2)) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("flags argument must not be NULL"))); + flags = range_parse_flags(text_to_cstring(PG_GETARG_TEXT_P(2))); + + lower.rngtypid = rngtypid; + lower.val = PG_ARGISNULL(0) ? (Datum)0 : arg1; + lower.inclusive = flags & RANGE_LB_INC; + lower.infinite = PG_ARGISNULL(0); + lower.lower = true; + + upper.rngtypid = rngtypid; + upper.val = PG_ARGISNULL(1) ? (Datum)0 : arg2; + upper.inclusive = flags & RANGE_UB_INC; + upper.infinite = PG_ARGISNULL(1); + upper.lower = false; + + range = DatumGetRangeType(make_range(fcinfo, &lower, &upper, false)); + + PG_RETURN_RANGE(range); +} + +/* range -> subtype */ +Datum +range_lower(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(0); + RangeBound lower; + RangeBound upper; + bool empty; + + range_deserialize(fcinfo, r1, &lower, &upper, &empty); + + if (empty) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("empty range has no lower bound"))); + if (lower.infinite) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("range lower bound is infinite"))); + + PG_RETURN_DATUM(lower.val); +} + +Datum +range_upper(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(0); + RangeBound lower; + RangeBound upper; + bool empty; + + range_deserialize(fcinfo, r1, &lower, &upper, &empty); + + if (empty) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("empty range has no upper bound"))); + if (upper.infinite) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("range upper bound is infinite"))); + + PG_RETURN_DATUM(upper.val); +} + + +/* range -> bool */ +Datum +range_empty(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(0); + RangeBound lower; + RangeBound upper; + bool empty; + + range_deserialize(fcinfo, r1, &lower, &upper, &empty); + + PG_RETURN_BOOL(empty); +} + +Datum +range_lower_inc(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(0); + RangeBound lower; + RangeBound upper; + bool empty; + + range_deserialize(fcinfo, r1, &lower, &upper, &empty); + + PG_RETURN_BOOL(lower.inclusive); +} + +Datum +range_upper_inc(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(0); + RangeBound lower; + RangeBound upper; + bool empty; + + range_deserialize(fcinfo, r1, &lower, &upper, &empty); + + PG_RETURN_BOOL(upper.inclusive); +} + +Datum +range_lower_inf(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(0); + RangeBound lower; + RangeBound upper; + bool empty; + + range_deserialize(fcinfo, r1, &lower, &upper, &empty); + + PG_RETURN_BOOL(lower.infinite); +} + +Datum +range_upper_inf(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(0); + RangeBound lower; + RangeBound upper; + bool empty; + + range_deserialize(fcinfo, r1, &lower, &upper, &empty); + + PG_RETURN_BOOL(upper.infinite); +} + + +/* range, range -> bool */ +Datum +range_eq(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(0); + RangeType *r2 = PG_GETARG_RANGE(1); + + RangeBound lower1, lower2; + RangeBound upper1, upper2; + bool empty1, empty2; + + range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); + range_deserialize(fcinfo, r2, &lower2, &upper2, &empty2); + + if (lower1.rngtypid != upper1.rngtypid || + lower1.rngtypid != lower2.rngtypid || + lower1.rngtypid != upper2.rngtypid) + elog(ERROR, "range types do not match"); + + if (empty1 && empty2) + PG_RETURN_BOOL(true); + if (empty1 != empty2) + PG_RETURN_BOOL(false); + + if (range_cmp_bounds(fcinfo, &lower1, &lower2) != 0) + PG_RETURN_BOOL(false); + + if (range_cmp_bounds(fcinfo, &upper1, &upper2) != 0) + PG_RETURN_BOOL(false); + + PG_RETURN_BOOL(true); +} + +Datum +range_ne(PG_FUNCTION_ARGS) +{ + bool eq = DatumGetBool(range_eq(fcinfo)); + + PG_RETURN_BOOL(!eq); +} + +Datum +range_contains_elem(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(0); + RangeType *r2; + Datum val = PG_GETARG_DATUM(1); + + RangeBound lower1, lower2; + RangeBound upper1, upper2; + bool empty1; + + range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); + + lower2.rngtypid = lower1.rngtypid; + lower2.inclusive = true; + lower2.infinite = false; + lower2.lower = true; + lower2.val = val; + + upper2.rngtypid = lower1.rngtypid; + upper2.inclusive = true; + upper2.infinite = false; + upper2.lower = false; + upper2.val = val; + + r2 = DatumGetRangeType(make_range(fcinfo, &lower2, &upper2, false)); + + PG_RETURN_BOOL(range_contains_internal(fcinfo, r1, r2)); +} + +Datum +range_contains(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(0); + RangeType *r2 = PG_GETARG_RANGE(1); + + PG_RETURN_BOOL(range_contains_internal(fcinfo, r1, r2)); +} + +Datum +elem_contained_by_range(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(1); + RangeType *r2; + Datum val = PG_GETARG_DATUM(0); + + RangeBound lower1, lower2; + RangeBound upper1, upper2; + bool empty1; + + range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); + + lower2.rngtypid = lower1.rngtypid; + lower2.inclusive = true; + lower2.infinite = false; + lower2.lower = true; + lower2.val = val; + + upper2.rngtypid = lower1.rngtypid; + upper2.inclusive = true; + upper2.infinite = false; + upper2.lower = false; + upper2.val = val; + + r2 = DatumGetRangeType(make_range(fcinfo, &lower2, &upper2, false)); + + PG_RETURN_BOOL(range_contains_internal(fcinfo, r1, r2)); +} + +Datum +range_contained_by(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(0); + RangeType *r2 = PG_GETARG_RANGE(1); + + PG_RETURN_BOOL(range_contains_internal(fcinfo, r2, r1)); +} + +Datum +range_before(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(0); + RangeType *r2 = PG_GETARG_RANGE(1); + + RangeBound lower1, lower2; + RangeBound upper1, upper2; + bool empty1, empty2; + + range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); + range_deserialize(fcinfo, r2, &lower2, &upper2, &empty2); + + if (lower1.rngtypid != upper1.rngtypid || + lower1.rngtypid != lower2.rngtypid || + lower1.rngtypid != upper2.rngtypid) + elog(ERROR, "range types do not match"); + + if (empty1 || empty2) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("input range is empty"))); + + if (range_cmp_bounds(fcinfo, &upper1, &lower2) < 0) + PG_RETURN_BOOL(true); + else + PG_RETURN_BOOL(false); +} + +Datum +range_after(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(0); + RangeType *r2 = PG_GETARG_RANGE(1); + + RangeBound lower1, lower2; + RangeBound upper1, upper2; + bool empty1, empty2; + + range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); + range_deserialize(fcinfo, r2, &lower2, &upper2, &empty2); + + if (lower1.rngtypid != upper1.rngtypid || + lower1.rngtypid != lower2.rngtypid || + lower1.rngtypid != upper2.rngtypid) + elog(ERROR, "range types do not match"); + + if (empty1 || empty2) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("input range is empty"))); + + if (range_cmp_bounds(fcinfo, &lower1, &upper2) > 0) + PG_RETURN_BOOL(true); + else + PG_RETURN_BOOL(false); +} + +Datum range_adjacent(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(0); + RangeType *r2 = PG_GETARG_RANGE(1); + + RangeTypeInfo rngtypinfo; + + RangeBound lower1, lower2; + RangeBound upper1, upper2; + bool empty1, empty2; + + range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); + range_deserialize(fcinfo, r2, &lower2, &upper2, &empty2); + + if (lower1.rngtypid != upper1.rngtypid || + lower1.rngtypid != lower2.rngtypid || + lower1.rngtypid != upper2.rngtypid) + elog(ERROR, "range types do not match"); + + if (empty1 || empty2) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("undefined for empty ranges"))); + + /* + * For two ranges to be adjacent, the lower boundary of one range + * has to match the upper boundary of the other. However, the + * inclusivity of those two boundaries must also be different. + * + * The semantics for range_cmp_bounds aren't quite what we need + * here, so we do the comparison more directly. + */ + + range_gettypinfo(fcinfo, lower1.rngtypid, &rngtypinfo); + + if (lower1.inclusive != upper2.inclusive) + { + if (DatumGetInt32(FunctionCall2Coll(&rngtypinfo.cmpFn, + rngtypinfo.collation, + lower1.val, upper2.val)) == 0) + PG_RETURN_BOOL(true); + } + + if (upper1.inclusive != lower2.inclusive) + { + if (DatumGetInt32(FunctionCall2Coll(&rngtypinfo.cmpFn, + rngtypinfo.collation, + upper1.val, lower2.val)) == 0) + PG_RETURN_BOOL(true); + } + + PG_RETURN_BOOL(false); +} + +Datum +range_overlaps(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(0); + RangeType *r2 = PG_GETARG_RANGE(1); + + RangeBound lower1, lower2; + RangeBound upper1, upper2; + bool empty1, empty2; + + range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); + range_deserialize(fcinfo, r2, &lower2, &upper2, &empty2); + + if (lower1.rngtypid != upper1.rngtypid || + lower1.rngtypid != lower2.rngtypid || + lower1.rngtypid != upper2.rngtypid) + elog(ERROR, "range types do not match"); + + if (empty1 || empty2) + PG_RETURN_BOOL(false); + + if (range_cmp_bounds(fcinfo, &lower1, &lower2) >= 0 && + range_cmp_bounds(fcinfo, &lower1, &upper2) <= 0) + PG_RETURN_BOOL(true); + + if (range_cmp_bounds(fcinfo, &lower2, &lower1) >= 0 && + range_cmp_bounds(fcinfo, &lower2, &upper1) <= 0) + PG_RETURN_BOOL(true); + + PG_RETURN_BOOL(false); +} + +Datum +range_overleft(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(0); + RangeType *r2 = PG_GETARG_RANGE(1); + + RangeBound lower1, lower2; + RangeBound upper1, upper2; + bool empty1, empty2; + + range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); + range_deserialize(fcinfo, r2, &lower2, &upper2, &empty2); + + if (lower1.rngtypid != upper1.rngtypid || + lower1.rngtypid != lower2.rngtypid || + lower1.rngtypid != upper2.rngtypid) + elog(ERROR, "range types do not match"); + + if (empty1 || empty2) + PG_RETURN_BOOL(false); + + if (range_cmp_bounds(fcinfo, &upper1, &upper2) <= 0) + PG_RETURN_BOOL(true); + + PG_RETURN_BOOL(false); +} + +Datum +range_overright(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(0); + RangeType *r2 = PG_GETARG_RANGE(1); + + RangeBound lower1, lower2; + RangeBound upper1, upper2; + bool empty1, empty2; + + range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); + range_deserialize(fcinfo, r2, &lower2, &upper2, &empty2); + + if (lower1.rngtypid != upper1.rngtypid || + lower1.rngtypid != lower2.rngtypid || + lower1.rngtypid != upper2.rngtypid) + elog(ERROR, "range types do not match"); + + if (empty1 || empty2) + PG_RETURN_BOOL(false); + + if (range_cmp_bounds(fcinfo, &lower1, &lower2) >= 0) + PG_RETURN_BOOL(true); + + PG_RETURN_BOOL(false); +} + + +/* range, range -> range */ +Datum +range_minus(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(0); + RangeType *r2 = PG_GETARG_RANGE(1); + + RangeBound lower1, lower2; + RangeBound upper1, upper2; + bool empty1, empty2; + + int cmp_l1l2, cmp_l1u2, cmp_u1l2, cmp_u1u2; + + range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); + range_deserialize(fcinfo, r2, &lower2, &upper2, &empty2); + + if (lower1.rngtypid != upper1.rngtypid || + lower1.rngtypid != lower2.rngtypid || + lower1.rngtypid != upper2.rngtypid) + elog(ERROR, "range types do not match"); + + if (empty1 || empty2) + PG_RETURN_RANGE(r1); + + cmp_l1l2 = range_cmp_bounds(fcinfo, &lower1, &lower2); + cmp_l1u2 = range_cmp_bounds(fcinfo, &lower1, &upper2); + cmp_u1l2 = range_cmp_bounds(fcinfo, &upper1, &lower2); + cmp_u1u2 = range_cmp_bounds(fcinfo, &upper1, &upper2); + + if (cmp_l1l2 < 0 && cmp_u1u2 > 0) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("result range is not contiguous"))); + + if (cmp_l1u2 > 0 || cmp_u1l2 < 0) + PG_RETURN_RANGE(r1); + + if (cmp_l1l2 >= 0 && cmp_u1u2 <= 0) + PG_RETURN_RANGE(make_empty_range(fcinfo, lower1.rngtypid)); + + if (cmp_l1l2 <= 0 && cmp_u1l2 >= 0 && cmp_u1u2 <= 0) + { + lower2.inclusive = !lower2.inclusive; + lower2.lower = false; /* it will become the upper bound */ + PG_RETURN_RANGE(make_range(fcinfo, &lower1, &lower2, false)); + } + + if (cmp_l1l2 >= 0 && cmp_u1u2 >= 0 && cmp_l1u2 <= 0) + { + upper2.inclusive = !upper2.inclusive; + upper2.lower = true; /* it will become the lower bound */ + PG_RETURN_RANGE(make_range(fcinfo, &upper2, &upper1, false)); + } + + elog(ERROR, "unexpected error in range_minus"); + PG_RETURN_VOID(); +} + +Datum +range_union(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(0); + RangeType *r2 = PG_GETARG_RANGE(1); + + RangeBound lower1, lower2; + RangeBound upper1, upper2; + bool empty1, empty2; + RangeBound *result_lower; + RangeBound *result_upper; + + range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); + range_deserialize(fcinfo, r2, &lower2, &upper2, &empty2); + + if (empty1) + PG_RETURN_RANGE(r2); + if (empty2) + PG_RETURN_RANGE(r1); + + if (!DatumGetBool(range_overlaps(fcinfo)) && + !DatumGetBool(range_adjacent(fcinfo))) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("result range is not contiguous"))); + + if (range_cmp_bounds(fcinfo, &lower1, &lower2) < 0) + result_lower = &lower1; + else + result_lower = &lower2; + + if (range_cmp_bounds(fcinfo, &upper1, &upper2) > 0) + result_upper = &upper1; + else + result_upper = &upper2; + + PG_RETURN_RANGE(make_range(fcinfo, result_lower, result_upper, false)); +} + +Datum +range_intersect(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(0); + RangeType *r2 = PG_GETARG_RANGE(1); + + RangeBound lower1, lower2; + RangeBound upper1, upper2; + bool empty1, empty2; + RangeBound *result_lower; + RangeBound *result_upper; + + range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); + range_deserialize(fcinfo, r2, &lower2, &upper2, &empty2); + + if (empty1 || empty2 || !DatumGetBool(range_overlaps(fcinfo))) + PG_RETURN_RANGE(make_empty_range(fcinfo, lower1.rngtypid)); + + if (range_cmp_bounds(fcinfo, &lower1, &lower2) >= 0) + result_lower = &lower1; + else + result_lower = &lower2; + + if (range_cmp_bounds(fcinfo, &upper1, &upper2) <= 0) + result_upper = &upper1; + else + result_upper = &upper2; + + PG_RETURN_RANGE(make_range(fcinfo, result_lower, result_upper, false)); +} + +/* Btree support */ + +Datum +range_cmp(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(0); + RangeType *r2 = PG_GETARG_RANGE(1); + + RangeBound lower1, lower2; + RangeBound upper1, upper2; + bool empty1, empty2; + + int cmp; + + range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); + range_deserialize(fcinfo, r2, &lower2, &upper2, &empty2); + + if (lower1.rngtypid != upper1.rngtypid || + lower1.rngtypid != lower2.rngtypid || + lower1.rngtypid != upper2.rngtypid) + elog(ERROR, "range types do not match"); + + if (empty1 && empty2) + PG_RETURN_INT32(0); + else if (empty1) + PG_RETURN_INT32(-1); + else if (empty2) + PG_RETURN_INT32(1); + + if ((cmp = range_cmp_bounds(fcinfo, &lower1, &lower2)) != 0) + PG_RETURN_INT32(cmp); + + PG_RETURN_INT32(range_cmp_bounds(fcinfo, &upper1, &upper2)); +} + +Datum +range_lt(PG_FUNCTION_ARGS) +{ + int cmp = range_cmp(fcinfo); + PG_RETURN_BOOL(cmp < 0); +} + +Datum +range_le(PG_FUNCTION_ARGS) +{ + int cmp = range_cmp(fcinfo); + PG_RETURN_BOOL(cmp <= 0); +} + +Datum +range_ge(PG_FUNCTION_ARGS) +{ + int cmp = range_cmp(fcinfo); + PG_RETURN_BOOL(cmp >= 0); +} + +Datum +range_gt(PG_FUNCTION_ARGS) +{ + int cmp = range_cmp(fcinfo); + PG_RETURN_BOOL(cmp > 0); +} + +/* Hash support */ +Datum +hash_range(PG_FUNCTION_ARGS) +{ + RangeType *r = PG_GETARG_RANGE(0); + RangeBound lower; + RangeBound upper; + bool empty; + char flags = 0; + uint32 lower_hash = 0; + uint32 upper_hash = 0; + uint32 result = 0; + + RangeTypeInfo rngtypinfo; + + TypeCacheEntry *typentry; + Oid subtype; + FunctionCallInfoData locfcinfo; + + + range_deserialize(fcinfo, r, &lower, &upper, &empty); + + if (lower.rngtypid != upper.rngtypid) + elog(ERROR, "range types do not match"); + + if (empty) + flags |= RANGE_EMPTY; + + flags |= (lower.inclusive) ? RANGE_LB_INC : 0; + flags |= (lower.infinite) ? RANGE_LB_INF : 0; + flags |= (upper.inclusive) ? RANGE_UB_INC : 0; + flags |= (upper.infinite) ? RANGE_UB_INF : 0; + + range_gettypinfo(fcinfo, lower.rngtypid, &rngtypinfo); + subtype = rngtypinfo.subtype; + + /* + * We arrange to look up the hash function only once per series of + * calls, assuming the subtype doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being + * used as an index support function. + */ + typentry = (TypeCacheEntry *) fcinfo->flinfo->fn_extra; + if (typentry == NULL || typentry->type_id != subtype) + { + typentry = lookup_type_cache(subtype, TYPECACHE_HASH_PROC_FINFO); + if (!OidIsValid(typentry->hash_proc_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify a hash function for type %s", + format_type_be(subtype)))); + fcinfo->flinfo->fn_extra = (void *) typentry; + } + + /* + * Apply the hash function to each bound (the hash function shouldn't + * care about the collation). + */ + InitFunctionCallInfoData(locfcinfo, &typentry->hash_proc_finfo, 1, + InvalidOid, NULL, NULL); + + if (RANGE_HAS_LBOUND(flags)) + { + locfcinfo.arg[0] = lower.val; + locfcinfo.argnull[0] = false; + locfcinfo.isnull = false; + lower_hash = DatumGetUInt32(FunctionCallInvoke(&locfcinfo)); + } + if (RANGE_HAS_UBOUND(flags)) + { + locfcinfo.arg[0] = upper.val; + locfcinfo.argnull[0] = false; + locfcinfo.isnull = false; + upper_hash = DatumGetUInt32(FunctionCallInvoke(&locfcinfo)); + } + + result = hash_uint32((uint32) flags); + result ^= lower_hash; + result = (result << 1) | (result >> 31); + result ^= upper_hash; + + PG_RETURN_INT32(result); +} + +/* + *---------------------------------------------------------- + * CANONICAL FUNCTIONS + * + * Functions for specific built-in range types. + *---------------------------------------------------------- + */ + +Datum +int4range_canonical(PG_FUNCTION_ARGS) +{ + RangeType *r = PG_GETARG_RANGE(0); + + RangeBound lower; + RangeBound upper; + bool empty; + + range_deserialize(fcinfo, r, &lower, &upper, &empty); + + if (empty) + PG_RETURN_RANGE(r); + + if (!lower.infinite && !lower.inclusive) + { + lower.val = DirectFunctionCall2(int4pl, lower.val, Int32GetDatum(1)); + lower.inclusive = true; + } + + if (!upper.infinite && upper.inclusive) + { + upper.val = DirectFunctionCall2(int4pl, upper.val, Int32GetDatum(1)); + upper.inclusive = false; + } + + PG_RETURN_RANGE(range_serialize(fcinfo, &lower, &upper, false)); +} + +Datum +int8range_canonical(PG_FUNCTION_ARGS) +{ + RangeType *r = PG_GETARG_RANGE(0); + + RangeBound lower; + RangeBound upper; + bool empty; + + range_deserialize(fcinfo, r, &lower, &upper, &empty); + + if (empty) + PG_RETURN_RANGE(r); + + if (!lower.infinite && !lower.inclusive) + { + lower.val = DirectFunctionCall2(int8pl, lower.val, Int64GetDatum(1)); + lower.inclusive = true; + } + + if (!upper.infinite && upper.inclusive) + { + upper.val = DirectFunctionCall2(int8pl, upper.val, Int64GetDatum(1)); + upper.inclusive = false; + } + + PG_RETURN_RANGE(range_serialize(fcinfo, &lower, &upper, false)); +} + +Datum +daterange_canonical(PG_FUNCTION_ARGS) +{ + RangeType *r = PG_GETARG_RANGE(0); + + RangeBound lower; + RangeBound upper; + bool empty; + + range_deserialize(fcinfo, r, &lower, &upper, &empty); + + if (empty) + PG_RETURN_RANGE(r); + + if (!lower.infinite && !lower.inclusive) + { + lower.val = DirectFunctionCall2(date_pli, lower.val, Int32GetDatum(1)); + lower.inclusive = true; + } + + if (!upper.infinite && upper.inclusive) + { + upper.val = DirectFunctionCall2(date_pli, upper.val, Int32GetDatum(1)); + upper.inclusive = false; + } + + PG_RETURN_RANGE(range_serialize(fcinfo, &lower, &upper, false)); +} + +/* + *---------------------------------------------------------- + * SUBTYPE_DIFF FUNCTIONS + * + * Functions for specific built-in range types. + *---------------------------------------------------------- + */ + +Datum +int4range_subdiff(PG_FUNCTION_ARGS) +{ + int32 v1 = PG_GETARG_INT32(0); + int32 v2 = PG_GETARG_INT32(1); + + PG_RETURN_FLOAT8((float8)(v1-v2)); +} + +Datum +int8range_subdiff(PG_FUNCTION_ARGS) +{ + int64 v1 = PG_GETARG_INT64(0); + int64 v2 = PG_GETARG_INT64(1); + + PG_RETURN_FLOAT8((float8)(v1-v2)); +} + +Datum +daterange_subdiff(PG_FUNCTION_ARGS) +{ + int32 v1 = PG_GETARG_INT32(0); + int32 v2 = PG_GETARG_INT32(1); + + PG_RETURN_FLOAT8((float8)(v1-v2)); +} + +Datum +numrange_subdiff(PG_FUNCTION_ARGS) +{ + Datum v1 = PG_GETARG_DATUM(0); + Datum v2 = PG_GETARG_DATUM(1); + Datum numresult; + float8 floatresult; + + numresult = DirectFunctionCall2(numeric_sub, v1, v2); + + floatresult = DatumGetFloat8( + DirectFunctionCall1(numeric_float8, numresult)); + + PG_RETURN_FLOAT8(floatresult); +} + +Datum +tsrange_subdiff(PG_FUNCTION_ARGS) +{ + Timestamp v1 = PG_GETARG_TIMESTAMP(0); + Timestamp v2 = PG_GETARG_TIMESTAMP(1); + float8 result; + +#ifdef HAVE_INT64_TIMESTAMP + result = ((float8)(v1-v2)) / USECS_PER_SEC; +#else + result = timestamp; +#endif + + PG_RETURN_FLOAT8(result); +} + +Datum +tstzrange_subdiff(PG_FUNCTION_ARGS) +{ + Timestamp v1 = PG_GETARG_TIMESTAMP(0); + Timestamp v2 = PG_GETARG_TIMESTAMP(1); + float8 result; + +#ifdef HAVE_INT64_TIMESTAMP + result = ((float8)(v1-v2)) / USECS_PER_SEC; +#else + result = timestamp; +#endif + + PG_RETURN_FLOAT8(result); +} + +/* + *---------------------------------------------------------- + * SUPPORT FUNCTIONS + * + * These functions aren't in pg_proc, but are useful if + * defining new generic range functions in C. + *---------------------------------------------------------- + */ + +/* + * Serialized format is: + * + * 4 bytes: Range type Oid + * Lower boundary, if any, aligned according to subtype's typalign + * Upper boundary, if any, aligned according to subtype's typalign + * 1 byte for flags + * + * This representation is chosen to be compact when the boundary + * values need to be MAXALIGNed. A palloc chunk always starts out + * MAXALIGNed, and the first 4 bytes will be the length header (range + * types are always variable-length), then the next 4 bytes will be + * the range type Oid. That leaves the first boundary item MAXALIGNed + * without the need for padding. + * + * However, it requires a slightly odd deserialization strategy, + * because we have to read the flags byte before we know whether to + * read a boundary value. + */ + +/* + * This serializes a range, but does not canonicalize it. This should + * only be called by a canonicalization function. + */ +Datum +range_serialize(FunctionCallInfo fcinfo, RangeBound *lower, RangeBound *upper, + bool empty) +{ + Datum range; + size_t msize; + Pointer ptr; + int16 typlen; + char typalign; + bool typbyval; + char typstorage; + char flags = 0; + + RangeTypeInfo rngtypinfo; + + if (lower->rngtypid != upper->rngtypid) + elog(ERROR, "range types do not match"); + + range_gettypinfo(fcinfo, lower->rngtypid, &rngtypinfo); + + typlen = rngtypinfo.subtyplen; + typalign = rngtypinfo.subtypalign; + typbyval = rngtypinfo.subtypbyval; + typstorage = rngtypinfo.subtypstorage; + + if (empty) + flags |= RANGE_EMPTY; + else if (range_cmp_bounds(fcinfo, lower, upper) > 0) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("range lower bound must be less than or equal to range upper bound"))); + + flags |= (lower->inclusive) ? RANGE_LB_INC : 0; + flags |= (lower->infinite) ? RANGE_LB_INF : 0; + flags |= (upper->inclusive) ? RANGE_UB_INC : 0; + flags |= (upper->infinite) ? RANGE_UB_INF : 0; + + msize = VARHDRSZ; + msize += sizeof(Oid); + + if (RANGE_HAS_LBOUND(flags)) + { + msize = datum_compute_size(msize, lower->val, typbyval, typalign, + typlen, typstorage); + } + + if (RANGE_HAS_UBOUND(flags)) + { + msize = datum_compute_size(msize, upper->val, typbyval, typalign, + typlen, typstorage); + } + + msize += sizeof(char); + + ptr = palloc0(msize); + range = (Datum) ptr; + + ptr += VARHDRSZ; + + memcpy(ptr, &lower->rngtypid, sizeof(Oid)); + ptr += sizeof(Oid); + + if (RANGE_HAS_LBOUND(flags)) + { + Assert(lower->lower); + ptr = datum_write(ptr, lower->val, typbyval, typalign, typlen, + typstorage); + } + + if (RANGE_HAS_UBOUND(flags)) + { + Assert(!upper->lower); + ptr = datum_write(ptr, upper->val, typbyval, typalign, typlen, + typstorage); + } + + memcpy(ptr, &flags, sizeof(char)); + ptr += sizeof(char); + + SET_VARSIZE(range, msize); + PG_RETURN_RANGE(range); +} + +void +range_deserialize(FunctionCallInfo fcinfo, RangeType *range, RangeBound *lower, + RangeBound *upper, bool *empty) +{ + Pointer ptr = VARDATA(range); + char typalign; + int16 typlen; + int16 typbyval; + char flags; + Oid rngtypid; + Datum lbound; + Datum ubound; + Pointer flags_ptr; + + RangeTypeInfo rngtypinfo; + + memset(lower, 0, sizeof(RangeBound)); + memset(upper, 0, sizeof(RangeBound)); + + /* peek at last byte to read the flag byte */ + flags_ptr = ptr + VARSIZE(range) - VARHDRSZ - 1; + memcpy(&flags, flags_ptr, sizeof(char)); + + memcpy(&rngtypid, ptr, sizeof(Oid)); + ptr += sizeof(Oid); + + if (rngtypid == ANYRANGEOID) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot output a value of type anyrange"))); + + range_gettypinfo(fcinfo, rngtypid, &rngtypinfo); + + typalign = rngtypinfo.subtypalign; + typlen = rngtypinfo.subtyplen; + typbyval = rngtypinfo.subtypbyval; + + if (RANGE_HAS_LBOUND(flags)) + { + ptr = (Pointer) att_align_pointer(ptr, typalign, typlen, ptr); + lbound = fetch_att(ptr, typbyval, typlen); + ptr = (Pointer) att_addlength_datum(ptr, typlen, PointerGetDatum(ptr)); + if (typlen == -1) + lbound = PointerGetDatum(PG_DETOAST_DATUM(lbound)); + } + else + lbound = (Datum) 0; + + if (RANGE_HAS_UBOUND(flags)) + { + ptr = (Pointer) att_align_pointer(ptr, typalign, typlen, ptr); + ubound = fetch_att(ptr, typbyval, typlen); + ptr = (Pointer) att_addlength_datum(ptr, typlen, PointerGetDatum(ptr)); + if (typlen == -1) + ubound = PointerGetDatum(PG_DETOAST_DATUM(ubound)); + } + else + ubound = (Datum) 0; + + *empty = flags & RANGE_EMPTY; + + lower->rngtypid = rngtypid; + lower->val = lbound; + lower->inclusive = flags & RANGE_LB_INC; + lower->infinite = flags & RANGE_LB_INF; + lower->lower = true; + + upper->rngtypid = rngtypid; + upper->val = ubound; + upper->inclusive = flags & RANGE_UB_INC; + upper->infinite = flags & RANGE_UB_INF; + upper->lower = false; +} + +/* + * This both serializes and caonicalizes (if applicable) the + * range. This should be used by most callers. + */ +Datum +make_range(FunctionCallInfo fcinfo, RangeBound *lower, RangeBound *upper, + bool empty) +{ + Datum range; + + RangeTypeInfo rngtypinfo; + + range_gettypinfo(fcinfo, lower->rngtypid, &rngtypinfo); + + if (lower->rngtypid != upper->rngtypid) + elog(ERROR, "range types do not match"); + + range = range_serialize(fcinfo, lower, upper, empty); + + if (rngtypinfo.canonicalFn.fn_addr != NULL) + range = FunctionCall1(&rngtypinfo.canonicalFn, range); + + PG_RETURN_RANGE(range); +} + +int +range_cmp_bounds(FunctionCallInfo fcinfo, RangeBound *b1, RangeBound *b2) +{ + int result; + + RangeTypeInfo rngtypinfo; + + if (b1->infinite && b2->infinite) + { + if (b1->lower == b2->lower) + return 0; + else + return (b1->lower) ? -1 : 1; + } + else if (b1->infinite && !b2->infinite) + return (b1->lower) ? -1 : 1; + else if (!b1->infinite && b2->infinite) + return (b2->lower) ? 1 : -1; + + range_gettypinfo(fcinfo, b1->rngtypid, &rngtypinfo); + result = DatumGetInt32(FunctionCall2Coll(&rngtypinfo.cmpFn, + rngtypinfo.collation, + b1->val, b2->val)); + + if (result == 0) + { + if (b1->inclusive && !b2->inclusive) + return (b2->lower) ? -1 : 1; + else if (!b1->inclusive && b2->inclusive) + return (b1->lower) ? 1 : -1; + } + + return result; +} + +RangeType * +make_empty_range(FunctionCallInfo fcinfo, Oid rngtypid) +{ + RangeBound lower; + RangeBound upper; + + memset(&lower, 0, sizeof(RangeBound)); + memset(&upper, 0, sizeof(RangeBound)); + + lower.rngtypid = rngtypid; + lower.lower = true; + upper.rngtypid = rngtypid; + upper.lower = false; + + return DatumGetRangeType(make_range(fcinfo, &lower, &upper, true)); +} + +/* + * Fills in rngtypinfo, from a cached copy if available. + */ +void +range_gettypinfo(FunctionCallInfo fcinfo, Oid rngtypid, + RangeTypeInfo *rngtypinfo) +{ + RangeTypeInfo *cached = (RangeTypeInfo *) fcinfo->flinfo->fn_extra; + + if (cached == NULL) + { + fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + sizeof(RangeTypeInfo)); + cached = (RangeTypeInfo *) fcinfo->flinfo->fn_extra; + cached->rngtypid = ~rngtypid; + } + + if (cached->rngtypid != rngtypid) + { + Form_pg_range pg_range; + Form_pg_opclass pg_opclass; + Form_pg_type pg_type; + HeapTuple tup; + + Oid subtypeOid; + Oid collationOid; + Oid canonicalOid; + Oid subdiffOid; + Oid opclassOid; + Oid cmpFnOid; + Oid opfamilyOid; + Oid opcintype; + int16 subtyplen; + char subtypalign; + char subtypstorage; + bool subtypbyval; + + /* get information from pg_range */ + tup = SearchSysCache1(RANGETYPE, ObjectIdGetDatum(rngtypid)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for range type %u", rngtypid); + + pg_range = (Form_pg_range) GETSTRUCT(tup); + + subtypeOid = pg_range->rngsubtype; + collationOid = pg_range->rngcollation; + canonicalOid = pg_range->rngcanonical; + opclassOid = pg_range->rngsubopc; + subdiffOid = pg_range->rngsubdiff; + + ReleaseSysCache(tup); + + /* get information from pg_opclass */ + tup = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclassOid)); + if (!HeapTupleIsValid(tup)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("operator class with OID %u does not exist", + opclassOid))); + + pg_opclass = (Form_pg_opclass) GETSTRUCT(tup); + + opfamilyOid = pg_opclass->opcfamily; + opcintype = pg_opclass->opcintype; + + ReleaseSysCache(tup); + + cmpFnOid = get_opfamily_proc(opfamilyOid, opcintype, opcintype, + BTORDER_PROC); + + if (!OidIsValid(cmpFnOid)) + elog(ERROR, "unable to find compare function for operator class %d", + opclassOid); + + /* get information from pg_type */ + tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(subtypeOid)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for type %u", subtypeOid); + + pg_type = (Form_pg_type) GETSTRUCT(tup); + + subtyplen = pg_type->typlen; + subtypalign = pg_type->typalign; + subtypstorage = pg_type->typstorage; + subtypbyval = pg_type->typbyval; + + ReleaseSysCache(tup); + + /* set up the cache */ + + if (OidIsValid(canonicalOid)) + fmgr_info(canonicalOid, &cached->canonicalFn); + else + cached->canonicalFn.fn_addr = NULL; + + if (OidIsValid(subdiffOid)) + fmgr_info(subdiffOid, &cached->subdiffFn); + else + cached->subdiffFn.fn_addr = NULL; + + fmgr_info(cmpFnOid, &cached->cmpFn); + cached->subtype = subtypeOid; + cached->collation = collationOid; + cached->subtyplen = subtyplen; + cached->subtypalign = subtypalign; + cached->subtypstorage = subtypstorage; + cached->subtypbyval = subtypbyval; + cached->rngtypid = rngtypid; + } + + memcpy(rngtypinfo, cached, sizeof(RangeTypeInfo)); +} + +/* + * Given a string representing the flags for the range type, return the flags + * represented as a char. + * + * Exported so that it can be called by DefineRange to check the default flags. + */ +char +range_parse_flags(char *flags_str) +{ + char flags = 0; + + if (flags_str[0] == '\0' || + flags_str[1] == '\0' || + flags_str[2] != '\0') + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid range bound flags"), + errhint("Valid values are '[]', '[)', '(]', and '()'."))); + + switch (flags_str[0]) + { + case '[': + flags |= RANGE_LB_INC; + break; + case '(': + break; + default: + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid range bound flags"), + errhint("Valid values are '[]', '[)', '(]', and '()'."))); + } + + switch (flags_str[1]) + { + case ']': + flags |= RANGE_UB_INC; + break; + case ')': + break; + default: + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid range bound flags"), + errhint("Valid values are '[]', '[)', '(]', and '()'."))); + } + + return flags; +} + +/* + *---------------------------------------------------------- + * STATIC FUNCTIONS + *---------------------------------------------------------- + */ + +/* + * Parse range input, modeled after record_in in rowtypes.c. + * + * := EMPTY + * | , + * := '[' | '(' + * := ']' | ')' + * + * Whitespace before or after is ignored. Whitespace within a + * is taken literally and becomes the input string for that bound. + * + * A of length zero is taken as "infinite" (i.e. no bound); unless it + * is surrounded by double-quotes, in which case it is the literal empty + * string. + * + * Within a , special characters (such as comma, parenthesis, or + * brackets) can be enclosed in double-quotes or escaped with backslash. Within + * double-quotes, a double-quote can be escaped with double-quote or backslash. + */ +static void +range_parse(char *string, char *flags, char **lbound_str, + char **ubound_str) +{ + char *ptr = string; + bool infinite; + + *flags = 0; + + /* consume whitespace */ + while (*ptr != '\0' && isspace(*ptr)) + ptr++; + + /* check for empty range */ + if (pg_strncasecmp(ptr, RANGE_EMPTY_LITERAL, + strlen(RANGE_EMPTY_LITERAL)) == 0) + { + *flags = RANGE_EMPTY; + + ptr += strlen(RANGE_EMPTY_LITERAL); + + /* the rest should be whitespace */ + while (*ptr != '\0' && isspace(*ptr)) + ptr++; + + /* should have consumed everything */ + if (*ptr != '\0') + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed range literal: \"%s\"", + string), + errdetail("Unexpected end of input."))); + + return; + } + + if (*ptr == '[' || *ptr == '(') + { + if (*ptr == '[') + *flags |= RANGE_LB_INC; + ptr++; + } + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed range literal: \"%s\"", + string), + errdetail("Missing left parenthesis or bracket."))); + } + + ptr = range_parse_bound(string, ptr, lbound_str, &infinite); + if (infinite) + { + *flags |= RANGE_LB_INF; + *flags &= ~RANGE_LB_INC; + } + + if (*ptr != ',') + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed range literal: \"%s\"", + string), + errdetail("Missing upper bound."))); + ptr++; + + ptr = range_parse_bound(string, ptr, ubound_str, &infinite); + + if (*ptr == ')' || *ptr == ']') + { + if (*ptr == ']') + *flags |= RANGE_UB_INC; + } + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed range literal: \"%s\"", + string), + errdetail("Too many boundaries."))); + } + + ptr++; + + if (infinite) + { + *flags |= RANGE_UB_INF; + *flags &= ~RANGE_UB_INC; + } + + /* consume whitespace */ + while (*ptr != '\0' && isspace(*ptr)) + ptr++; + + if (*ptr != '\0') + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed range literal: \"%s\"", + string), + errdetail("Junk after right parenthesis or bracket."))); + + return; +} + +static char * +range_parse_bound(char *string, char *ptr, char **bound_str, bool *infinite) +{ + StringInfoData buf; + + /* Check for null: completely empty input means null */ + if (*ptr == ',' || *ptr == ')' || *ptr == ']') + { + *bound_str = NULL; + *infinite = true; + } + else + { + /* Extract string for this column */ + bool inquote = false; + + initStringInfo(&buf); + while (inquote || !(*ptr == ',' || *ptr == ')' || *ptr == ']')) + { + char ch = *ptr++; + + if (ch == '\0') + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed range literal: \"%s\"", + string), + errdetail("Unexpected end of input."))); + if (ch == '\\') + { + if (*ptr == '\0') + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed range literal: \"%s\"", + string), + errdetail("Unexpected end of input."))); + appendStringInfoChar(&buf, *ptr++); + } + else if (ch == '\"') + { + if (!inquote) + inquote = true; + else if (*ptr == '\"') + { + /* doubled quote within quote sequence */ + appendStringInfoChar(&buf, *ptr++); + } + else + inquote = false; + } + else + appendStringInfoChar(&buf, ch); + } + + *bound_str = buf.data; + *infinite = false; + } + + return ptr; +} + +static char * +range_deparse(char flags, char *lbound_str, char *ubound_str) +{ + StringInfoData buf; + + initStringInfo(&buf); + + if (flags & RANGE_EMPTY) + return pstrdup(RANGE_EMPTY_LITERAL); + + appendStringInfoString(&buf, (flags & RANGE_LB_INC) ? "[" : "("); + + if (RANGE_HAS_LBOUND(flags)) + appendStringInfoString(&buf, range_bound_escape(lbound_str)); + + appendStringInfoString(&buf, ","); + + if (RANGE_HAS_UBOUND(flags)) + appendStringInfoString(&buf, range_bound_escape(ubound_str)); + + appendStringInfoString(&buf, (flags & RANGE_UB_INC) ? "]" : ")"); + + return buf.data; +} + +static char * +range_bound_escape(char *value) +{ + bool nq; + char *tmp; + StringInfoData buf; + + initStringInfo(&buf); + + /* Detect whether we need double quotes for this value */ + nq = (value[0] == '\0'); /* force quotes for empty string */ + for (tmp = value; *tmp; tmp++) + { + char ch = *tmp; + + if (ch == '"' || ch == '\\' || + ch == '(' || ch == ')' || + ch == '[' || ch == ']' || + ch == ',' || + isspace((unsigned char) ch)) + { + nq = true; + break; + } + } + + /* And emit the string */ + if (nq) + appendStringInfoChar(&buf, '"'); + for (tmp = value; *tmp; tmp++) + { + char ch = *tmp; + + if (ch == '"' || ch == '\\') + appendStringInfoChar(&buf, ch); + appendStringInfoChar(&buf, ch); + } + if (nq) + appendStringInfoChar(&buf, '"'); + + return buf.data; +} + +static bool +range_contains_internal(FunctionCallInfo fcinfo, RangeType *r1, RangeType *r2) +{ + RangeBound lower1; + RangeBound upper1; + bool empty1; + RangeBound lower2; + RangeBound upper2; + bool empty2; + + range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); + range_deserialize(fcinfo, r2, &lower2, &upper2, &empty2); + + if (lower1.rngtypid != upper1.rngtypid || + lower1.rngtypid != lower2.rngtypid || + lower1.rngtypid != upper2.rngtypid) + elog(ERROR, "range types do not match"); + + if (empty2) + return true; + else if (empty1) + return false; + + if (range_cmp_bounds(fcinfo, &lower1, &lower2) > 0) + return false; + if (range_cmp_bounds(fcinfo, &upper1, &upper2) < 0) + return false; + + return true; +} + +/* + * datum_compute_size() and datum_write() are modeled after + * heap_compute_data_size() and heap_fill_tuple(). + */ + +static Size +datum_compute_size(Size data_length, Datum val, bool typbyval, char typalign, + int16 typlen, char typstorage) +{ + if (TYPE_IS_PACKABLE(typlen, typstorage) && + VARATT_CAN_MAKE_SHORT(DatumGetPointer(val))) + { + /* + * we're anticipating converting to a short varlena header, so + * adjust length and don't count any alignment + */ + data_length += VARATT_CONVERTED_SHORT_SIZE(DatumGetPointer(val)); + } + else + { + data_length = att_align_datum(data_length, typalign, typlen, val); + data_length = att_addlength_datum(data_length, typlen, val); + } + + return data_length; +} + +/* + * Modified version of the code in heap_fill_tuple(). Writes the datum to ptr + * using the correct alignment, and also uses short varlena header if + * applicable. + */ +static Pointer +datum_write(Pointer ptr, Datum datum, bool typbyval, char typalign, + int16 typlen, char typstorage) +{ + Size data_length; + + if (typbyval) + { + /* pass-by-value */ + ptr = (char *) att_align_nominal(ptr, typalign); + store_att_byval(ptr, datum, typlen); + data_length = typlen; + } + else if (typlen == -1) + { + /* varlena */ + Pointer val = DatumGetPointer(datum); + + if (VARATT_IS_EXTERNAL(val)) + { + /* no alignment, since it's short by definition */ + data_length = VARSIZE_EXTERNAL(val); + memcpy(ptr, val, data_length); + } + else if (VARATT_IS_SHORT(val)) + { + /* no alignment for short varlenas */ + data_length = VARSIZE_SHORT(val); + memcpy(ptr, val, data_length); + } + else if (TYPE_IS_PACKABLE(typlen, typstorage) && + VARATT_CAN_MAKE_SHORT(val)) + { + /* convert to short varlena -- no alignment */ + data_length = VARATT_CONVERTED_SHORT_SIZE(val); + SET_VARSIZE_SHORT(ptr, data_length); + memcpy(ptr + 1, VARDATA(val), data_length - 1); + } + else + { + /* full 4-byte header varlena */ + ptr = (char *) att_align_nominal(ptr, typalign); + data_length = VARSIZE(val); + memcpy(ptr, val, data_length); + } + } + else if (typlen == -2) + { + /* cstring ... never needs alignment */ + Assert(typalign == 'c'); + data_length = strlen(DatumGetCString(datum)) + 1; + memcpy(ptr, DatumGetPointer(datum), data_length); + } + else + { + /* fixed-length pass-by-reference */ + ptr = (char *) att_align_nominal(ptr, typalign); + Assert(typlen > 0); + data_length = typlen; + memcpy(ptr, DatumGetPointer(datum), data_length); + } + + ptr += data_length; + + return ptr; +} diff --git a/src/backend/utils/adt/rangetypes_gist.c b/src/backend/utils/adt/rangetypes_gist.c new file mode 100644 index 0000000000..9dc7fd1b5a --- /dev/null +++ b/src/backend/utils/adt/rangetypes_gist.c @@ -0,0 +1,587 @@ +/*------------------------------------------------------------------------- + * + * rangetypes_gist.c + * GiST support for range types. + * + * Copyright (c) 2006-2011, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/utils/adt/rangetypes_gist.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/gist.h" +#include "access/skey.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/lsyscache.h" +#include "utils/rangetypes.h" + +#define RANGESTRAT_EQ 1 +#define RANGESTRAT_NE 2 +#define RANGESTRAT_OVERLAPS 3 +#define RANGESTRAT_CONTAINS_ELEM 4 +#define RANGESTRAT_ELEM_CONTAINED_BY 5 +#define RANGESTRAT_CONTAINS 6 +#define RANGESTRAT_CONTAINED_BY 7 +#define RANGESTRAT_BEFORE 8 +#define RANGESTRAT_AFTER 9 +#define RANGESTRAT_OVERLEFT 10 +#define RANGESTRAT_OVERRIGHT 11 +#define RANGESTRAT_ADJACENT 12 + +static RangeType *range_super_union(FunctionCallInfo fcinfo, RangeType *r1, + RangeType *r2); +static bool range_gist_consistent_int(FunctionCallInfo fcinfo, + StrategyNumber strategy, RangeType *key, + RangeType *query); +static bool range_gist_consistent_leaf(FunctionCallInfo fcinfo, + StrategyNumber strategy, RangeType *key, + RangeType *query); +static int sort_item_cmp(const void *a, const void *b); + +/* + * Auxiliary structure for picksplit method. + */ +typedef struct +{ + int index; + RangeType *data; + FunctionCallInfo fcinfo; +} PickSplitSortItem; + + +Datum +range_gist_consistent(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + Datum dquery = PG_GETARG_DATUM(1); + StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); + /* Oid subtype = PG_GETARG_OID(3); */ + bool *recheck = (bool *) PG_GETARG_POINTER(4); + RangeType *key = DatumGetRangeType(entry->key); + RangeType *query; + + RangeBound lower; + RangeBound upper; + bool empty; + Oid rngtypid; + + *recheck = false; + range_deserialize(fcinfo, key, &lower, &upper, &empty); + rngtypid = lower.rngtypid; + + switch (strategy) + { + RangeBound lower; + RangeBound upper; + + /* + * For contains and contained by operators, the other operand is a + * "point" of the subtype. Construct a singleton range containing just + * that value. + */ + case RANGESTRAT_CONTAINS_ELEM: + case RANGESTRAT_ELEM_CONTAINED_BY: + lower.rngtypid = rngtypid; + lower.inclusive = true; + lower.val = dquery; + lower.lower = true; + lower.infinite = false; + upper.rngtypid = rngtypid; + upper.inclusive = true; + upper.val = dquery; + upper.lower = false; + upper.infinite = false; + query = DatumGetRangeType( + make_range(fcinfo, &lower, &upper, false)); + break; + + default: + query = DatumGetRangeType(dquery); + break; + } + + if (GIST_LEAF(entry)) + PG_RETURN_BOOL(range_gist_consistent_leaf( + fcinfo, strategy, key, query)); + else + PG_RETURN_BOOL(range_gist_consistent_int( + fcinfo, strategy, key, query)); +} + +Datum +range_gist_union(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + GISTENTRY *ent = entryvec->vector; + RangeType *result_range; + int i; + + result_range = DatumGetRangeType(ent[0].key); + + for (i = 1; i < entryvec->n; i++) + { + result_range = range_super_union(fcinfo, result_range, + DatumGetRangeType(ent[i].key)); + } + + PG_RETURN_RANGE(result_range); +} + +Datum +range_gist_compress(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + PG_RETURN_POINTER(entry); +} + +Datum +range_gist_decompress(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + PG_RETURN_POINTER(entry); +} + +Datum +range_gist_penalty(PG_FUNCTION_ARGS) +{ + GISTENTRY *origentry = (GISTENTRY *) PG_GETARG_POINTER(0); + GISTENTRY *newentry = (GISTENTRY *) PG_GETARG_POINTER(1); + float *penalty = (float *) PG_GETARG_POINTER(2); + RangeType *orig = DatumGetRangeType(origentry->key); + RangeType *new = DatumGetRangeType(newentry->key); + RangeType *s_union = range_super_union(fcinfo, orig, new); + + FmgrInfo *subtype_diff; + + RangeBound lower1, lower2; + RangeBound upper1, upper2; + bool empty1, empty2; + + float lower_diff, upper_diff; + + RangeTypeInfo rngtypinfo; + + range_deserialize(fcinfo, orig, &lower1, &upper1, &empty1); + range_deserialize(fcinfo, s_union, &lower2, &upper2, &empty2); + + range_gettypinfo(fcinfo, lower1.rngtypid, &rngtypinfo); + subtype_diff = &rngtypinfo.subdiffFn; + + Assert(empty1 || !empty2); + + if (empty1 && empty2) + return 0; + else if (empty1 && !empty2) + { + if (lower2.infinite || upper2.infinite) + /* from empty to infinite */ + return get_float8_infinity(); + else if (subtype_diff->fn_addr != NULL) + /* from empty to upper2-lower2 */ + return DatumGetFloat8(FunctionCall2(subtype_diff, + upper2.val, lower2.val)); + else + /* wild guess */ + return 1.0; + } + + Assert(lower2.infinite || !lower1.infinite); + + if (lower2.infinite && !lower1.infinite) + lower_diff = get_float8_infinity(); + else if (lower2.infinite && lower1.infinite) + lower_diff = 0; + else if (subtype_diff->fn_addr != NULL) + { + lower_diff = DatumGetFloat8(FunctionCall2(subtype_diff, + lower1.val, lower2.val)); + if (lower_diff < 0) + lower_diff = 0; /* subtype_diff is broken */ + } + else /* only know whether there is a difference or not */ + lower_diff = (float) range_cmp_bounds(fcinfo, &lower1, &lower2); + + Assert(upper2.infinite || !upper1.infinite); + + if (upper2.infinite && !upper1.infinite) + upper_diff = get_float8_infinity(); + else if (upper2.infinite && upper1.infinite) + upper_diff = 0; + else if (subtype_diff->fn_addr != NULL) + { + upper_diff = DatumGetFloat8(FunctionCall2(subtype_diff, + upper2.val, upper1.val)); + if (upper_diff < 0) + upper_diff = 0; /* subtype_diff is broken */ + } + else /* only know whether there is a difference or not */ + upper_diff = (float) range_cmp_bounds(fcinfo, &upper2, &upper1); + + Assert(lower_diff >= 0 && upper_diff >= 0); + + *penalty = (float) (lower_diff + upper_diff); + PG_RETURN_POINTER(penalty); +} + +/* + * The GiST PickSplit method for ranges + * + * Algorithm based on sorting. Incoming array of periods is sorted using + * sort_item_cmp function. After that first half of periods goes to the left + * datum, and the second half of periods goes to the right datum. + */ +Datum +range_gist_picksplit(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + GIST_SPLITVEC *v = (GIST_SPLITVEC *) PG_GETARG_POINTER(1); + OffsetNumber i; + RangeType *pred_left; + RangeType *pred_right; + PickSplitSortItem *sortItems; + int nbytes; + OffsetNumber split_idx; + OffsetNumber *left; + OffsetNumber *right; + OffsetNumber maxoff; + + maxoff = entryvec->n - 1; + nbytes = (maxoff + 1) * sizeof(OffsetNumber); + sortItems = (PickSplitSortItem *) palloc( + maxoff * sizeof(PickSplitSortItem)); + v->spl_left = (OffsetNumber *) palloc(nbytes); + v->spl_right = (OffsetNumber *) palloc(nbytes); + + /* + * Preparing auxiliary array and sorting. + */ + for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i)) + { + sortItems[i - 1].index = i; + sortItems[i - 1].data = DatumGetRangeType(entryvec->vector[i].key); + sortItems[i - 1].fcinfo = fcinfo; + } + qsort(sortItems, maxoff, sizeof(PickSplitSortItem), sort_item_cmp); + split_idx = maxoff / 2; + + left = v->spl_left; + v->spl_nleft = 0; + right = v->spl_right; + v->spl_nright = 0; + + /* + * First half of segs goes to the left datum. + */ + pred_left = DatumGetRangeType(sortItems[0].data); + *left++ = sortItems[0].index; + v->spl_nleft++; + for (i = 1; i < split_idx; i++) + { + pred_left = range_super_union(fcinfo, pred_left, + DatumGetRangeType(sortItems[i].data)); + *left++ = sortItems[i].index; + v->spl_nleft++; + } + + /* + * Second half of segs goes to the right datum. + */ + pred_right = DatumGetRangeType(sortItems[split_idx].data); + *right++ = sortItems[split_idx].index; + v->spl_nright++; + for (i = split_idx + 1; i < maxoff; i++) + { + pred_right = range_super_union(fcinfo, pred_right, + DatumGetRangeType(sortItems[i].data)); + *right++ = sortItems[i].index; + v->spl_nright++; + } + + *left = *right = FirstOffsetNumber; /* sentinel value, see dosplit() */ + + v->spl_ldatum = RangeTypeGetDatum(pred_left); + v->spl_rdatum = RangeTypeGetDatum(pred_right); + + PG_RETURN_POINTER(v); +} + +Datum +range_gist_same(PG_FUNCTION_ARGS) +{ + Datum r1 = PG_GETARG_DATUM(0); + Datum r2 = PG_GETARG_DATUM(1); + bool *result = (bool *) PG_GETARG_POINTER(2); + + *result = DatumGetBool(OidFunctionCall2(F_RANGE_EQ, r1, r2)); + PG_RETURN_POINTER(result); +} + +/* + *---------------------------------------------------------- + * STATIC FUNCTIONS + *---------------------------------------------------------- + */ + +/* return the smallest range that contains r1 and r2 */ +static RangeType * +range_super_union(FunctionCallInfo fcinfo, RangeType *r1, RangeType *r2) +{ + RangeBound lower1, lower2; + RangeBound upper1, upper2; + bool empty1, empty2; + RangeBound *result_lower; + RangeBound *result_upper; + + range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); + range_deserialize(fcinfo, r2, &lower2, &upper2, &empty2); + + if (empty1) + return r2; + if (empty2) + return r1; + + if (range_cmp_bounds(fcinfo, &lower1, &lower2) <= 0) + result_lower = &lower1; + else + result_lower = &lower2; + + if (range_cmp_bounds(fcinfo, &upper1, &upper2) >= 0) + result_upper = &upper1; + else + result_upper = &upper2; + + /* optimization to avoid constructing a new range */ + if (result_lower == &lower1 && result_upper == &upper1) + return r1; + if (result_lower == &lower2 && result_upper == &upper2) + return r2; + + return DatumGetRangeType( + make_range(fcinfo, result_lower, result_upper, false)); +} + +static bool +range_gist_consistent_int(FunctionCallInfo fcinfo, StrategyNumber strategy, + RangeType *key, RangeType *query) +{ + Oid proc = InvalidOid; + + RangeBound lower1, lower2; + RangeBound upper1, upper2; + bool empty1, empty2; + + bool retval; + bool negate = false; + + range_deserialize(fcinfo, key, &lower1, &upper1, &empty1); + range_deserialize(fcinfo, query, &lower2, &upper2, &empty2); + + switch (strategy) + { + case RANGESTRAT_EQ: + proc = F_RANGE_CONTAINS; + break; + case RANGESTRAT_NE: + return true; + break; + case RANGESTRAT_OVERLAPS: + proc = F_RANGE_OVERLAPS; + break; + case RANGESTRAT_CONTAINS_ELEM: + case RANGESTRAT_CONTAINS: + proc = F_RANGE_CONTAINS; + break; + case RANGESTRAT_ELEM_CONTAINED_BY: + case RANGESTRAT_CONTAINED_BY: + return true; + break; + case RANGESTRAT_BEFORE: + if (empty1) + return false; + proc = F_RANGE_OVERRIGHT; + negate = true; + break; + case RANGESTRAT_AFTER: + if (empty1) + return false; + proc = F_RANGE_OVERLEFT; + negate = true; + break; + case RANGESTRAT_OVERLEFT: + if (empty1) + return false; + proc = F_RANGE_AFTER; + negate = true; + break; + case RANGESTRAT_OVERRIGHT: + if (empty1) + return false; + proc = F_RANGE_BEFORE; + negate = true; + break; + case RANGESTRAT_ADJACENT: + if (empty1 || empty2) + return false; + if (DatumGetBool( + OidFunctionCall2(F_RANGE_ADJACENT, + RangeTypeGetDatum(key), + RangeTypeGetDatum(query)))) + return true; + proc = F_RANGE_OVERLAPS; + break; + } + + retval = DatumGetBool(OidFunctionCall2(proc, RangeTypeGetDatum(key), + RangeTypeGetDatum(query))); + + if (negate) + retval = !retval; + + PG_RETURN_BOOL(retval); +} + +static bool +range_gist_consistent_leaf(FunctionCallInfo fcinfo, StrategyNumber strategy, + RangeType *key, RangeType *query) +{ + Oid proc = InvalidOid; + + RangeBound lower1, lower2; + RangeBound upper1, upper2; + bool empty1, empty2; + + range_deserialize(fcinfo, key, &lower1, &upper1, &empty1); + range_deserialize(fcinfo, query, &lower2, &upper2, &empty2); + + switch (strategy) + { + case RANGESTRAT_EQ: + proc = F_RANGE_EQ; + break; + case RANGESTRAT_NE: + proc = F_RANGE_NE; + break; + case RANGESTRAT_OVERLAPS: + proc = F_RANGE_OVERLAPS; + break; + case RANGESTRAT_CONTAINS_ELEM: + case RANGESTRAT_CONTAINS: + proc = F_RANGE_CONTAINS; + break; + case RANGESTRAT_ELEM_CONTAINED_BY: + case RANGESTRAT_CONTAINED_BY: + proc = F_RANGE_CONTAINED_BY; + break; + case RANGESTRAT_BEFORE: + if (empty1 || empty2) + return false; + proc = F_RANGE_BEFORE; + break; + case RANGESTRAT_AFTER: + if (empty1 || empty2) + return false; + proc = F_RANGE_AFTER; + break; + case RANGESTRAT_OVERLEFT: + if (empty1 || empty2) + return false; + proc = F_RANGE_OVERLEFT; + break; + case RANGESTRAT_OVERRIGHT: + if (empty1 || empty2) + return false; + proc = F_RANGE_OVERRIGHT; + break; + case RANGESTRAT_ADJACENT: + if (empty1 || empty2) + return false; + proc = F_RANGE_ADJACENT; + break; + } + + return DatumGetBool(OidFunctionCall2(proc, RangeTypeGetDatum(key), + RangeTypeGetDatum(query))); +} + +/* + * Compare function for PickSplitSortItem. This is actually the + * interesting part of the picksplit algorithm. + * + * We want to separate out empty ranges, bounded ranges, and unbounded + * ranges. We assume that "contains" and "overlaps" are the most + * important queries, so empty ranges will rarely match and unbounded + * ranges frequently will. Bounded ranges should be in the middle. + * + * Empty ranges we push all the way to the left, then bounded ranges + * (sorted on lower bound, then upper), then ranges with no lower + * bound, then ranges with no upper bound; and finally, ranges with no + * upper or lower bound all the way to the right. + */ +static int +sort_item_cmp(const void *a, const void *b) +{ + PickSplitSortItem *i1 = (PickSplitSortItem *)a; + PickSplitSortItem *i2 = (PickSplitSortItem *)b; + RangeType *r1 = i1->data; + RangeType *r2 = i2->data; + + RangeBound lower1, lower2; + RangeBound upper1, upper2; + bool empty1, empty2; + + FunctionCallInfo fcinfo = i1->fcinfo; + + int cmp; + + range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); + range_deserialize(fcinfo, r2, &lower2, &upper2, &empty2); + + if (empty1 || empty2) + { + if (empty1 && empty2) + return 0; + else if (empty1) + return -1; + else if (empty2) + return 1; + else + Assert(false); + } + + /* + * If both lower or both upper bounds are infinite, we sort by + * ascending range size. That means that if both upper bounds are + * infinite, we sort by the lower bound _descending_. That creates + * a slightly odd total order, but keeps the pages with very + * unselective predicates grouped more closely together on the + * right. + */ + if (lower1.infinite || upper1.infinite || + lower2.infinite || upper2.infinite) + { + if (lower1.infinite && lower2.infinite) + return range_cmp_bounds(fcinfo, &upper1, &upper2); + else if (lower1.infinite) + return -1; + else if (lower2.infinite) + return 1; + else if (upper1.infinite && upper2.infinite) + return -1 * range_cmp_bounds(fcinfo, &lower1, &lower2); + else if (upper1.infinite) + return 1; + else if (upper2.infinite) + return -1; + else + Assert(false); + } + + if ((cmp = range_cmp_bounds(fcinfo, &lower1, &lower2)) != 0) + return cmp; + + return range_cmp_bounds(fcinfo, &upper1, &upper2); +} diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index 326f1eee92..1b4d26d659 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -26,6 +26,7 @@ #include "catalog/pg_opclass.h" #include "catalog/pg_operator.h" #include "catalog/pg_proc.h" +#include "catalog/pg_range.h" #include "catalog/pg_statistic.h" #include "catalog/pg_type.h" #include "miscadmin.h" @@ -2250,6 +2251,16 @@ type_is_enum(Oid typid) return (get_typtype(typid) == TYPTYPE_ENUM); } +/* + * type_is_range + * Returns true if the given type is an range type. + */ +bool +type_is_range(Oid typid) +{ + return (get_typtype(typid) == TYPTYPE_RANGE); +} + /* * get_type_category_preferred * @@ -2855,3 +2866,22 @@ get_namespace_name(Oid nspid) else return NULL; } + +Oid +get_range_subtype(Oid rangeOid) +{ + HeapTuple tp; + + tp = SearchSysCache1(RANGETYPE, ObjectIdGetDatum(rangeOid)); + if (HeapTupleIsValid(tp)) + { + Form_pg_range rngtup = (Form_pg_range) GETSTRUCT(tp); + Oid result; + + result = rngtup->rngsubtype; + ReleaseSysCache(tp); + return result; + } + else + return InvalidOid; +} diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c index 99e5f1d9fe..71b09abb23 100644 --- a/src/backend/utils/cache/syscache.c +++ b/src/backend/utils/cache/syscache.c @@ -43,6 +43,7 @@ #include "catalog/pg_operator.h" #include "catalog/pg_opfamily.h" #include "catalog/pg_proc.h" +#include "catalog/pg_range.h" #include "catalog/pg_rewrite.h" #include "catalog/pg_statistic.h" #include "catalog/pg_tablespace.h" @@ -554,6 +555,17 @@ static const struct cachedesc cacheinfo[] = { }, 2048 }, + {RangeRelationId, /* RANGETYPE */ + RangeTypidIndexId, + 1, + { + Anum_pg_range_rngtypid, + 0, + 0, + 0 + }, + 1024 + }, {RelationRelationId, /* RELNAMENSP */ ClassNameNspIndexId, 2, diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c index 0911c8083b..3cc2a7ee07 100644 --- a/src/backend/utils/fmgr/funcapi.c +++ b/src/backend/utils/fmgr/funcapi.c @@ -407,11 +407,13 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args, int nargs = declared_args->dim1; bool have_anyelement_result = false; bool have_anyarray_result = false; + bool have_anyrange_result = false; bool have_anynonarray = false; bool have_anyenum = false; Oid anyelement_type = InvalidOid; Oid anyarray_type = InvalidOid; - Oid anycollation; + Oid anyrange_type = InvalidOid; + Oid anycollation = InvalidOid; int i; /* See if there are any polymorphic outputs; quick out if not */ @@ -433,11 +435,15 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args, have_anyelement_result = true; have_anyenum = true; break; + case ANYRANGEOID: + have_anyrange_result = true; + break; default: break; } } - if (!have_anyelement_result && !have_anyarray_result) + if (!have_anyelement_result && !have_anyarray_result && + !have_anyrange_result) return true; /* @@ -461,20 +467,47 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args, if (!OidIsValid(anyarray_type)) anyarray_type = get_call_expr_argtype(call_expr, i); break; + case ANYRANGEOID: + if (!OidIsValid(anyrange_type)) + anyrange_type = get_call_expr_argtype(call_expr, i); + break; default: break; } } /* If nothing found, parser messed up */ - if (!OidIsValid(anyelement_type) && !OidIsValid(anyarray_type)) + if (!OidIsValid(anyelement_type) && !OidIsValid(anyarray_type) && + !OidIsValid(anyrange_type)) + return false; + + /* + * We can't deduce a range type from the subtype, because there may be + * multiple range types for a single subtype. + */ + if (have_anyrange_result && !OidIsValid(anyrange_type)) return false; /* If needed, deduce one polymorphic type from the other */ if (have_anyelement_result && !OidIsValid(anyelement_type)) - anyelement_type = resolve_generic_type(ANYELEMENTOID, - anyarray_type, - ANYARRAYOID); + { + if (OidIsValid(anyarray_type)) + anyelement_type = resolve_generic_type(ANYELEMENTOID, + anyarray_type, + ANYARRAYOID); + if (OidIsValid(anyrange_type)) + { + Oid subtype = resolve_generic_type(ANYELEMENTOID, + anyrange_type, + ANYRANGEOID); + if (OidIsValid(anyelement_type) && + anyelement_type != subtype) + return false; + else + anyelement_type = subtype; + } + } + if (have_anyarray_result && !OidIsValid(anyarray_type)) anyarray_type = resolve_generic_type(ANYARRAYOID, anyelement_type, @@ -492,7 +525,12 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args, * Identify the collation to use for polymorphic OUT parameters. (It'll * necessarily be the same for both anyelement and anyarray.) */ - anycollation = get_typcollation(OidIsValid(anyelement_type) ? anyelement_type : anyarray_type); + + if (OidIsValid(anyelement_type)) + anycollation = get_typcollation(anyelement_type); + else if (OidIsValid(anyarray_type)) + anycollation = get_typcollation(anyarray_type); + if (OidIsValid(anycollation)) { /* @@ -529,6 +567,14 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args, 0); TupleDescInitEntryCollation(tupdesc, i + 1, anycollation); break; + case ANYRANGEOID: + TupleDescInitEntry(tupdesc, i + 1, + NameStr(tupdesc->attrs[i]->attname), + anyrange_type, + -1, + 0); + TupleDescInitEntryCollation(tupdesc, i + 1, anycollation); + break; default: break; } @@ -552,8 +598,10 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes, { bool have_anyelement_result = false; bool have_anyarray_result = false; + bool have_anyrange_result = false; Oid anyelement_type = InvalidOid; Oid anyarray_type = InvalidOid; + Oid anyrange_type = InvalidOid; int inargno; int i; @@ -597,6 +645,21 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes, argtypes[i] = anyarray_type; } break; + case ANYRANGEOID: + if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE) + have_anyrange_result = true; + else + { + if (!OidIsValid(anyrange_type)) + { + anyrange_type = get_call_expr_argtype(call_expr, + inargno); + if (!OidIsValid(anyrange_type)) + return false; + } + argtypes[i] = anyrange_type; + } + break; default: break; } @@ -605,18 +668,42 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes, } /* Done? */ - if (!have_anyelement_result && !have_anyarray_result) + if (!have_anyelement_result && !have_anyarray_result && + !have_anyrange_result) return true; + /* + * We can't deduce a range type from the subtype, because there may be + * multiple range types for a single subtype. + */ + if (have_anyrange_result && !OidIsValid(anyrange_type)) + return false; + /* If no input polymorphics, parser messed up */ - if (!OidIsValid(anyelement_type) && !OidIsValid(anyarray_type)) + if (!OidIsValid(anyelement_type) && !OidIsValid(anyarray_type) && + !OidIsValid(anyrange_type)) return false; /* If needed, deduce one polymorphic type from the other */ if (have_anyelement_result && !OidIsValid(anyelement_type)) - anyelement_type = resolve_generic_type(ANYELEMENTOID, - anyarray_type, - ANYARRAYOID); + { + if (OidIsValid(anyarray_type)) + anyelement_type = resolve_generic_type(ANYELEMENTOID, + anyarray_type, + ANYARRAYOID); + if (OidIsValid(anyrange_type)) + { + Oid subtype = resolve_generic_type(ANYELEMENTOID, + anyrange_type, + ANYRANGEOID); + if (OidIsValid(anyelement_type) && + anyelement_type != subtype) + return false; + else + anyelement_type = subtype; + } + } + if (have_anyarray_result && !OidIsValid(anyarray_type)) anyarray_type = resolve_generic_type(ANYARRAYOID, anyelement_type, @@ -637,6 +724,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes, case ANYARRAYOID: argtypes[i] = anyarray_type; break; + case ANYRANGEOID: + argtypes[i] = anyrange_type; + break; default: break; } @@ -663,6 +753,7 @@ get_type_func_class(Oid typid) case TYPTYPE_BASE: case TYPTYPE_DOMAIN: case TYPTYPE_ENUM: + case TYPTYPE_RANGE: return TYPEFUNC_SCALAR; case TYPTYPE_PSEUDO: if (typid == RECORDOID) diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index c17b52cea8..88a867fe8e 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -52,6 +52,7 @@ #include "catalog/pg_largeobject.h" #include "catalog/pg_largeobject_metadata.h" #include "catalog/pg_proc.h" +#include "catalog/pg_range.h" #include "catalog/pg_trigger.h" #include "catalog/pg_type.h" #include "libpq/libpq-fs.h" @@ -167,6 +168,7 @@ static void dumpExtension(Archive *fout, ExtensionInfo *extinfo); static void dumpType(Archive *fout, TypeInfo *tyinfo); static void dumpBaseType(Archive *fout, TypeInfo *tyinfo); static void dumpEnumType(Archive *fout, TypeInfo *tyinfo); +static void dumpRangeType(Archive *fout, TypeInfo *tyinfo); static void dumpDomain(Archive *fout, TypeInfo *tyinfo); static void dumpCompositeType(Archive *fout, TypeInfo *tyinfo); static void dumpCompositeTypeColComments(Archive *fout, TypeInfo *tyinfo); @@ -2989,7 +2991,8 @@ getTypes(int *numTypes) * should copy the base type's catId, but then it might capture the * pg_depend entries for the type, which we don't want. */ - if (tyinfo[i].dobj.dump && tyinfo[i].typtype == TYPTYPE_BASE) + if (tyinfo[i].dobj.dump && (tyinfo[i].typtype == TYPTYPE_BASE || + tyinfo[i].typtype == TYPTYPE_RANGE)) { stinfo = (ShellTypeInfo *) malloc(sizeof(ShellTypeInfo)); stinfo->dobj.objType = DO_SHELL_TYPE; @@ -3700,7 +3703,32 @@ getFuncs(int *numFuncs) * so be sure to fetch any such functions. */ - if (g_fout->remoteVersion >= 70300) + if (g_fout->remoteVersion >= 90200) + { + appendPQExpBuffer(query, + "SELECT tableoid, oid, proname, prolang, " + "pronargs, proargtypes, prorettype, proacl, " + "pronamespace, " + "(%s proowner) AS rolname " + "FROM pg_proc p " + "WHERE NOT proisagg AND " + " NOT EXISTS (SELECT 1 FROM pg_depend " + " WHERE classid = 'pg_proc'::regclass AND " + " objid = p.oid AND deptype = 'i') AND " + "(pronamespace != " + "(SELECT oid FROM pg_namespace " + "WHERE nspname = 'pg_catalog')", + username_subquery); + if (binary_upgrade && g_fout->remoteVersion >= 90100) + appendPQExpBuffer(query, + " OR EXISTS(SELECT 1 FROM pg_depend WHERE " + "classid = 'pg_proc'::regclass AND " + "objid = p.oid AND " + "refclassid = 'pg_extension'::regclass AND " + "deptype = 'e')"); + appendPQExpBuffer(query, ")"); + } + else if (g_fout->remoteVersion >= 70300) { appendPQExpBuffer(query, "SELECT tableoid, oid, proname, prolang, " @@ -7309,6 +7337,8 @@ dumpType(Archive *fout, TypeInfo *tyinfo) dumpCompositeType(fout, tyinfo); else if (tyinfo->typtype == TYPTYPE_ENUM) dumpEnumType(fout, tyinfo); + else if (tyinfo->typtype == TYPTYPE_RANGE) + dumpRangeType(fout, tyinfo); } /* @@ -7432,6 +7462,156 @@ dumpEnumType(Archive *fout, TypeInfo *tyinfo) destroyPQExpBuffer(query); } +/* + * dumpRangeType + * writes out to fout the queries to recreate a user-defined range type + */ +static void +dumpRangeType(Archive *fout, TypeInfo *tyinfo) +{ + PQExpBuffer q = createPQExpBuffer(); + PQExpBuffer delq = createPQExpBuffer(); + PQExpBuffer labelq = createPQExpBuffer(); + PQExpBuffer query = createPQExpBuffer(); + PGresult *res; + + Oid collationOid; + Oid opclassOid; + Oid analyzeOid; + Oid canonicalOid; + Oid subdiffOid; + + /* Set proper schema search path */ + selectSourceSchema("pg_catalog"); + + appendPQExpBuffer(query, + "SELECT rngtypid, " + "format_type(rngsubtype, NULL) as rngsubtype, " + "rngsubtype::oid as rngsubtypeoid, " + "opc.opcname AS opcname, " + "CASE WHEN rngcollation = st.typcollation THEN 0 " + " ELSE rngcollation END AS collation, " + "CASE WHEN opcdefault THEN 0 ELSE rngsubopc END " + " AS rngsubopc, " + "(SELECT nspname FROM pg_namespace nsp " + " WHERE nsp.oid = opc.opcnamespace) AS opcnsp, " + "t.typanalyze, t.typanalyze::oid as typanalyzeoid, " + "rngcanonical, rngcanonical::oid as rngcanonicaloid, " + "rngsubdiff, rngsubdiff::oid as rngsubdiffoid " + "FROM pg_catalog.pg_type t, pg_type st, " + " pg_catalog.pg_opclass opc, pg_catalog.pg_range r " + "WHERE t.oid = rngtypid AND st.oid = rngsubtype AND " + " opc.oid = rngsubopc AND rngtypid = '%u'", + tyinfo->dobj.catId.oid); + + res = PQexec(g_conn, query->data); + check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); + + /* + * DROP must be fully qualified in case same name appears in pg_catalog. + * CASCADE shouldn't be required here as for normal types since the I/O + * functions are generic and do not get dropped. + */ + appendPQExpBuffer(delq, "DROP TYPE %s.", + fmtId(tyinfo->dobj.namespace->dobj.name)); + appendPQExpBuffer(delq, "%s;\n", + fmtId(tyinfo->dobj.name)); + + /* We might already have a shell type, but setting pg_type_oid is harmless */ + if (binary_upgrade) + binary_upgrade_set_type_oids_by_type_oid(q, tyinfo->dobj.catId.oid); + + appendPQExpBuffer(q, "CREATE TYPE %s AS RANGE (", + fmtId(tyinfo->dobj.name)); + + /* SUBTYPE */ + appendPQExpBuffer(q, "\n SUBTYPE = %s", + PQgetvalue(res, 0, PQfnumber(res, "rngsubtype"))); + + /* COLLATION */ + collationOid = atooid(PQgetvalue(res, 0, + PQfnumber(res, "collation"))); + if (OidIsValid(collationOid)) + { + CollInfo *coll; + + coll = findCollationByOid(collationOid); + if (coll) + { + /* always schema-qualify, don't try to be smart */ + appendPQExpBuffer(q, ",\n COLLATION = %s.", + fmtId(coll->dobj.namespace->dobj.name)); + appendPQExpBuffer(q, "%s", + fmtId(coll->dobj.name)); + } + } + + /* SUBTYPE_OPCLASS */ + opclassOid = atooid(PQgetvalue(res, 0, + PQfnumber(res, "rngsubopc"))); + if (OidIsValid(opclassOid)) + { + char *opcname = PQgetvalue(res, 0, PQfnumber(res, "opcname")); + char *nspname = PQgetvalue(res, 0, PQfnumber(res, "opcnsp")); + + /* always schema-qualify, don't try to be smart */ + appendPQExpBuffer(q, ",\n SUBTYPE_OPCLASS = %s.", + fmtId(nspname)); + appendPQExpBuffer(q, "%s", fmtId(opcname)); + } + + /* ANALYZE */ + analyzeOid = atooid(PQgetvalue(res, 0, + PQfnumber(res, "typanalyzeoid"))); + if (OidIsValid(analyzeOid)) + appendPQExpBuffer(q, ",\n ANALYZE = %s", + PQgetvalue(res, 0, PQfnumber(res, "typanalyze"))); + + /* CANONICAL */ + canonicalOid = atooid(PQgetvalue(res, 0, + PQfnumber(res, "rngcanonicaloid"))); + if (OidIsValid(canonicalOid)) + appendPQExpBuffer(q, ",\n CANONICAL = %s", + PQgetvalue(res, 0, PQfnumber(res, "rngcanonical"))); + + /* SUBTYPE_DIFF */ + subdiffOid = atooid(PQgetvalue(res, 0, PQfnumber(res, "rngsubdiffoid"))); + if (OidIsValid(subdiffOid)) + appendPQExpBuffer(q, ",\n SUBTYPE_DIFF = %s", + PQgetvalue(res, 0, PQfnumber(res, "rngsubdiff"))); + + appendPQExpBuffer(q, "\n);\n"); + + appendPQExpBuffer(labelq, "TYPE %s", fmtId(tyinfo->dobj.name)); + + if (binary_upgrade) + binary_upgrade_extension_member(q, &tyinfo->dobj, labelq->data); + + ArchiveEntry(fout, tyinfo->dobj.catId, tyinfo->dobj.dumpId, + tyinfo->dobj.name, + tyinfo->dobj.namespace->dobj.name, + NULL, + tyinfo->rolname, false, + "TYPE", SECTION_PRE_DATA, + q->data, delq->data, NULL, + tyinfo->dobj.dependencies, tyinfo->dobj.nDeps, + NULL, NULL); + + /* Dump Type Comments and Security Labels */ + dumpComment(fout, labelq->data, + tyinfo->dobj.namespace->dobj.name, tyinfo->rolname, + tyinfo->dobj.catId, 0, tyinfo->dobj.dumpId); + dumpSecLabel(fout, labelq->data, + tyinfo->dobj.namespace->dobj.name, tyinfo->rolname, + tyinfo->dobj.catId, 0, tyinfo->dobj.dumpId); + + PQclear(res); + destroyPQExpBuffer(q); + destroyPQExpBuffer(delq); + destroyPQExpBuffer(labelq); + destroyPQExpBuffer(query); +} + /* * dumpBaseType * writes out to fout the queries to recreate a user-defined base type diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index c6273c1267..0a0ebcf916 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,7 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 201110221 +/* COMMITTER: please set appropriately */ +#define CATALOG_VERSION_NO 201111111 #endif diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h index 9a8e6ffc8a..0bf2d4d640 100644 --- a/src/include/catalog/indexing.h +++ b/src/include/catalog/indexing.h @@ -303,6 +303,9 @@ DECLARE_UNIQUE_INDEX(pg_extension_oid_index, 3080, on pg_extension using btree(o DECLARE_UNIQUE_INDEX(pg_extension_name_index, 3081, on pg_extension using btree(extname name_ops)); #define ExtensionNameIndexId 3081 +DECLARE_UNIQUE_INDEX(pg_range_rgntypid_index, 3542, on pg_range using btree(rngtypid oid_ops)); +#define RangeTypidIndexId 3542 + /* last step of initialization script: build the indexes declared above */ BUILD_INDICES diff --git a/src/include/catalog/pg_amop.h b/src/include/catalog/pg_amop.h index 3b88c41599..ede0c2ddf6 100644 --- a/src/include/catalog/pg_amop.h +++ b/src/include/catalog/pg_amop.h @@ -709,4 +709,34 @@ DATA(insert ( 3683 3615 3615 5 s 3679 403 0 )); DATA(insert ( 3702 3615 3615 7 s 3693 783 0 )); DATA(insert ( 3702 3615 3615 8 s 3694 783 0 )); +/* + * btree range_ops + */ +DATA(insert ( 3901 3831 3831 1 s 3884 403 0 )); +DATA(insert ( 3901 3831 3831 2 s 3885 403 0 )); +DATA(insert ( 3901 3831 3831 3 s 3882 403 0 )); +DATA(insert ( 3901 3831 3831 4 s 3886 403 0 )); +DATA(insert ( 3901 3831 3831 5 s 3887 403 0 )); + +/* + * hash range_ops + */ +DATA(insert ( 3903 3831 3831 1 s 3882 405 0 )); + +/* + * GiST range_ops + */ +DATA(insert ( 3919 3831 3831 1 s 3882 783 0 )); +DATA(insert ( 3919 3831 3831 2 s 3883 783 0 )); +DATA(insert ( 3919 3831 3831 3 s 3888 783 0 )); +DATA(insert ( 3919 3831 2776 4 s 3889 783 0 )); +DATA(insert ( 3919 2776 3831 5 s 3891 783 0 )); +DATA(insert ( 3919 3831 3831 6 s 3890 783 0 )); +DATA(insert ( 3919 3831 3831 7 s 3892 783 0 )); +DATA(insert ( 3919 3831 3831 8 s 3893 783 0 )); +DATA(insert ( 3919 3831 3831 9 s 3894 783 0 )); +DATA(insert ( 3919 3831 3831 10 s 3895 783 0 )); +DATA(insert ( 3919 3831 3831 11 s 3896 783 0 )); +DATA(insert ( 3919 3831 3831 12 s 3897 783 0 )); + #endif /* PG_AMOP_H */ diff --git a/src/include/catalog/pg_amproc.h b/src/include/catalog/pg_amproc.h index 9e2da2c30b..e5d43f7c1d 100644 --- a/src/include/catalog/pg_amproc.h +++ b/src/include/catalog/pg_amproc.h @@ -336,5 +336,14 @@ DATA(insert ( 3659 3614 3614 4 3658 )); DATA(insert ( 3659 3614 3614 5 2700 )); DATA(insert ( 3626 3614 3614 1 3622 )); DATA(insert ( 3683 3615 3615 1 3668 )); +DATA(insert ( 3901 3831 3831 1 3870 )); +DATA(insert ( 3903 3831 3831 1 3902 )); +DATA(insert ( 3919 3831 3831 1 3875 )); +DATA(insert ( 3919 3831 3831 2 3876 )); +DATA(insert ( 3919 3831 3831 3 3877 )); +DATA(insert ( 3919 3831 3831 4 3878 )); +DATA(insert ( 3919 3831 3831 5 3879 )); +DATA(insert ( 3919 3831 3831 6 3880 )); +DATA(insert ( 3919 3831 3831 7 3881 )); #endif /* PG_AMPROC_H */ diff --git a/src/include/catalog/pg_opclass.h b/src/include/catalog/pg_opclass.h index d723b2561a..05ffa0384c 100644 --- a/src/include/catalog/pg_opclass.h +++ b/src/include/catalog/pg_opclass.h @@ -213,5 +213,8 @@ DATA(insert ( 783 tsvector_ops PGNSP PGUID 3655 3614 t 3642 )); DATA(insert ( 2742 tsvector_ops PGNSP PGUID 3659 3614 t 25 )); DATA(insert ( 403 tsquery_ops PGNSP PGUID 3683 3615 t 0 )); DATA(insert ( 783 tsquery_ops PGNSP PGUID 3702 3615 t 20 )); +DATA(insert ( 403 range_ops PGNSP PGUID 3901 3831 t 0 )); +DATA(insert ( 405 range_ops PGNSP PGUID 3903 3831 t 0 )); +DATA(insert ( 783 range_ops PGNSP PGUID 3919 3831 t 0 )); #endif /* PG_OPCLASS_H */ diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h index 64f1391b00..f587f5b198 100644 --- a/src/include/catalog/pg_operator.h +++ b/src/include/catalog/pg_operator.h @@ -1661,6 +1661,45 @@ DESCR("less than or equal"); DATA(insert OID = 2993 ( ">=" PGNSP PGUID b f f 2249 2249 16 2992 2990 record_ge scalargtsel scalargtjoinsel )); DESCR("greater than or equal"); +/* generic range type operators */ +DATA(insert OID = 3882 ( "=" PGNSP PGUID b t t 3831 3831 16 3882 3883 range_eq eqsel eqjoinsel )); +DESCR("equal"); +DATA(insert OID = 3883 ( "<>" PGNSP PGUID b f f 3831 3831 16 3883 3882 range_ne neqsel neqjoinsel )); +DESCR("not equal"); +DATA(insert OID = 3884 ( "<" PGNSP PGUID b f f 3831 3831 16 3887 3886 range_lt scalarltsel scalarltjoinsel )); +DESCR("less than"); +DATA(insert OID = 3885 ( "<=" PGNSP PGUID b f f 3831 3831 16 3886 3887 range_le scalarltsel scalarltjoinsel )); +DESCR("less than or equal"); +DATA(insert OID = 3886 ( ">=" PGNSP PGUID b f f 3831 3831 16 3885 3884 range_ge scalargtsel scalargtjoinsel )); +DESCR("greater than or equal"); +DATA(insert OID = 3887 ( ">" PGNSP PGUID b f f 3831 3831 16 3884 3885 range_gt scalargtsel scalargtjoinsel )); +DESCR("greater than"); +DATA(insert OID = 3888 ( "&&" PGNSP PGUID b f f 3831 3831 16 3888 0 3857 - - )); +DESCR("overlaps"); +DATA(insert OID = 3889 ( "@>" PGNSP PGUID b f f 3831 2776 16 3891 0 3858 - - )); +DESCR("contains"); +DATA(insert OID = 3890 ( "@>" PGNSP PGUID b f f 3831 3831 16 3892 0 3859 - - )); +DESCR("contains"); +DATA(insert OID = 3891 ( "<@" PGNSP PGUID b f f 2776 3831 16 3889 0 3860 - - )); +DESCR("contained by"); +DATA(insert OID = 3892 ( "<@" PGNSP PGUID b f f 3831 3831 16 3890 0 3861 - - )); +DESCR("contained by"); +DATA(insert OID = 3893 ( "<<" PGNSP PGUID b f f 3831 3831 16 0 0 before scalarltsel scalarltjoinsel )); +DESCR("left of"); +DATA(insert OID = 3894 ( ">>" PGNSP PGUID b f f 3831 3831 16 0 0 after scalargtsel scalargtjoinsel )); +DESCR("right of"); +DATA(insert OID = 3895 ( "&<" PGNSP PGUID b f f 3831 3831 16 0 0 overleft scalarltsel scalarltjoinsel )); +DESCR("overlaps to left"); +DATA(insert OID = 3896 ( "&>" PGNSP PGUID b f f 3831 3831 16 0 0 overright scalargtsel scalargtjoinsel )); +DESCR("overlaps to right"); +DATA(insert OID = 3897 ( "-|-" PGNSP PGUID b f f 3831 3831 16 3897 0 adjacent - - )); +DESCR("adjacent"); +DATA(insert OID = 3898 ( "+" PGNSP PGUID b f f 3831 3831 3831 3898 0 range_union - - )); +DESCR("range union"); +DATA(insert OID = 3899 ( "-" PGNSP PGUID b f f 3831 3831 3831 0 0 minus - - )); +DESCR("range difference"); +DATA(insert OID = 3900 ( "*" PGNSP PGUID b f f 3831 3831 3831 3900 0 range_intersect - - )); +DESCR("intersection"); /* * function prototypes diff --git a/src/include/catalog/pg_opfamily.h b/src/include/catalog/pg_opfamily.h index 548727dbd2..5ea949bec6 100644 --- a/src/include/catalog/pg_opfamily.h +++ b/src/include/catalog/pg_opfamily.h @@ -139,5 +139,8 @@ DATA(insert OID = 3655 ( 783 tsvector_ops PGNSP PGUID )); DATA(insert OID = 3659 ( 2742 tsvector_ops PGNSP PGUID )); DATA(insert OID = 3683 ( 403 tsquery_ops PGNSP PGUID )); DATA(insert OID = 3702 ( 783 tsquery_ops PGNSP PGUID )); +DATA(insert OID = 3901 ( 403 range_ops PGNSP PGUID )); +DATA(insert OID = 3903 ( 405 range_ops PGNSP PGUID )); +DATA(insert OID = 3919 ( 783 range_ops PGNSP PGUID )); #endif /* PG_OPFAMILY_H */ diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 64b7a6a314..3b654ff7c4 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -4334,6 +4334,153 @@ DESCR("fetch the last row value"); DATA(insert OID = 3114 ( nth_value PGNSP PGUID 12 1 0 0 0 f t f t f i 2 0 2283 "2283 23" _null_ _null_ _null_ _null_ window_nth_value _null_ _null_ _null_ )); DESCR("fetch the Nth row value"); +/* procs for range types */ +DATA(insert OID = 3832 ( anyrange_in PGNSP PGUID 12 1 0 0 0 f f f t f s 3 0 3831 "2275 26 23" _null_ _null_ _null_ _null_ anyrange_in _null_ _null_ _null_ )); +DESCR("I/O"); +DATA(insert OID = 3833 ( anyrange_out PGNSP PGUID 12 1 0 0 0 f f f t f s 1 0 2275 "3831" _null_ _null_ _null_ _null_ anyrange_out _null_ _null_ _null_ )); +DESCR("I/O"); +DATA(insert OID = 3834 ( range_in PGNSP PGUID 12 1 0 0 0 f f f t f s 3 0 3831 "2275 26 23" _null_ _null_ _null_ _null_ range_in _null_ _null_ _null_ )); +DESCR("I/O"); +DATA(insert OID = 3835 ( range_out PGNSP PGUID 12 1 0 0 0 f f f t f s 1 0 2275 "3831" _null_ _null_ _null_ _null_ range_out _null_ _null_ _null_ )); +DESCR("I/O"); +DATA(insert OID = 3836 ( range_recv PGNSP PGUID 12 1 0 0 0 f f f t f s 3 0 3831 "2281 26 23" _null_ _null_ _null_ _null_ range_recv _null_ _null_ _null_ )); +DESCR("I/O"); +DATA(insert OID = 3837 ( range_send PGNSP PGUID 12 1 0 0 0 f f f t f s 1 0 17 "3831" _null_ _null_ _null_ _null_ range_send _null_ _null_ _null_ )); +DESCR("I/O"); +DATA(insert OID = 3848 ( lower PGNSP PGUID 12 1 0 0 0 f f f t f i 1 0 2283 "3831" _null_ _null_ _null_ _null_ range_lower _null_ _null_ _null_ )); +DESCR("return the range's lower bound"); +DATA(insert OID = 3849 ( upper PGNSP PGUID 12 1 0 0 0 f f f t f i 1 0 2283 "3831" _null_ _null_ _null_ _null_ range_upper _null_ _null_ _null_ )); +DESCR("return the range's upper bound"); +DATA(insert OID = 3850 ( isempty PGNSP PGUID 12 1 0 0 0 f f f t f i 1 0 16 "3831" _null_ _null_ _null_ _null_ range_empty _null_ _null_ _null_ )); +DESCR("is the range empty?"); +DATA(insert OID = 3851 ( lower_inc PGNSP PGUID 12 1 0 0 0 f f f t f i 1 0 16 "3831" _null_ _null_ _null_ _null_ range_lower_inc _null_ _null_ _null_ )); +DESCR("is the range's lower bound inclusive?"); +DATA(insert OID = 3852 ( upper_inc PGNSP PGUID 12 1 0 0 0 f f f t f i 1 0 16 "3831" _null_ _null_ _null_ _null_ range_upper_inc _null_ _null_ _null_ )); +DESCR("is the range's upper bound inclusive?"); +DATA(insert OID = 3853 ( lower_inf PGNSP PGUID 12 1 0 0 0 f f f t f i 1 0 16 "3831" _null_ _null_ _null_ _null_ range_lower_inf _null_ _null_ _null_ )); +DESCR("is the range's lower bound infinite?"); +DATA(insert OID = 3854 ( upper_inf PGNSP PGUID 12 1 0 0 0 f f f t f i 1 0 16 "3831" _null_ _null_ _null_ _null_ range_upper_inf _null_ _null_ _null_ )); +DESCR("is the range's upper bound infinite?"); +DATA(insert OID = 3855 ( range_eq PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 16 "3831 3831" _null_ _null_ _null_ _null_ range_eq _null_ _null_ _null_ )); +DESCR("implementation of = operator"); +DATA(insert OID = 3856 ( range_ne PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 16 "3831 3831" _null_ _null_ _null_ _null_ range_ne _null_ _null_ _null_ )); +DESCR("implementation of <> operator"); +DATA(insert OID = 3857 ( overlaps PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 16 "3831 3831" _null_ _null_ _null_ _null_ range_overlaps _null_ _null_ _null_ )); +DESCR("implementation of && operator"); +DATA(insert OID = 3858 ( contains PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 16 "3831 2776" _null_ _null_ _null_ _null_ range_contains_elem _null_ _null_ _null_ )); +DESCR("implementation of @> operator"); +DATA(insert OID = 3859 ( contains PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 16 "3831 3831" _null_ _null_ _null_ _null_ range_contains _null_ _null_ _null_ )); +DESCR("implementation of @> operator"); +DATA(insert OID = 3860 ( contained_by PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 16 "2776 3831" _null_ _null_ _null_ _null_ elem_contained_by_range _null_ _null_ _null_ )); +DESCR("implementation of <@ operator"); +DATA(insert OID = 3861 ( contained_by PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 16 "3831 3831" _null_ _null_ _null_ _null_ range_contained_by _null_ _null_ _null_ )); +DESCR("implementation of <@ operator"); +DATA(insert OID = 3862 ( adjacent PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 16 "3831 3831" _null_ _null_ _null_ _null_ range_adjacent _null_ _null_ _null_ )); +DESCR("implementation of -|- operator"); +DATA(insert OID = 3863 ( before PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 16 "3831 3831" _null_ _null_ _null_ _null_ range_before _null_ _null_ _null_ )); +DESCR("implementation of << operator"); +DATA(insert OID = 3864 ( after PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 16 "3831 3831" _null_ _null_ _null_ _null_ range_after _null_ _null_ _null_ )); +DESCR("implementation of >> operator"); +DATA(insert OID = 3865 ( overleft PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 16 "3831 3831" _null_ _null_ _null_ _null_ range_overleft _null_ _null_ _null_ )); +DESCR("implementation of &< operator"); +DATA(insert OID = 3866 ( overright PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 16 "3831 3831" _null_ _null_ _null_ _null_ range_overright _null_ _null_ _null_ )); +DESCR("implementation of &> operator"); +DATA(insert OID = 3867 ( range_union PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 3831 "3831 3831" _null_ _null_ _null_ _null_ range_union _null_ _null_ _null_ )); +DESCR("implementation of + operator"); +DATA(insert OID = 3868 ( range_intersect PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 3831 "3831 3831" _null_ _null_ _null_ _null_ range_intersect _null_ _null_ _null_ )); +DESCR("implementation of * operator"); +DATA(insert OID = 3869 ( minus PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 3831 "3831 3831" _null_ _null_ _null_ _null_ range_minus _null_ _null_ _null_ )); +DESCR("implementation of - operator"); +DATA(insert OID = 3870 ( range_cmp PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 23 "3831 3831" _null_ _null_ _null_ _null_ range_cmp _null_ _null_ _null_ )); +DESCR("less-equal-greater"); +DATA(insert OID = 3871 ( range_lt PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 16 "3831 3831" _null_ _null_ _null_ _null_ range_lt _null_ _null_ _null_ )); +DATA(insert OID = 3872 ( range_le PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 16 "3831 3831" _null_ _null_ _null_ _null_ range_le _null_ _null_ _null_ )); +DATA(insert OID = 3873 ( range_ge PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 16 "3831 3831" _null_ _null_ _null_ _null_ range_ge _null_ _null_ _null_ )); +DATA(insert OID = 3874 ( range_gt PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 16 "3831 3831" _null_ _null_ _null_ _null_ range_gt _null_ _null_ _null_ )); +DATA(insert OID = 3875 ( range_gist_consistent PGNSP PGUID 12 1 0 0 0 f f f t f i 5 0 16 "2281 3831 21 26 2281" _null_ _null_ _null_ _null_ range_gist_consistent _null_ _null_ _null_ )); +DESCR("GiST support"); +DATA(insert OID = 3876 ( range_gist_union PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 2281 "2281 2281" _null_ _null_ _null_ _null_ range_gist_union _null_ _null_ _null_ )); +DESCR("GiST support"); +DATA(insert OID = 3877 ( range_gist_compress PGNSP PGUID 12 1 0 0 0 f f f t f i 1 0 2281 "2281" _null_ _null_ _null_ _null_ range_gist_compress _null_ _null_ _null_ )); +DESCR("GiST support"); +DATA(insert OID = 3878 ( range_gist_decompress PGNSP PGUID 12 1 0 0 0 f f f t f i 1 0 2281 "2281" _null_ _null_ _null_ _null_ range_gist_decompress _null_ _null_ _null_ )); +DESCR("GiST support"); +DATA(insert OID = 3879 ( range_gist_penalty PGNSP PGUID 12 1 0 0 0 f f f t f i 3 0 2281 "2281 2281 2281" _null_ _null_ _null_ _null_ range_gist_penalty _null_ _null_ _null_ )); +DESCR("GiST support"); +DATA(insert OID = 3880 ( range_gist_picksplit PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 2281 "2281 2281" _null_ _null_ _null_ _null_ range_gist_picksplit _null_ _null_ _null_ )); +DESCR("GiST support"); +DATA(insert OID = 3881 ( range_gist_same PGNSP PGUID 12 1 0 0 0 f f f t f i 3 0 2281 "2281 2281 2281" _null_ _null_ _null_ _null_ range_gist_same _null_ _null_ _null_ )); +DESCR("GiST support"); +DATA(insert OID = 3902 ( hash_range PGNSP PGUID 12 1 0 0 0 f f f t f i 1 0 23 "3831" _null_ _null_ _null_ _null_ hash_range _null_ _null_ _null_ )); +DESCR("hash a range"); +DATA(insert OID = 3914 ( int4range_canonical PGNSP PGUID 12 1 0 0 0 f f f t f i 1 0 3904 "3904" _null_ _null_ _null_ _null_ int4range_canonical _null_ _null_ _null_ )); +DESCR("convert an int4 range to canonical form"); +DATA(insert OID = 3928 ( int8range_canonical PGNSP PGUID 12 1 0 0 0 f f f t f i 1 0 3926 "3926" _null_ _null_ _null_ _null_ int8range_canonical _null_ _null_ _null_ )); +DESCR("convert an int8 range to canonical form"); +DATA(insert OID = 3915 ( daterange_canonical PGNSP PGUID 12 1 0 0 0 f f f t f i 1 0 3906 "3906" _null_ _null_ _null_ _null_ daterange_canonical _null_ _null_ _null_ )); +DESCR("convert a date range to canonical form"); +DATA(insert OID = 3922 ( int4range_subdiff PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 701 "23 23" _null_ _null_ _null_ _null_ int4range_subdiff _null_ _null_ _null_ )); +DESCR("float8 difference of two int4 values"); +DATA(insert OID = 3923 ( int8range_subdiff PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 701 "20 20" _null_ _null_ _null_ _null_ int8range_subdiff _null_ _null_ _null_ )); +DESCR("float8 difference of two int8 values"); +DATA(insert OID = 3924 ( numrange_subdiff PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 701 "1700 1700" _null_ _null_ _null_ _null_ numrange_subdiff _null_ _null_ _null_ )); +DESCR("float8 difference of two numeric values"); +DATA(insert OID = 3925 ( daterange_subdiff PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 701 "1082 1082" _null_ _null_ _null_ _null_ daterange_subdiff _null_ _null_ _null_ )); +DESCR("float8 difference of two date values"); +DATA(insert OID = 3929 ( tsrange_subdiff PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 701 "1114 1114" _null_ _null_ _null_ _null_ tsrange_subdiff _null_ _null_ _null_ )); +DESCR("float8 difference of two timestamp values"); +DATA(insert OID = 3930 ( tstzrange_subdiff PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 701 "1184 1184" _null_ _null_ _null_ _null_ tstzrange_subdiff _null_ _null_ _null_ )); +DESCR("float8 difference of two timestamp with time zone values"); + + +DATA(insert OID = 3838 ( int4range PGNSP PGUID 12 1 0 0 0 f f f f f i 0 0 3904 "" _null_ _null_ _null_ _null_ range_constructor0 _null_ _null_ _null_ )); +DESCR("int4range constructor"); +DATA(insert OID = 3839 ( int4range PGNSP PGUID 12 1 0 0 0 f f f f f i 1 0 3904 "23" _null_ _null_ _null_ _null_ range_constructor1 _null_ _null_ _null_ )); +DESCR("int4range constructor"); +DATA(insert OID = 3840 ( int4range PGNSP PGUID 12 1 0 0 0 f f f f f i 2 0 3904 "23 23" _null_ _null_ _null_ _null_ range_constructor2 _null_ _null_ _null_ )); +DESCR("int4range constructor"); +DATA(insert OID = 3841 ( int4range PGNSP PGUID 12 1 0 0 0 f f f f f i 3 0 3904 "23 23 25" _null_ _null_ _null_ _null_ range_constructor3 _null_ _null_ _null_ )); +DESCR("int4range constructor"); +DATA(insert OID = 3842 ( numrange PGNSP PGUID 12 1 0 0 0 f f f f f i 0 0 3906 "" _null_ _null_ _null_ _null_ range_constructor0 _null_ _null_ _null_ )); +DESCR("numrange constructor"); +DATA(insert OID = 3843 ( numrange PGNSP PGUID 12 1 0 0 0 f f f f f i 1 0 3906 "1700" _null_ _null_ _null_ _null_ range_constructor1 _null_ _null_ _null_ )); +DESCR("numrange constructor"); +DATA(insert OID = 3844 ( numrange PGNSP PGUID 12 1 0 0 0 f f f f f i 2 0 3906 "1700 1700" _null_ _null_ _null_ _null_ range_constructor2 _null_ _null_ _null_ )); +DESCR("numrange constructor"); +DATA(insert OID = 3845 ( numrange PGNSP PGUID 12 1 0 0 0 f f f f f i 3 0 3906 "1700 1700 25" _null_ _null_ _null_ _null_ range_constructor3 _null_ _null_ _null_ )); +DESCR("numrange constructor"); +DATA(insert OID = 3846 ( tsrange PGNSP PGUID 12 1 0 0 0 f f f f f i 0 0 3908 "" _null_ _null_ _null_ _null_ range_constructor0 _null_ _null_ _null_ )); +DESCR("tsrange constructor"); +DATA(insert OID = 3847 ( tsrange PGNSP PGUID 12 1 0 0 0 f f f f f i 1 0 3908 "1114" _null_ _null_ _null_ _null_ range_constructor1 _null_ _null_ _null_ )); +DESCR("tsrange constructor"); +DATA(insert OID = 3933 ( tsrange PGNSP PGUID 12 1 0 0 0 f f f f f i 2 0 3908 "1114 1114" _null_ _null_ _null_ _null_ range_constructor2 _null_ _null_ _null_ )); +DESCR("tsrange constructor"); +DATA(insert OID = 3934 ( tsrange PGNSP PGUID 12 1 0 0 0 f f f f f i 3 0 3908 "1114 1114 25" _null_ _null_ _null_ _null_ range_constructor3 _null_ _null_ _null_ )); +DESCR("tsrange constructor"); +DATA(insert OID = 3935 ( tstzrange PGNSP PGUID 12 1 0 0 0 f f f f f i 0 0 3910 "" _null_ _null_ _null_ _null_ range_constructor0 _null_ _null_ _null_ )); +DESCR("tstzrange constructor"); +DATA(insert OID = 3936 ( tstzrange PGNSP PGUID 12 1 0 0 0 f f f f f i 1 0 3910 "1184" _null_ _null_ _null_ _null_ range_constructor1 _null_ _null_ _null_ )); +DESCR("tstzrange constructor"); +DATA(insert OID = 3937 ( tstzrange PGNSP PGUID 12 1 0 0 0 f f f f f i 2 0 3910 "1184 1184" _null_ _null_ _null_ _null_ range_constructor2 _null_ _null_ _null_ )); +DESCR("tstzrange constructor"); +DATA(insert OID = 3938 ( tstzrange PGNSP PGUID 12 1 0 0 0 f f f f f i 3 0 3910 "1184 1184 25" _null_ _null_ _null_ _null_ range_constructor3 _null_ _null_ _null_ )); +DESCR("tstzrange constructor"); +DATA(insert OID = 3939 ( daterange PGNSP PGUID 12 1 0 0 0 f f f f f i 0 0 3912 "" _null_ _null_ _null_ _null_ range_constructor0 _null_ _null_ _null_ )); +DESCR("daterange constructor"); +DATA(insert OID = 3940 ( daterange PGNSP PGUID 12 1 0 0 0 f f f f f i 1 0 3912 "1082" _null_ _null_ _null_ _null_ range_constructor1 _null_ _null_ _null_ )); +DESCR("daterange constructor"); +DATA(insert OID = 3941 ( daterange PGNSP PGUID 12 1 0 0 0 f f f f f i 2 0 3912 "1082 1082" _null_ _null_ _null_ _null_ range_constructor2 _null_ _null_ _null_ )); +DESCR("daterange constructor"); +DATA(insert OID = 3942 ( daterange PGNSP PGUID 12 1 0 0 0 f f f f f i 3 0 3912 "1082 1082 25" _null_ _null_ _null_ _null_ range_constructor3 _null_ _null_ _null_ )); +DESCR("daterange constructor"); +DATA(insert OID = 3943 ( int8range PGNSP PGUID 12 1 0 0 0 f f f f f i 0 0 3926 "" _null_ _null_ _null_ _null_ range_constructor0 _null_ _null_ _null_ )); +DESCR("int8range constructor"); +DATA(insert OID = 3944 ( int8range PGNSP PGUID 12 1 0 0 0 f f f f f i 1 0 3926 "20" _null_ _null_ _null_ _null_ range_constructor1 _null_ _null_ _null_ )); +DESCR("int8range constructor"); +DATA(insert OID = 3945 ( int8range PGNSP PGUID 12 1 0 0 0 f f f f f i 2 0 3926 "20 20" _null_ _null_ _null_ _null_ range_constructor2 _null_ _null_ _null_ )); +DESCR("int8range constructor"); +DATA(insert OID = 3946 ( int8range PGNSP PGUID 12 1 0 0 0 f f f f f i 3 0 3926 "20 20 25" _null_ _null_ _null_ _null_ range_constructor3 _null_ _null_ _null_ )); +DESCR("int8range constructor"); /* * Symbolic values for provolatile column: these indicate whether the result diff --git a/src/include/catalog/pg_range.h b/src/include/catalog/pg_range.h new file mode 100644 index 0000000000..19b437db8d --- /dev/null +++ b/src/include/catalog/pg_range.h @@ -0,0 +1,84 @@ +/*------------------------------------------------------------------------- + * + * pg_range.h + * definition of the system "range" relation (pg_range) + * along with the relation's initial contents. + * + * + * Copyright (c) 2006-2010, PostgreSQL Global Development Group + * + * src/include/catalog/pg_range.h + * + * NOTES + * the genbki.pl script reads this file and generates .bki + * information from the DATA() statements. + * + * XXX do NOT break up DATA() statements into multiple lines! + * the scripts are not as smart as you might think... + * + *------------------------------------------------------------------------- + */ +#ifndef PG_RANGE_H +#define PG_RANGE_H + +#include "catalog/genbki.h" + +/* ---------------- + * pg_range definition. cpp turns this into + * typedef struct FormData_pg_range + * ---------------- + */ +#define RangeRelationId 3541 + +CATALOG(pg_range,3541) BKI_WITHOUT_OIDS +{ + Oid rngtypid; /* OID of owning range type */ + Oid rngsubtype; /* OID of range's subtype */ + Oid rngcollation; /* collation for this range type, or 0 */ + Oid rngsubopc; /* subtype's btree opclass */ + regproc rngcanonical; /* canonicalize range, or 0 */ + regproc rngsubdiff; /* subtype difference as a float8 (for GiST) */ +} FormData_pg_range; + +/* ---------------- + * Form_pg_range corresponds to a pointer to a tuple with + * the format of pg_range relation. + * ---------------- + */ +typedef FormData_pg_range *Form_pg_range; + +/* ---------------- + * compiler constants for pg_range + * ---------------- + */ +#define Natts_pg_range 6 +#define Anum_pg_range_rngtypid 1 +#define Anum_pg_range_rngsubtype 2 +#define Anum_pg_range_rngcollation 3 +#define Anum_pg_range_rngsubopc 4 +#define Anum_pg_range_rngcanonical 5 +#define Anum_pg_range_rngsubdiff 6 + +#define RANGE_DEFAULT_FLAGS "[)" + +/* + * prototypes for functions in pg_range.c + */ + +extern void RangeCreate(Oid rangeTypeOid, Oid rangeSubType, Oid rangeCollation, + Oid rangeSubOpclass, RegProcedure rangeCanonical, + RegProcedure rangeSubDiff); +extern void RangeDelete(Oid rangeTypeOid); + +/* ---------------- + * initial contents of pg_range + * ---------------- + */ +DATA(insert ( 3904 23 0 1978 int4range_canonical int4range_subdiff)); +DATA(insert ( 3906 1700 0 10037 - numrange_subdiff)); +DATA(insert ( 3908 1114 0 10054 - tsrange_subdiff)); +DATA(insert ( 3910 1184 0 10047 - tstzrange_subdiff)); +DATA(insert ( 3912 1082 0 10019 daterange_canonical daterange_subdiff)); +DATA(insert ( 3926 20 0 10029 int8range_canonical int8range_subdiff)); + +#endif /* PG_RANGE_H */ diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h index d72ca2342f..b24fbc97f4 100644 --- a/src/include/catalog/pg_type.h +++ b/src/include/catalog/pg_type.h @@ -591,6 +591,28 @@ DATA(insert OID = 2970 ( txid_snapshot PGNSP PGUID -1 f b U f t \054 0 0 2949 tx DESCR("txid snapshot"); DATA(insert OID = 2949 ( _txid_snapshot PGNSP PGUID -1 f b A f t \054 0 2970 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 0 _null_ _null_ )); +/* range types */ + +DATA(insert OID = 3904 ( int4range PGNSP PGUID -1 f r R f t \054 0 0 3905 range_in range_out range_recv range_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DESCR("range of int4s"); +#define INT4RANGEOID 3904 +DATA(insert OID = 3905 ( _int4range PGNSP PGUID -1 f b A f t \054 0 3904 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 3906 ( numrange PGNSP PGUID -1 f r R f t \054 0 0 3907 range_in range_out range_recv range_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DESCR("range of numerics"); +DATA(insert OID = 3907 ( _numrange PGNSP PGUID -1 f b A f t \054 0 3906 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 3908 ( tsrange PGNSP PGUID -1 f r R f t \054 0 0 3909 range_in range_out range_recv range_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DESCR("range of timestamps"); +DATA(insert OID = 3909 ( _tsrange PGNSP PGUID -1 f b A f t \054 0 3908 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 3910 ( tstzrange PGNSP PGUID -1 f r R f t \054 0 0 3911 range_in range_out range_recv range_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DESCR("range of timestamps with time zone"); +DATA(insert OID = 3911 ( _tstzrange PGNSP PGUID -1 f b A f t \054 0 3910 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 3912 ( daterange PGNSP PGUID -1 f r R f t \054 0 0 3913 range_in range_out range_recv range_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DESCR("range of dates"); +DATA(insert OID = 3913 ( _daterange PGNSP PGUID -1 f b A f t \054 0 3912 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 3926 ( int8range PGNSP PGUID -1 f r R f t \054 0 0 3927 range_in range_out range_recv range_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DESCR("range of int8s"); +DATA(insert OID = 3927 ( _int8range PGNSP PGUID -1 f b A f t \054 0 3926 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); + /* * pseudo-types * @@ -632,6 +654,8 @@ DATA(insert OID = 3500 ( anyenum PGNSP PGUID 4 t p P f t \054 0 0 0 anyenum_in #define ANYENUMOID 3500 DATA(insert OID = 3115 ( fdw_handler PGNSP PGUID 4 t p P f t \054 0 0 0 fdw_handler_in fdw_handler_out - - - - - i p f 0 -1 0 0 _null_ _null_ )); #define FDW_HANDLEROID 3115 +DATA(insert OID = 3831 ( anyrange PGNSP PGUID 4 t p P f t \054 0 0 0 anyrange_in anyrange_out - - - - - i p f 0 -1 0 0 _null_ _null_ )); +#define ANYRANGEOID 3831 /* @@ -642,6 +666,7 @@ DATA(insert OID = 3115 ( fdw_handler PGNSP PGUID 4 t p P f t \054 0 0 0 fdw_han #define TYPTYPE_DOMAIN 'd' /* domain over another type */ #define TYPTYPE_ENUM 'e' /* enumerated type */ #define TYPTYPE_PSEUDO 'p' /* pseudo-type */ +#define TYPTYPE_RANGE 'r' /* range type */ #define TYPCATEGORY_INVALID '\0' /* not an allowed category */ #define TYPCATEGORY_ARRAY 'A' @@ -653,6 +678,7 @@ DATA(insert OID = 3115 ( fdw_handler PGNSP PGUID 4 t p P f t \054 0 0 0 fdw_han #define TYPCATEGORY_NETWORK 'I' /* think INET */ #define TYPCATEGORY_NUMERIC 'N' #define TYPCATEGORY_PSEUDOTYPE 'P' +#define TYPCATEGORY_RANGE 'R' #define TYPCATEGORY_STRING 'S' #define TYPCATEGORY_TIMESPAN 'T' #define TYPCATEGORY_USER 'U' @@ -664,6 +690,7 @@ DATA(insert OID = 3115 ( fdw_handler PGNSP PGUID 4 t p P f t \054 0 0 0 fdw_han ((typid) == ANYELEMENTOID || \ (typid) == ANYARRAYOID || \ (typid) == ANYNONARRAYOID || \ - (typid) == ANYENUMOID) + (typid) == ANYENUMOID || \ + (typid) == ANYRANGEOID) #endif /* PG_TYPE_H */ diff --git a/src/include/commands/typecmds.h b/src/include/commands/typecmds.h index 429a964f91..0c32895863 100644 --- a/src/include/commands/typecmds.h +++ b/src/include/commands/typecmds.h @@ -23,6 +23,7 @@ extern void DefineType(List *names, List *parameters); extern void RemoveTypeById(Oid typeOid); extern void DefineDomain(CreateDomainStmt *stmt); extern void DefineEnum(CreateEnumStmt *stmt); +extern void DefineRange(CreateRangeStmt *stmt); extern void AlterEnum(AlterEnumStmt *stmt); extern Oid DefineCompositeType(const RangeVar *typevar, List *coldeflist); extern Oid AssignTypeArrayOid(void); diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 7aa299485f..824d8b5dc9 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -346,6 +346,7 @@ typedef enum NodeTag T_ReassignOwnedStmt, T_CompositeTypeStmt, T_CreateEnumStmt, + T_CreateRangeStmt, T_AlterEnumStmt, T_AlterTSDictionaryStmt, T_AlterTSConfigurationStmt, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 9998e2f24d..af6565e7e4 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2334,6 +2334,17 @@ typedef struct AlterEnumStmt bool newValIsAfter; /* place new enum value after neighbor? */ } AlterEnumStmt; +/* ---------------------- + * Create Type Statement, range types + * ---------------------- + */ +typedef struct CreateRangeStmt +{ + NodeTag type; + List *typeName; /* qualified name (list of Value strings) */ + List *params; /* range parameters (list of DefElem) */ +} CreateRangeStmt; + /* ---------------------- * Create View Statement * ---------------------- diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h index 215951589a..d3ad4f1428 100644 --- a/src/include/utils/lsyscache.h +++ b/src/include/utils/lsyscache.h @@ -119,6 +119,7 @@ extern Node *get_typdefault(Oid typid); extern char get_typtype(Oid typid); extern bool type_is_rowtype(Oid typid); extern bool type_is_enum(Oid typid); +extern bool type_is_range(Oid typid); extern void get_type_category_preferred(Oid typid, char *typcategory, bool *typispreferred); @@ -147,6 +148,7 @@ extern void free_attstatsslot(Oid atttype, Datum *values, int nvalues, float4 *numbers, int nnumbers); extern char *get_namespace_name(Oid nspid); +extern Oid get_range_subtype(Oid rangeOid); #define type_is_array(typid) (get_element_type(typid) != InvalidOid) /* type_is_array_domain accepts both plain arrays and domains over arrays */ diff --git a/src/include/utils/rangetypes.h b/src/include/utils/rangetypes.h new file mode 100644 index 0000000000..a7595b15f6 --- /dev/null +++ b/src/include/utils/rangetypes.h @@ -0,0 +1,159 @@ +/*------------------------------------------------------------------------- + * + * rangetypes.h + * Declarations for Postgres range types. + * + */ + +#ifndef RANGETYPES_H +#define RANGETYPES_H + +#include "fmgr.h" + +typedef struct varlena RangeType; + +typedef struct +{ + Datum val; + Oid rngtypid; + bool infinite; + bool lower; + bool inclusive; +} RangeBound; + +typedef struct +{ + FmgrInfo canonicalFn; + FmgrInfo cmpFn; + FmgrInfo subdiffFn; + Oid rngtypid; + Oid subtype; + Oid collation; + int16 subtyplen; + char subtypalign; + char subtypstorage; + bool subtypbyval; +} RangeTypeInfo; + +/* + * fmgr macros for range type objects + */ +#define DatumGetRangeType(X) ((RangeType *) PG_DETOAST_DATUM(X)) +#define DatumGetRangeTypeCopy(X) ((RangeType *) PG_DETOAST_DATUM_COPY(X)) +#define RangeTypeGetDatum(X) PointerGetDatum(X) +#define PG_GETARG_RANGE(n) DatumGetRangeType(PG_GETARG_DATUM(n)) +#define PG_GETARG_RANGE_COPY(n) DatumGetRangeTypeCopy(PG_GETARG_DATUM(n)) +#define PG_RETURN_RANGE(x) return RangeTypeGetDatum(x) + +/* + * prototypes for functions defined in rangetypes.c + */ + +/* IO */ +extern Datum anyrange_in(PG_FUNCTION_ARGS); +extern Datum anyrange_out(PG_FUNCTION_ARGS); +extern Datum range_in(PG_FUNCTION_ARGS); +extern Datum range_out(PG_FUNCTION_ARGS); +extern Datum range_recv(PG_FUNCTION_ARGS); +extern Datum range_send(PG_FUNCTION_ARGS); + +/* constructors */ +extern Datum range_constructor0(PG_FUNCTION_ARGS); +extern Datum range_constructor1(PG_FUNCTION_ARGS); +extern Datum range_constructor2(PG_FUNCTION_ARGS); +extern Datum range_constructor3(PG_FUNCTION_ARGS); +extern Datum range_make1(PG_FUNCTION_ARGS); +extern Datum range_linf_(PG_FUNCTION_ARGS); +extern Datum range_uinf_(PG_FUNCTION_ARGS); +extern Datum range_linfi(PG_FUNCTION_ARGS); +extern Datum range_uinfi(PG_FUNCTION_ARGS); +extern Datum range(PG_FUNCTION_ARGS); +extern Datum range__(PG_FUNCTION_ARGS); +extern Datum range_i(PG_FUNCTION_ARGS); +extern Datum rangei_(PG_FUNCTION_ARGS); +extern Datum rangeii(PG_FUNCTION_ARGS); + +/* range -> subtype */ +extern Datum range_lower(PG_FUNCTION_ARGS); +extern Datum range_upper(PG_FUNCTION_ARGS); + +/* range -> bool */ +extern Datum range_empty(PG_FUNCTION_ARGS); +extern Datum range_lower_inc(PG_FUNCTION_ARGS); +extern Datum range_upper_inc(PG_FUNCTION_ARGS); +extern Datum range_lower_inf(PG_FUNCTION_ARGS); +extern Datum range_upper_inf(PG_FUNCTION_ARGS); + +/* range, point -> bool */ +extern Datum range_contains_elem(PG_FUNCTION_ARGS); +extern Datum elem_contained_by_range(PG_FUNCTION_ARGS); + +/* range, range -> bool */ +extern Datum range_eq(PG_FUNCTION_ARGS); +extern Datum range_ne(PG_FUNCTION_ARGS); +extern Datum range_contains(PG_FUNCTION_ARGS); +extern Datum range_contained_by(PG_FUNCTION_ARGS); +extern Datum range_before(PG_FUNCTION_ARGS); +extern Datum range_after(PG_FUNCTION_ARGS); +extern Datum range_adjacent(PG_FUNCTION_ARGS); +extern Datum range_overlaps(PG_FUNCTION_ARGS); +extern Datum range_overleft(PG_FUNCTION_ARGS); +extern Datum range_overright(PG_FUNCTION_ARGS); + +/* range, range -> range */ +extern Datum range_minus(PG_FUNCTION_ARGS); +extern Datum range_union(PG_FUNCTION_ARGS); +extern Datum range_intersect(PG_FUNCTION_ARGS); + +/* BTree support */ +extern Datum range_cmp(PG_FUNCTION_ARGS); +extern Datum range_lt(PG_FUNCTION_ARGS); +extern Datum range_le(PG_FUNCTION_ARGS); +extern Datum range_ge(PG_FUNCTION_ARGS); +extern Datum range_gt(PG_FUNCTION_ARGS); + +/* Hash support */ +extern Datum hash_range(PG_FUNCTION_ARGS); + +/* GiST support (rangetypes_gist.c) */ +extern Datum range_gist_consistent(PG_FUNCTION_ARGS); +extern Datum range_gist_compress(PG_FUNCTION_ARGS); +extern Datum range_gist_decompress(PG_FUNCTION_ARGS); +extern Datum range_gist_union(PG_FUNCTION_ARGS); +extern Datum range_gist_penalty(PG_FUNCTION_ARGS); +extern Datum range_gist_picksplit(PG_FUNCTION_ARGS); +extern Datum range_gist_same(PG_FUNCTION_ARGS); + +/* Canonical functions */ +Datum int4range_canonical(PG_FUNCTION_ARGS); +Datum int8range_canonical(PG_FUNCTION_ARGS); +Datum daterange_canonical(PG_FUNCTION_ARGS); + +/* Subtype Difference functions */ +Datum int4range_subdiff(PG_FUNCTION_ARGS); +Datum int8range_subdiff(PG_FUNCTION_ARGS); +Datum numrange_subdiff(PG_FUNCTION_ARGS); +Datum daterange_subdiff(PG_FUNCTION_ARGS); +Datum tsrange_subdiff(PG_FUNCTION_ARGS); +Datum tstzrange_subdiff(PG_FUNCTION_ARGS); + +/* for defining more generic functions */ +extern Datum make_range(FunctionCallInfo fcinfo, RangeBound *lower, + RangeBound *upper, bool empty); +extern void range_deserialize(FunctionCallInfo fcinfo, RangeType *range, + RangeBound *lower, RangeBound *upper, + bool *empty); +extern int range_cmp_bounds(FunctionCallInfo fcinfo, RangeBound *b1, + RangeBound *b2); +extern RangeType *make_empty_range(FunctionCallInfo fcinfo, Oid rngtypid); +extern void range_gettypinfo(FunctionCallInfo fcinfo, Oid rngtypid, + RangeTypeInfo *rngtypinfo); + +/* for defining a range "canonicalize" function */ +extern Datum range_serialize(FunctionCallInfo fcinfo, RangeBound *lower, + RangeBound *upper, bool empty); + +/* for use in DefineRange */ +extern char range_parse_flags(char *flags_str); + +#endif /* RANGETYPES_H */ diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h index 55d22303a7..35782fc3e7 100644 --- a/src/include/utils/syscache.h +++ b/src/include/utils/syscache.h @@ -70,6 +70,7 @@ enum SysCacheIdentifier OPFAMILYOID, PROCNAMEARGSNSP, PROCOID, + RANGETYPE, RELNAMENSP, RELOID, RULERELNAME, diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c index 578cae5734..48399d3929 100644 --- a/src/pl/plpgsql/src/pl_comp.c +++ b/src/pl/plpgsql/src/pl_comp.c @@ -490,6 +490,8 @@ do_compile(FunctionCallInfo fcinfo, { if (rettypeid == ANYARRAYOID) rettypeid = INT4ARRAYOID; + else if (rettypeid == ANYRANGEOID) + rettypeid = INT4RANGEOID; else /* ANYELEMENT or ANYNONARRAY */ rettypeid = INT4OID; /* XXX what could we use for ANYENUM? */ @@ -2119,6 +2121,7 @@ build_datatype(HeapTuple typeTup, int32 typmod, Oid collation) case TYPTYPE_BASE: case TYPTYPE_DOMAIN: case TYPTYPE_ENUM: + case TYPTYPE_RANGE: typ->ttype = PLPGSQL_TTYPE_SCALAR; break; case TYPTYPE_COMPOSITE: @@ -2373,8 +2376,8 @@ compute_function_hashkey(FunctionCallInfo fcinfo, /* * This is the same as the standard resolve_polymorphic_argtypes() function, * but with a special case for validation: assume that polymorphic arguments - * are integer or integer-array. Also, we go ahead and report the error - * if we can't resolve the types. + * are integer, integer-range or integer-array. Also, we go ahead and report + * the error if we can't resolve the types. */ static void plpgsql_resolve_polymorphic_argtypes(int numargs, @@ -2407,6 +2410,9 @@ plpgsql_resolve_polymorphic_argtypes(int numargs, case ANYENUMOID: /* XXX dubious */ argtypes[i] = INT4OID; break; + case ANYRANGEOID: + argtypes[i] = INT4RANGEOID; + break; case ANYARRAYOID: argtypes[i] = INT4ARRAYOID; break; diff --git a/src/test/regress/expected/collate.linux.utf8.out b/src/test/regress/expected/collate.linux.utf8.out index 8cee6ed812..f9659f7739 100644 --- a/src/test/regress/expected/collate.linux.utf8.out +++ b/src/test/regress/expected/collate.linux.utf8.out @@ -1049,3 +1049,20 @@ Composite type "public.collate_dep_test2" DROP TABLE collate_dep_test1, collate_dep_test4t; DROP TYPE collate_dep_test2; +-- test range types and collations +create type textrange_c as range(subtype=text, collation="C"); +create type textrange_en_us as range(subtype=text, collation="en_US"); +select textrange_c('A','Z') @> 'b'::text; + ?column? +---------- + f +(1 row) + +select textrange_en_us('A','Z') @> 'b'::text; + ?column? +---------- + t +(1 row) + +drop type textrange_c; +drop type textrange_en_us; diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index a25f90cbfd..19b559ffa1 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -147,7 +147,9 @@ WHERE p1.oid != p2.oid AND p1.prosrc = p2.prosrc AND p1.prolang = 12 AND p2.prolang = 12 AND NOT p1.proisagg AND NOT p2.proisagg AND - (p1.prorettype < p2.prorettype) + (p1.prorettype < p2.prorettype) AND + -- range constructor functions are shared by all range types. + NOT p1.prosrc LIKE 'range_constructor%' ORDER BY 1, 2; prorettype | prorettype ------------+------------ @@ -161,7 +163,9 @@ WHERE p1.oid != p2.oid AND p1.prosrc = p2.prosrc AND p1.prolang = 12 AND p2.prolang = 12 AND NOT p1.proisagg AND NOT p2.proisagg AND - (p1.proargtypes[0] < p2.proargtypes[0]) + (p1.proargtypes[0] < p2.proargtypes[0]) AND + -- range constructor functions are shared by all range types. + NOT p1.prosrc LIKE 'range_constructor%' ORDER BY 1, 2; proargtypes | proargtypes -------------+------------- @@ -178,7 +182,9 @@ WHERE p1.oid != p2.oid AND p1.prosrc = p2.prosrc AND p1.prolang = 12 AND p2.prolang = 12 AND NOT p1.proisagg AND NOT p2.proisagg AND - (p1.proargtypes[1] < p2.proargtypes[1]) + (p1.proargtypes[1] < p2.proargtypes[1]) AND + -- range constructor functions are shared by all range types. + NOT p1.prosrc LIKE 'range_constructor%' ORDER BY 1, 2; proargtypes | proargtypes -------------+------------- @@ -1015,19 +1021,30 @@ ORDER BY 1, 2, 3; 403 | 5 | ~>~ 405 | 1 | = 783 | 1 | << + 783 | 1 | = 783 | 1 | @@ 783 | 2 | &< + 783 | 2 | <> 783 | 3 | && 783 | 4 | &> + 783 | 4 | @> + 783 | 5 | <@ 783 | 5 | >> + 783 | 6 | @> 783 | 6 | ~= + 783 | 7 | <@ 783 | 7 | @> + 783 | 8 | << 783 | 8 | <@ 783 | 9 | &<| + 783 | 9 | >> + 783 | 10 | &< 783 | 10 | <<| 783 | 10 | <^ + 783 | 11 | &> 783 | 11 | >^ 783 | 11 | |>> + 783 | 12 | -|- 783 | 12 | |&> 783 | 13 | ~ 783 | 14 | @ @@ -1044,7 +1061,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = -(40 rows) +(51 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing @@ -1053,9 +1070,15 @@ SELECT p1.amopfamily, p1.amopopr, p2.oid, p2.oprname FROM pg_amop AS p1, pg_operator AS p2 WHERE p1.amopopr = p2.oid AND p1.amoppurpose = 's' AND (p2.oprrest = 0 OR p2.oprjoin = 0); - amopfamily | amopopr | oid | oprname -------------+---------+-----+--------- -(0 rows) + amopfamily | amopopr | oid | oprname +------------+---------+------+--------- + 3919 | 3888 | 3888 | && + 3919 | 3889 | 3889 | @> + 3919 | 3891 | 3891 | <@ + 3919 | 3890 | 3890 | @> + 3919 | 3892 | 3892 | <@ + 3919 | 3897 | 3897 | -|- +(6 rows) -- Check that each opclass in an opfamily has associated operators, that is -- ones whose oprleft matches opcintype (possibly by coercion). diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out index 238bf5f0ae..fc9d401944 100644 --- a/src/test/regress/expected/plpgsql.out +++ b/src/test/regress/expected/plpgsql.out @@ -4571,3 +4571,17 @@ ERROR: value for domain orderedarray violates check constraint "sorted" CONTEXT: PL/pgSQL function "testoa" line 5 at assignment drop function arrayassign1(); drop function testoa(x1 int, x2 int, x3 int); +-- Test resolve_polymorphic_argtypes() codepath. It is only taken when +-- a function is invoked from a different backend from where it's defined, +-- so we create the a function with polymorphic argument, reconnect, and +-- and then call it. +create function rangetypes_plpgsql(out a anyelement, b anyrange, c anyarray) + language plpgsql as + $$ begin a := upper(b) + c[1]; return; end; $$; +\c - +select rangetypes_plpgsql(int4range(1,10),ARRAY[2,20]); + rangetypes_plpgsql +-------------------- + 12 +(1 row) + diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out new file mode 100644 index 0000000000..495508c42e --- /dev/null +++ b/src/test/regress/expected/rangetypes.out @@ -0,0 +1,951 @@ +-- +-- test parser +-- +create type textrange as range (subtype=text, collation="C"); +-- negative tests; should fail +select ''::textrange; +ERROR: malformed range literal: "" +LINE 1: select ''::textrange; + ^ +DETAIL: Missing left parenthesis or bracket. +select '-[a,z)'::textrange; +ERROR: malformed range literal: "-[a,z)" +LINE 1: select '-[a,z)'::textrange; + ^ +DETAIL: Missing left parenthesis or bracket. +select '[a,z) - '::textrange; +ERROR: malformed range literal: "[a,z) - " +LINE 1: select '[a,z) - '::textrange; + ^ +DETAIL: Junk after right parenthesis or bracket. +select '(",a)'::textrange; +ERROR: malformed range literal: "(",a)" +LINE 1: select '(",a)'::textrange; + ^ +DETAIL: Unexpected end of input. +select '(,,a)'::textrange; +ERROR: malformed range literal: "(,,a)" +LINE 1: select '(,,a)'::textrange; + ^ +DETAIL: Too many boundaries. +select '(),a)'::textrange; +ERROR: malformed range literal: "(),a)" +LINE 1: select '(),a)'::textrange; + ^ +DETAIL: Missing upper bound. +select '(a,))'::textrange; +ERROR: malformed range literal: "(a,))" +LINE 1: select '(a,))'::textrange; + ^ +DETAIL: Junk after right parenthesis or bracket. +select '(],a)'::textrange; +ERROR: malformed range literal: "(],a)" +LINE 1: select '(],a)'::textrange; + ^ +DETAIL: Missing upper bound. +select '(a,])'::textrange; +ERROR: malformed range literal: "(a,])" +LINE 1: select '(a,])'::textrange; + ^ +DETAIL: Junk after right parenthesis or bracket. +-- should succeed +select ' empty '::textrange; + textrange +----------- + empty +(1 row) + +select ' ( empty, empty ) '::textrange; + textrange +---------------------- + (" empty"," empty ") +(1 row) + +select ' ( " a " " a ", " z " " z " ) '::textrange; + textrange +-------------------------- + (" a a "," z z ") +(1 row) + +select '(,z)'::textrange; + textrange +----------- + (,z) +(1 row) + +select '(a,)'::textrange; + textrange +----------- + (a,) +(1 row) + +select '[,z]'::textrange; + textrange +----------- + (,z] +(1 row) + +select '[a,]'::textrange; + textrange +----------- + [a,) +(1 row) + +select '( , )'::textrange; + textrange +----------- + (" "," ") +(1 row) + +select '("","")'::textrange; + textrange +----------- + ("","") +(1 row) + +select '["",""]'::textrange; + textrange +----------- + ["",""] +(1 row) + +select '(",",",")'::textrange; + textrange +----------- + (",",",") +(1 row) + +select '("\\","\\")'::textrange +select '(\\,a)'::textrange; +ERROR: syntax error at or near "select" +LINE 2: select '(\\,a)'::textrange; + ^ +select '((,z)'::textrange; + textrange +----------- + ("(",z) +(1 row) + +select '([,z)'::textrange; + textrange +----------- + ("[",z) +(1 row) + +select '(!,()'::textrange; + textrange +----------- + (!,"(") +(1 row) + +select '(!,[)'::textrange; + textrange +----------- + (!,"[") +(1 row) + +drop type textrange; +-- +-- create some test data and test the operators +-- +CREATE TABLE numrange_test (nr NUMRANGE); +create index numrange_test_btree on numrange_test(nr); +SET enable_seqscan = f; +INSERT INTO numrange_test VALUES('[,)'); +INSERT INTO numrange_test VALUES('[3,]'); +INSERT INTO numrange_test VALUES('[, 5)'); +INSERT INTO numrange_test VALUES(numrange(1.1, 2.2)); +INSERT INTO numrange_test VALUES('empty'); +INSERT INTO numrange_test VALUES(numrange(1.7)); +SELECT isempty(nr) FROM numrange_test; + isempty +--------- + f + f + f + f + t + f +(6 rows) + +SELECT lower_inc(nr), lower(nr), upper(nr), upper_inc(nr) FROM numrange_test + WHERE NOT isempty(nr) AND NOT lower_inf(nr) AND NOT upper_inf(nr); + lower_inc | lower | upper | upper_inc +-----------+-------+-------+----------- + t | 1.1 | 2.2 | f + t | 1.7 | 1.7 | t +(2 rows) + +SELECT * FROM numrange_test WHERE contains(nr, numrange(1.9,1.91)); + nr +----------- + (,) + (,5) + [1.1,2.2) +(3 rows) + +SELECT * FROM numrange_test WHERE nr @> numrange(1.0,10000.1); + nr +----- + (,) +(1 row) + +SELECT * FROM numrange_test WHERE contained_by(numrange(-1e7,-10000.1), nr); + nr +------ + (,) + (,5) +(2 rows) + +SELECT * FROM numrange_test WHERE 1.9 <@ nr; + nr +----------- + (,) + (,5) + [1.1,2.2) +(3 rows) + +SELECT * FROM numrange_test WHERE nr = 'empty'; + nr +------- + empty +(1 row) + +SELECT * FROM numrange_test WHERE range_eq(nr, '(1.1, 2.2)'); + nr +---- +(0 rows) + +SELECT * FROM numrange_test WHERE nr = '[1.1, 2.2)'; + nr +----------- + [1.1,2.2) +(1 row) + +select numrange(2.0, 1.0); +ERROR: range lower bound must be less than or equal to range upper bound +select numrange(2.0, 3.0) -|- numrange(3.0, 4.0); + ?column? +---------- + t +(1 row) + +select adjacent(numrange(2.0, 3.0), numrange(3.1, 4.0)); + adjacent +---------- + f +(1 row) + +select numrange(2.0, 3.0, '[]') -|- numrange(3.0, 4.0, '()'); + ?column? +---------- + t +(1 row) + +select numrange(1.0, 2.0) -|- numrange(2.0, 3.0,'[]'); + ?column? +---------- + t +(1 row) + +select adjacent(numrange(2.0, 3.0, '(]'), numrange(1.0, 2.0, '(]')); + adjacent +---------- + t +(1 row) + +select numrange(1.1, 3.3) <@ numrange(0.1,10.1); + ?column? +---------- + t +(1 row) + +select numrange(0.1, 10.1) <@ numrange(1.1,3.3); + ?column? +---------- + f +(1 row) + +select numrange(1.1, 2.2) - numrange(2.0, 3.0); + ?column? +----------- + [1.1,2.0) +(1 row) + +select numrange(1.1, 2.2) - numrange(2.2, 3.0); + ?column? +----------- + [1.1,2.2) +(1 row) + +select numrange(1.1, 2.2,'[]') - numrange(2.0, 3.0); + ?column? +----------- + [1.1,2.0) +(1 row) + +select minus(numrange(10.1,12.2,'[]'), numrange(110.0,120.2,'(]')); + minus +------------- + [10.1,12.2] +(1 row) + +select minus(numrange(10.1,12.2,'[]'), numrange(0.0,120.2,'(]')); + minus +------- + empty +(1 row) + +select numrange(4.5, 5.5, '[]') && numrange(5.5, 6.5); + ?column? +---------- + t +(1 row) + +select numrange(1.0, 2.0) << numrange(3.0, 4.0); + ?column? +---------- + t +(1 row) + +select numrange(1.0, 2.0) >> numrange(3.0, 4.0); + ?column? +---------- + f +(1 row) + +select numrange(3.0, 70.0) &< numrange(6.6, 100.0); + ?column? +---------- + t +(1 row) + +select numrange(1.1, 2.2) < numrange(1.0, 200.2); + ?column? +---------- + f +(1 row) + +select numrange(1.1, 2.2) < numrange(1.1, 1.2); + ?column? +---------- + f +(1 row) + +select numrange(1.0, 2.0) + numrange(2.0, 3.0); + ?column? +----------- + [1.0,3.0) +(1 row) + +select numrange(1.0, 2.0) + numrange(1.5, 3.0); + ?column? +----------- + [1.0,3.0) +(1 row) + +select numrange(1.0, 2.0) + numrange(2.5, 3.0); +ERROR: result range is not contiguous +select numrange(1.0, 2.0) * numrange(2.0, 3.0); + ?column? +---------- + empty +(1 row) + +select numrange(1.0, 2.0) * numrange(1.5, 3.0); + ?column? +----------- + [1.5,2.0) +(1 row) + +select numrange(1.0, 2.0) * numrange(2.5, 3.0); + ?column? +---------- + empty +(1 row) + +select * from numrange_test where nr < numrange(-1000.0, -1000.0,'[]'); + nr +------- + (,) + (,5) + empty +(3 rows) + +select * from numrange_test where nr < numrange(0.0, 1.0,'[]'); + nr +------- + (,) + (,5) + empty +(3 rows) + +select * from numrange_test where nr < numrange(1000.0, 1001.0,'[]'); + nr +----------- + (,) + [3,) + (,5) + [1.1,2.2) + empty + [1.7,1.7] +(6 rows) + +select * from numrange_test where nr > numrange(-1001.0, -1000.0,'[]'); + nr +----------- + [3,) + [1.1,2.2) + [1.7,1.7] +(3 rows) + +select * from numrange_test where nr > numrange(0.0, 1.0,'[]'); + nr +----------- + [3,) + [1.1,2.2) + [1.7,1.7] +(3 rows) + +select * from numrange_test where nr > numrange(1000.0, 1000.0,'[]'); + nr +---- +(0 rows) + +create table numrange_test2(nr numrange); +create index numrange_test2_hash_idx on numrange_test2 (nr); +INSERT INTO numrange_test2 VALUES('[, 5)'); +INSERT INTO numrange_test2 VALUES(numrange(1.1, 2.2)); +INSERT INTO numrange_test2 VALUES(numrange(1.1, 2.2)); +INSERT INTO numrange_test2 VALUES(numrange(1.1, 2.2,'()')); +INSERT INTO numrange_test2 VALUES('empty'); +select * from numrange_test2 where nr = 'empty'::numrange; + nr +------- + empty +(1 row) + +select * from numrange_test2 where nr = numrange(1.1, 2.2); + nr +----------- + [1.1,2.2) + [1.1,2.2) +(2 rows) + +select * from numrange_test2 where nr = numrange(1.1, 2.3); + nr +---- +(0 rows) + +set enable_nestloop=t; +set enable_hashjoin=f; +set enable_mergejoin=f; +select * from numrange_test natural join numrange_test2 order by nr; + nr +----------- + empty + (,5) + [1.1,2.2) + [1.1,2.2) +(4 rows) + +set enable_nestloop=f; +set enable_hashjoin=t; +set enable_mergejoin=f; +select * from numrange_test natural join numrange_test2 order by nr; + nr +----------- + empty + (,5) + [1.1,2.2) + [1.1,2.2) +(4 rows) + +set enable_nestloop=f; +set enable_hashjoin=f; +set enable_mergejoin=t; +select * from numrange_test natural join numrange_test2 order by nr; + nr +----------- + empty + (,5) + [1.1,2.2) + [1.1,2.2) +(4 rows) + +set enable_nestloop to default; +set enable_hashjoin to default; +set enable_mergejoin to default; +SET enable_seqscan TO DEFAULT; +DROP TABLE numrange_test; +DROP TABLE numrange_test2; +-- test canonical form for int4range +select int4range(1,10,'[]'); + int4range +----------- + [1,11) +(1 row) + +select int4range(1,10,'[)'); + int4range +----------- + [1,10) +(1 row) + +select int4range(1,10,'(]'); + int4range +----------- + [2,11) +(1 row) + +select int4range(1,10,'[]'); + int4range +----------- + [1,11) +(1 row) + +-- test canonical form for daterange +select daterange('2000-01-10'::date, '2000-01-20'::date,'[]'); + daterange +------------------------- + [01-10-2000,01-21-2000) +(1 row) + +select daterange('2000-01-10'::date, '2000-01-20'::date,'[)'); + daterange +------------------------- + [01-10-2000,01-20-2000) +(1 row) + +select daterange('2000-01-10'::date, '2000-01-20'::date,'(]'); + daterange +------------------------- + [01-11-2000,01-21-2000) +(1 row) + +select daterange('2000-01-10'::date, '2000-01-20'::date,'[]'); + daterange +------------------------- + [01-10-2000,01-21-2000) +(1 row) + +create table test_range_gist(ir int4range); +create index test_range_gist_idx on test_range_gist using gist (ir); +insert into test_range_gist select int4range(g, g+10) from generate_series(1,2000) g; +insert into test_range_gist select 'empty'::int4range from generate_series(1,500) g; +insert into test_range_gist select int4range(g, g+10000) from generate_series(1,1000) g; +insert into test_range_gist select 'empty'::int4range from generate_series(1,500) g; +insert into test_range_gist select int4range(NULL,g*10,'(]') from generate_series(1,100) g; +insert into test_range_gist select int4range(g*10,NULL,'(]') from generate_series(1,100) g; +insert into test_range_gist select int4range(g, g+10) from generate_series(1,2000) g; +BEGIN; +SET LOCAL enable_seqscan = t; +SET LOCAL enable_bitmapscan = f; +SET LOCAL enable_indexscan = f; +select count(*) from test_range_gist where ir @> 'empty'::int4range; + count +------- + 6200 +(1 row) + +select count(*) from test_range_gist where ir = int4range(10,20); + count +------- + 2 +(1 row) + +select count(*) from test_range_gist where ir @> 10; + count +------- + 130 +(1 row) + +select count(*) from test_range_gist where ir @> int4range(10,20); + count +------- + 111 +(1 row) + +select count(*) from test_range_gist where ir && int4range(10,20); + count +------- + 158 +(1 row) + +select count(*) from test_range_gist where ir <@ int4range(10,50); + count +------- + 1062 +(1 row) + +select count(*) from (select * from test_range_gist where not isempty(ir)) s where ir << int4range(100,500); + count +------- + 189 +(1 row) + +select count(*) from (select * from test_range_gist where not isempty(ir)) s where ir >> int4range(100,500); + count +------- + 3554 +(1 row) + +select count(*) from (select * from test_range_gist where not isempty(ir)) s where ir &< int4range(100,500); + count +------- + 1029 +(1 row) + +select count(*) from (select * from test_range_gist where not isempty(ir)) s where ir &> int4range(100,500); + count +------- + 4794 +(1 row) + +select count(*) from (select * from test_range_gist where not isempty(ir)) s where ir -|- int4range(100,500); + count +------- + 5 +(1 row) + +COMMIT; +BEGIN; +SET LOCAL enable_seqscan = f; +SET LOCAL enable_bitmapscan = f; +SET LOCAL enable_indexscan = t; +select count(*) from test_range_gist where ir @> 'empty'::int4range; + count +------- + 6200 +(1 row) + +select count(*) from test_range_gist where ir = int4range(10,20); + count +------- + 2 +(1 row) + +select count(*) from test_range_gist where ir @> 10; + count +------- + 130 +(1 row) + +select count(*) from test_range_gist where ir @> int4range(10,20); + count +------- + 111 +(1 row) + +select count(*) from test_range_gist where ir && int4range(10,20); + count +------- + 158 +(1 row) + +select count(*) from test_range_gist where ir <@ int4range(10,50); + count +------- + 1062 +(1 row) + +select count(*) from test_range_gist where ir << int4range(100,500); + count +------- + 189 +(1 row) + +select count(*) from test_range_gist where ir >> int4range(100,500); + count +------- + 3554 +(1 row) + +select count(*) from test_range_gist where ir &< int4range(100,500); + count +------- + 1029 +(1 row) + +select count(*) from test_range_gist where ir &> int4range(100,500); + count +------- + 4794 +(1 row) + +select count(*) from test_range_gist where ir -|- int4range(100,500); + count +------- + 5 +(1 row) + +COMMIT; +drop index test_range_gist_idx; +create index test_range_gist_idx on test_range_gist using gist (ir); +BEGIN; +SET LOCAL enable_seqscan = f; +SET LOCAL enable_bitmapscan = f; +SET LOCAL enable_indexscan = t; +select count(*) from test_range_gist where ir @> 'empty'::int4range; + count +------- + 6200 +(1 row) + +select count(*) from test_range_gist where ir = int4range(10,20); + count +------- + 2 +(1 row) + +select count(*) from test_range_gist where ir @> 10; + count +------- + 130 +(1 row) + +select count(*) from test_range_gist where ir @> int4range(10,20); + count +------- + 111 +(1 row) + +select count(*) from test_range_gist where ir && int4range(10,20); + count +------- + 158 +(1 row) + +select count(*) from test_range_gist where ir <@ int4range(10,50); + count +------- + 1062 +(1 row) + +select count(*) from test_range_gist where ir << int4range(100,500); + count +------- + 189 +(1 row) + +select count(*) from test_range_gist where ir >> int4range(100,500); + count +------- + 3554 +(1 row) + +select count(*) from test_range_gist where ir &< int4range(100,500); + count +------- + 1029 +(1 row) + +select count(*) from test_range_gist where ir &> int4range(100,500); + count +------- + 4794 +(1 row) + +select count(*) from test_range_gist where ir -|- int4range(100,500); + count +------- + 5 +(1 row) + +COMMIT; +drop table test_range_gist; +-- +-- Btree_gist is not included by default, so to test exclusion +-- constraints with range types, use singleton int ranges for the "=" +-- portion of the constraint. +-- +create table test_range_excl( + room int4range, + speaker int4range, + during tsrange, + exclude using gist (room with =, during with &&), + exclude using gist (speaker with =, during with &&) +); +NOTICE: CREATE TABLE / EXCLUDE will create implicit index "test_range_excl_room_during_excl" for table "test_range_excl" +NOTICE: CREATE TABLE / EXCLUDE will create implicit index "test_range_excl_speaker_during_excl" for table "test_range_excl" +insert into test_range_excl + values(int4range(123), int4range(1), '[2010-01-02 10:00, 2010-01-02 11:00)'); +insert into test_range_excl + values(int4range(123), int4range(2), '[2010-01-02 11:00, 2010-01-02 12:00)'); +insert into test_range_excl + values(int4range(123), int4range(3), '[2010-01-02 10:10, 2010-01-02 11:10)'); +ERROR: conflicting key value violates exclusion constraint "test_range_excl_room_during_excl" +DETAIL: Key (room, during)=([123,124), ["Sat Jan 02 10:10:00 2010","Sat Jan 02 11:10:00 2010")) conflicts with existing key (room, during)=([123,124), ["Sat Jan 02 10:00:00 2010","Sat Jan 02 11:00:00 2010")). +insert into test_range_excl + values(int4range(124), int4range(3), '[2010-01-02 10:10, 2010-01-02 11:10)'); +insert into test_range_excl + values(int4range(125), int4range(1), '[2010-01-02 10:10, 2010-01-02 11:10)'); +ERROR: conflicting key value violates exclusion constraint "test_range_excl_speaker_during_excl" +DETAIL: Key (speaker, during)=([1,2), ["Sat Jan 02 10:10:00 2010","Sat Jan 02 11:10:00 2010")) conflicts with existing key (speaker, during)=([1,2), ["Sat Jan 02 10:00:00 2010","Sat Jan 02 11:00:00 2010")). +drop table test_range_excl; +-- test bigint ranges +select int8range(10000000000::int8, 20000000000::int8,'(]'); + int8range +--------------------------- + [10000000001,20000000001) +(1 row) + +-- test tstz ranges +set timezone to '-08'; +select '[2010-01-01 01:00:00 -05, 2010-01-01 02:00:00 -08)'::tstzrange; + tstzrange +----------------------------------------------------------------- + ["Thu Dec 31 22:00:00 2009 -08","Fri Jan 01 02:00:00 2010 -08") +(1 row) + +-- should fail +select '[2010-01-01 01:00:00 -08, 2010-01-01 02:00:00 -05)'::tstzrange; +ERROR: range lower bound must be less than or equal to range upper bound +LINE 1: select '[2010-01-01 01:00:00 -08, 2010-01-01 02:00:00 -05)':... + ^ +set timezone to default; +-- +-- Test user-defined range of floats +-- +--should fail +create type float8range as range (subtype=float8, subtype_diff=float4mi); +ERROR: function float4mi(double precision, double precision) does not exist +--should succeed +create type float8range as range (subtype=float8, subtype_diff=float8mi); +select '[123.001, 5.e9)'::float8range @> 888.882::float8; + ?column? +---------- + t +(1 row) + +create table float8range_test(f8r float8range, i int); +insert into float8range_test values(float8range(-100.00007, '1.111113e9')); +select * from float8range_test; + f8r | i +-------------------------+--- + [-100.00007,1111113000) | +(1 row) + +drop table float8range_test; +drop type float8range; +-- +-- Test range types over domains +-- +create domain mydomain as int4; +create type mydomainrange as range(subtype=mydomain); +select '[4,50)'::mydomainrange @> 7::mydomain; + ?column? +---------- + t +(1 row) + +drop type mydomainrange; +drop domain mydomain; +-- +-- Test domains over range types +-- +create domain restrictedrange as int4range check (upper(value) < 10); +select '[4,5)'::restrictedrange @> 7; + ?column? +---------- + f +(1 row) + +select '[4,50)'::restrictedrange @> 7; -- should fail +ERROR: value for domain restrictedrange violates check constraint "restrictedrange_check" +drop domain restrictedrange; +-- +-- Test multiple range types over the same subtype +-- +create type textrange1 as range(subtype=text, collation="C"); +create type textrange2 as range(subtype=text, collation="C"); +select textrange1('a','Z') @> 'b'::text; +ERROR: range lower bound must be less than or equal to range upper bound +select textrange2('a','z') @> 'b'::text; + ?column? +---------- + t +(1 row) + +drop type textrange1; +drop type textrange2; +-- +-- Test out polymorphic type system +-- +create function anyarray_anyrange_func(a anyarray, r anyrange) + returns anyelement as 'select $1[1] + lower($2);' language sql; +select anyarray_anyrange_func(ARRAY[1,2], int4range(10,20)); + anyarray_anyrange_func +------------------------ + 11 +(1 row) + +-- should fail +select anyarray_anyrange_func(ARRAY[1,2], numrange(10,20)); +ERROR: function anyarray_anyrange_func(integer[], numrange) does not exist +LINE 1: select anyarray_anyrange_func(ARRAY[1,2], numrange(10,20)); + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +drop function anyarray_anyrange_func(anyarray, anyrange); +-- should fail +create function bogus_func(anyelement) + returns anyrange as 'select int4range(1,10)' language sql; +ERROR: cannot determine result data type +DETAIL: A function returning ANYRANGE must have at least one ANYRANGE argument. +-- should fail +create function bogus_func(int) + returns anyrange as 'select int4range(1,10)' language sql; +ERROR: cannot determine result data type +DETAIL: A function returning a polymorphic type must have at least one polymorphic argument. +create function range_add_bounds(anyrange) + returns anyelement as 'select lower($1) + upper($1)' language sql; +select range_add_bounds(numrange(1.0001, 123.123)); + range_add_bounds +------------------ + 124.1231 +(1 row) + +-- +-- Arrays of ranges +-- +select ARRAY[numrange(1.1), numrange(12.3,155.5)]; + array +------------------------------ + {"[1.1,1.1]","[12.3,155.5)"} +(1 row) + +-- +-- Ranges of arrays +-- +create type arrayrange as range (subtype=int4[]); +select arrayrange(ARRAY[1,2], ARRAY[2,1]); + arrayrange +------------------- + ["{1,2}","{2,1}") +(1 row) + +drop type arrayrange; +-- +-- OUT/INOUT/TABLE functions +-- +create function outparam_succeed(i anyrange, out r anyrange, out t text) + as $$ select $1, 'foo' $$ language sql; +create function inoutparam_succeed(out i anyelement, inout r anyrange) + as $$ select $1, $2 $$ language sql; +create function table_succeed(i anyelement, r anyrange) returns table(i anyelement, r anyrange) + as $$ select $1, $2 $$ language sql; +-- should fail +create function outparam_fail(i anyelement, out r anyrange, out t text) + as $$ select '[1,10]', 'foo' $$ language sql; +ERROR: cannot determine result data type +DETAIL: A function returning ANYRANGE must have at least one ANYRANGE argument. +--should fail +create function inoutparam_fail(inout i anyelement, out r anyrange) + as $$ select $1, '[1,10]' $$ language sql; +ERROR: cannot determine result data type +DETAIL: A function returning ANYRANGE must have at least one ANYRANGE argument. +--should fail +create function table_succeed(i anyelement) returns table(i anyelement, r anyrange) + as $$ select $1, '[1,10]' $$ language sql; +ERROR: cannot determine result data type +DETAIL: A function returning ANYRANGE must have at least one ANYRANGE argument. diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out index d42b0ea045..38c88d977a 100644 --- a/src/test/regress/expected/sanity_check.out +++ b/src/test/regress/expected/sanity_check.out @@ -116,6 +116,7 @@ SELECT relname, relhasindex pg_opfamily | t pg_pltemplate | t pg_proc | t + pg_range | t pg_rewrite | t pg_seclabel | t pg_shdepend | t @@ -158,7 +159,7 @@ SELECT relname, relhasindex timetz_tbl | f tinterval_tbl | f varchar_tbl | f -(147 rows) +(148 rows) -- -- another sanity check: every system catalog that has OIDs should have diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out index e30ecbc6fe..7ca7a95ec6 100644 --- a/src/test/regress/expected/type_sanity.out +++ b/src/test/regress/expected/type_sanity.out @@ -17,7 +17,7 @@ SELECT p1.oid, p1.typname FROM pg_type as p1 WHERE p1.typnamespace = 0 OR (p1.typlen <= 0 AND p1.typlen != -1 AND p1.typlen != -2) OR - (p1.typtype not in ('b', 'c', 'd', 'e', 'p')) OR + (p1.typtype not in ('b', 'c', 'd', 'e', 'p', 'r')) OR NOT p1.typisdefined OR (p1.typalign not in ('c', 's', 'i', 'd')) OR (p1.typstorage not in ('p', 'x', 'e', 'm')); diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 376f28d99a..70976d115c 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -13,7 +13,7 @@ test: tablespace # ---------- # The first group of parallel tests # ---------- -test: boolean char name varchar text int2 int4 int8 oid float4 float8 bit numeric txid uuid enum money +test: boolean char name varchar text int2 int4 int8 oid float4 float8 bit numeric txid uuid enum money rangetypes # Depends on things setup during char, varchar and text test: strings diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index bb654f9c61..2e87d9eefd 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -18,6 +18,7 @@ test: txid test: uuid test: enum test: money +test: rangetypes test: strings test: numerology test: point diff --git a/src/test/regress/sql/collate.linux.utf8.sql b/src/test/regress/sql/collate.linux.utf8.sql index 73f5587b70..3b7cc6cf2b 100644 --- a/src/test/regress/sql/collate.linux.utf8.sql +++ b/src/test/regress/sql/collate.linux.utf8.sql @@ -385,3 +385,14 @@ DROP COLLATION test0 CASCADE; DROP TABLE collate_dep_test1, collate_dep_test4t; DROP TYPE collate_dep_test2; + +-- test range types and collations + +create type textrange_c as range(subtype=text, collation="C"); +create type textrange_en_us as range(subtype=text, collation="en_US"); + +select textrange_c('A','Z') @> 'b'::text; +select textrange_en_us('A','Z') @> 'b'::text; + +drop type textrange_c; +drop type textrange_en_us; diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql index 65ae868d98..7f936c8154 100644 --- a/src/test/regress/sql/opr_sanity.sql +++ b/src/test/regress/sql/opr_sanity.sql @@ -133,7 +133,9 @@ WHERE p1.oid != p2.oid AND p1.prosrc = p2.prosrc AND p1.prolang = 12 AND p2.prolang = 12 AND NOT p1.proisagg AND NOT p2.proisagg AND - (p1.prorettype < p2.prorettype) + (p1.prorettype < p2.prorettype) AND + -- range constructor functions are shared by all range types. + NOT p1.prosrc LIKE 'range_constructor%' ORDER BY 1, 2; SELECT DISTINCT p1.proargtypes[0], p2.proargtypes[0] @@ -142,7 +144,9 @@ WHERE p1.oid != p2.oid AND p1.prosrc = p2.prosrc AND p1.prolang = 12 AND p2.prolang = 12 AND NOT p1.proisagg AND NOT p2.proisagg AND - (p1.proargtypes[0] < p2.proargtypes[0]) + (p1.proargtypes[0] < p2.proargtypes[0]) AND + -- range constructor functions are shared by all range types. + NOT p1.prosrc LIKE 'range_constructor%' ORDER BY 1, 2; SELECT DISTINCT p1.proargtypes[1], p2.proargtypes[1] @@ -151,7 +155,9 @@ WHERE p1.oid != p2.oid AND p1.prosrc = p2.prosrc AND p1.prolang = 12 AND p2.prolang = 12 AND NOT p1.proisagg AND NOT p2.proisagg AND - (p1.proargtypes[1] < p2.proargtypes[1]) + (p1.proargtypes[1] < p2.proargtypes[1]) AND + -- range constructor functions are shared by all range types. + NOT p1.prosrc LIKE 'range_constructor%' ORDER BY 1, 2; SELECT DISTINCT p1.proargtypes[2], p2.proargtypes[2] diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql index b47c2de312..2906943f06 100644 --- a/src/test/regress/sql/plpgsql.sql +++ b/src/test/regress/sql/plpgsql.sql @@ -3600,3 +3600,13 @@ select testoa(1,2,1); -- fail at update drop function arrayassign1(); drop function testoa(x1 int, x2 int, x3 int); + +-- Test resolve_polymorphic_argtypes() codepath. It is only taken when +-- a function is invoked from a different backend from where it's defined, +-- so we create the a function with polymorphic argument, reconnect, and +-- and then call it. +create function rangetypes_plpgsql(out a anyelement, b anyrange, c anyarray) + language plpgsql as + $$ begin a := upper(b) + c[1]; return; end; $$; +\c - +select rangetypes_plpgsql(int4range(1,10),ARRAY[2,20]); diff --git a/src/test/regress/sql/rangetypes.sql b/src/test/regress/sql/rangetypes.sql new file mode 100644 index 0000000000..44885c870c --- /dev/null +++ b/src/test/regress/sql/rangetypes.sql @@ -0,0 +1,371 @@ + +-- +-- test parser +-- + +create type textrange as range (subtype=text, collation="C"); + +-- negative tests; should fail +select ''::textrange; +select '-[a,z)'::textrange; +select '[a,z) - '::textrange; +select '(",a)'::textrange; +select '(,,a)'::textrange; +select '(),a)'::textrange; +select '(a,))'::textrange; +select '(],a)'::textrange; +select '(a,])'::textrange; + +-- should succeed +select ' empty '::textrange; +select ' ( empty, empty ) '::textrange; +select ' ( " a " " a ", " z " " z " ) '::textrange; +select '(,z)'::textrange; +select '(a,)'::textrange; +select '[,z]'::textrange; +select '[a,]'::textrange; +select '( , )'::textrange; +select '("","")'::textrange; +select '["",""]'::textrange; +select '(",",",")'::textrange; +select '("\\","\\")'::textrange +select '(\\,a)'::textrange; +select '((,z)'::textrange; +select '([,z)'::textrange; +select '(!,()'::textrange; +select '(!,[)'::textrange; + +drop type textrange; + +-- +-- create some test data and test the operators +-- + +CREATE TABLE numrange_test (nr NUMRANGE); +create index numrange_test_btree on numrange_test(nr); +SET enable_seqscan = f; + +INSERT INTO numrange_test VALUES('[,)'); +INSERT INTO numrange_test VALUES('[3,]'); +INSERT INTO numrange_test VALUES('[, 5)'); +INSERT INTO numrange_test VALUES(numrange(1.1, 2.2)); +INSERT INTO numrange_test VALUES('empty'); +INSERT INTO numrange_test VALUES(numrange(1.7)); + +SELECT isempty(nr) FROM numrange_test; +SELECT lower_inc(nr), lower(nr), upper(nr), upper_inc(nr) FROM numrange_test + WHERE NOT isempty(nr) AND NOT lower_inf(nr) AND NOT upper_inf(nr); + +SELECT * FROM numrange_test WHERE contains(nr, numrange(1.9,1.91)); +SELECT * FROM numrange_test WHERE nr @> numrange(1.0,10000.1); +SELECT * FROM numrange_test WHERE contained_by(numrange(-1e7,-10000.1), nr); +SELECT * FROM numrange_test WHERE 1.9 <@ nr; +SELECT * FROM numrange_test WHERE nr = 'empty'; +SELECT * FROM numrange_test WHERE range_eq(nr, '(1.1, 2.2)'); +SELECT * FROM numrange_test WHERE nr = '[1.1, 2.2)'; + +select numrange(2.0, 1.0); + +select numrange(2.0, 3.0) -|- numrange(3.0, 4.0); +select adjacent(numrange(2.0, 3.0), numrange(3.1, 4.0)); +select numrange(2.0, 3.0, '[]') -|- numrange(3.0, 4.0, '()'); +select numrange(1.0, 2.0) -|- numrange(2.0, 3.0,'[]'); +select adjacent(numrange(2.0, 3.0, '(]'), numrange(1.0, 2.0, '(]')); + +select numrange(1.1, 3.3) <@ numrange(0.1,10.1); +select numrange(0.1, 10.1) <@ numrange(1.1,3.3); + +select numrange(1.1, 2.2) - numrange(2.0, 3.0); +select numrange(1.1, 2.2) - numrange(2.2, 3.0); +select numrange(1.1, 2.2,'[]') - numrange(2.0, 3.0); +select minus(numrange(10.1,12.2,'[]'), numrange(110.0,120.2,'(]')); +select minus(numrange(10.1,12.2,'[]'), numrange(0.0,120.2,'(]')); + +select numrange(4.5, 5.5, '[]') && numrange(5.5, 6.5); +select numrange(1.0, 2.0) << numrange(3.0, 4.0); +select numrange(1.0, 2.0) >> numrange(3.0, 4.0); +select numrange(3.0, 70.0) &< numrange(6.6, 100.0); + +select numrange(1.1, 2.2) < numrange(1.0, 200.2); +select numrange(1.1, 2.2) < numrange(1.1, 1.2); + +select numrange(1.0, 2.0) + numrange(2.0, 3.0); +select numrange(1.0, 2.0) + numrange(1.5, 3.0); +select numrange(1.0, 2.0) + numrange(2.5, 3.0); + +select numrange(1.0, 2.0) * numrange(2.0, 3.0); +select numrange(1.0, 2.0) * numrange(1.5, 3.0); +select numrange(1.0, 2.0) * numrange(2.5, 3.0); + +select * from numrange_test where nr < numrange(-1000.0, -1000.0,'[]'); +select * from numrange_test where nr < numrange(0.0, 1.0,'[]'); +select * from numrange_test where nr < numrange(1000.0, 1001.0,'[]'); +select * from numrange_test where nr > numrange(-1001.0, -1000.0,'[]'); +select * from numrange_test where nr > numrange(0.0, 1.0,'[]'); +select * from numrange_test where nr > numrange(1000.0, 1000.0,'[]'); + +create table numrange_test2(nr numrange); +create index numrange_test2_hash_idx on numrange_test2 (nr); +INSERT INTO numrange_test2 VALUES('[, 5)'); +INSERT INTO numrange_test2 VALUES(numrange(1.1, 2.2)); +INSERT INTO numrange_test2 VALUES(numrange(1.1, 2.2)); +INSERT INTO numrange_test2 VALUES(numrange(1.1, 2.2,'()')); +INSERT INTO numrange_test2 VALUES('empty'); + +select * from numrange_test2 where nr = 'empty'::numrange; +select * from numrange_test2 where nr = numrange(1.1, 2.2); +select * from numrange_test2 where nr = numrange(1.1, 2.3); + +set enable_nestloop=t; +set enable_hashjoin=f; +set enable_mergejoin=f; +select * from numrange_test natural join numrange_test2 order by nr; +set enable_nestloop=f; +set enable_hashjoin=t; +set enable_mergejoin=f; +select * from numrange_test natural join numrange_test2 order by nr; +set enable_nestloop=f; +set enable_hashjoin=f; +set enable_mergejoin=t; +select * from numrange_test natural join numrange_test2 order by nr; + +set enable_nestloop to default; +set enable_hashjoin to default; +set enable_mergejoin to default; +SET enable_seqscan TO DEFAULT; +DROP TABLE numrange_test; +DROP TABLE numrange_test2; + +-- test canonical form for int4range +select int4range(1,10,'[]'); +select int4range(1,10,'[)'); +select int4range(1,10,'(]'); +select int4range(1,10,'[]'); + +-- test canonical form for daterange +select daterange('2000-01-10'::date, '2000-01-20'::date,'[]'); +select daterange('2000-01-10'::date, '2000-01-20'::date,'[)'); +select daterange('2000-01-10'::date, '2000-01-20'::date,'(]'); +select daterange('2000-01-10'::date, '2000-01-20'::date,'[]'); + +create table test_range_gist(ir int4range); +create index test_range_gist_idx on test_range_gist using gist (ir); + +insert into test_range_gist select int4range(g, g+10) from generate_series(1,2000) g; +insert into test_range_gist select 'empty'::int4range from generate_series(1,500) g; +insert into test_range_gist select int4range(g, g+10000) from generate_series(1,1000) g; +insert into test_range_gist select 'empty'::int4range from generate_series(1,500) g; +insert into test_range_gist select int4range(NULL,g*10,'(]') from generate_series(1,100) g; +insert into test_range_gist select int4range(g*10,NULL,'(]') from generate_series(1,100) g; +insert into test_range_gist select int4range(g, g+10) from generate_series(1,2000) g; + +BEGIN; +SET LOCAL enable_seqscan = t; +SET LOCAL enable_bitmapscan = f; +SET LOCAL enable_indexscan = f; + +select count(*) from test_range_gist where ir @> 'empty'::int4range; +select count(*) from test_range_gist where ir = int4range(10,20); +select count(*) from test_range_gist where ir @> 10; +select count(*) from test_range_gist where ir @> int4range(10,20); +select count(*) from test_range_gist where ir && int4range(10,20); +select count(*) from test_range_gist where ir <@ int4range(10,50); +select count(*) from (select * from test_range_gist where not isempty(ir)) s where ir << int4range(100,500); +select count(*) from (select * from test_range_gist where not isempty(ir)) s where ir >> int4range(100,500); +select count(*) from (select * from test_range_gist where not isempty(ir)) s where ir &< int4range(100,500); +select count(*) from (select * from test_range_gist where not isempty(ir)) s where ir &> int4range(100,500); +select count(*) from (select * from test_range_gist where not isempty(ir)) s where ir -|- int4range(100,500); +COMMIT; + +BEGIN; +SET LOCAL enable_seqscan = f; +SET LOCAL enable_bitmapscan = f; +SET LOCAL enable_indexscan = t; + +select count(*) from test_range_gist where ir @> 'empty'::int4range; +select count(*) from test_range_gist where ir = int4range(10,20); +select count(*) from test_range_gist where ir @> 10; +select count(*) from test_range_gist where ir @> int4range(10,20); +select count(*) from test_range_gist where ir && int4range(10,20); +select count(*) from test_range_gist where ir <@ int4range(10,50); +select count(*) from test_range_gist where ir << int4range(100,500); +select count(*) from test_range_gist where ir >> int4range(100,500); +select count(*) from test_range_gist where ir &< int4range(100,500); +select count(*) from test_range_gist where ir &> int4range(100,500); +select count(*) from test_range_gist where ir -|- int4range(100,500); +COMMIT; + +drop index test_range_gist_idx; +create index test_range_gist_idx on test_range_gist using gist (ir); + +BEGIN; +SET LOCAL enable_seqscan = f; +SET LOCAL enable_bitmapscan = f; +SET LOCAL enable_indexscan = t; + +select count(*) from test_range_gist where ir @> 'empty'::int4range; +select count(*) from test_range_gist where ir = int4range(10,20); +select count(*) from test_range_gist where ir @> 10; +select count(*) from test_range_gist where ir @> int4range(10,20); +select count(*) from test_range_gist where ir && int4range(10,20); +select count(*) from test_range_gist where ir <@ int4range(10,50); +select count(*) from test_range_gist where ir << int4range(100,500); +select count(*) from test_range_gist where ir >> int4range(100,500); +select count(*) from test_range_gist where ir &< int4range(100,500); +select count(*) from test_range_gist where ir &> int4range(100,500); +select count(*) from test_range_gist where ir -|- int4range(100,500); +COMMIT; + +drop table test_range_gist; + +-- +-- Btree_gist is not included by default, so to test exclusion +-- constraints with range types, use singleton int ranges for the "=" +-- portion of the constraint. +-- + +create table test_range_excl( + room int4range, + speaker int4range, + during tsrange, + exclude using gist (room with =, during with &&), + exclude using gist (speaker with =, during with &&) +); + +insert into test_range_excl + values(int4range(123), int4range(1), '[2010-01-02 10:00, 2010-01-02 11:00)'); +insert into test_range_excl + values(int4range(123), int4range(2), '[2010-01-02 11:00, 2010-01-02 12:00)'); +insert into test_range_excl + values(int4range(123), int4range(3), '[2010-01-02 10:10, 2010-01-02 11:10)'); +insert into test_range_excl + values(int4range(124), int4range(3), '[2010-01-02 10:10, 2010-01-02 11:10)'); +insert into test_range_excl + values(int4range(125), int4range(1), '[2010-01-02 10:10, 2010-01-02 11:10)'); + +drop table test_range_excl; + +-- test bigint ranges +select int8range(10000000000::int8, 20000000000::int8,'(]'); +-- test tstz ranges +set timezone to '-08'; +select '[2010-01-01 01:00:00 -05, 2010-01-01 02:00:00 -08)'::tstzrange; +-- should fail +select '[2010-01-01 01:00:00 -08, 2010-01-01 02:00:00 -05)'::tstzrange; +set timezone to default; + +-- +-- Test user-defined range of floats +-- + +--should fail +create type float8range as range (subtype=float8, subtype_diff=float4mi); + +--should succeed +create type float8range as range (subtype=float8, subtype_diff=float8mi); +select '[123.001, 5.e9)'::float8range @> 888.882::float8; +create table float8range_test(f8r float8range, i int); +insert into float8range_test values(float8range(-100.00007, '1.111113e9')); +select * from float8range_test; +drop table float8range_test; +drop type float8range; + +-- +-- Test range types over domains +-- + +create domain mydomain as int4; +create type mydomainrange as range(subtype=mydomain); +select '[4,50)'::mydomainrange @> 7::mydomain; +drop type mydomainrange; +drop domain mydomain; + +-- +-- Test domains over range types +-- + +create domain restrictedrange as int4range check (upper(value) < 10); +select '[4,5)'::restrictedrange @> 7; +select '[4,50)'::restrictedrange @> 7; -- should fail +drop domain restrictedrange; + +-- +-- Test multiple range types over the same subtype +-- + +create type textrange1 as range(subtype=text, collation="C"); +create type textrange2 as range(subtype=text, collation="C"); + +select textrange1('a','Z') @> 'b'::text; +select textrange2('a','z') @> 'b'::text; + +drop type textrange1; +drop type textrange2; + +-- +-- Test out polymorphic type system +-- + +create function anyarray_anyrange_func(a anyarray, r anyrange) + returns anyelement as 'select $1[1] + lower($2);' language sql; + +select anyarray_anyrange_func(ARRAY[1,2], int4range(10,20)); + +-- should fail +select anyarray_anyrange_func(ARRAY[1,2], numrange(10,20)); + +drop function anyarray_anyrange_func(anyarray, anyrange); + +-- should fail +create function bogus_func(anyelement) + returns anyrange as 'select int4range(1,10)' language sql; + +-- should fail +create function bogus_func(int) + returns anyrange as 'select int4range(1,10)' language sql; + +create function range_add_bounds(anyrange) + returns anyelement as 'select lower($1) + upper($1)' language sql; + +select range_add_bounds(numrange(1.0001, 123.123)); + +-- +-- Arrays of ranges +-- + +select ARRAY[numrange(1.1), numrange(12.3,155.5)]; + +-- +-- Ranges of arrays +-- + +create type arrayrange as range (subtype=int4[]); + +select arrayrange(ARRAY[1,2], ARRAY[2,1]); + +drop type arrayrange; + +-- +-- OUT/INOUT/TABLE functions +-- + +create function outparam_succeed(i anyrange, out r anyrange, out t text) + as $$ select $1, 'foo' $$ language sql; + +create function inoutparam_succeed(out i anyelement, inout r anyrange) + as $$ select $1, $2 $$ language sql; + +create function table_succeed(i anyelement, r anyrange) returns table(i anyelement, r anyrange) + as $$ select $1, $2 $$ language sql; + +-- should fail +create function outparam_fail(i anyelement, out r anyrange, out t text) + as $$ select '[1,10]', 'foo' $$ language sql; + +--should fail +create function inoutparam_fail(inout i anyelement, out r anyrange) + as $$ select $1, '[1,10]' $$ language sql; + +--should fail +create function table_succeed(i anyelement) returns table(i anyelement, r anyrange) + as $$ select $1, '[1,10]' $$ language sql; diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql index fa6dd75f07..1638861bc1 100644 --- a/src/test/regress/sql/type_sanity.sql +++ b/src/test/regress/sql/type_sanity.sql @@ -20,7 +20,7 @@ SELECT p1.oid, p1.typname FROM pg_type as p1 WHERE p1.typnamespace = 0 OR (p1.typlen <= 0 AND p1.typlen != -1 AND p1.typlen != -2) OR - (p1.typtype not in ('b', 'c', 'd', 'e', 'p')) OR + (p1.typtype not in ('b', 'c', 'd', 'e', 'p', 'r')) OR NOT p1.typisdefined OR (p1.typalign not in ('c', 's', 'i', 'd')) OR (p1.typstorage not in ('p', 'x', 'e', 'm'));