提交 4429f6a9 编写于 作者: H Heikki Linnakangas

Support range data types.

Selectivity estimation functions are missing for some range type operators,
which is a TODO.

Jeff Davis
上级 43342891
......@@ -218,6 +218,11 @@
<entry>functions and procedures</entry>
</row>
<row>
<entry><link linkend="catalog-pg-range"><structname>pg_range</structname></link></entry>
<entry>information about range types</entry>
</row>
<row>
<entry><link linkend="catalog-pg-rewrite"><structname>pg_rewrite</structname></link></entry>
<entry>query rewrite rules</entry>
......@@ -4594,6 +4599,78 @@
</sect1>
<sect1 id="catalog-pg-range">
<title><structname>pg_range</structname></title>
<indexterm zone="catalog-pg-range">
<primary>pg_range</primary>
</indexterm>
<para>
The catalog <structname>pg_range</structname> stores information about range types.
</para>
<table>
<title><structname>pg_range</> Columns</title>
<tgroup cols="4">
<thead>
<row>
<entry>Name</entry>
<entry>Type</entry>
<entry>References</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry><structfield>rngtypid</structfield></entry>
<entry><type>oid</type></entry>
<entry><literal><link linkend="catalog-pg-type"><structname>pg_type</structname></link>.oid</literal></entry>
<entry>The type that is a range type</entry>
</row>
<row>
<entry><structfield>rngsubtype</structfield></entry>
<entry><type>oid</type></entry>
<entry><literal><link linkend="catalog-pg-type"><structname>pg_type</structname></link>.oid</literal></entry>
<entry>Subtype of this range type, e.g. <type>integer</type> is the subtype of <type>int4range</type></entry>
</row>
<row>
<entry><structfield>rngcollation</structfield></entry>
<entry><type>oid</type></entry>
<entry><literal><link linkend="catalog-pg-collation"><structname>pg_collation</structname></link>.oid</literal></entry>
<entry>The collation used when comparing range boundaries</entry>
</row>
<row>
<entry><structfield>rngsubopc</structfield></entry>
<entry><type>oid</type></entry>
<entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
<entry>The operator class used when comparing range boundaries</entry>
</row>
<row>
<entry><structfield>rngcanonical</structfield></entry>
<entry><type>regproc</type></entry>
<entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
<entry>A function to convert a range into its canonical form</entry>
</row>
<row>
<entry><structfield>rngsubdiff</structfield></entry>
<entry><type>regproc</type></entry>
<entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
<entry>A function to return the distance between two lower and upper bound, as a <type>double precision</type>. Used for GiST support</entry>
</row>
</tbody>
</tgroup>
</table>
</sect1>
<sect1 id="catalog-pg-rewrite">
<title><structname>pg_rewrite</structname></title>
......
......@@ -4173,6 +4173,8 @@ SET xmloption TO { DOCUMENT | CONTENT };
&rowtypes;
&rangetypes;
<sect1 id="datatype-oid">
<title>Object Identifier Types</title>
......@@ -4443,6 +4445,10 @@ SELECT * FROM pg_attribute
<primary>anyenum</primary>
</indexterm>
<indexterm zone="datatype-pseudo">
<primary>anyrange</primary>
</indexterm>
<indexterm zone="datatype-pseudo">
<primary>void</primary>
</indexterm>
......@@ -4519,6 +4525,13 @@ SELECT * FROM pg_attribute
<xref linkend="datatype-enum">).</entry>
</row>
<row>
<entry><type>anyrange</></entry>
<entry>Indicates that a function accepts any range data type
(see <xref linkend="extend-types-polymorphic"> and
<xref linkend="rangetypes">).</entry>
</row>
<row>
<entry><type>anynonarray</></entry>
<entry>Indicates that a function accepts any non-array data type
......@@ -4583,7 +4596,8 @@ SELECT * FROM pg_attribute
only <type>void</> and <type>record</> as a result type (plus
<type>trigger</> when the function is used as a trigger). Some also
support polymorphic functions using the types <type>anyarray</>,
<type>anyelement</>, <type>anyenum</>, and <type>anynonarray</>.
<type>anyelement</>, <type>anyenum</>, <type>anyrange</>, and
<type>anynonarray</>.
</para>
<para>
......
......@@ -198,14 +198,15 @@
</indexterm>
<para>
Four pseudo-types of special interest are <type>anyelement</>,
<type>anyarray</>, <type>anynonarray</>, and <type>anyenum</>,
which are collectively called <firstterm>polymorphic types</>.
Any function declared using these types is said to be
a <firstterm>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 <type>anyelement</>,
<type>anyarray</>, <type>anynonarray</>, <type>anyenum</>,
and <type>anyrange</>, which are collectively
called <firstterm>polymorphic types</>. Any function declared
using these types is said to be a <firstterm>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.
</para>
<para>
......@@ -221,6 +222,11 @@
<type>anyelement</type>, the actual array type in the
<type>anyarray</type> positions must be an array whose elements are
the same type appearing in the <type>anyelement</type> positions.
Similarly, if there are positions declared <type>anyrange</type>
and others declared
<type>anyelement</type>, the actual range type in the
<type>anyrange</type> positions must be a range whose subtype is
the same type appearing in the <type>anyelement</type> positions.
<type>anynonarray</> is treated exactly the same as <type>anyelement</>,
but adds the additional constraint that the actual type must not be
an array type.
......
......@@ -25,6 +25,7 @@
<!ENTITY mvcc SYSTEM "mvcc.sgml">
<!ENTITY perform SYSTEM "perform.sgml">
<!ENTITY queries SYSTEM "queries.sgml">
<!entity rangetypes SYSTEM "rangetypes.sgml">
<!ENTITY rowtypes SYSTEM "rowtypes.sgml">
<!ENTITY syntax SYSTEM "syntax.sgml">
<!ENTITY textsearch SYSTEM "textsearch.sgml">
......
......@@ -10457,6 +10457,310 @@ SELECT NULLIF(value, '(none)') ...
</para>
</sect1>
<sect1 id="functions-range">
<title>Range Functions and Operators</title>
<para>
<xref linkend="range-operators-table"> shows the operators
available for range types.
</para>
<table id="range-operators-table">
<title>Range Operators</title>
<tgroup cols="4">
<thead>
<row>
<entry>Operator</entry>
<entry>Description</entry>
<entry>Example</entry>
<entry>Result</entry>
</row>
</thead>
<tbody>
<row>
<entry> <literal>=</literal> </entry>
<entry>equal</entry>
<entry><literal>int4range(1,5) = '[1,4]'::int4range</literal></entry>
<entry><literal>t</literal></entry>
</row>
<row>
<entry> <literal>&lt;&gt;</literal> </entry>
<entry>not equal</entry>
<entry><literal>numrange(1.1,2.2) &lt;&gt; numrange(1.1,2.3)</literal></entry>
<entry><literal>t</literal></entry>
</row>
<row>
<entry> <literal>&lt;</literal> </entry>
<entry>less than</entry>
<entry><literal>int4range(1,10) &lt; int4range(2,3)</literal></entry>
<entry><literal>t</literal></entry>
</row>
<row>
<entry> <literal>&gt;</literal> </entry>
<entry>greater than</entry>
<entry><literal>int4range(1,10) &gt; int4range(1,5)</literal></entry>
<entry><literal>t</literal></entry>
</row>
<row>
<entry> <literal>&lt;=</literal> </entry>
<entry>less than or equal</entry>
<entry><literal>numrange(1.1,2.2) &lt;= numrange(1.1,2.2)</literal></entry>
<entry><literal>t</literal></entry>
</row>
<row>
<entry> <literal>&gt;=</literal> </entry>
<entry>greater than or equal</entry>
<entry><literal>numrange(1.1,2.2) &gt;= numrange(1.1,2.0)</literal></entry>
<entry><literal>t</literal></entry>
</row>
<row>
<entry> <literal>@&gt;</literal> </entry>
<entry>contains</entry>
<entry><literal>'[2011-01-01,2011-03-01)'::tsrange @&gt; '2011-01-10'::timestamp</literal></entry>
<entry><literal>t</literal></entry>
</row>
<row>
<entry> <literal>&lt;@</literal> </entry>
<entry>is contained by</entry>
<entry><literal>int4range(2,4) &lt;@ int4range(1,7)</literal></entry>
<entry><literal>t</literal></entry>
</row>
<row>
<entry> <literal>&amp;&amp;</literal> </entry>
<entry>overlap (have points in common)</entry>
<entry><literal>int8range(3,7) &amp;&amp; int8range(4,12)</literal></entry>
<entry><literal>t</literal></entry>
</row>
<row>
<entry> <literal>&lt;&lt;</literal> </entry>
<entry>strictly left of</entry>
<entry><literal>int8range(1,10) &lt;&lt; int8range(100,110)</literal></entry>
<entry><literal>t</literal></entry>
</row>
<row>
<entry> <literal>&gt;&gt;</literal> </entry>
<entry>strictly right of</entry>
<entry><literal>int8range(50,60) &gt;&gt; int8range(20,30)</literal></entry>
<entry><literal>t</literal></entry>
</row>
<row>
<entry> <literal>&amp;&lt;</literal> </entry>
<entry>Does not extend to the right of?</entry>
<entry><literal>int8range(1,20) &amp;&lt; int8range(18,20)</literal></entry>
<entry><literal>t</literal></entry>
</row>
<row>
<entry> <literal>&amp;&gt;</literal> </entry>
<entry>Does not extend to the left of?</entry>
<entry><literal>int8range(7,20) &amp;&gt; int8range(5,10)</literal></entry>
<entry><literal>t</literal></entry>
</row>
<row>
<entry> <literal>-|-</literal> </entry>
<entry>adjacent?</entry>
<entry><literal>numrange(1.1,2.2) -|- numrange(2.2,3.3)</literal></entry>
<entry><literal>t</literal></entry>
</row>
<row>
<entry> <literal>+</literal> </entry>
<entry>Union</entry>
<entry><literal>numrange(5,15) + numrange(10,20)</literal></entry>
<entry><literal>[5,20)</literal></entry>
</row>
<row>
<entry> <literal>-</literal> </entry>
<entry>Difference</entry>
<entry><literal>int8range(5,15) - int8range(10,20)</literal></entry>
<entry><literal>[5,10)</literal></entry>
</row>
<row>
<entry> <literal>*</literal> </entry>
<entry>Intersection</entry>
<entry><literal>int8range(5,15) * int8range(10,20)</literal></entry>
<entry><literal>[10,15)</literal></entry>
</row>
<row>
<entry> <literal>!?</literal> </entry>
<entry>Is empty?</entry>
<entry><literal>'empty'::int4range !?</literal></entry>
<entry><literal>t</literal></entry>
</row>
<row>
<entry> <literal>?</literal> </entry>
<entry>Is non-empty?</entry>
<entry><literal>numrange(1.0,2.0)?</literal></entry>
<entry><literal>t</literal></entry>
</row>
</tbody>
</tgroup>
</table>
<para>
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.
</para>
<para>
See <xref linkend="rangetypes"> for more details about range operator
behavior.
</para>
<para>
<xref linkend="range-functions-table"> shows the functions
available for use with range types. See <xref linkend="rangetypes">
for more information and examples of the use of these functions.
</para>
<indexterm>
<primary>lower</primary>
</indexterm>
<indexterm>
<primary>upper</primary>
</indexterm>
<indexterm>
<primary>empty</primary>
</indexterm>
<indexterm>
<primary>non_empty</primary>
</indexterm>
<indexterm>
<primary>lower_inc</primary>
</indexterm>
<indexterm>
<primary>upper_inc</primary>
</indexterm>
<indexterm>
<primary>lower_inf</primary>
</indexterm>
<indexterm>
<primary>upper_inf</primary>
</indexterm>
<table id="range-functions-table">
<title>Range Functions</title>
<tgroup cols="5">
<thead>
<row>
<entry>Function</entry>
<entry>Return Type</entry>
<entry>Description</entry>
<entry>Example</entry>
<entry>Result</entry>
</row>
</thead>
<tbody>
<row>
<entry>
<literal>
<function>lower</function>(<type>anyrange</type>)
</literal>
</entry>
<entry><type>anyrange</type></entry>
<entry>lower bound of range</entry>
<entry><literal>lower(numrange(1.1,2.2))</literal></entry>
<entry><literal>1.1</literal></entry>
</row>
<row>
<entry>
<literal>
<function>upper</function>(<type>anyrange</type>)
</literal>
</entry>
<entry><type>anyrange</type></entry>
<entry>upper bound of range</entry>
<entry><literal>upper(numrange(1.1,2.2))</literal></entry>
<entry><literal>2.2</literal></entry>
</row>
<row>
<entry>
<literal>
<function>empty</function>(<type>anyrange</type>)
</literal>
</entry>
<entry><type>anyrange</type></entry>
<entry>is the range empty?</entry>
<entry><literal>empty(numrange(1.1,2.2))</literal></entry>
<entry><literal>false</literal></entry>
</row>
<row>
<entry>
<literal>
<function>non_empty</function>(<type>anyrange</type>)
</literal>
</entry>
<entry><type>anyrange</type></entry>
<entry>is the range non-empty?</entry>
<entry><literal>non_empty(numrange(1.1,2.2))</literal></entry>
<entry><literal>true</literal></entry>
</row>
<row>
<entry>
<literal>
<function>lower_inc</function>(<type>anyrange</type>)
</literal>
</entry>
<entry><type>anyrange</type></entry>
<entry>is the lower bound of the range inclusive?</entry>
<entry><literal>lower_inc(numrange(1.1,2.2))</literal></entry>
<entry><literal>true</literal></entry>
</row>
<row>
<entry>
<literal>
<function>upper_inc</function>(<type>anyrange</type>)
</literal>
</entry>
<entry><type>anyrange</type></entry>
<entry>is the upper bound of the range inclusive?</entry>
<entry><literal>upper_inc(numrange(1.1,2.2))</literal></entry>
<entry><literal>false</literal></entry>
</row>
<row>
<entry>
<literal>
<function>lower_inf</function>(<type>anyrange</type>)
</literal>
</entry>
<entry><type>anyrange</type></entry>
<entry>is the lower bound of the range infinite?</entry>
<entry><literal>lower_inf('(,)'::daterange)</literal></entry>
<entry><literal>true</literal></entry>
</row>
<row>
<entry>
<literal>
<function>upper_inf</function>(<type>anyrange</type>)
</literal>
</entry>
<entry><type>anyrange</type></entry>
<entry>is the upper bound of the range infinite?</entry>
<entry><literal>upper_inf('(,)'::daterange)</literal></entry>
<entry><literal>true</literal></entry>
</row>
</tbody>
</tgroup>
</table>
</sect1>
<sect1 id="functions-aggregate">
<title>Aggregate Functions</title>
......
......@@ -139,7 +139,7 @@
<application>PL/pgSQL</> functions can also be declared to accept
and return the polymorphic types
<type>anyelement</type>, <type>anyarray</type>, <type>anynonarray</type>,
and <type>anyenum</>. The actual
<type>anyenum</>, and <type>anyrange</type>. The actual
data types handled by a polymorphic function can vary from call to
call, as discussed in <xref linkend="extend-types-polymorphic">.
An example is shown in <xref linkend="plpgsql-declaration-parameters">.
......@@ -500,8 +500,8 @@ $$ LANGUAGE plpgsql;
<para>
When the return type of a <application>PL/pgSQL</application>
function is declared as a polymorphic type (<type>anyelement</type>,
<type>anyarray</type>, <type>anynonarray</type>, or <type>anyenum</>),
a special parameter <literal>$0</literal>
<type>anyarray</type>, <type>anynonarray</type>, <type>anyenum</type>,
or <type>anyrange</type>), a special parameter <literal>$0</literal>
is created. Its data type is the actual return type of the function,
as deduced from the actual input types (see <xref
linkend="extend-types-polymorphic">).
......
<!-- doc/src/sgml/rangetypes.sgml -->
<sect1 id="rangetypes">
<title>Range Types</title>
<indexterm>
<primary>range type</primary>
</indexterm>
<para>
Range types are data types representing a range of values over some
sub-type with a total order. For instance, ranges
of <type>timestamp</type> might be used to represent the ranges of
time that a meeting room is reserved. In this case the data type
is <type>tsrange</type> (short for "timestamp range"),
and <type>timestamp</type> is the sub-type with a total order.
</para>
<para>
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.
</para>
<sect2 id="rangetypes-builtin">
<title>Built-in Range Types</title>
<para>
PostgreSQL comes with the following built-in range types:
<itemizedlist>
<listitem>
<para>
<type>INT4RANGE</type> -- Range of <type>INTEGER</type>. This is a discrete range type, see <xref linkend="rangetypes-discrete">.
</para>
</listitem>
<listitem>
<para>
<type>INT8RANGE</type> -- Range of <type>BIGINT</type>. This is a discrete range type, see <xref linkend="rangetypes-discrete">.
</para>
</listitem>
<listitem>
<para>
<type>NUMRANGE</type> -- Range of <type>NUMERIC</type>.
</para>
</listitem>
<listitem>
<para>
<type>TSRANGE</type> -- Range of <type>TIMESTAMP WITHOUT TIME ZONE</type>.
</para>
</listitem>
<listitem>
<para>
<type>TSTZRANGE</type> -- Range of <type>TIMESTAMP WITH TIME ZONE</type>.
</para>
</listitem>
<listitem>
<para>
<type>DATERANGE</type> -- Range of <type>DATE</type>. This is a discrete range type, see <xref linkend="rangetypes-discrete">.
</para>
</listitem>
</itemizedlist>
In addition, you can define your own; see <xref linkend="SQL-CREATETYPE"> for more information.
</para>
</sect2>
<sect2 id="rangetypes-examples">
<title>Examples</title>
<para>
<programlisting>
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)? ;
</programlisting>
See <xref linkend="range-functions-table">
and <xref linkend="range-operators-table"> for complete lists of
functions and operators on range types.
</para>
</sect2>
<sect2 id="rangetypes-inclusivity">
<title>Inclusive and Exclusive Bounds</title>
<para>
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.
</para>
<para>
An inclusive lower bound is represented by <literal>[</literal>
while an exclusive lower bound is represented
by <literal>(</literal> (see <xref linkend="rangetypes-construct">
and <xref linkend="rangetypes-io"> below). Likewise, an inclusive
upper bound is represented by <literal>]</literal>, while an
exclusive upper bound is represented by <literal>)</literal>.
</para>
<para>
Functions <literal>lower_inc</literal>
and <literal>upper_inc</literal> test the inclusivity of the lower
and upper bounds of a range, respectively.
</para>
</sect2>
<sect2 id="rangetypes-infinite">
<title>Infinite (unbounded) Ranges</title>
<para>
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.
</para>
<para>
Functions <literal>lower_inf</literal>
and <literal>upper_inf</literal> test the range for infinite lower
and upper bounds of a range, respectively.
</para>
</sect2>
<sect2 id="rangetypes-io">
<title>Input/Output</title>
<para>
The input follows one of the following patterns:
<synopsis>
(<replaceable>lower-bound</replaceable>,<replaceable>upper-bound</replaceable>)
(<replaceable>lower-bound</replaceable>,<replaceable>upper-bound</replaceable>]
[<replaceable>lower-bound</replaceable>,<replaceable>upper-bound</replaceable>)
[<replaceable>lower-bound</replaceable>,<replaceable>upper-bound</replaceable>]
empty
</synopsis>
Notice that the final pattern is <literal>empty</literal>, which
represents an empty range (a range that contains no points).
</para>
<para>
The <replaceable>lower-bound</replaceable> may be either a string
that is valid input for the sub-type, or omitted (to indicate no
lower bound); and <replaceable>upper-bound</replaceable> may be
either a string that is valid input for the sub-type, or omitted (to
indicate no upper bound).
</para>
<para>
Either the <replaceable>lower-bound</replaceable> or
the <replaceable>upper-bound</replaceable> may be quoted
using <literal>""</literal> (double quotation marks), which will allow
special characters such as "<literal>,</literal>". Within quotation
marks, "<literal>\</literal>" (backslash) serves as an escape
character.
</para>
<para>
The choice between the other input formats affects the inclusivity
of the bounds. See <xref linkend="rangetypes-inclusivity">.
</para>
<para>
Examples:
<programlisting>
-- 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]'
</programlisting>
</para>
</sect2>
<sect2 id="rangetypes-construct">
<title>Constructing Ranges</title>
<para>
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 <literal>[ )</literal> form; and the three-argument form
constructs a range in a form specified by the third argument. For
example:
<programlisting>
-- Three-argument form: lower bound, upper bound, and third argument indicating
-- inclusivity/exclusivity of bounds (if omitted, defaults to <literal>'[)'</literal>).
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 <xref linkend="rangetypes-discrete">.
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);
</programlisting>
</para>
</sect2>
<sect2 id="rangetypes-discrete">
<title>Discrete Range Types</title>
<para>
Discrete ranges are those that have a
defined <literal>canonical</literal> function. Loosely speaking, a
discrete range has a sub-type with a well-defined "step";
e.g. <type>INTEGER</type> or <type>DATE</type>.
</para>
<para>
The <literal>canonical</literal> function should take an input range
value, and return an equal range value that may have a different
formatting. For instance, the integer range <literal>[1,
7]</literal> could be represented by the equal integer
range <literal>[1, 8)</literal>. The two values are equal because
there are no points within the integer domain
between <literal>7</literal> and <literal>8</literal>, so not
including the end point <literal>8</literal> is the same as
including the end point <literal>7</literal>. The canonical output
for two values that are equal, like <literal>[1, 7]</literal>
and <literal>[1, 8)</literal>, 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. <literal>[1, 7]</literal> and <literal>[1, 8)</literal>) will
always be treated as unequal.
</para>
<para>
For types such as <type>NUMRANGE</type>, this is not possible,
because there are always points in between two
distinct <type>NUMERIC</type> values.
</para>
<para>
The built-in range
types <type>INT4RANGE</type>, <type>INT8RANGE</type>,
and <type>DATERNAGE</type> all use a canonical form that includes
the lower bound and excludes the upper bound; that is, <literal>[
)</literal>. User-defined ranges can use other conventions, however.
</para>
</sect2>
<sect2 id="rangetypes-defining">
<title>Defining New Range Types</title>
<para>
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 <type>FLOAT</type> (or, if the
subtype itself is a user-defined type).
</para>
<para>
For example: to define a new range type of sub-type <type>DOUBLE PRECISION</type>:
<programlisting>
CREATE TYPE FLOATRANGE AS RANGE (
SUBTYPE = DOUBLE PRECISION
);
SELECT '[1.234, 5.678]'::floatrange;
</programlisting>
Because <type>DOUBLE PRECISION</type> has no meaningful "step", we
do not define a <literal>canonical</literal>
function. See <xref linkend="SQL-CREATETYPE"> for more
information.
</para>
<para>
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.
</para>
</sect2>
<sect2 id="rangetypes-gist">
<indexterm>
<primary>range type</primary>
<secondary>gist</secondary>
</indexterm>
<title>Indexing</title>
<para>
GiST indexes can be applied to a table containing a range type. For instance:
<programlisting>
CREATE INDEX reservation_idx ON reservation USING gist (during);
</programlisting>
This index may speed up queries
involving <literal>&amp;&amp;</literal>
(overlaps), <literal>@&gt;</literal> (contains), and all the boolean
operators found in this
table: <xref linkend="range-operators-table">.
</para>
</sect2>
<sect2 id="rangetypes-constraint">
<indexterm>
<primary>range type</primary>
<secondary>exclude</secondary>
</indexterm>
<title>Constraints on Ranges</title>
<para>
While <literal>UNIQUE</literal> is a natural constraint for scalar
values, it is usually unsuitable for range types. Instead, an
exclusion constraint is often more appropriate
(see <link linkend="SQL-CREATETABLE-EXCLUDE">CREATE TABLE
... CONSTRAINT ... EXCLUDE</link>). Exclusion constraints allow the
specification of constraints such as "non-overlapping" on a range
type. For example:
<programlisting>
ALTER TABLE reservation
ADD EXCLUDE USING gist (during WITH &&);
</programlisting>
That constraint will prevent any overlapping values from existing
in the table at the same time:
<programlisting>
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 )).
</programlisting>
</para>
<para>
Combine range types and exclusion constraints
with <link linkend="btree-gist">btree_gist</link> for maximum
flexibility defining
constraints. After <literal>btree_gist</literal> is installed, the
following constraint will prevent overlapping ranges only if the
meeting room numbers are equal:
<programlisting>
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
</programlisting>
</para>
</sect2>
</sect1>
......@@ -27,6 +27,15 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> AS
CREATE TYPE <replaceable class="parameter">name</replaceable> AS ENUM
( [ '<replaceable class="parameter">label</replaceable>' [, ... ] ] )
CREATE TYPE <replaceable class="parameter">name</replaceable> AS RANGE (
SUBTYPE = <replaceable class="parameter">subtype</replaceable>,
[ , SUBTYPE_OPCLASS = <replaceable class="parameter">subtype_operator_class</replaceable> ]
[ , SUBTYPE_DIFF = <replaceable class="parameter">subtype_diff_function</replaceable> ]
[ , CANONICAL = <replaceable class="parameter">canonical_function</replaceable> ]
[ , ANALYZE = <replaceable class="parameter">analyze_function</replaceable> ]
[ , COLLATION = <replaceable class="parameter">collation</replaceable> ]
)
CREATE TYPE <replaceable class="parameter">name</replaceable> (
INPUT = <replaceable class="parameter">input_function</replaceable>,
OUTPUT = <replaceable class="parameter">output_function</replaceable>
......@@ -98,11 +107,61 @@ CREATE TYPE <replaceable class="parameter">name</replaceable>
</para>
</refsect2>
<refsect2 id="SQL-CREATETYPE-RANGE">
<title>Range Types</title>
<para>
The third form of <command>CREATE TYPE</command> creates a new
range type, as described in <xref linkend="rangetypes">.
</para>
<para>
The <replaceable class="parameter">subtype</replaceable> parameter
can be any type with an associated btree opclass (uses the type's
default btree operator class unless specified with
<replaceable class="parameter">subtype_operator_class</replaceable>).
</para>
<para>
The <replaceable class="parameter">subtype_diff</replaceable>
function takes two values of type
<replaceable class="parameter">subtype</replaceable> as argument, and
returns the distance between the two values as
<type>double precision</type>. This function is used for GiST indexing
(see <xref linkend="gist"> for more information), and should be provided
for efficiency.
</para>
<para>
The <replaceable class="parameter">canonical</replaceable>
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 <xref linkend="rangetypes">
for more information. To define
a <replaceable class="parameter">canonical</replaceable> function,
you must first create a <firstterm>shell type</>, which is a
placeholder type that has no properties except a name and an
owner. This is done by issuing the command <literal>CREATE TYPE
<replaceable>name</></literal>, with no additional parameters.
</para>
<para>
The <replaceable class="parameter">analyze</replaceable>
function is the same as for creating a base type.
</para>
<para>
The <replaceable class="parameter">collation</replaceable> option
specifies the collation used when determining the total order for
the range.
</para>
</refsect2>
<refsect2>
<title>Base Types</title>
<para>
The third form of <command>CREATE TYPE</command> creates a new base type
The fourth form of <command>CREATE TYPE</command> 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.)
......
......@@ -997,8 +997,8 @@ $$ LANGUAGE SQL;
<para>
<acronym>SQL</acronym> functions can be declared to accept and
return the polymorphic types <type>anyelement</type>,
<type>anyarray</type>, <type>anynonarray</type>, and
<type>anyenum</type>. See <xref
<type>anyarray</type>, <type>anynonarray</type>,
<type>anyenum</type>, and <type>anyrange</type>. See <xref
linkend="extend-types-polymorphic"> for a more detailed
explanation of polymorphic functions. Here is a polymorphic
function <function>make_array</function> 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
<type>anyelement</type>, <type>anyarray</type>, <type>anynonarray</type>,
and <type>anyenum</type>.
<type>anyenum</type>, and <type>anyrange</type>.
See <xref linkend="extend-types-polymorphic"> for a more detailed explanation
of polymorphic functions. When function arguments or return types
are defined as polymorphic types, the function author cannot know
......
......@@ -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 \
)
......
......@@ -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
......
/*-------------------------------------------------------------------------
*
* 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);
}
......@@ -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
*
......
......@@ -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)
{
/*
......
......@@ -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;
......
......@@ -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;
......
......@@ -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);
......
......@@ -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))
......
......@@ -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;
......
......@@ -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 \
......
......@@ -889,7 +889,6 @@ date_timestamp(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMP(result);
}
/* timestamp_date()
* Convert timestamp to date data type.
*/
......
......@@ -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.
......
此差异已折叠。
/*-------------------------------------------------------------------------
*
* 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);
}
......@@ -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;
}
......@@ -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,
......
此差异已折叠。
此差异已折叠。
......@@ -53,6 +53,7 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 201110221
/* COMMITTER: please set appropriately */
#define CATALOG_VERSION_NO 201111111
#endif
......@@ -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
......
此差异已折叠。
此差异已折叠。
......@@ -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 */
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册