public inbox for [email protected]help / color / mirror / Atom feed
[PATCH v30 4/6] Subscripting documentation 28+ messages / 2 participants [nested] [flat]
* [PATCH v30 4/6] Subscripting documentation @ 2019-02-01 10:47 erthalion <[email protected]> 0 siblings, 0 replies; 28+ messages in thread From: erthalion @ 2019-02-01 10:47 UTC (permalink / raw) Supporting documentation for generalized subscripting. It includes the description of a new field in pg_type, the new section for jsonb documentation about subscripting feature on this data type, and also the tutorial about how to write subscripting operator for a custom data type. Reviewed-by: Tom Lane, Arthur Zakirov, Oleksandr Shulgin --- doc/src/sgml/catalogs.sgml | 8 ++ doc/src/sgml/extend.sgml | 6 + doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/json.sgml | 39 ++++++ doc/src/sgml/ref/create_type.sgml | 33 ++++- doc/src/sgml/xsubscripting.sgml | 111 +++++++++++++++++ src/tutorial/Makefile | 4 +- src/tutorial/subscripting.c | 201 ++++++++++++++++++++++++++++++ src/tutorial/subscripting.source | 71 +++++++++++ 9 files changed, 470 insertions(+), 4 deletions(-) create mode 100644 doc/src/sgml/xsubscripting.sgml create mode 100644 src/tutorial/subscripting.c create mode 100644 src/tutorial/subscripting.source diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 34bc0d0526..328a1da6fe 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -7911,6 +7911,14 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l </para></entry> </row> + <row> + <entry><structfield>typsubshandler</structfield></entry> + <entry><type>regproc</type></entry> + <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry> + <entry>Custom subscripting function with type-specific logic for parsing + and validation, or 0 if this type doesn't support subscripting.</entry> + </row> + <row> <entry><structfield>typdefaultbin</structfield></entry> <entry><type>pg_node_tree</type></entry> diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml index 9ec1af780b..057010157e 100644 --- a/doc/src/sgml/extend.sgml +++ b/doc/src/sgml/extend.sgml @@ -33,6 +33,11 @@ operators (starting in <xref linkend="xoper"/>) </para> </listitem> + <listitem> + <para> + subscripting procedure (starting in <xref linkend="xsubscripting"/>) + </para> + </listitem> <listitem> <para> operator classes for indexes (starting in <xref linkend="xindex"/>) @@ -314,6 +319,7 @@ &xaggr; &xtypes; &xoper; + &xsubscripting; &xindex; diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 3da2365ea9..650e21b7e1 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -69,6 +69,7 @@ <!ENTITY xplang SYSTEM "xplang.sgml"> <!ENTITY xoper SYSTEM "xoper.sgml"> <!ENTITY xtypes SYSTEM "xtypes.sgml"> +<!ENTITY xsubscripting SYSTEM "xsubscripting.sgml"> <!ENTITY plperl SYSTEM "plperl.sgml"> <!ENTITY plpython SYSTEM "plpython.sgml"> <!ENTITY plsql SYSTEM "plpgsql.sgml"> diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml index 1b6aaf0a55..189f03b41e 100644 --- a/doc/src/sgml/json.sgml +++ b/doc/src/sgml/json.sgml @@ -600,6 +600,45 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu </para> </sect2> + <sect2 id="jsonb-subscripting"> + <title><type>jsonb</type> Subscripting</title> + <para> + <type>jsonb</type> data type supports array-style subscripting expressions + to extract or update particular element. It's possible to use multiple + subscripting expressions to extract nested values. In this case a chain of + subscripting expressions follows the same rules as the + <literal>path</literal> argument in <literal>jsonb_set</literal> function, + e.g. in case of arrays it is a 0-based operation or that negative integers + that appear in <literal>path</literal> count from the end of JSON arrays. + The result of subscripting expressions is always jsonb data type. An + example of subscripting syntax: +<programlisting> +-- Extract value by key +SELECT ('{"a": 1}'::jsonb)['a']; + +-- Extract nested value by key path +SELECT ('{"a": {"b": {"c": 1}}}'::jsonb)['a']['b']['c']; + +-- Extract element by index +SELECT ('[1, "2", null]'::jsonb)[1]; + +-- Update value by key +UPDATE table_name SET jsonb_field['key'] = 1; + +-- Select records using where clause with subscripting. Since the result of +-- subscripting is jsonb and we basically want to compare two jsonb objects, we +-- need to put the value in double quotes to be able to convert it to jsonb. +SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"'; +</programlisting> + + There is no special indexing support for such kind of expressions, but you + always can create a functional index that includes it +<programlisting> +CREATE INDEX idx ON table_name ((jsonb_field['key'])); +</programlisting> + </para> + </sect2> + <sect2> <title>Transforms</title> diff --git a/doc/src/sgml/ref/create_type.sgml b/doc/src/sgml/ref/create_type.sgml index 175315f3d7..bd622f5ef3 100644 --- a/doc/src/sgml/ref/create_type.sgml +++ b/doc/src/sgml/ref/create_type.sgml @@ -54,6 +54,7 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> ( [ , ELEMENT = <replaceable class="parameter">element</replaceable> ] [ , DELIMITER = <replaceable class="parameter">delimiter</replaceable> ] [ , COLLATABLE = <replaceable class="parameter">collatable</replaceable> ] + [ , SUBSCRIPTING_HANDLER = <replaceable class="parameter">subscripting_handler_function</replaceable> ] ) CREATE TYPE <replaceable class="parameter">name</replaceable> @@ -196,8 +197,9 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> <replaceable class="parameter">receive_function</replaceable>, <replaceable class="parameter">send_function</replaceable>, <replaceable class="parameter">type_modifier_input_function</replaceable>, - <replaceable class="parameter">type_modifier_output_function</replaceable> and - <replaceable class="parameter">analyze_function</replaceable> + <replaceable class="parameter">type_modifier_output_function</replaceable>, + <replaceable class="parameter">analyze_function</replaceable>, + <replaceable class="parameter">subscripting_handler_function</replaceable> are optional. Generally these functions have to be coded in C or another low-level language. </para> @@ -454,6 +456,22 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> make use of the collation information; this does not happen automatically merely by marking the type collatable. </para> + + <para> + The optional + <replaceable class="parameter">subscripting_handler_function</replaceable> + contains type-specific logic for subscripting of the data type. + By default, there is no such function provided, which means that the data + type doesn't support subscripting. The subscripting function must be + declared to take a single argument of type <type>internal</type>, and return + a <type>internal</type> result. There are two examples of implementation for + subscripting functions in case of array + (<replaceable class="parameter">array_subscripting_handler</replaceable>) + and jsonb + (<replaceable class="parameter">jsonb_subscripting_handler</replaceable>) + types in <filename>src/backend/utils/adt/arrayfuncs.c</filename> and + <filename>src/backend/utils/adt/jsonfuncs.c</filename> corresponding. + </para> </refsect2> <refsect2> @@ -769,6 +787,17 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> </para> </listitem> </varlistentry> + + <varlistentry> + <term><replaceable class="parameter">subscripting_handler_function</replaceable></term> + <listitem> + <para> + The name of a function that returns list of type-specific callback functions to + support subscripting logic for the data type. + </para> + </listitem> + </varlistentry> + </variablelist> </refsect1> diff --git a/doc/src/sgml/xsubscripting.sgml b/doc/src/sgml/xsubscripting.sgml new file mode 100644 index 0000000000..d701631223 --- /dev/null +++ b/doc/src/sgml/xsubscripting.sgml @@ -0,0 +1,111 @@ +<!-- doc/src/sgml/xsubscripting.sgml --> + + <sect1 id="xsubscripting"> + <title>User-defined subscripting procedure</title> + + <indexterm zone="xsubscripting"> + <primary>custom subscripting</primary> + </indexterm> + <para> + When you define a new base type, you can also specify a custom procedures to + handle subscripting expressions. They must contain logic for verification and + evaluation of this expression, i.e. fetching or updating some data in this + data type. For instance: +</para> +<programlisting><![CDATA[ +typedef struct Custom +{ + int first; + int second; +} Custom; + +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = someType; + sbsref->refassgntype = someType; + + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + // some validation and coercion logic + + return sbsref; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some assignment logic + + return newContainer; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some fetch logic based on sbsdata +}]]> +</programlisting> + +<para> + Then you can define a subscripting procedures and a custom data type: +</para> +<programlisting> +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '<replaceable>filename</replaceable>' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 4, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler, +); +</programlisting> + +<para> + and use it as usual: +</para> +<programlisting> +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; +</programlisting> + + + <para> + The examples of custom subscripting implementation can be found in + <filename>subscripting.sql</filename> and <filename>subscripting.c</filename> + in the <filename>src/tutorial</filename> directory of the source distribution. + See the <filename>README</filename> file in that directory for instructions + about running the examples. + </para> + +</sect1> diff --git a/src/tutorial/Makefile b/src/tutorial/Makefile index 16dc390f71..0ead60c2d4 100644 --- a/src/tutorial/Makefile +++ b/src/tutorial/Makefile @@ -13,8 +13,8 @@ # #------------------------------------------------------------------------- -MODULES = complex funcs -DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql +MODULES = complex funcs subscripting +DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql subscripting.sql ifdef NO_PGXS subdir = src/tutorial diff --git a/src/tutorial/subscripting.c b/src/tutorial/subscripting.c new file mode 100644 index 0000000000..1eb8c45652 --- /dev/null +++ b/src/tutorial/subscripting.c @@ -0,0 +1,201 @@ +/* + * src/tutorial/subscripting.c + * + ****************************************************************************** + This file contains routines that can be bound to a Postgres backend and + called by the backend in the process of processing queries. The calling + format for these routines is dictated by Postgres architecture. +******************************************************************************/ + +#include "postgres.h" + +#include "catalog/pg_type.h" +#include "executor/executor.h" +#include "executor/execExpr.h" +#include "nodes/nodeFuncs.h" +#include "parser/parse_coerce.h" +#include "utils/builtins.h" +#include "utils/fmgrprotos.h" + +PG_MODULE_MAGIC; + +typedef struct Custom +{ + int first; + int second; +} Custom; + +SubscriptingRef * custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref); +SubscriptingRef * custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate); +Datum custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate); +Datum custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate); + +PG_FUNCTION_INFO_V1(custom_in); +PG_FUNCTION_INFO_V1(custom_out); +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +/***************************************************************************** + * Input/Output functions + *****************************************************************************/ + +Datum +custom_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + int firstValue, + secondValue; + Custom *result; + + if (sscanf(str, " ( %d , %d )", &firstValue, &secondValue) != 2) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for complex: \"%s\"", + str))); + + + result = (Custom *) palloc(sizeof(Custom)); + result->first = firstValue; + result->second = secondValue; + PG_RETURN_POINTER(result); +} + +Datum +custom_out(PG_FUNCTION_ARGS) +{ + Custom *custom = (Custom *) PG_GETARG_POINTER(0); + char *result; + + result = psprintf("(%d, %d)", custom->first, custom->second); + PG_RETURN_CSTRING(result); +} + +/***************************************************************************** + * Custom subscripting logic functions + *****************************************************************************/ + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = INT4OID; + sbsref->refassgntype = INT4OID; + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + List *upperIndexpr = NIL; + ListCell *l; + + if (sbsref->reflowerindexpr != NIL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *)lfirst(sbsref->reflowerindexpr->head)))))); + + foreach(l, sbsref->refupperindexpr) + { + Node *subexpr = (Node *) lfirst(l); + + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *) lfirst(sbsref->refupperindexpr->head)))))); + + subexpr = coerce_to_target_type(pstate, + subexpr, exprType(subexpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript must have integer type"), + parser_errposition(pstate, exprLocation(subexpr)))); + + upperIndexpr = lappend(upperIndexpr, subexpr); + + if (isAssignment) + { + Node *assignExpr = (Node *) sbsref->refassgnexpr; + Node *new_from; + + new_from = coerce_to_target_type(pstate, + assignExpr, exprType(assignExpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (new_from == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom assignment requires int type"), + errhint("You will need to rewrite or cast the expression."), + parser_errposition(pstate, exprLocation(assignExpr)))); + sbsref->refassgnexpr = (Expr *)new_from; + } + } + + sbsref->refupperindexpr = upperIndexpr; + + return sbsref; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + Custom *container= (Custom *) containerSource; + int index; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + return (Datum) container->first; + else + return (Datum) container->second; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + int index; + Custom *container = (Custom *) containerSource; + + if (sbstate->resnull) + return containerSource; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + container->first = DatumGetInt32(sbstate->replacevalue); + else + container->second = DatumGetInt32(sbstate->replacevalue); + + return (Datum) container; +} diff --git a/src/tutorial/subscripting.source b/src/tutorial/subscripting.source new file mode 100644 index 0000000000..837cf30612 --- /dev/null +++ b/src/tutorial/subscripting.source @@ -0,0 +1,71 @@ +--------------------------------------------------------------------------- +-- +-- subscripting.sql- +-- This file shows how to create a new subscripting procedure for +-- user-defined type. +-- +-- +-- Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group +-- Portions Copyright (c) 1994, Regents of the University of California +-- +-- src/tutorial/subscripting.source +-- +--------------------------------------------------------------------------- + +----------------------------- +-- Creating a new type: +-- We are going to create a new type called 'complex' which represents +-- complex numbers. +-- A user-defined type must have an input and an output function, and +-- optionally can have binary input and output functions. All of these +-- are usually user-defined C functions. +----------------------------- + +-- Assume the user defined functions are in /home/erthalion/programms/postgresql-master/src/tutorial/complex$DLSUFFIX +-- (we do not want to assume this is in the dynamic loader search path). +-- Look at $PWD/complex.c for the source. Note that we declare all of +-- them as STRICT, so we do not need to cope with NULL inputs in the +-- C code. We also mark them IMMUTABLE, since they always return the +-- same outputs given the same inputs. + +-- the input function 'complex_in' takes a null-terminated string (the +-- textual representation of the type) and turns it into the internal +-- (in memory) representation. You will get a message telling you 'complex' +-- does not exist yet but that's okay. + +CREATE FUNCTION custom_in(cstring) + RETURNS custom + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +-- the output function 'complex_out' takes the internal representation and +-- converts it into the textual representation. + +CREATE FUNCTION custom_out(custom) + RETURNS cstring + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 8, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler +); + +-- we can use it in a table + +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; -- 2.21.0 --g5st7yssq7xavhfb Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v30-0005-Polymorphic-subscripting.patch" ^ permalink raw reply [nested|flat] 28+ messages in thread
* [PATCH v35 4/5] Subscripting documentation @ 2019-02-01 10:47 erthalion <[email protected]> 0 siblings, 0 replies; 28+ messages in thread From: erthalion @ 2019-02-01 10:47 UTC (permalink / raw) Supporting documentation for generalized subscripting. It includes the description of a new field in pg_type, the new section for jsonb documentation about subscripting feature on this data type, and also the tutorial about how to write subscripting operator for a custom data type. Reviewed-by: Tom Lane, Arthur Zakirov, Oleksandr Shulgin --- doc/src/sgml/catalogs.sgml | 8 ++ doc/src/sgml/extend.sgml | 6 + doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/json.sgml | 39 ++++++ doc/src/sgml/ref/create_type.sgml | 33 ++++- doc/src/sgml/xsubscripting.sgml | 113 +++++++++++++++++ src/tutorial/Makefile | 4 +- src/tutorial/subscripting.c | 204 ++++++++++++++++++++++++++++++ src/tutorial/subscripting.source | 71 +++++++++++ 9 files changed, 475 insertions(+), 4 deletions(-) create mode 100644 doc/src/sgml/xsubscripting.sgml create mode 100644 src/tutorial/subscripting.c create mode 100644 src/tutorial/subscripting.source diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 7e99928d0c..104cea3584 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -8990,6 +8990,14 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l </para></entry> </row> + <row> + <entry><structfield>typsubshandler</structfield></entry> + <entry><type>regproc</type></entry> + <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry> + <entry>Custom subscripting function with type-specific logic for parsing + and validation, or 0 if this type doesn't support subscripting.</entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>typdefault</structfield> <type>text</type> diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml index e486006224..23c1764571 100644 --- a/doc/src/sgml/extend.sgml +++ b/doc/src/sgml/extend.sgml @@ -33,6 +33,11 @@ operators (starting in <xref linkend="xoper"/>) </para> </listitem> + <listitem> + <para> + subscripting procedure (starting in <xref linkend="xsubscripting"/>) + </para> + </listitem> <listitem> <para> operator classes for indexes (starting in <xref linkend="xindex"/>) @@ -481,6 +486,7 @@ RETURNS anycompatible AS ... &xaggr; &xtypes; &xoper; + &xsubscripting; &xindex; diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 828396d4a9..3bebb34c1a 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -69,6 +69,7 @@ <!ENTITY xplang SYSTEM "xplang.sgml"> <!ENTITY xoper SYSTEM "xoper.sgml"> <!ENTITY xtypes SYSTEM "xtypes.sgml"> +<!ENTITY xsubscripting SYSTEM "xsubscripting.sgml"> <!ENTITY plperl SYSTEM "plperl.sgml"> <!ENTITY plpython SYSTEM "plpython.sgml"> <!ENTITY plsql SYSTEM "plpgsql.sgml"> diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml index c0a6554d4d..5c538dca05 100644 --- a/doc/src/sgml/json.sgml +++ b/doc/src/sgml/json.sgml @@ -602,6 +602,45 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu </para> </sect2> + <sect2 id="jsonb-subscripting"> + <title><type>jsonb</type> Subscripting</title> + <para> + <type>jsonb</type> data type supports array-style subscripting expressions + to extract or update particular elements. It's possible to use multiple + subscripting expressions to extract nested values. In this case, a chain of + subscripting expressions follows the same rules as the + <literal>path</literal> argument in <literal>jsonb_set</literal> function, + e.g. in case of arrays it is a 0-based operation or that negative integers + that appear in <literal>path</literal> count from the end of JSON arrays. + The result of subscripting expressions is always jsonb data type. An + example of subscripting syntax: +<programlisting> +-- Extract value by key +SELECT ('{"a": 1}'::jsonb)['a']; + +-- Extract nested value by key path +SELECT ('{"a": {"b": {"c": 1}}}'::jsonb)['a']['b']['c']; + +-- Extract element by index +SELECT ('[1, "2", null]'::jsonb)[1]; + +-- Update value by key +UPDATE table_name SET jsonb_field['key'] = 1; + +-- Select records using where clause with subscripting. Since the result of +-- subscripting is jsonb and we basically want to compare two jsonb objects, we +-- need to put the value in double quotes to be able to convert it to jsonb. +SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"'; +</programlisting> + + There is no special indexing support for such kind of expressions, but you + can always create a functional index that includes it +<programlisting> +CREATE INDEX idx ON table_name ((jsonb_field['key'])); +</programlisting> + </para> + </sect2> + <sect2> <title>Transforms</title> diff --git a/doc/src/sgml/ref/create_type.sgml b/doc/src/sgml/ref/create_type.sgml index 111f8e65d2..a34df4d247 100644 --- a/doc/src/sgml/ref/create_type.sgml +++ b/doc/src/sgml/ref/create_type.sgml @@ -54,6 +54,7 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> ( [ , ELEMENT = <replaceable class="parameter">element</replaceable> ] [ , DELIMITER = <replaceable class="parameter">delimiter</replaceable> ] [ , COLLATABLE = <replaceable class="parameter">collatable</replaceable> ] + [ , SUBSCRIPTING_HANDLER = <replaceable class="parameter">subscripting_handler_function</replaceable> ] ) CREATE TYPE <replaceable class="parameter">name</replaceable> @@ -196,8 +197,9 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> <replaceable class="parameter">receive_function</replaceable>, <replaceable class="parameter">send_function</replaceable>, <replaceable class="parameter">type_modifier_input_function</replaceable>, - <replaceable class="parameter">type_modifier_output_function</replaceable> and - <replaceable class="parameter">analyze_function</replaceable> + <replaceable class="parameter">type_modifier_output_function</replaceable>, + <replaceable class="parameter">analyze_function</replaceable>, + <replaceable class="parameter">subscripting_handler_function</replaceable> are optional. Generally these functions have to be coded in C or another low-level language. </para> @@ -454,6 +456,22 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> make use of the collation information; this does not happen automatically merely by marking the type collatable. </para> + + <para> + The optional + <replaceable class="parameter">subscripting_handler_function</replaceable> + contains type-specific logic for subscripting of the data type. + By default, there is no such function provided, which means that the data + type doesn't support subscripting. The subscripting function must be + declared to take a single argument of type <type>internal</type>, and return + a <type>internal</type> result. There are two examples of implementation for + subscripting functions in case of array + (<replaceable class="parameter">array_subscripting_handler</replaceable>) + and jsonb + (<replaceable class="parameter">jsonb_subscripting_handler</replaceable>) + types in <filename>src/backend/utils/adt/arrayfuncs.c</filename> and + <filename>src/backend/utils/adt/jsonfuncs.c</filename>, respectively. + </para> </refsect2> <refsect2> @@ -769,6 +787,17 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> </para> </listitem> </varlistentry> + + <varlistentry> + <term><replaceable class="parameter">subscripting_handler_function</replaceable></term> + <listitem> + <para> + The name of a function that returns list of type-specific callback functions to + support subscripting logic for the data type. + </para> + </listitem> + </varlistentry> + </variablelist> </refsect1> diff --git a/doc/src/sgml/xsubscripting.sgml b/doc/src/sgml/xsubscripting.sgml new file mode 100644 index 0000000000..5282df0361 --- /dev/null +++ b/doc/src/sgml/xsubscripting.sgml @@ -0,0 +1,113 @@ +<!-- doc/src/sgml/xsubscripting.sgml --> + + <sect1 id="xsubscripting"> + <title>User-defined subscripting procedure</title> + + <indexterm zone="xsubscripting"> + <primary>custom subscripting</primary> + </indexterm> + <para> + When you define a new base type, you can also specify a custom procedure to + handle subscripting expressions. It must contain logic for verification and + evaluation of this expression, i.e. fetching or updating some data in this + data type. For instance: +</para> +<programlisting><![CDATA[ +typedef struct Custom +{ + int first; + int second; +} Custom; + +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = someType; + sbsref->refassgntype = someType; + + // You can also store any necessary information into sbsref->refopaque + + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + // some validation and coercion logic + + return sbsref; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some assignment logic + + return newContainer; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some fetch logic based on sbstate +}]]> +</programlisting> + +<para> + Then you can define a subscripting procedure and a custom data type: +</para> +<programlisting> +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '<replaceable>filename</replaceable>' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 4, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler, +); +</programlisting> + +<para> + and use it as usual: +</para> +<programlisting> +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; +</programlisting> + + + <para> + The examples of custom subscripting implementation can be found in + <filename>subscripting.sql</filename> and <filename>subscripting.c</filename> + in the <filename>src/tutorial</filename> directory of the source distribution. + See the <filename>README</filename> file in that directory for instructions + about running the examples. + </para> + +</sect1> diff --git a/src/tutorial/Makefile b/src/tutorial/Makefile index 16dc390f71..0ead60c2d4 100644 --- a/src/tutorial/Makefile +++ b/src/tutorial/Makefile @@ -13,8 +13,8 @@ # #------------------------------------------------------------------------- -MODULES = complex funcs -DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql +MODULES = complex funcs subscripting +DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql subscripting.sql ifdef NO_PGXS subdir = src/tutorial diff --git a/src/tutorial/subscripting.c b/src/tutorial/subscripting.c new file mode 100644 index 0000000000..aaac7927c6 --- /dev/null +++ b/src/tutorial/subscripting.c @@ -0,0 +1,204 @@ +/* + * src/tutorial/subscripting.c + * + ****************************************************************************** + This file contains routines that can be bound to a Postgres backend and + called by the backend in the process of processing queries. The calling + format for these routines is dictated by Postgres architecture. +******************************************************************************/ + +#include "postgres.h" + +#include "catalog/pg_type.h" +#include "executor/executor.h" +#include "executor/execExpr.h" +#include "nodes/nodeFuncs.h" +#include "parser/parse_coerce.h" +#include "utils/builtins.h" +#include "utils/fmgrprotos.h" + +PG_MODULE_MAGIC; + +typedef struct Custom +{ + int first; + int second; +} Custom; + +SubscriptingRef * custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref); +SubscriptingRef * custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate); +Datum custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate); +Datum custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate); + +PG_FUNCTION_INFO_V1(custom_in); +PG_FUNCTION_INFO_V1(custom_out); +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +/***************************************************************************** + * Input/Output functions + *****************************************************************************/ + +Datum +custom_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + int firstValue, + secondValue; + Custom *result; + + if (sscanf(str, " ( %d , %d )", &firstValue, &secondValue) != 2) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for complex: \"%s\"", + str))); + + + result = (Custom *) palloc(sizeof(Custom)); + result->first = firstValue; + result->second = secondValue; + PG_RETURN_POINTER(result); +} + +Datum +custom_out(PG_FUNCTION_ARGS) +{ + Custom *custom = (Custom *) PG_GETARG_POINTER(0); + char *result; + + result = psprintf("(%d, %d)", custom->first, custom->second); + PG_RETURN_CSTRING(result); +} + +/***************************************************************************** + * Custom subscripting logic functions + *****************************************************************************/ + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = INT4OID; + sbsref->refassgntype = INT4OID; + + /* You can also store any necessary information into sbsref->refopaque */ + + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + List *upperIndexpr = NIL; + ListCell *l; + + if (sbsref->reflowerindexpr != NIL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *)lfirst(sbsref->reflowerindexpr->head)))))); + + foreach(l, sbsref->refupperindexpr) + { + Node *subexpr = (Node *) lfirst(l); + + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *) lfirst(sbsref->refupperindexpr->head)))))); + + subexpr = coerce_to_target_type(pstate, + subexpr, exprType(subexpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript must have integer type"), + parser_errposition(pstate, exprLocation(subexpr)))); + + upperIndexpr = lappend(upperIndexpr, subexpr); + + if (isAssignment) + { + Node *assignExpr = (Node *) sbsref->refassgnexpr; + Node *new_from; + + new_from = coerce_to_target_type(pstate, + assignExpr, exprType(assignExpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (new_from == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom assignment requires int type"), + errhint("You will need to rewrite or cast the expression."), + parser_errposition(pstate, exprLocation(assignExpr)))); + sbsref->refassgnexpr = (Expr *)new_from; + } + } + + sbsref->refupperindexpr = upperIndexpr; + + return sbsref; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + Custom *container= (Custom *) containerSource; + int index; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + return (Datum) container->first; + else + return (Datum) container->second; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + int index; + Custom *container = (Custom *) containerSource; + + if (sbstate->resnull) + return containerSource; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + container->first = DatumGetInt32(sbstate->replacevalue); + else + container->second = DatumGetInt32(sbstate->replacevalue); + + return (Datum) container; +} diff --git a/src/tutorial/subscripting.source b/src/tutorial/subscripting.source new file mode 100644 index 0000000000..837cf30612 --- /dev/null +++ b/src/tutorial/subscripting.source @@ -0,0 +1,71 @@ +--------------------------------------------------------------------------- +-- +-- subscripting.sql- +-- This file shows how to create a new subscripting procedure for +-- user-defined type. +-- +-- +-- Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group +-- Portions Copyright (c) 1994, Regents of the University of California +-- +-- src/tutorial/subscripting.source +-- +--------------------------------------------------------------------------- + +----------------------------- +-- Creating a new type: +-- We are going to create a new type called 'complex' which represents +-- complex numbers. +-- A user-defined type must have an input and an output function, and +-- optionally can have binary input and output functions. All of these +-- are usually user-defined C functions. +----------------------------- + +-- Assume the user defined functions are in /home/erthalion/programms/postgresql-master/src/tutorial/complex$DLSUFFIX +-- (we do not want to assume this is in the dynamic loader search path). +-- Look at $PWD/complex.c for the source. Note that we declare all of +-- them as STRICT, so we do not need to cope with NULL inputs in the +-- C code. We also mark them IMMUTABLE, since they always return the +-- same outputs given the same inputs. + +-- the input function 'complex_in' takes a null-terminated string (the +-- textual representation of the type) and turns it into the internal +-- (in memory) representation. You will get a message telling you 'complex' +-- does not exist yet but that's okay. + +CREATE FUNCTION custom_in(cstring) + RETURNS custom + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +-- the output function 'complex_out' takes the internal representation and +-- converts it into the textual representation. + +CREATE FUNCTION custom_out(custom) + RETURNS cstring + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 8, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler +); + +-- we can use it in a table + +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; -- 2.21.0 --evj76j7ekwacmcsz Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v35-0005-Filling-gaps-in-jsonb-arrays.patch" ^ permalink raw reply [nested|flat] 28+ messages in thread
* [PATCH v34 4/5] Subscripting documentation @ 2019-02-01 10:47 erthalion <[email protected]> 0 siblings, 0 replies; 28+ messages in thread From: erthalion @ 2019-02-01 10:47 UTC (permalink / raw) Supporting documentation for generalized subscripting. It includes the description of a new field in pg_type, the new section for jsonb documentation about subscripting feature on this data type, and also the tutorial about how to write subscripting operator for a custom data type. Reviewed-by: Tom Lane, Arthur Zakirov, Oleksandr Shulgin --- doc/src/sgml/catalogs.sgml | 8 ++ doc/src/sgml/extend.sgml | 6 + doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/json.sgml | 39 ++++++ doc/src/sgml/ref/create_type.sgml | 33 ++++- doc/src/sgml/xsubscripting.sgml | 111 +++++++++++++++++ src/tutorial/Makefile | 4 +- src/tutorial/subscripting.c | 201 ++++++++++++++++++++++++++++++ src/tutorial/subscripting.source | 71 +++++++++++ 9 files changed, 470 insertions(+), 4 deletions(-) create mode 100644 doc/src/sgml/xsubscripting.sgml create mode 100644 src/tutorial/subscripting.c create mode 100644 src/tutorial/subscripting.source diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 7e99928d0c..104cea3584 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -8990,6 +8990,14 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l </para></entry> </row> + <row> + <entry><structfield>typsubshandler</structfield></entry> + <entry><type>regproc</type></entry> + <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry> + <entry>Custom subscripting function with type-specific logic for parsing + and validation, or 0 if this type doesn't support subscripting.</entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>typdefault</structfield> <type>text</type> diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml index e486006224..23c1764571 100644 --- a/doc/src/sgml/extend.sgml +++ b/doc/src/sgml/extend.sgml @@ -33,6 +33,11 @@ operators (starting in <xref linkend="xoper"/>) </para> </listitem> + <listitem> + <para> + subscripting procedure (starting in <xref linkend="xsubscripting"/>) + </para> + </listitem> <listitem> <para> operator classes for indexes (starting in <xref linkend="xindex"/>) @@ -481,6 +486,7 @@ RETURNS anycompatible AS ... &xaggr; &xtypes; &xoper; + &xsubscripting; &xindex; diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 828396d4a9..3bebb34c1a 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -69,6 +69,7 @@ <!ENTITY xplang SYSTEM "xplang.sgml"> <!ENTITY xoper SYSTEM "xoper.sgml"> <!ENTITY xtypes SYSTEM "xtypes.sgml"> +<!ENTITY xsubscripting SYSTEM "xsubscripting.sgml"> <!ENTITY plperl SYSTEM "plperl.sgml"> <!ENTITY plpython SYSTEM "plpython.sgml"> <!ENTITY plsql SYSTEM "plpgsql.sgml"> diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml index c0a6554d4d..5c538dca05 100644 --- a/doc/src/sgml/json.sgml +++ b/doc/src/sgml/json.sgml @@ -602,6 +602,45 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu </para> </sect2> + <sect2 id="jsonb-subscripting"> + <title><type>jsonb</type> Subscripting</title> + <para> + <type>jsonb</type> data type supports array-style subscripting expressions + to extract or update particular elements. It's possible to use multiple + subscripting expressions to extract nested values. In this case, a chain of + subscripting expressions follows the same rules as the + <literal>path</literal> argument in <literal>jsonb_set</literal> function, + e.g. in case of arrays it is a 0-based operation or that negative integers + that appear in <literal>path</literal> count from the end of JSON arrays. + The result of subscripting expressions is always jsonb data type. An + example of subscripting syntax: +<programlisting> +-- Extract value by key +SELECT ('{"a": 1}'::jsonb)['a']; + +-- Extract nested value by key path +SELECT ('{"a": {"b": {"c": 1}}}'::jsonb)['a']['b']['c']; + +-- Extract element by index +SELECT ('[1, "2", null]'::jsonb)[1]; + +-- Update value by key +UPDATE table_name SET jsonb_field['key'] = 1; + +-- Select records using where clause with subscripting. Since the result of +-- subscripting is jsonb and we basically want to compare two jsonb objects, we +-- need to put the value in double quotes to be able to convert it to jsonb. +SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"'; +</programlisting> + + There is no special indexing support for such kind of expressions, but you + can always create a functional index that includes it +<programlisting> +CREATE INDEX idx ON table_name ((jsonb_field['key'])); +</programlisting> + </para> + </sect2> + <sect2> <title>Transforms</title> diff --git a/doc/src/sgml/ref/create_type.sgml b/doc/src/sgml/ref/create_type.sgml index 111f8e65d2..a34df4d247 100644 --- a/doc/src/sgml/ref/create_type.sgml +++ b/doc/src/sgml/ref/create_type.sgml @@ -54,6 +54,7 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> ( [ , ELEMENT = <replaceable class="parameter">element</replaceable> ] [ , DELIMITER = <replaceable class="parameter">delimiter</replaceable> ] [ , COLLATABLE = <replaceable class="parameter">collatable</replaceable> ] + [ , SUBSCRIPTING_HANDLER = <replaceable class="parameter">subscripting_handler_function</replaceable> ] ) CREATE TYPE <replaceable class="parameter">name</replaceable> @@ -196,8 +197,9 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> <replaceable class="parameter">receive_function</replaceable>, <replaceable class="parameter">send_function</replaceable>, <replaceable class="parameter">type_modifier_input_function</replaceable>, - <replaceable class="parameter">type_modifier_output_function</replaceable> and - <replaceable class="parameter">analyze_function</replaceable> + <replaceable class="parameter">type_modifier_output_function</replaceable>, + <replaceable class="parameter">analyze_function</replaceable>, + <replaceable class="parameter">subscripting_handler_function</replaceable> are optional. Generally these functions have to be coded in C or another low-level language. </para> @@ -454,6 +456,22 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> make use of the collation information; this does not happen automatically merely by marking the type collatable. </para> + + <para> + The optional + <replaceable class="parameter">subscripting_handler_function</replaceable> + contains type-specific logic for subscripting of the data type. + By default, there is no such function provided, which means that the data + type doesn't support subscripting. The subscripting function must be + declared to take a single argument of type <type>internal</type>, and return + a <type>internal</type> result. There are two examples of implementation for + subscripting functions in case of array + (<replaceable class="parameter">array_subscripting_handler</replaceable>) + and jsonb + (<replaceable class="parameter">jsonb_subscripting_handler</replaceable>) + types in <filename>src/backend/utils/adt/arrayfuncs.c</filename> and + <filename>src/backend/utils/adt/jsonfuncs.c</filename>, respectively. + </para> </refsect2> <refsect2> @@ -769,6 +787,17 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> </para> </listitem> </varlistentry> + + <varlistentry> + <term><replaceable class="parameter">subscripting_handler_function</replaceable></term> + <listitem> + <para> + The name of a function that returns list of type-specific callback functions to + support subscripting logic for the data type. + </para> + </listitem> + </varlistentry> + </variablelist> </refsect1> diff --git a/doc/src/sgml/xsubscripting.sgml b/doc/src/sgml/xsubscripting.sgml new file mode 100644 index 0000000000..7224e81fa2 --- /dev/null +++ b/doc/src/sgml/xsubscripting.sgml @@ -0,0 +1,111 @@ +<!-- doc/src/sgml/xsubscripting.sgml --> + + <sect1 id="xsubscripting"> + <title>User-defined subscripting procedure</title> + + <indexterm zone="xsubscripting"> + <primary>custom subscripting</primary> + </indexterm> + <para> + When you define a new base type, you can also specify a custom procedure to + handle subscripting expressions. It must contain logic for verification and + evaluation of this expression, i.e. fetching or updating some data in this + data type. For instance: +</para> +<programlisting><![CDATA[ +typedef struct Custom +{ + int first; + int second; +} Custom; + +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = someType; + sbsref->refassgntype = someType; + + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + // some validation and coercion logic + + return sbsref; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some assignment logic + + return newContainer; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some fetch logic based on sbstate +}]]> +</programlisting> + +<para> + Then you can define a subscripting procedure and a custom data type: +</para> +<programlisting> +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '<replaceable>filename</replaceable>' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 4, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler, +); +</programlisting> + +<para> + and use it as usual: +</para> +<programlisting> +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; +</programlisting> + + + <para> + The examples of custom subscripting implementation can be found in + <filename>subscripting.sql</filename> and <filename>subscripting.c</filename> + in the <filename>src/tutorial</filename> directory of the source distribution. + See the <filename>README</filename> file in that directory for instructions + about running the examples. + </para> + +</sect1> diff --git a/src/tutorial/Makefile b/src/tutorial/Makefile index 16dc390f71..0ead60c2d4 100644 --- a/src/tutorial/Makefile +++ b/src/tutorial/Makefile @@ -13,8 +13,8 @@ # #------------------------------------------------------------------------- -MODULES = complex funcs -DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql +MODULES = complex funcs subscripting +DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql subscripting.sql ifdef NO_PGXS subdir = src/tutorial diff --git a/src/tutorial/subscripting.c b/src/tutorial/subscripting.c new file mode 100644 index 0000000000..1eb8c45652 --- /dev/null +++ b/src/tutorial/subscripting.c @@ -0,0 +1,201 @@ +/* + * src/tutorial/subscripting.c + * + ****************************************************************************** + This file contains routines that can be bound to a Postgres backend and + called by the backend in the process of processing queries. The calling + format for these routines is dictated by Postgres architecture. +******************************************************************************/ + +#include "postgres.h" + +#include "catalog/pg_type.h" +#include "executor/executor.h" +#include "executor/execExpr.h" +#include "nodes/nodeFuncs.h" +#include "parser/parse_coerce.h" +#include "utils/builtins.h" +#include "utils/fmgrprotos.h" + +PG_MODULE_MAGIC; + +typedef struct Custom +{ + int first; + int second; +} Custom; + +SubscriptingRef * custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref); +SubscriptingRef * custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate); +Datum custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate); +Datum custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate); + +PG_FUNCTION_INFO_V1(custom_in); +PG_FUNCTION_INFO_V1(custom_out); +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +/***************************************************************************** + * Input/Output functions + *****************************************************************************/ + +Datum +custom_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + int firstValue, + secondValue; + Custom *result; + + if (sscanf(str, " ( %d , %d )", &firstValue, &secondValue) != 2) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for complex: \"%s\"", + str))); + + + result = (Custom *) palloc(sizeof(Custom)); + result->first = firstValue; + result->second = secondValue; + PG_RETURN_POINTER(result); +} + +Datum +custom_out(PG_FUNCTION_ARGS) +{ + Custom *custom = (Custom *) PG_GETARG_POINTER(0); + char *result; + + result = psprintf("(%d, %d)", custom->first, custom->second); + PG_RETURN_CSTRING(result); +} + +/***************************************************************************** + * Custom subscripting logic functions + *****************************************************************************/ + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = INT4OID; + sbsref->refassgntype = INT4OID; + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + List *upperIndexpr = NIL; + ListCell *l; + + if (sbsref->reflowerindexpr != NIL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *)lfirst(sbsref->reflowerindexpr->head)))))); + + foreach(l, sbsref->refupperindexpr) + { + Node *subexpr = (Node *) lfirst(l); + + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *) lfirst(sbsref->refupperindexpr->head)))))); + + subexpr = coerce_to_target_type(pstate, + subexpr, exprType(subexpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript must have integer type"), + parser_errposition(pstate, exprLocation(subexpr)))); + + upperIndexpr = lappend(upperIndexpr, subexpr); + + if (isAssignment) + { + Node *assignExpr = (Node *) sbsref->refassgnexpr; + Node *new_from; + + new_from = coerce_to_target_type(pstate, + assignExpr, exprType(assignExpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (new_from == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom assignment requires int type"), + errhint("You will need to rewrite or cast the expression."), + parser_errposition(pstate, exprLocation(assignExpr)))); + sbsref->refassgnexpr = (Expr *)new_from; + } + } + + sbsref->refupperindexpr = upperIndexpr; + + return sbsref; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + Custom *container= (Custom *) containerSource; + int index; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + return (Datum) container->first; + else + return (Datum) container->second; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + int index; + Custom *container = (Custom *) containerSource; + + if (sbstate->resnull) + return containerSource; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + container->first = DatumGetInt32(sbstate->replacevalue); + else + container->second = DatumGetInt32(sbstate->replacevalue); + + return (Datum) container; +} diff --git a/src/tutorial/subscripting.source b/src/tutorial/subscripting.source new file mode 100644 index 0000000000..837cf30612 --- /dev/null +++ b/src/tutorial/subscripting.source @@ -0,0 +1,71 @@ +--------------------------------------------------------------------------- +-- +-- subscripting.sql- +-- This file shows how to create a new subscripting procedure for +-- user-defined type. +-- +-- +-- Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group +-- Portions Copyright (c) 1994, Regents of the University of California +-- +-- src/tutorial/subscripting.source +-- +--------------------------------------------------------------------------- + +----------------------------- +-- Creating a new type: +-- We are going to create a new type called 'complex' which represents +-- complex numbers. +-- A user-defined type must have an input and an output function, and +-- optionally can have binary input and output functions. All of these +-- are usually user-defined C functions. +----------------------------- + +-- Assume the user defined functions are in /home/erthalion/programms/postgresql-master/src/tutorial/complex$DLSUFFIX +-- (we do not want to assume this is in the dynamic loader search path). +-- Look at $PWD/complex.c for the source. Note that we declare all of +-- them as STRICT, so we do not need to cope with NULL inputs in the +-- C code. We also mark them IMMUTABLE, since they always return the +-- same outputs given the same inputs. + +-- the input function 'complex_in' takes a null-terminated string (the +-- textual representation of the type) and turns it into the internal +-- (in memory) representation. You will get a message telling you 'complex' +-- does not exist yet but that's okay. + +CREATE FUNCTION custom_in(cstring) + RETURNS custom + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +-- the output function 'complex_out' takes the internal representation and +-- converts it into the textual representation. + +CREATE FUNCTION custom_out(custom) + RETURNS cstring + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 8, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler +); + +-- we can use it in a table + +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; -- 2.21.0 --j77abfbqdsj2lq6d Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v34-0005-Filling-gaps-in-jsonb-arrays.patch" ^ permalink raw reply [nested|flat] 28+ messages in thread
* [PATCH v33 4/5] Subscripting documentation @ 2019-02-01 10:47 erthalion <[email protected]> 0 siblings, 0 replies; 28+ messages in thread From: erthalion @ 2019-02-01 10:47 UTC (permalink / raw) Supporting documentation for generalized subscripting. It includes the description of a new field in pg_type, the new section for jsonb documentation about subscripting feature on this data type, and also the tutorial about how to write subscripting operator for a custom data type. Reviewed-by: Tom Lane, Arthur Zakirov, Oleksandr Shulgin --- doc/src/sgml/catalogs.sgml | 8 ++ doc/src/sgml/extend.sgml | 6 + doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/json.sgml | 39 ++++++ doc/src/sgml/ref/create_type.sgml | 33 ++++- doc/src/sgml/xsubscripting.sgml | 111 +++++++++++++++++ src/tutorial/Makefile | 4 +- src/tutorial/subscripting.c | 201 ++++++++++++++++++++++++++++++ src/tutorial/subscripting.source | 71 +++++++++++ 9 files changed, 470 insertions(+), 4 deletions(-) create mode 100644 doc/src/sgml/xsubscripting.sgml create mode 100644 src/tutorial/subscripting.c create mode 100644 src/tutorial/subscripting.source diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 048ff284f7..b17e9ee382 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -8971,6 +8971,14 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l </para></entry> </row> + <row> + <entry><structfield>typsubshandler</structfield></entry> + <entry><type>regproc</type></entry> + <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry> + <entry>Custom subscripting function with type-specific logic for parsing + and validation, or 0 if this type doesn't support subscripting.</entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>typdefault</structfield> <type>text</type> diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml index 890ff97b7a..65cf5706b6 100644 --- a/doc/src/sgml/extend.sgml +++ b/doc/src/sgml/extend.sgml @@ -33,6 +33,11 @@ operators (starting in <xref linkend="xoper"/>) </para> </listitem> + <listitem> + <para> + subscripting procedure (starting in <xref linkend="xsubscripting"/>) + </para> + </listitem> <listitem> <para> operator classes for indexes (starting in <xref linkend="xindex"/>) @@ -481,6 +486,7 @@ RETURNS anycompatible AS ... &xaggr; &xtypes; &xoper; + &xsubscripting; &xindex; diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 64b5da0070..4c3fd37fc1 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -69,6 +69,7 @@ <!ENTITY xplang SYSTEM "xplang.sgml"> <!ENTITY xoper SYSTEM "xoper.sgml"> <!ENTITY xtypes SYSTEM "xtypes.sgml"> +<!ENTITY xsubscripting SYSTEM "xsubscripting.sgml"> <!ENTITY plperl SYSTEM "plperl.sgml"> <!ENTITY plpython SYSTEM "plpython.sgml"> <!ENTITY plsql SYSTEM "plpgsql.sgml"> diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml index c0a6554d4d..5c538dca05 100644 --- a/doc/src/sgml/json.sgml +++ b/doc/src/sgml/json.sgml @@ -602,6 +602,45 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu </para> </sect2> + <sect2 id="jsonb-subscripting"> + <title><type>jsonb</type> Subscripting</title> + <para> + <type>jsonb</type> data type supports array-style subscripting expressions + to extract or update particular elements. It's possible to use multiple + subscripting expressions to extract nested values. In this case, a chain of + subscripting expressions follows the same rules as the + <literal>path</literal> argument in <literal>jsonb_set</literal> function, + e.g. in case of arrays it is a 0-based operation or that negative integers + that appear in <literal>path</literal> count from the end of JSON arrays. + The result of subscripting expressions is always jsonb data type. An + example of subscripting syntax: +<programlisting> +-- Extract value by key +SELECT ('{"a": 1}'::jsonb)['a']; + +-- Extract nested value by key path +SELECT ('{"a": {"b": {"c": 1}}}'::jsonb)['a']['b']['c']; + +-- Extract element by index +SELECT ('[1, "2", null]'::jsonb)[1]; + +-- Update value by key +UPDATE table_name SET jsonb_field['key'] = 1; + +-- Select records using where clause with subscripting. Since the result of +-- subscripting is jsonb and we basically want to compare two jsonb objects, we +-- need to put the value in double quotes to be able to convert it to jsonb. +SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"'; +</programlisting> + + There is no special indexing support for such kind of expressions, but you + can always create a functional index that includes it +<programlisting> +CREATE INDEX idx ON table_name ((jsonb_field['key'])); +</programlisting> + </para> + </sect2> + <sect2> <title>Transforms</title> diff --git a/doc/src/sgml/ref/create_type.sgml b/doc/src/sgml/ref/create_type.sgml index 111f8e65d2..a34df4d247 100644 --- a/doc/src/sgml/ref/create_type.sgml +++ b/doc/src/sgml/ref/create_type.sgml @@ -54,6 +54,7 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> ( [ , ELEMENT = <replaceable class="parameter">element</replaceable> ] [ , DELIMITER = <replaceable class="parameter">delimiter</replaceable> ] [ , COLLATABLE = <replaceable class="parameter">collatable</replaceable> ] + [ , SUBSCRIPTING_HANDLER = <replaceable class="parameter">subscripting_handler_function</replaceable> ] ) CREATE TYPE <replaceable class="parameter">name</replaceable> @@ -196,8 +197,9 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> <replaceable class="parameter">receive_function</replaceable>, <replaceable class="parameter">send_function</replaceable>, <replaceable class="parameter">type_modifier_input_function</replaceable>, - <replaceable class="parameter">type_modifier_output_function</replaceable> and - <replaceable class="parameter">analyze_function</replaceable> + <replaceable class="parameter">type_modifier_output_function</replaceable>, + <replaceable class="parameter">analyze_function</replaceable>, + <replaceable class="parameter">subscripting_handler_function</replaceable> are optional. Generally these functions have to be coded in C or another low-level language. </para> @@ -454,6 +456,22 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> make use of the collation information; this does not happen automatically merely by marking the type collatable. </para> + + <para> + The optional + <replaceable class="parameter">subscripting_handler_function</replaceable> + contains type-specific logic for subscripting of the data type. + By default, there is no such function provided, which means that the data + type doesn't support subscripting. The subscripting function must be + declared to take a single argument of type <type>internal</type>, and return + a <type>internal</type> result. There are two examples of implementation for + subscripting functions in case of array + (<replaceable class="parameter">array_subscripting_handler</replaceable>) + and jsonb + (<replaceable class="parameter">jsonb_subscripting_handler</replaceable>) + types in <filename>src/backend/utils/adt/arrayfuncs.c</filename> and + <filename>src/backend/utils/adt/jsonfuncs.c</filename>, respectively. + </para> </refsect2> <refsect2> @@ -769,6 +787,17 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> </para> </listitem> </varlistentry> + + <varlistentry> + <term><replaceable class="parameter">subscripting_handler_function</replaceable></term> + <listitem> + <para> + The name of a function that returns list of type-specific callback functions to + support subscripting logic for the data type. + </para> + </listitem> + </varlistentry> + </variablelist> </refsect1> diff --git a/doc/src/sgml/xsubscripting.sgml b/doc/src/sgml/xsubscripting.sgml new file mode 100644 index 0000000000..7224e81fa2 --- /dev/null +++ b/doc/src/sgml/xsubscripting.sgml @@ -0,0 +1,111 @@ +<!-- doc/src/sgml/xsubscripting.sgml --> + + <sect1 id="xsubscripting"> + <title>User-defined subscripting procedure</title> + + <indexterm zone="xsubscripting"> + <primary>custom subscripting</primary> + </indexterm> + <para> + When you define a new base type, you can also specify a custom procedure to + handle subscripting expressions. It must contain logic for verification and + evaluation of this expression, i.e. fetching or updating some data in this + data type. For instance: +</para> +<programlisting><![CDATA[ +typedef struct Custom +{ + int first; + int second; +} Custom; + +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = someType; + sbsref->refassgntype = someType; + + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + // some validation and coercion logic + + return sbsref; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some assignment logic + + return newContainer; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some fetch logic based on sbstate +}]]> +</programlisting> + +<para> + Then you can define a subscripting procedure and a custom data type: +</para> +<programlisting> +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '<replaceable>filename</replaceable>' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 4, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler, +); +</programlisting> + +<para> + and use it as usual: +</para> +<programlisting> +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; +</programlisting> + + + <para> + The examples of custom subscripting implementation can be found in + <filename>subscripting.sql</filename> and <filename>subscripting.c</filename> + in the <filename>src/tutorial</filename> directory of the source distribution. + See the <filename>README</filename> file in that directory for instructions + about running the examples. + </para> + +</sect1> diff --git a/src/tutorial/Makefile b/src/tutorial/Makefile index 16dc390f71..0ead60c2d4 100644 --- a/src/tutorial/Makefile +++ b/src/tutorial/Makefile @@ -13,8 +13,8 @@ # #------------------------------------------------------------------------- -MODULES = complex funcs -DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql +MODULES = complex funcs subscripting +DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql subscripting.sql ifdef NO_PGXS subdir = src/tutorial diff --git a/src/tutorial/subscripting.c b/src/tutorial/subscripting.c new file mode 100644 index 0000000000..1eb8c45652 --- /dev/null +++ b/src/tutorial/subscripting.c @@ -0,0 +1,201 @@ +/* + * src/tutorial/subscripting.c + * + ****************************************************************************** + This file contains routines that can be bound to a Postgres backend and + called by the backend in the process of processing queries. The calling + format for these routines is dictated by Postgres architecture. +******************************************************************************/ + +#include "postgres.h" + +#include "catalog/pg_type.h" +#include "executor/executor.h" +#include "executor/execExpr.h" +#include "nodes/nodeFuncs.h" +#include "parser/parse_coerce.h" +#include "utils/builtins.h" +#include "utils/fmgrprotos.h" + +PG_MODULE_MAGIC; + +typedef struct Custom +{ + int first; + int second; +} Custom; + +SubscriptingRef * custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref); +SubscriptingRef * custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate); +Datum custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate); +Datum custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate); + +PG_FUNCTION_INFO_V1(custom_in); +PG_FUNCTION_INFO_V1(custom_out); +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +/***************************************************************************** + * Input/Output functions + *****************************************************************************/ + +Datum +custom_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + int firstValue, + secondValue; + Custom *result; + + if (sscanf(str, " ( %d , %d )", &firstValue, &secondValue) != 2) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for complex: \"%s\"", + str))); + + + result = (Custom *) palloc(sizeof(Custom)); + result->first = firstValue; + result->second = secondValue; + PG_RETURN_POINTER(result); +} + +Datum +custom_out(PG_FUNCTION_ARGS) +{ + Custom *custom = (Custom *) PG_GETARG_POINTER(0); + char *result; + + result = psprintf("(%d, %d)", custom->first, custom->second); + PG_RETURN_CSTRING(result); +} + +/***************************************************************************** + * Custom subscripting logic functions + *****************************************************************************/ + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = INT4OID; + sbsref->refassgntype = INT4OID; + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + List *upperIndexpr = NIL; + ListCell *l; + + if (sbsref->reflowerindexpr != NIL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *)lfirst(sbsref->reflowerindexpr->head)))))); + + foreach(l, sbsref->refupperindexpr) + { + Node *subexpr = (Node *) lfirst(l); + + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *) lfirst(sbsref->refupperindexpr->head)))))); + + subexpr = coerce_to_target_type(pstate, + subexpr, exprType(subexpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript must have integer type"), + parser_errposition(pstate, exprLocation(subexpr)))); + + upperIndexpr = lappend(upperIndexpr, subexpr); + + if (isAssignment) + { + Node *assignExpr = (Node *) sbsref->refassgnexpr; + Node *new_from; + + new_from = coerce_to_target_type(pstate, + assignExpr, exprType(assignExpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (new_from == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom assignment requires int type"), + errhint("You will need to rewrite or cast the expression."), + parser_errposition(pstate, exprLocation(assignExpr)))); + sbsref->refassgnexpr = (Expr *)new_from; + } + } + + sbsref->refupperindexpr = upperIndexpr; + + return sbsref; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + Custom *container= (Custom *) containerSource; + int index; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + return (Datum) container->first; + else + return (Datum) container->second; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + int index; + Custom *container = (Custom *) containerSource; + + if (sbstate->resnull) + return containerSource; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + container->first = DatumGetInt32(sbstate->replacevalue); + else + container->second = DatumGetInt32(sbstate->replacevalue); + + return (Datum) container; +} diff --git a/src/tutorial/subscripting.source b/src/tutorial/subscripting.source new file mode 100644 index 0000000000..837cf30612 --- /dev/null +++ b/src/tutorial/subscripting.source @@ -0,0 +1,71 @@ +--------------------------------------------------------------------------- +-- +-- subscripting.sql- +-- This file shows how to create a new subscripting procedure for +-- user-defined type. +-- +-- +-- Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group +-- Portions Copyright (c) 1994, Regents of the University of California +-- +-- src/tutorial/subscripting.source +-- +--------------------------------------------------------------------------- + +----------------------------- +-- Creating a new type: +-- We are going to create a new type called 'complex' which represents +-- complex numbers. +-- A user-defined type must have an input and an output function, and +-- optionally can have binary input and output functions. All of these +-- are usually user-defined C functions. +----------------------------- + +-- Assume the user defined functions are in /home/erthalion/programms/postgresql-master/src/tutorial/complex$DLSUFFIX +-- (we do not want to assume this is in the dynamic loader search path). +-- Look at $PWD/complex.c for the source. Note that we declare all of +-- them as STRICT, so we do not need to cope with NULL inputs in the +-- C code. We also mark them IMMUTABLE, since they always return the +-- same outputs given the same inputs. + +-- the input function 'complex_in' takes a null-terminated string (the +-- textual representation of the type) and turns it into the internal +-- (in memory) representation. You will get a message telling you 'complex' +-- does not exist yet but that's okay. + +CREATE FUNCTION custom_in(cstring) + RETURNS custom + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +-- the output function 'complex_out' takes the internal representation and +-- converts it into the textual representation. + +CREATE FUNCTION custom_out(custom) + RETURNS cstring + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 8, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler +); + +-- we can use it in a table + +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; -- 2.21.0 --u66dlpg6gaevsofr Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v33-0005-Filling-gaps-in-jsonb-arrays.patch" ^ permalink raw reply [nested|flat] 28+ messages in thread
* [PATCH v33 4/5] Subscripting documentation @ 2019-02-01 10:47 erthalion <[email protected]> 0 siblings, 0 replies; 28+ messages in thread From: erthalion @ 2019-02-01 10:47 UTC (permalink / raw) Supporting documentation for generalized subscripting. It includes the description of a new field in pg_type, the new section for jsonb documentation about subscripting feature on this data type, and also the tutorial about how to write subscripting operator for a custom data type. Reviewed-by: Tom Lane, Arthur Zakirov, Oleksandr Shulgin --- doc/src/sgml/catalogs.sgml | 8 ++ doc/src/sgml/extend.sgml | 6 + doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/json.sgml | 39 ++++++ doc/src/sgml/ref/create_type.sgml | 33 ++++- doc/src/sgml/xsubscripting.sgml | 111 +++++++++++++++++ src/tutorial/Makefile | 4 +- src/tutorial/subscripting.c | 201 ++++++++++++++++++++++++++++++ src/tutorial/subscripting.source | 71 +++++++++++ 9 files changed, 470 insertions(+), 4 deletions(-) create mode 100644 doc/src/sgml/xsubscripting.sgml create mode 100644 src/tutorial/subscripting.c create mode 100644 src/tutorial/subscripting.source diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 048ff284f7..b17e9ee382 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -8971,6 +8971,14 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l </para></entry> </row> + <row> + <entry><structfield>typsubshandler</structfield></entry> + <entry><type>regproc</type></entry> + <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry> + <entry>Custom subscripting function with type-specific logic for parsing + and validation, or 0 if this type doesn't support subscripting.</entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>typdefault</structfield> <type>text</type> diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml index 890ff97b7a..65cf5706b6 100644 --- a/doc/src/sgml/extend.sgml +++ b/doc/src/sgml/extend.sgml @@ -33,6 +33,11 @@ operators (starting in <xref linkend="xoper"/>) </para> </listitem> + <listitem> + <para> + subscripting procedure (starting in <xref linkend="xsubscripting"/>) + </para> + </listitem> <listitem> <para> operator classes for indexes (starting in <xref linkend="xindex"/>) @@ -481,6 +486,7 @@ RETURNS anycompatible AS ... &xaggr; &xtypes; &xoper; + &xsubscripting; &xindex; diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 64b5da0070..4c3fd37fc1 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -69,6 +69,7 @@ <!ENTITY xplang SYSTEM "xplang.sgml"> <!ENTITY xoper SYSTEM "xoper.sgml"> <!ENTITY xtypes SYSTEM "xtypes.sgml"> +<!ENTITY xsubscripting SYSTEM "xsubscripting.sgml"> <!ENTITY plperl SYSTEM "plperl.sgml"> <!ENTITY plpython SYSTEM "plpython.sgml"> <!ENTITY plsql SYSTEM "plpgsql.sgml"> diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml index c0a6554d4d..5c538dca05 100644 --- a/doc/src/sgml/json.sgml +++ b/doc/src/sgml/json.sgml @@ -602,6 +602,45 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu </para> </sect2> + <sect2 id="jsonb-subscripting"> + <title><type>jsonb</type> Subscripting</title> + <para> + <type>jsonb</type> data type supports array-style subscripting expressions + to extract or update particular elements. It's possible to use multiple + subscripting expressions to extract nested values. In this case, a chain of + subscripting expressions follows the same rules as the + <literal>path</literal> argument in <literal>jsonb_set</literal> function, + e.g. in case of arrays it is a 0-based operation or that negative integers + that appear in <literal>path</literal> count from the end of JSON arrays. + The result of subscripting expressions is always jsonb data type. An + example of subscripting syntax: +<programlisting> +-- Extract value by key +SELECT ('{"a": 1}'::jsonb)['a']; + +-- Extract nested value by key path +SELECT ('{"a": {"b": {"c": 1}}}'::jsonb)['a']['b']['c']; + +-- Extract element by index +SELECT ('[1, "2", null]'::jsonb)[1]; + +-- Update value by key +UPDATE table_name SET jsonb_field['key'] = 1; + +-- Select records using where clause with subscripting. Since the result of +-- subscripting is jsonb and we basically want to compare two jsonb objects, we +-- need to put the value in double quotes to be able to convert it to jsonb. +SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"'; +</programlisting> + + There is no special indexing support for such kind of expressions, but you + can always create a functional index that includes it +<programlisting> +CREATE INDEX idx ON table_name ((jsonb_field['key'])); +</programlisting> + </para> + </sect2> + <sect2> <title>Transforms</title> diff --git a/doc/src/sgml/ref/create_type.sgml b/doc/src/sgml/ref/create_type.sgml index 111f8e65d2..a34df4d247 100644 --- a/doc/src/sgml/ref/create_type.sgml +++ b/doc/src/sgml/ref/create_type.sgml @@ -54,6 +54,7 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> ( [ , ELEMENT = <replaceable class="parameter">element</replaceable> ] [ , DELIMITER = <replaceable class="parameter">delimiter</replaceable> ] [ , COLLATABLE = <replaceable class="parameter">collatable</replaceable> ] + [ , SUBSCRIPTING_HANDLER = <replaceable class="parameter">subscripting_handler_function</replaceable> ] ) CREATE TYPE <replaceable class="parameter">name</replaceable> @@ -196,8 +197,9 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> <replaceable class="parameter">receive_function</replaceable>, <replaceable class="parameter">send_function</replaceable>, <replaceable class="parameter">type_modifier_input_function</replaceable>, - <replaceable class="parameter">type_modifier_output_function</replaceable> and - <replaceable class="parameter">analyze_function</replaceable> + <replaceable class="parameter">type_modifier_output_function</replaceable>, + <replaceable class="parameter">analyze_function</replaceable>, + <replaceable class="parameter">subscripting_handler_function</replaceable> are optional. Generally these functions have to be coded in C or another low-level language. </para> @@ -454,6 +456,22 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> make use of the collation information; this does not happen automatically merely by marking the type collatable. </para> + + <para> + The optional + <replaceable class="parameter">subscripting_handler_function</replaceable> + contains type-specific logic for subscripting of the data type. + By default, there is no such function provided, which means that the data + type doesn't support subscripting. The subscripting function must be + declared to take a single argument of type <type>internal</type>, and return + a <type>internal</type> result. There are two examples of implementation for + subscripting functions in case of array + (<replaceable class="parameter">array_subscripting_handler</replaceable>) + and jsonb + (<replaceable class="parameter">jsonb_subscripting_handler</replaceable>) + types in <filename>src/backend/utils/adt/arrayfuncs.c</filename> and + <filename>src/backend/utils/adt/jsonfuncs.c</filename>, respectively. + </para> </refsect2> <refsect2> @@ -769,6 +787,17 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> </para> </listitem> </varlistentry> + + <varlistentry> + <term><replaceable class="parameter">subscripting_handler_function</replaceable></term> + <listitem> + <para> + The name of a function that returns list of type-specific callback functions to + support subscripting logic for the data type. + </para> + </listitem> + </varlistentry> + </variablelist> </refsect1> diff --git a/doc/src/sgml/xsubscripting.sgml b/doc/src/sgml/xsubscripting.sgml new file mode 100644 index 0000000000..7224e81fa2 --- /dev/null +++ b/doc/src/sgml/xsubscripting.sgml @@ -0,0 +1,111 @@ +<!-- doc/src/sgml/xsubscripting.sgml --> + + <sect1 id="xsubscripting"> + <title>User-defined subscripting procedure</title> + + <indexterm zone="xsubscripting"> + <primary>custom subscripting</primary> + </indexterm> + <para> + When you define a new base type, you can also specify a custom procedure to + handle subscripting expressions. It must contain logic for verification and + evaluation of this expression, i.e. fetching or updating some data in this + data type. For instance: +</para> +<programlisting><![CDATA[ +typedef struct Custom +{ + int first; + int second; +} Custom; + +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = someType; + sbsref->refassgntype = someType; + + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + // some validation and coercion logic + + return sbsref; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some assignment logic + + return newContainer; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some fetch logic based on sbstate +}]]> +</programlisting> + +<para> + Then you can define a subscripting procedure and a custom data type: +</para> +<programlisting> +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '<replaceable>filename</replaceable>' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 4, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler, +); +</programlisting> + +<para> + and use it as usual: +</para> +<programlisting> +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; +</programlisting> + + + <para> + The examples of custom subscripting implementation can be found in + <filename>subscripting.sql</filename> and <filename>subscripting.c</filename> + in the <filename>src/tutorial</filename> directory of the source distribution. + See the <filename>README</filename> file in that directory for instructions + about running the examples. + </para> + +</sect1> diff --git a/src/tutorial/Makefile b/src/tutorial/Makefile index 16dc390f71..0ead60c2d4 100644 --- a/src/tutorial/Makefile +++ b/src/tutorial/Makefile @@ -13,8 +13,8 @@ # #------------------------------------------------------------------------- -MODULES = complex funcs -DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql +MODULES = complex funcs subscripting +DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql subscripting.sql ifdef NO_PGXS subdir = src/tutorial diff --git a/src/tutorial/subscripting.c b/src/tutorial/subscripting.c new file mode 100644 index 0000000000..1eb8c45652 --- /dev/null +++ b/src/tutorial/subscripting.c @@ -0,0 +1,201 @@ +/* + * src/tutorial/subscripting.c + * + ****************************************************************************** + This file contains routines that can be bound to a Postgres backend and + called by the backend in the process of processing queries. The calling + format for these routines is dictated by Postgres architecture. +******************************************************************************/ + +#include "postgres.h" + +#include "catalog/pg_type.h" +#include "executor/executor.h" +#include "executor/execExpr.h" +#include "nodes/nodeFuncs.h" +#include "parser/parse_coerce.h" +#include "utils/builtins.h" +#include "utils/fmgrprotos.h" + +PG_MODULE_MAGIC; + +typedef struct Custom +{ + int first; + int second; +} Custom; + +SubscriptingRef * custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref); +SubscriptingRef * custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate); +Datum custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate); +Datum custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate); + +PG_FUNCTION_INFO_V1(custom_in); +PG_FUNCTION_INFO_V1(custom_out); +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +/***************************************************************************** + * Input/Output functions + *****************************************************************************/ + +Datum +custom_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + int firstValue, + secondValue; + Custom *result; + + if (sscanf(str, " ( %d , %d )", &firstValue, &secondValue) != 2) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for complex: \"%s\"", + str))); + + + result = (Custom *) palloc(sizeof(Custom)); + result->first = firstValue; + result->second = secondValue; + PG_RETURN_POINTER(result); +} + +Datum +custom_out(PG_FUNCTION_ARGS) +{ + Custom *custom = (Custom *) PG_GETARG_POINTER(0); + char *result; + + result = psprintf("(%d, %d)", custom->first, custom->second); + PG_RETURN_CSTRING(result); +} + +/***************************************************************************** + * Custom subscripting logic functions + *****************************************************************************/ + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = INT4OID; + sbsref->refassgntype = INT4OID; + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + List *upperIndexpr = NIL; + ListCell *l; + + if (sbsref->reflowerindexpr != NIL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *)lfirst(sbsref->reflowerindexpr->head)))))); + + foreach(l, sbsref->refupperindexpr) + { + Node *subexpr = (Node *) lfirst(l); + + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *) lfirst(sbsref->refupperindexpr->head)))))); + + subexpr = coerce_to_target_type(pstate, + subexpr, exprType(subexpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript must have integer type"), + parser_errposition(pstate, exprLocation(subexpr)))); + + upperIndexpr = lappend(upperIndexpr, subexpr); + + if (isAssignment) + { + Node *assignExpr = (Node *) sbsref->refassgnexpr; + Node *new_from; + + new_from = coerce_to_target_type(pstate, + assignExpr, exprType(assignExpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (new_from == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom assignment requires int type"), + errhint("You will need to rewrite or cast the expression."), + parser_errposition(pstate, exprLocation(assignExpr)))); + sbsref->refassgnexpr = (Expr *)new_from; + } + } + + sbsref->refupperindexpr = upperIndexpr; + + return sbsref; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + Custom *container= (Custom *) containerSource; + int index; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + return (Datum) container->first; + else + return (Datum) container->second; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + int index; + Custom *container = (Custom *) containerSource; + + if (sbstate->resnull) + return containerSource; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + container->first = DatumGetInt32(sbstate->replacevalue); + else + container->second = DatumGetInt32(sbstate->replacevalue); + + return (Datum) container; +} diff --git a/src/tutorial/subscripting.source b/src/tutorial/subscripting.source new file mode 100644 index 0000000000..837cf30612 --- /dev/null +++ b/src/tutorial/subscripting.source @@ -0,0 +1,71 @@ +--------------------------------------------------------------------------- +-- +-- subscripting.sql- +-- This file shows how to create a new subscripting procedure for +-- user-defined type. +-- +-- +-- Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group +-- Portions Copyright (c) 1994, Regents of the University of California +-- +-- src/tutorial/subscripting.source +-- +--------------------------------------------------------------------------- + +----------------------------- +-- Creating a new type: +-- We are going to create a new type called 'complex' which represents +-- complex numbers. +-- A user-defined type must have an input and an output function, and +-- optionally can have binary input and output functions. All of these +-- are usually user-defined C functions. +----------------------------- + +-- Assume the user defined functions are in /home/erthalion/programms/postgresql-master/src/tutorial/complex$DLSUFFIX +-- (we do not want to assume this is in the dynamic loader search path). +-- Look at $PWD/complex.c for the source. Note that we declare all of +-- them as STRICT, so we do not need to cope with NULL inputs in the +-- C code. We also mark them IMMUTABLE, since they always return the +-- same outputs given the same inputs. + +-- the input function 'complex_in' takes a null-terminated string (the +-- textual representation of the type) and turns it into the internal +-- (in memory) representation. You will get a message telling you 'complex' +-- does not exist yet but that's okay. + +CREATE FUNCTION custom_in(cstring) + RETURNS custom + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +-- the output function 'complex_out' takes the internal representation and +-- converts it into the textual representation. + +CREATE FUNCTION custom_out(custom) + RETURNS cstring + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 8, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler +); + +-- we can use it in a table + +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; -- 2.21.0 --u66dlpg6gaevsofr Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v33-0005-Filling-gaps-in-jsonb-arrays.patch" ^ permalink raw reply [nested|flat] 28+ messages in thread
* [PATCH v33 4/5] Subscripting documentation @ 2019-02-01 10:47 erthalion <[email protected]> 0 siblings, 0 replies; 28+ messages in thread From: erthalion @ 2019-02-01 10:47 UTC (permalink / raw) Supporting documentation for generalized subscripting. It includes the description of a new field in pg_type, the new section for jsonb documentation about subscripting feature on this data type, and also the tutorial about how to write subscripting operator for a custom data type. Reviewed-by: Tom Lane, Arthur Zakirov, Oleksandr Shulgin --- doc/src/sgml/catalogs.sgml | 8 ++ doc/src/sgml/extend.sgml | 6 + doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/json.sgml | 39 ++++++ doc/src/sgml/ref/create_type.sgml | 33 ++++- doc/src/sgml/xsubscripting.sgml | 111 +++++++++++++++++ src/tutorial/Makefile | 4 +- src/tutorial/subscripting.c | 201 ++++++++++++++++++++++++++++++ src/tutorial/subscripting.source | 71 +++++++++++ 9 files changed, 470 insertions(+), 4 deletions(-) create mode 100644 doc/src/sgml/xsubscripting.sgml create mode 100644 src/tutorial/subscripting.c create mode 100644 src/tutorial/subscripting.source diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 048ff284f7..b17e9ee382 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -8971,6 +8971,14 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l </para></entry> </row> + <row> + <entry><structfield>typsubshandler</structfield></entry> + <entry><type>regproc</type></entry> + <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry> + <entry>Custom subscripting function with type-specific logic for parsing + and validation, or 0 if this type doesn't support subscripting.</entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>typdefault</structfield> <type>text</type> diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml index 890ff97b7a..65cf5706b6 100644 --- a/doc/src/sgml/extend.sgml +++ b/doc/src/sgml/extend.sgml @@ -33,6 +33,11 @@ operators (starting in <xref linkend="xoper"/>) </para> </listitem> + <listitem> + <para> + subscripting procedure (starting in <xref linkend="xsubscripting"/>) + </para> + </listitem> <listitem> <para> operator classes for indexes (starting in <xref linkend="xindex"/>) @@ -481,6 +486,7 @@ RETURNS anycompatible AS ... &xaggr; &xtypes; &xoper; + &xsubscripting; &xindex; diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 64b5da0070..4c3fd37fc1 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -69,6 +69,7 @@ <!ENTITY xplang SYSTEM "xplang.sgml"> <!ENTITY xoper SYSTEM "xoper.sgml"> <!ENTITY xtypes SYSTEM "xtypes.sgml"> +<!ENTITY xsubscripting SYSTEM "xsubscripting.sgml"> <!ENTITY plperl SYSTEM "plperl.sgml"> <!ENTITY plpython SYSTEM "plpython.sgml"> <!ENTITY plsql SYSTEM "plpgsql.sgml"> diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml index c0a6554d4d..5c538dca05 100644 --- a/doc/src/sgml/json.sgml +++ b/doc/src/sgml/json.sgml @@ -602,6 +602,45 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu </para> </sect2> + <sect2 id="jsonb-subscripting"> + <title><type>jsonb</type> Subscripting</title> + <para> + <type>jsonb</type> data type supports array-style subscripting expressions + to extract or update particular elements. It's possible to use multiple + subscripting expressions to extract nested values. In this case, a chain of + subscripting expressions follows the same rules as the + <literal>path</literal> argument in <literal>jsonb_set</literal> function, + e.g. in case of arrays it is a 0-based operation or that negative integers + that appear in <literal>path</literal> count from the end of JSON arrays. + The result of subscripting expressions is always jsonb data type. An + example of subscripting syntax: +<programlisting> +-- Extract value by key +SELECT ('{"a": 1}'::jsonb)['a']; + +-- Extract nested value by key path +SELECT ('{"a": {"b": {"c": 1}}}'::jsonb)['a']['b']['c']; + +-- Extract element by index +SELECT ('[1, "2", null]'::jsonb)[1]; + +-- Update value by key +UPDATE table_name SET jsonb_field['key'] = 1; + +-- Select records using where clause with subscripting. Since the result of +-- subscripting is jsonb and we basically want to compare two jsonb objects, we +-- need to put the value in double quotes to be able to convert it to jsonb. +SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"'; +</programlisting> + + There is no special indexing support for such kind of expressions, but you + can always create a functional index that includes it +<programlisting> +CREATE INDEX idx ON table_name ((jsonb_field['key'])); +</programlisting> + </para> + </sect2> + <sect2> <title>Transforms</title> diff --git a/doc/src/sgml/ref/create_type.sgml b/doc/src/sgml/ref/create_type.sgml index 111f8e65d2..a34df4d247 100644 --- a/doc/src/sgml/ref/create_type.sgml +++ b/doc/src/sgml/ref/create_type.sgml @@ -54,6 +54,7 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> ( [ , ELEMENT = <replaceable class="parameter">element</replaceable> ] [ , DELIMITER = <replaceable class="parameter">delimiter</replaceable> ] [ , COLLATABLE = <replaceable class="parameter">collatable</replaceable> ] + [ , SUBSCRIPTING_HANDLER = <replaceable class="parameter">subscripting_handler_function</replaceable> ] ) CREATE TYPE <replaceable class="parameter">name</replaceable> @@ -196,8 +197,9 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> <replaceable class="parameter">receive_function</replaceable>, <replaceable class="parameter">send_function</replaceable>, <replaceable class="parameter">type_modifier_input_function</replaceable>, - <replaceable class="parameter">type_modifier_output_function</replaceable> and - <replaceable class="parameter">analyze_function</replaceable> + <replaceable class="parameter">type_modifier_output_function</replaceable>, + <replaceable class="parameter">analyze_function</replaceable>, + <replaceable class="parameter">subscripting_handler_function</replaceable> are optional. Generally these functions have to be coded in C or another low-level language. </para> @@ -454,6 +456,22 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> make use of the collation information; this does not happen automatically merely by marking the type collatable. </para> + + <para> + The optional + <replaceable class="parameter">subscripting_handler_function</replaceable> + contains type-specific logic for subscripting of the data type. + By default, there is no such function provided, which means that the data + type doesn't support subscripting. The subscripting function must be + declared to take a single argument of type <type>internal</type>, and return + a <type>internal</type> result. There are two examples of implementation for + subscripting functions in case of array + (<replaceable class="parameter">array_subscripting_handler</replaceable>) + and jsonb + (<replaceable class="parameter">jsonb_subscripting_handler</replaceable>) + types in <filename>src/backend/utils/adt/arrayfuncs.c</filename> and + <filename>src/backend/utils/adt/jsonfuncs.c</filename>, respectively. + </para> </refsect2> <refsect2> @@ -769,6 +787,17 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> </para> </listitem> </varlistentry> + + <varlistentry> + <term><replaceable class="parameter">subscripting_handler_function</replaceable></term> + <listitem> + <para> + The name of a function that returns list of type-specific callback functions to + support subscripting logic for the data type. + </para> + </listitem> + </varlistentry> + </variablelist> </refsect1> diff --git a/doc/src/sgml/xsubscripting.sgml b/doc/src/sgml/xsubscripting.sgml new file mode 100644 index 0000000000..7224e81fa2 --- /dev/null +++ b/doc/src/sgml/xsubscripting.sgml @@ -0,0 +1,111 @@ +<!-- doc/src/sgml/xsubscripting.sgml --> + + <sect1 id="xsubscripting"> + <title>User-defined subscripting procedure</title> + + <indexterm zone="xsubscripting"> + <primary>custom subscripting</primary> + </indexterm> + <para> + When you define a new base type, you can also specify a custom procedure to + handle subscripting expressions. It must contain logic for verification and + evaluation of this expression, i.e. fetching or updating some data in this + data type. For instance: +</para> +<programlisting><![CDATA[ +typedef struct Custom +{ + int first; + int second; +} Custom; + +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = someType; + sbsref->refassgntype = someType; + + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + // some validation and coercion logic + + return sbsref; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some assignment logic + + return newContainer; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some fetch logic based on sbstate +}]]> +</programlisting> + +<para> + Then you can define a subscripting procedure and a custom data type: +</para> +<programlisting> +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '<replaceable>filename</replaceable>' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 4, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler, +); +</programlisting> + +<para> + and use it as usual: +</para> +<programlisting> +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; +</programlisting> + + + <para> + The examples of custom subscripting implementation can be found in + <filename>subscripting.sql</filename> and <filename>subscripting.c</filename> + in the <filename>src/tutorial</filename> directory of the source distribution. + See the <filename>README</filename> file in that directory for instructions + about running the examples. + </para> + +</sect1> diff --git a/src/tutorial/Makefile b/src/tutorial/Makefile index 16dc390f71..0ead60c2d4 100644 --- a/src/tutorial/Makefile +++ b/src/tutorial/Makefile @@ -13,8 +13,8 @@ # #------------------------------------------------------------------------- -MODULES = complex funcs -DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql +MODULES = complex funcs subscripting +DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql subscripting.sql ifdef NO_PGXS subdir = src/tutorial diff --git a/src/tutorial/subscripting.c b/src/tutorial/subscripting.c new file mode 100644 index 0000000000..1eb8c45652 --- /dev/null +++ b/src/tutorial/subscripting.c @@ -0,0 +1,201 @@ +/* + * src/tutorial/subscripting.c + * + ****************************************************************************** + This file contains routines that can be bound to a Postgres backend and + called by the backend in the process of processing queries. The calling + format for these routines is dictated by Postgres architecture. +******************************************************************************/ + +#include "postgres.h" + +#include "catalog/pg_type.h" +#include "executor/executor.h" +#include "executor/execExpr.h" +#include "nodes/nodeFuncs.h" +#include "parser/parse_coerce.h" +#include "utils/builtins.h" +#include "utils/fmgrprotos.h" + +PG_MODULE_MAGIC; + +typedef struct Custom +{ + int first; + int second; +} Custom; + +SubscriptingRef * custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref); +SubscriptingRef * custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate); +Datum custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate); +Datum custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate); + +PG_FUNCTION_INFO_V1(custom_in); +PG_FUNCTION_INFO_V1(custom_out); +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +/***************************************************************************** + * Input/Output functions + *****************************************************************************/ + +Datum +custom_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + int firstValue, + secondValue; + Custom *result; + + if (sscanf(str, " ( %d , %d )", &firstValue, &secondValue) != 2) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for complex: \"%s\"", + str))); + + + result = (Custom *) palloc(sizeof(Custom)); + result->first = firstValue; + result->second = secondValue; + PG_RETURN_POINTER(result); +} + +Datum +custom_out(PG_FUNCTION_ARGS) +{ + Custom *custom = (Custom *) PG_GETARG_POINTER(0); + char *result; + + result = psprintf("(%d, %d)", custom->first, custom->second); + PG_RETURN_CSTRING(result); +} + +/***************************************************************************** + * Custom subscripting logic functions + *****************************************************************************/ + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = INT4OID; + sbsref->refassgntype = INT4OID; + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + List *upperIndexpr = NIL; + ListCell *l; + + if (sbsref->reflowerindexpr != NIL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *)lfirst(sbsref->reflowerindexpr->head)))))); + + foreach(l, sbsref->refupperindexpr) + { + Node *subexpr = (Node *) lfirst(l); + + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *) lfirst(sbsref->refupperindexpr->head)))))); + + subexpr = coerce_to_target_type(pstate, + subexpr, exprType(subexpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript must have integer type"), + parser_errposition(pstate, exprLocation(subexpr)))); + + upperIndexpr = lappend(upperIndexpr, subexpr); + + if (isAssignment) + { + Node *assignExpr = (Node *) sbsref->refassgnexpr; + Node *new_from; + + new_from = coerce_to_target_type(pstate, + assignExpr, exprType(assignExpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (new_from == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom assignment requires int type"), + errhint("You will need to rewrite or cast the expression."), + parser_errposition(pstate, exprLocation(assignExpr)))); + sbsref->refassgnexpr = (Expr *)new_from; + } + } + + sbsref->refupperindexpr = upperIndexpr; + + return sbsref; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + Custom *container= (Custom *) containerSource; + int index; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + return (Datum) container->first; + else + return (Datum) container->second; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + int index; + Custom *container = (Custom *) containerSource; + + if (sbstate->resnull) + return containerSource; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + container->first = DatumGetInt32(sbstate->replacevalue); + else + container->second = DatumGetInt32(sbstate->replacevalue); + + return (Datum) container; +} diff --git a/src/tutorial/subscripting.source b/src/tutorial/subscripting.source new file mode 100644 index 0000000000..837cf30612 --- /dev/null +++ b/src/tutorial/subscripting.source @@ -0,0 +1,71 @@ +--------------------------------------------------------------------------- +-- +-- subscripting.sql- +-- This file shows how to create a new subscripting procedure for +-- user-defined type. +-- +-- +-- Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group +-- Portions Copyright (c) 1994, Regents of the University of California +-- +-- src/tutorial/subscripting.source +-- +--------------------------------------------------------------------------- + +----------------------------- +-- Creating a new type: +-- We are going to create a new type called 'complex' which represents +-- complex numbers. +-- A user-defined type must have an input and an output function, and +-- optionally can have binary input and output functions. All of these +-- are usually user-defined C functions. +----------------------------- + +-- Assume the user defined functions are in /home/erthalion/programms/postgresql-master/src/tutorial/complex$DLSUFFIX +-- (we do not want to assume this is in the dynamic loader search path). +-- Look at $PWD/complex.c for the source. Note that we declare all of +-- them as STRICT, so we do not need to cope with NULL inputs in the +-- C code. We also mark them IMMUTABLE, since they always return the +-- same outputs given the same inputs. + +-- the input function 'complex_in' takes a null-terminated string (the +-- textual representation of the type) and turns it into the internal +-- (in memory) representation. You will get a message telling you 'complex' +-- does not exist yet but that's okay. + +CREATE FUNCTION custom_in(cstring) + RETURNS custom + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +-- the output function 'complex_out' takes the internal representation and +-- converts it into the textual representation. + +CREATE FUNCTION custom_out(custom) + RETURNS cstring + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 8, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler +); + +-- we can use it in a table + +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; -- 2.21.0 --u66dlpg6gaevsofr Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v33-0005-Filling-gaps-in-jsonb-arrays.patch" ^ permalink raw reply [nested|flat] 28+ messages in thread
* [PATCH v33 4/5] Subscripting documentation @ 2019-02-01 10:47 erthalion <[email protected]> 0 siblings, 0 replies; 28+ messages in thread From: erthalion @ 2019-02-01 10:47 UTC (permalink / raw) Supporting documentation for generalized subscripting. It includes the description of a new field in pg_type, the new section for jsonb documentation about subscripting feature on this data type, and also the tutorial about how to write subscripting operator for a custom data type. Reviewed-by: Tom Lane, Arthur Zakirov, Oleksandr Shulgin --- doc/src/sgml/catalogs.sgml | 8 ++ doc/src/sgml/extend.sgml | 6 + doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/json.sgml | 39 ++++++ doc/src/sgml/ref/create_type.sgml | 33 ++++- doc/src/sgml/xsubscripting.sgml | 111 +++++++++++++++++ src/tutorial/Makefile | 4 +- src/tutorial/subscripting.c | 201 ++++++++++++++++++++++++++++++ src/tutorial/subscripting.source | 71 +++++++++++ 9 files changed, 470 insertions(+), 4 deletions(-) create mode 100644 doc/src/sgml/xsubscripting.sgml create mode 100644 src/tutorial/subscripting.c create mode 100644 src/tutorial/subscripting.source diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 048ff284f7..b17e9ee382 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -8971,6 +8971,14 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l </para></entry> </row> + <row> + <entry><structfield>typsubshandler</structfield></entry> + <entry><type>regproc</type></entry> + <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry> + <entry>Custom subscripting function with type-specific logic for parsing + and validation, or 0 if this type doesn't support subscripting.</entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>typdefault</structfield> <type>text</type> diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml index 890ff97b7a..65cf5706b6 100644 --- a/doc/src/sgml/extend.sgml +++ b/doc/src/sgml/extend.sgml @@ -33,6 +33,11 @@ operators (starting in <xref linkend="xoper"/>) </para> </listitem> + <listitem> + <para> + subscripting procedure (starting in <xref linkend="xsubscripting"/>) + </para> + </listitem> <listitem> <para> operator classes for indexes (starting in <xref linkend="xindex"/>) @@ -481,6 +486,7 @@ RETURNS anycompatible AS ... &xaggr; &xtypes; &xoper; + &xsubscripting; &xindex; diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 64b5da0070..4c3fd37fc1 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -69,6 +69,7 @@ <!ENTITY xplang SYSTEM "xplang.sgml"> <!ENTITY xoper SYSTEM "xoper.sgml"> <!ENTITY xtypes SYSTEM "xtypes.sgml"> +<!ENTITY xsubscripting SYSTEM "xsubscripting.sgml"> <!ENTITY plperl SYSTEM "plperl.sgml"> <!ENTITY plpython SYSTEM "plpython.sgml"> <!ENTITY plsql SYSTEM "plpgsql.sgml"> diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml index c0a6554d4d..5c538dca05 100644 --- a/doc/src/sgml/json.sgml +++ b/doc/src/sgml/json.sgml @@ -602,6 +602,45 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu </para> </sect2> + <sect2 id="jsonb-subscripting"> + <title><type>jsonb</type> Subscripting</title> + <para> + <type>jsonb</type> data type supports array-style subscripting expressions + to extract or update particular elements. It's possible to use multiple + subscripting expressions to extract nested values. In this case, a chain of + subscripting expressions follows the same rules as the + <literal>path</literal> argument in <literal>jsonb_set</literal> function, + e.g. in case of arrays it is a 0-based operation or that negative integers + that appear in <literal>path</literal> count from the end of JSON arrays. + The result of subscripting expressions is always jsonb data type. An + example of subscripting syntax: +<programlisting> +-- Extract value by key +SELECT ('{"a": 1}'::jsonb)['a']; + +-- Extract nested value by key path +SELECT ('{"a": {"b": {"c": 1}}}'::jsonb)['a']['b']['c']; + +-- Extract element by index +SELECT ('[1, "2", null]'::jsonb)[1]; + +-- Update value by key +UPDATE table_name SET jsonb_field['key'] = 1; + +-- Select records using where clause with subscripting. Since the result of +-- subscripting is jsonb and we basically want to compare two jsonb objects, we +-- need to put the value in double quotes to be able to convert it to jsonb. +SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"'; +</programlisting> + + There is no special indexing support for such kind of expressions, but you + can always create a functional index that includes it +<programlisting> +CREATE INDEX idx ON table_name ((jsonb_field['key'])); +</programlisting> + </para> + </sect2> + <sect2> <title>Transforms</title> diff --git a/doc/src/sgml/ref/create_type.sgml b/doc/src/sgml/ref/create_type.sgml index 111f8e65d2..a34df4d247 100644 --- a/doc/src/sgml/ref/create_type.sgml +++ b/doc/src/sgml/ref/create_type.sgml @@ -54,6 +54,7 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> ( [ , ELEMENT = <replaceable class="parameter">element</replaceable> ] [ , DELIMITER = <replaceable class="parameter">delimiter</replaceable> ] [ , COLLATABLE = <replaceable class="parameter">collatable</replaceable> ] + [ , SUBSCRIPTING_HANDLER = <replaceable class="parameter">subscripting_handler_function</replaceable> ] ) CREATE TYPE <replaceable class="parameter">name</replaceable> @@ -196,8 +197,9 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> <replaceable class="parameter">receive_function</replaceable>, <replaceable class="parameter">send_function</replaceable>, <replaceable class="parameter">type_modifier_input_function</replaceable>, - <replaceable class="parameter">type_modifier_output_function</replaceable> and - <replaceable class="parameter">analyze_function</replaceable> + <replaceable class="parameter">type_modifier_output_function</replaceable>, + <replaceable class="parameter">analyze_function</replaceable>, + <replaceable class="parameter">subscripting_handler_function</replaceable> are optional. Generally these functions have to be coded in C or another low-level language. </para> @@ -454,6 +456,22 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> make use of the collation information; this does not happen automatically merely by marking the type collatable. </para> + + <para> + The optional + <replaceable class="parameter">subscripting_handler_function</replaceable> + contains type-specific logic for subscripting of the data type. + By default, there is no such function provided, which means that the data + type doesn't support subscripting. The subscripting function must be + declared to take a single argument of type <type>internal</type>, and return + a <type>internal</type> result. There are two examples of implementation for + subscripting functions in case of array + (<replaceable class="parameter">array_subscripting_handler</replaceable>) + and jsonb + (<replaceable class="parameter">jsonb_subscripting_handler</replaceable>) + types in <filename>src/backend/utils/adt/arrayfuncs.c</filename> and + <filename>src/backend/utils/adt/jsonfuncs.c</filename>, respectively. + </para> </refsect2> <refsect2> @@ -769,6 +787,17 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> </para> </listitem> </varlistentry> + + <varlistentry> + <term><replaceable class="parameter">subscripting_handler_function</replaceable></term> + <listitem> + <para> + The name of a function that returns list of type-specific callback functions to + support subscripting logic for the data type. + </para> + </listitem> + </varlistentry> + </variablelist> </refsect1> diff --git a/doc/src/sgml/xsubscripting.sgml b/doc/src/sgml/xsubscripting.sgml new file mode 100644 index 0000000000..7224e81fa2 --- /dev/null +++ b/doc/src/sgml/xsubscripting.sgml @@ -0,0 +1,111 @@ +<!-- doc/src/sgml/xsubscripting.sgml --> + + <sect1 id="xsubscripting"> + <title>User-defined subscripting procedure</title> + + <indexterm zone="xsubscripting"> + <primary>custom subscripting</primary> + </indexterm> + <para> + When you define a new base type, you can also specify a custom procedure to + handle subscripting expressions. It must contain logic for verification and + evaluation of this expression, i.e. fetching or updating some data in this + data type. For instance: +</para> +<programlisting><![CDATA[ +typedef struct Custom +{ + int first; + int second; +} Custom; + +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = someType; + sbsref->refassgntype = someType; + + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + // some validation and coercion logic + + return sbsref; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some assignment logic + + return newContainer; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some fetch logic based on sbstate +}]]> +</programlisting> + +<para> + Then you can define a subscripting procedure and a custom data type: +</para> +<programlisting> +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '<replaceable>filename</replaceable>' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 4, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler, +); +</programlisting> + +<para> + and use it as usual: +</para> +<programlisting> +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; +</programlisting> + + + <para> + The examples of custom subscripting implementation can be found in + <filename>subscripting.sql</filename> and <filename>subscripting.c</filename> + in the <filename>src/tutorial</filename> directory of the source distribution. + See the <filename>README</filename> file in that directory for instructions + about running the examples. + </para> + +</sect1> diff --git a/src/tutorial/Makefile b/src/tutorial/Makefile index 16dc390f71..0ead60c2d4 100644 --- a/src/tutorial/Makefile +++ b/src/tutorial/Makefile @@ -13,8 +13,8 @@ # #------------------------------------------------------------------------- -MODULES = complex funcs -DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql +MODULES = complex funcs subscripting +DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql subscripting.sql ifdef NO_PGXS subdir = src/tutorial diff --git a/src/tutorial/subscripting.c b/src/tutorial/subscripting.c new file mode 100644 index 0000000000..1eb8c45652 --- /dev/null +++ b/src/tutorial/subscripting.c @@ -0,0 +1,201 @@ +/* + * src/tutorial/subscripting.c + * + ****************************************************************************** + This file contains routines that can be bound to a Postgres backend and + called by the backend in the process of processing queries. The calling + format for these routines is dictated by Postgres architecture. +******************************************************************************/ + +#include "postgres.h" + +#include "catalog/pg_type.h" +#include "executor/executor.h" +#include "executor/execExpr.h" +#include "nodes/nodeFuncs.h" +#include "parser/parse_coerce.h" +#include "utils/builtins.h" +#include "utils/fmgrprotos.h" + +PG_MODULE_MAGIC; + +typedef struct Custom +{ + int first; + int second; +} Custom; + +SubscriptingRef * custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref); +SubscriptingRef * custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate); +Datum custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate); +Datum custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate); + +PG_FUNCTION_INFO_V1(custom_in); +PG_FUNCTION_INFO_V1(custom_out); +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +/***************************************************************************** + * Input/Output functions + *****************************************************************************/ + +Datum +custom_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + int firstValue, + secondValue; + Custom *result; + + if (sscanf(str, " ( %d , %d )", &firstValue, &secondValue) != 2) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for complex: \"%s\"", + str))); + + + result = (Custom *) palloc(sizeof(Custom)); + result->first = firstValue; + result->second = secondValue; + PG_RETURN_POINTER(result); +} + +Datum +custom_out(PG_FUNCTION_ARGS) +{ + Custom *custom = (Custom *) PG_GETARG_POINTER(0); + char *result; + + result = psprintf("(%d, %d)", custom->first, custom->second); + PG_RETURN_CSTRING(result); +} + +/***************************************************************************** + * Custom subscripting logic functions + *****************************************************************************/ + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = INT4OID; + sbsref->refassgntype = INT4OID; + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + List *upperIndexpr = NIL; + ListCell *l; + + if (sbsref->reflowerindexpr != NIL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *)lfirst(sbsref->reflowerindexpr->head)))))); + + foreach(l, sbsref->refupperindexpr) + { + Node *subexpr = (Node *) lfirst(l); + + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *) lfirst(sbsref->refupperindexpr->head)))))); + + subexpr = coerce_to_target_type(pstate, + subexpr, exprType(subexpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript must have integer type"), + parser_errposition(pstate, exprLocation(subexpr)))); + + upperIndexpr = lappend(upperIndexpr, subexpr); + + if (isAssignment) + { + Node *assignExpr = (Node *) sbsref->refassgnexpr; + Node *new_from; + + new_from = coerce_to_target_type(pstate, + assignExpr, exprType(assignExpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (new_from == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom assignment requires int type"), + errhint("You will need to rewrite or cast the expression."), + parser_errposition(pstate, exprLocation(assignExpr)))); + sbsref->refassgnexpr = (Expr *)new_from; + } + } + + sbsref->refupperindexpr = upperIndexpr; + + return sbsref; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + Custom *container= (Custom *) containerSource; + int index; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + return (Datum) container->first; + else + return (Datum) container->second; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + int index; + Custom *container = (Custom *) containerSource; + + if (sbstate->resnull) + return containerSource; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + container->first = DatumGetInt32(sbstate->replacevalue); + else + container->second = DatumGetInt32(sbstate->replacevalue); + + return (Datum) container; +} diff --git a/src/tutorial/subscripting.source b/src/tutorial/subscripting.source new file mode 100644 index 0000000000..837cf30612 --- /dev/null +++ b/src/tutorial/subscripting.source @@ -0,0 +1,71 @@ +--------------------------------------------------------------------------- +-- +-- subscripting.sql- +-- This file shows how to create a new subscripting procedure for +-- user-defined type. +-- +-- +-- Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group +-- Portions Copyright (c) 1994, Regents of the University of California +-- +-- src/tutorial/subscripting.source +-- +--------------------------------------------------------------------------- + +----------------------------- +-- Creating a new type: +-- We are going to create a new type called 'complex' which represents +-- complex numbers. +-- A user-defined type must have an input and an output function, and +-- optionally can have binary input and output functions. All of these +-- are usually user-defined C functions. +----------------------------- + +-- Assume the user defined functions are in /home/erthalion/programms/postgresql-master/src/tutorial/complex$DLSUFFIX +-- (we do not want to assume this is in the dynamic loader search path). +-- Look at $PWD/complex.c for the source. Note that we declare all of +-- them as STRICT, so we do not need to cope with NULL inputs in the +-- C code. We also mark them IMMUTABLE, since they always return the +-- same outputs given the same inputs. + +-- the input function 'complex_in' takes a null-terminated string (the +-- textual representation of the type) and turns it into the internal +-- (in memory) representation. You will get a message telling you 'complex' +-- does not exist yet but that's okay. + +CREATE FUNCTION custom_in(cstring) + RETURNS custom + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +-- the output function 'complex_out' takes the internal representation and +-- converts it into the textual representation. + +CREATE FUNCTION custom_out(custom) + RETURNS cstring + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 8, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler +); + +-- we can use it in a table + +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; -- 2.21.0 --u66dlpg6gaevsofr Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v33-0005-Filling-gaps-in-jsonb-arrays.patch" ^ permalink raw reply [nested|flat] 28+ messages in thread
* [PATCH v33 4/5] Subscripting documentation @ 2019-02-01 10:47 erthalion <[email protected]> 0 siblings, 0 replies; 28+ messages in thread From: erthalion @ 2019-02-01 10:47 UTC (permalink / raw) Supporting documentation for generalized subscripting. It includes the description of a new field in pg_type, the new section for jsonb documentation about subscripting feature on this data type, and also the tutorial about how to write subscripting operator for a custom data type. Reviewed-by: Tom Lane, Arthur Zakirov, Oleksandr Shulgin --- doc/src/sgml/catalogs.sgml | 8 ++ doc/src/sgml/extend.sgml | 6 + doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/json.sgml | 39 ++++++ doc/src/sgml/ref/create_type.sgml | 33 ++++- doc/src/sgml/xsubscripting.sgml | 111 +++++++++++++++++ src/tutorial/Makefile | 4 +- src/tutorial/subscripting.c | 201 ++++++++++++++++++++++++++++++ src/tutorial/subscripting.source | 71 +++++++++++ 9 files changed, 470 insertions(+), 4 deletions(-) create mode 100644 doc/src/sgml/xsubscripting.sgml create mode 100644 src/tutorial/subscripting.c create mode 100644 src/tutorial/subscripting.source diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 048ff284f7..b17e9ee382 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -8971,6 +8971,14 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l </para></entry> </row> + <row> + <entry><structfield>typsubshandler</structfield></entry> + <entry><type>regproc</type></entry> + <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry> + <entry>Custom subscripting function with type-specific logic for parsing + and validation, or 0 if this type doesn't support subscripting.</entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>typdefault</structfield> <type>text</type> diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml index 890ff97b7a..65cf5706b6 100644 --- a/doc/src/sgml/extend.sgml +++ b/doc/src/sgml/extend.sgml @@ -33,6 +33,11 @@ operators (starting in <xref linkend="xoper"/>) </para> </listitem> + <listitem> + <para> + subscripting procedure (starting in <xref linkend="xsubscripting"/>) + </para> + </listitem> <listitem> <para> operator classes for indexes (starting in <xref linkend="xindex"/>) @@ -481,6 +486,7 @@ RETURNS anycompatible AS ... &xaggr; &xtypes; &xoper; + &xsubscripting; &xindex; diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 64b5da0070..4c3fd37fc1 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -69,6 +69,7 @@ <!ENTITY xplang SYSTEM "xplang.sgml"> <!ENTITY xoper SYSTEM "xoper.sgml"> <!ENTITY xtypes SYSTEM "xtypes.sgml"> +<!ENTITY xsubscripting SYSTEM "xsubscripting.sgml"> <!ENTITY plperl SYSTEM "plperl.sgml"> <!ENTITY plpython SYSTEM "plpython.sgml"> <!ENTITY plsql SYSTEM "plpgsql.sgml"> diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml index c0a6554d4d..5c538dca05 100644 --- a/doc/src/sgml/json.sgml +++ b/doc/src/sgml/json.sgml @@ -602,6 +602,45 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu </para> </sect2> + <sect2 id="jsonb-subscripting"> + <title><type>jsonb</type> Subscripting</title> + <para> + <type>jsonb</type> data type supports array-style subscripting expressions + to extract or update particular elements. It's possible to use multiple + subscripting expressions to extract nested values. In this case, a chain of + subscripting expressions follows the same rules as the + <literal>path</literal> argument in <literal>jsonb_set</literal> function, + e.g. in case of arrays it is a 0-based operation or that negative integers + that appear in <literal>path</literal> count from the end of JSON arrays. + The result of subscripting expressions is always jsonb data type. An + example of subscripting syntax: +<programlisting> +-- Extract value by key +SELECT ('{"a": 1}'::jsonb)['a']; + +-- Extract nested value by key path +SELECT ('{"a": {"b": {"c": 1}}}'::jsonb)['a']['b']['c']; + +-- Extract element by index +SELECT ('[1, "2", null]'::jsonb)[1]; + +-- Update value by key +UPDATE table_name SET jsonb_field['key'] = 1; + +-- Select records using where clause with subscripting. Since the result of +-- subscripting is jsonb and we basically want to compare two jsonb objects, we +-- need to put the value in double quotes to be able to convert it to jsonb. +SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"'; +</programlisting> + + There is no special indexing support for such kind of expressions, but you + can always create a functional index that includes it +<programlisting> +CREATE INDEX idx ON table_name ((jsonb_field['key'])); +</programlisting> + </para> + </sect2> + <sect2> <title>Transforms</title> diff --git a/doc/src/sgml/ref/create_type.sgml b/doc/src/sgml/ref/create_type.sgml index 111f8e65d2..a34df4d247 100644 --- a/doc/src/sgml/ref/create_type.sgml +++ b/doc/src/sgml/ref/create_type.sgml @@ -54,6 +54,7 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> ( [ , ELEMENT = <replaceable class="parameter">element</replaceable> ] [ , DELIMITER = <replaceable class="parameter">delimiter</replaceable> ] [ , COLLATABLE = <replaceable class="parameter">collatable</replaceable> ] + [ , SUBSCRIPTING_HANDLER = <replaceable class="parameter">subscripting_handler_function</replaceable> ] ) CREATE TYPE <replaceable class="parameter">name</replaceable> @@ -196,8 +197,9 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> <replaceable class="parameter">receive_function</replaceable>, <replaceable class="parameter">send_function</replaceable>, <replaceable class="parameter">type_modifier_input_function</replaceable>, - <replaceable class="parameter">type_modifier_output_function</replaceable> and - <replaceable class="parameter">analyze_function</replaceable> + <replaceable class="parameter">type_modifier_output_function</replaceable>, + <replaceable class="parameter">analyze_function</replaceable>, + <replaceable class="parameter">subscripting_handler_function</replaceable> are optional. Generally these functions have to be coded in C or another low-level language. </para> @@ -454,6 +456,22 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> make use of the collation information; this does not happen automatically merely by marking the type collatable. </para> + + <para> + The optional + <replaceable class="parameter">subscripting_handler_function</replaceable> + contains type-specific logic for subscripting of the data type. + By default, there is no such function provided, which means that the data + type doesn't support subscripting. The subscripting function must be + declared to take a single argument of type <type>internal</type>, and return + a <type>internal</type> result. There are two examples of implementation for + subscripting functions in case of array + (<replaceable class="parameter">array_subscripting_handler</replaceable>) + and jsonb + (<replaceable class="parameter">jsonb_subscripting_handler</replaceable>) + types in <filename>src/backend/utils/adt/arrayfuncs.c</filename> and + <filename>src/backend/utils/adt/jsonfuncs.c</filename>, respectively. + </para> </refsect2> <refsect2> @@ -769,6 +787,17 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> </para> </listitem> </varlistentry> + + <varlistentry> + <term><replaceable class="parameter">subscripting_handler_function</replaceable></term> + <listitem> + <para> + The name of a function that returns list of type-specific callback functions to + support subscripting logic for the data type. + </para> + </listitem> + </varlistentry> + </variablelist> </refsect1> diff --git a/doc/src/sgml/xsubscripting.sgml b/doc/src/sgml/xsubscripting.sgml new file mode 100644 index 0000000000..7224e81fa2 --- /dev/null +++ b/doc/src/sgml/xsubscripting.sgml @@ -0,0 +1,111 @@ +<!-- doc/src/sgml/xsubscripting.sgml --> + + <sect1 id="xsubscripting"> + <title>User-defined subscripting procedure</title> + + <indexterm zone="xsubscripting"> + <primary>custom subscripting</primary> + </indexterm> + <para> + When you define a new base type, you can also specify a custom procedure to + handle subscripting expressions. It must contain logic for verification and + evaluation of this expression, i.e. fetching or updating some data in this + data type. For instance: +</para> +<programlisting><![CDATA[ +typedef struct Custom +{ + int first; + int second; +} Custom; + +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = someType; + sbsref->refassgntype = someType; + + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + // some validation and coercion logic + + return sbsref; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some assignment logic + + return newContainer; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some fetch logic based on sbstate +}]]> +</programlisting> + +<para> + Then you can define a subscripting procedure and a custom data type: +</para> +<programlisting> +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '<replaceable>filename</replaceable>' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 4, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler, +); +</programlisting> + +<para> + and use it as usual: +</para> +<programlisting> +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; +</programlisting> + + + <para> + The examples of custom subscripting implementation can be found in + <filename>subscripting.sql</filename> and <filename>subscripting.c</filename> + in the <filename>src/tutorial</filename> directory of the source distribution. + See the <filename>README</filename> file in that directory for instructions + about running the examples. + </para> + +</sect1> diff --git a/src/tutorial/Makefile b/src/tutorial/Makefile index 16dc390f71..0ead60c2d4 100644 --- a/src/tutorial/Makefile +++ b/src/tutorial/Makefile @@ -13,8 +13,8 @@ # #------------------------------------------------------------------------- -MODULES = complex funcs -DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql +MODULES = complex funcs subscripting +DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql subscripting.sql ifdef NO_PGXS subdir = src/tutorial diff --git a/src/tutorial/subscripting.c b/src/tutorial/subscripting.c new file mode 100644 index 0000000000..1eb8c45652 --- /dev/null +++ b/src/tutorial/subscripting.c @@ -0,0 +1,201 @@ +/* + * src/tutorial/subscripting.c + * + ****************************************************************************** + This file contains routines that can be bound to a Postgres backend and + called by the backend in the process of processing queries. The calling + format for these routines is dictated by Postgres architecture. +******************************************************************************/ + +#include "postgres.h" + +#include "catalog/pg_type.h" +#include "executor/executor.h" +#include "executor/execExpr.h" +#include "nodes/nodeFuncs.h" +#include "parser/parse_coerce.h" +#include "utils/builtins.h" +#include "utils/fmgrprotos.h" + +PG_MODULE_MAGIC; + +typedef struct Custom +{ + int first; + int second; +} Custom; + +SubscriptingRef * custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref); +SubscriptingRef * custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate); +Datum custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate); +Datum custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate); + +PG_FUNCTION_INFO_V1(custom_in); +PG_FUNCTION_INFO_V1(custom_out); +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +/***************************************************************************** + * Input/Output functions + *****************************************************************************/ + +Datum +custom_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + int firstValue, + secondValue; + Custom *result; + + if (sscanf(str, " ( %d , %d )", &firstValue, &secondValue) != 2) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for complex: \"%s\"", + str))); + + + result = (Custom *) palloc(sizeof(Custom)); + result->first = firstValue; + result->second = secondValue; + PG_RETURN_POINTER(result); +} + +Datum +custom_out(PG_FUNCTION_ARGS) +{ + Custom *custom = (Custom *) PG_GETARG_POINTER(0); + char *result; + + result = psprintf("(%d, %d)", custom->first, custom->second); + PG_RETURN_CSTRING(result); +} + +/***************************************************************************** + * Custom subscripting logic functions + *****************************************************************************/ + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = INT4OID; + sbsref->refassgntype = INT4OID; + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + List *upperIndexpr = NIL; + ListCell *l; + + if (sbsref->reflowerindexpr != NIL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *)lfirst(sbsref->reflowerindexpr->head)))))); + + foreach(l, sbsref->refupperindexpr) + { + Node *subexpr = (Node *) lfirst(l); + + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *) lfirst(sbsref->refupperindexpr->head)))))); + + subexpr = coerce_to_target_type(pstate, + subexpr, exprType(subexpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript must have integer type"), + parser_errposition(pstate, exprLocation(subexpr)))); + + upperIndexpr = lappend(upperIndexpr, subexpr); + + if (isAssignment) + { + Node *assignExpr = (Node *) sbsref->refassgnexpr; + Node *new_from; + + new_from = coerce_to_target_type(pstate, + assignExpr, exprType(assignExpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (new_from == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom assignment requires int type"), + errhint("You will need to rewrite or cast the expression."), + parser_errposition(pstate, exprLocation(assignExpr)))); + sbsref->refassgnexpr = (Expr *)new_from; + } + } + + sbsref->refupperindexpr = upperIndexpr; + + return sbsref; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + Custom *container= (Custom *) containerSource; + int index; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + return (Datum) container->first; + else + return (Datum) container->second; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + int index; + Custom *container = (Custom *) containerSource; + + if (sbstate->resnull) + return containerSource; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + container->first = DatumGetInt32(sbstate->replacevalue); + else + container->second = DatumGetInt32(sbstate->replacevalue); + + return (Datum) container; +} diff --git a/src/tutorial/subscripting.source b/src/tutorial/subscripting.source new file mode 100644 index 0000000000..837cf30612 --- /dev/null +++ b/src/tutorial/subscripting.source @@ -0,0 +1,71 @@ +--------------------------------------------------------------------------- +-- +-- subscripting.sql- +-- This file shows how to create a new subscripting procedure for +-- user-defined type. +-- +-- +-- Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group +-- Portions Copyright (c) 1994, Regents of the University of California +-- +-- src/tutorial/subscripting.source +-- +--------------------------------------------------------------------------- + +----------------------------- +-- Creating a new type: +-- We are going to create a new type called 'complex' which represents +-- complex numbers. +-- A user-defined type must have an input and an output function, and +-- optionally can have binary input and output functions. All of these +-- are usually user-defined C functions. +----------------------------- + +-- Assume the user defined functions are in /home/erthalion/programms/postgresql-master/src/tutorial/complex$DLSUFFIX +-- (we do not want to assume this is in the dynamic loader search path). +-- Look at $PWD/complex.c for the source. Note that we declare all of +-- them as STRICT, so we do not need to cope with NULL inputs in the +-- C code. We also mark them IMMUTABLE, since they always return the +-- same outputs given the same inputs. + +-- the input function 'complex_in' takes a null-terminated string (the +-- textual representation of the type) and turns it into the internal +-- (in memory) representation. You will get a message telling you 'complex' +-- does not exist yet but that's okay. + +CREATE FUNCTION custom_in(cstring) + RETURNS custom + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +-- the output function 'complex_out' takes the internal representation and +-- converts it into the textual representation. + +CREATE FUNCTION custom_out(custom) + RETURNS cstring + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 8, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler +); + +-- we can use it in a table + +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; -- 2.21.0 --u66dlpg6gaevsofr Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v33-0005-Filling-gaps-in-jsonb-arrays.patch" ^ permalink raw reply [nested|flat] 28+ messages in thread
* [PATCH v33 4/5] Subscripting documentation @ 2019-02-01 10:47 erthalion <[email protected]> 0 siblings, 0 replies; 28+ messages in thread From: erthalion @ 2019-02-01 10:47 UTC (permalink / raw) Supporting documentation for generalized subscripting. It includes the description of a new field in pg_type, the new section for jsonb documentation about subscripting feature on this data type, and also the tutorial about how to write subscripting operator for a custom data type. Reviewed-by: Tom Lane, Arthur Zakirov, Oleksandr Shulgin --- doc/src/sgml/catalogs.sgml | 8 ++ doc/src/sgml/extend.sgml | 6 + doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/json.sgml | 39 ++++++ doc/src/sgml/ref/create_type.sgml | 33 ++++- doc/src/sgml/xsubscripting.sgml | 111 +++++++++++++++++ src/tutorial/Makefile | 4 +- src/tutorial/subscripting.c | 201 ++++++++++++++++++++++++++++++ src/tutorial/subscripting.source | 71 +++++++++++ 9 files changed, 470 insertions(+), 4 deletions(-) create mode 100644 doc/src/sgml/xsubscripting.sgml create mode 100644 src/tutorial/subscripting.c create mode 100644 src/tutorial/subscripting.source diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 048ff284f7..b17e9ee382 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -8971,6 +8971,14 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l </para></entry> </row> + <row> + <entry><structfield>typsubshandler</structfield></entry> + <entry><type>regproc</type></entry> + <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry> + <entry>Custom subscripting function with type-specific logic for parsing + and validation, or 0 if this type doesn't support subscripting.</entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>typdefault</structfield> <type>text</type> diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml index 890ff97b7a..65cf5706b6 100644 --- a/doc/src/sgml/extend.sgml +++ b/doc/src/sgml/extend.sgml @@ -33,6 +33,11 @@ operators (starting in <xref linkend="xoper"/>) </para> </listitem> + <listitem> + <para> + subscripting procedure (starting in <xref linkend="xsubscripting"/>) + </para> + </listitem> <listitem> <para> operator classes for indexes (starting in <xref linkend="xindex"/>) @@ -481,6 +486,7 @@ RETURNS anycompatible AS ... &xaggr; &xtypes; &xoper; + &xsubscripting; &xindex; diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 64b5da0070..4c3fd37fc1 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -69,6 +69,7 @@ <!ENTITY xplang SYSTEM "xplang.sgml"> <!ENTITY xoper SYSTEM "xoper.sgml"> <!ENTITY xtypes SYSTEM "xtypes.sgml"> +<!ENTITY xsubscripting SYSTEM "xsubscripting.sgml"> <!ENTITY plperl SYSTEM "plperl.sgml"> <!ENTITY plpython SYSTEM "plpython.sgml"> <!ENTITY plsql SYSTEM "plpgsql.sgml"> diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml index c0a6554d4d..5c538dca05 100644 --- a/doc/src/sgml/json.sgml +++ b/doc/src/sgml/json.sgml @@ -602,6 +602,45 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu </para> </sect2> + <sect2 id="jsonb-subscripting"> + <title><type>jsonb</type> Subscripting</title> + <para> + <type>jsonb</type> data type supports array-style subscripting expressions + to extract or update particular elements. It's possible to use multiple + subscripting expressions to extract nested values. In this case, a chain of + subscripting expressions follows the same rules as the + <literal>path</literal> argument in <literal>jsonb_set</literal> function, + e.g. in case of arrays it is a 0-based operation or that negative integers + that appear in <literal>path</literal> count from the end of JSON arrays. + The result of subscripting expressions is always jsonb data type. An + example of subscripting syntax: +<programlisting> +-- Extract value by key +SELECT ('{"a": 1}'::jsonb)['a']; + +-- Extract nested value by key path +SELECT ('{"a": {"b": {"c": 1}}}'::jsonb)['a']['b']['c']; + +-- Extract element by index +SELECT ('[1, "2", null]'::jsonb)[1]; + +-- Update value by key +UPDATE table_name SET jsonb_field['key'] = 1; + +-- Select records using where clause with subscripting. Since the result of +-- subscripting is jsonb and we basically want to compare two jsonb objects, we +-- need to put the value in double quotes to be able to convert it to jsonb. +SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"'; +</programlisting> + + There is no special indexing support for such kind of expressions, but you + can always create a functional index that includes it +<programlisting> +CREATE INDEX idx ON table_name ((jsonb_field['key'])); +</programlisting> + </para> + </sect2> + <sect2> <title>Transforms</title> diff --git a/doc/src/sgml/ref/create_type.sgml b/doc/src/sgml/ref/create_type.sgml index 111f8e65d2..a34df4d247 100644 --- a/doc/src/sgml/ref/create_type.sgml +++ b/doc/src/sgml/ref/create_type.sgml @@ -54,6 +54,7 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> ( [ , ELEMENT = <replaceable class="parameter">element</replaceable> ] [ , DELIMITER = <replaceable class="parameter">delimiter</replaceable> ] [ , COLLATABLE = <replaceable class="parameter">collatable</replaceable> ] + [ , SUBSCRIPTING_HANDLER = <replaceable class="parameter">subscripting_handler_function</replaceable> ] ) CREATE TYPE <replaceable class="parameter">name</replaceable> @@ -196,8 +197,9 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> <replaceable class="parameter">receive_function</replaceable>, <replaceable class="parameter">send_function</replaceable>, <replaceable class="parameter">type_modifier_input_function</replaceable>, - <replaceable class="parameter">type_modifier_output_function</replaceable> and - <replaceable class="parameter">analyze_function</replaceable> + <replaceable class="parameter">type_modifier_output_function</replaceable>, + <replaceable class="parameter">analyze_function</replaceable>, + <replaceable class="parameter">subscripting_handler_function</replaceable> are optional. Generally these functions have to be coded in C or another low-level language. </para> @@ -454,6 +456,22 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> make use of the collation information; this does not happen automatically merely by marking the type collatable. </para> + + <para> + The optional + <replaceable class="parameter">subscripting_handler_function</replaceable> + contains type-specific logic for subscripting of the data type. + By default, there is no such function provided, which means that the data + type doesn't support subscripting. The subscripting function must be + declared to take a single argument of type <type>internal</type>, and return + a <type>internal</type> result. There are two examples of implementation for + subscripting functions in case of array + (<replaceable class="parameter">array_subscripting_handler</replaceable>) + and jsonb + (<replaceable class="parameter">jsonb_subscripting_handler</replaceable>) + types in <filename>src/backend/utils/adt/arrayfuncs.c</filename> and + <filename>src/backend/utils/adt/jsonfuncs.c</filename>, respectively. + </para> </refsect2> <refsect2> @@ -769,6 +787,17 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> </para> </listitem> </varlistentry> + + <varlistentry> + <term><replaceable class="parameter">subscripting_handler_function</replaceable></term> + <listitem> + <para> + The name of a function that returns list of type-specific callback functions to + support subscripting logic for the data type. + </para> + </listitem> + </varlistentry> + </variablelist> </refsect1> diff --git a/doc/src/sgml/xsubscripting.sgml b/doc/src/sgml/xsubscripting.sgml new file mode 100644 index 0000000000..7224e81fa2 --- /dev/null +++ b/doc/src/sgml/xsubscripting.sgml @@ -0,0 +1,111 @@ +<!-- doc/src/sgml/xsubscripting.sgml --> + + <sect1 id="xsubscripting"> + <title>User-defined subscripting procedure</title> + + <indexterm zone="xsubscripting"> + <primary>custom subscripting</primary> + </indexterm> + <para> + When you define a new base type, you can also specify a custom procedure to + handle subscripting expressions. It must contain logic for verification and + evaluation of this expression, i.e. fetching or updating some data in this + data type. For instance: +</para> +<programlisting><![CDATA[ +typedef struct Custom +{ + int first; + int second; +} Custom; + +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = someType; + sbsref->refassgntype = someType; + + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + // some validation and coercion logic + + return sbsref; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some assignment logic + + return newContainer; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some fetch logic based on sbstate +}]]> +</programlisting> + +<para> + Then you can define a subscripting procedure and a custom data type: +</para> +<programlisting> +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '<replaceable>filename</replaceable>' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 4, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler, +); +</programlisting> + +<para> + and use it as usual: +</para> +<programlisting> +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; +</programlisting> + + + <para> + The examples of custom subscripting implementation can be found in + <filename>subscripting.sql</filename> and <filename>subscripting.c</filename> + in the <filename>src/tutorial</filename> directory of the source distribution. + See the <filename>README</filename> file in that directory for instructions + about running the examples. + </para> + +</sect1> diff --git a/src/tutorial/Makefile b/src/tutorial/Makefile index 16dc390f71..0ead60c2d4 100644 --- a/src/tutorial/Makefile +++ b/src/tutorial/Makefile @@ -13,8 +13,8 @@ # #------------------------------------------------------------------------- -MODULES = complex funcs -DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql +MODULES = complex funcs subscripting +DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql subscripting.sql ifdef NO_PGXS subdir = src/tutorial diff --git a/src/tutorial/subscripting.c b/src/tutorial/subscripting.c new file mode 100644 index 0000000000..1eb8c45652 --- /dev/null +++ b/src/tutorial/subscripting.c @@ -0,0 +1,201 @@ +/* + * src/tutorial/subscripting.c + * + ****************************************************************************** + This file contains routines that can be bound to a Postgres backend and + called by the backend in the process of processing queries. The calling + format for these routines is dictated by Postgres architecture. +******************************************************************************/ + +#include "postgres.h" + +#include "catalog/pg_type.h" +#include "executor/executor.h" +#include "executor/execExpr.h" +#include "nodes/nodeFuncs.h" +#include "parser/parse_coerce.h" +#include "utils/builtins.h" +#include "utils/fmgrprotos.h" + +PG_MODULE_MAGIC; + +typedef struct Custom +{ + int first; + int second; +} Custom; + +SubscriptingRef * custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref); +SubscriptingRef * custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate); +Datum custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate); +Datum custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate); + +PG_FUNCTION_INFO_V1(custom_in); +PG_FUNCTION_INFO_V1(custom_out); +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +/***************************************************************************** + * Input/Output functions + *****************************************************************************/ + +Datum +custom_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + int firstValue, + secondValue; + Custom *result; + + if (sscanf(str, " ( %d , %d )", &firstValue, &secondValue) != 2) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for complex: \"%s\"", + str))); + + + result = (Custom *) palloc(sizeof(Custom)); + result->first = firstValue; + result->second = secondValue; + PG_RETURN_POINTER(result); +} + +Datum +custom_out(PG_FUNCTION_ARGS) +{ + Custom *custom = (Custom *) PG_GETARG_POINTER(0); + char *result; + + result = psprintf("(%d, %d)", custom->first, custom->second); + PG_RETURN_CSTRING(result); +} + +/***************************************************************************** + * Custom subscripting logic functions + *****************************************************************************/ + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = INT4OID; + sbsref->refassgntype = INT4OID; + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + List *upperIndexpr = NIL; + ListCell *l; + + if (sbsref->reflowerindexpr != NIL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *)lfirst(sbsref->reflowerindexpr->head)))))); + + foreach(l, sbsref->refupperindexpr) + { + Node *subexpr = (Node *) lfirst(l); + + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *) lfirst(sbsref->refupperindexpr->head)))))); + + subexpr = coerce_to_target_type(pstate, + subexpr, exprType(subexpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript must have integer type"), + parser_errposition(pstate, exprLocation(subexpr)))); + + upperIndexpr = lappend(upperIndexpr, subexpr); + + if (isAssignment) + { + Node *assignExpr = (Node *) sbsref->refassgnexpr; + Node *new_from; + + new_from = coerce_to_target_type(pstate, + assignExpr, exprType(assignExpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (new_from == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom assignment requires int type"), + errhint("You will need to rewrite or cast the expression."), + parser_errposition(pstate, exprLocation(assignExpr)))); + sbsref->refassgnexpr = (Expr *)new_from; + } + } + + sbsref->refupperindexpr = upperIndexpr; + + return sbsref; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + Custom *container= (Custom *) containerSource; + int index; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + return (Datum) container->first; + else + return (Datum) container->second; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + int index; + Custom *container = (Custom *) containerSource; + + if (sbstate->resnull) + return containerSource; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + container->first = DatumGetInt32(sbstate->replacevalue); + else + container->second = DatumGetInt32(sbstate->replacevalue); + + return (Datum) container; +} diff --git a/src/tutorial/subscripting.source b/src/tutorial/subscripting.source new file mode 100644 index 0000000000..837cf30612 --- /dev/null +++ b/src/tutorial/subscripting.source @@ -0,0 +1,71 @@ +--------------------------------------------------------------------------- +-- +-- subscripting.sql- +-- This file shows how to create a new subscripting procedure for +-- user-defined type. +-- +-- +-- Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group +-- Portions Copyright (c) 1994, Regents of the University of California +-- +-- src/tutorial/subscripting.source +-- +--------------------------------------------------------------------------- + +----------------------------- +-- Creating a new type: +-- We are going to create a new type called 'complex' which represents +-- complex numbers. +-- A user-defined type must have an input and an output function, and +-- optionally can have binary input and output functions. All of these +-- are usually user-defined C functions. +----------------------------- + +-- Assume the user defined functions are in /home/erthalion/programms/postgresql-master/src/tutorial/complex$DLSUFFIX +-- (we do not want to assume this is in the dynamic loader search path). +-- Look at $PWD/complex.c for the source. Note that we declare all of +-- them as STRICT, so we do not need to cope with NULL inputs in the +-- C code. We also mark them IMMUTABLE, since they always return the +-- same outputs given the same inputs. + +-- the input function 'complex_in' takes a null-terminated string (the +-- textual representation of the type) and turns it into the internal +-- (in memory) representation. You will get a message telling you 'complex' +-- does not exist yet but that's okay. + +CREATE FUNCTION custom_in(cstring) + RETURNS custom + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +-- the output function 'complex_out' takes the internal representation and +-- converts it into the textual representation. + +CREATE FUNCTION custom_out(custom) + RETURNS cstring + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 8, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler +); + +-- we can use it in a table + +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; -- 2.21.0 --u66dlpg6gaevsofr Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v33-0005-Filling-gaps-in-jsonb-arrays.patch" ^ permalink raw reply [nested|flat] 28+ messages in thread
* [PATCH v33 4/5] Subscripting documentation @ 2019-02-01 10:47 erthalion <[email protected]> 0 siblings, 0 replies; 28+ messages in thread From: erthalion @ 2019-02-01 10:47 UTC (permalink / raw) Supporting documentation for generalized subscripting. It includes the description of a new field in pg_type, the new section for jsonb documentation about subscripting feature on this data type, and also the tutorial about how to write subscripting operator for a custom data type. Reviewed-by: Tom Lane, Arthur Zakirov, Oleksandr Shulgin --- doc/src/sgml/catalogs.sgml | 8 ++ doc/src/sgml/extend.sgml | 6 + doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/json.sgml | 39 ++++++ doc/src/sgml/ref/create_type.sgml | 33 ++++- doc/src/sgml/xsubscripting.sgml | 111 +++++++++++++++++ src/tutorial/Makefile | 4 +- src/tutorial/subscripting.c | 201 ++++++++++++++++++++++++++++++ src/tutorial/subscripting.source | 71 +++++++++++ 9 files changed, 470 insertions(+), 4 deletions(-) create mode 100644 doc/src/sgml/xsubscripting.sgml create mode 100644 src/tutorial/subscripting.c create mode 100644 src/tutorial/subscripting.source diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 048ff284f7..b17e9ee382 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -8971,6 +8971,14 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l </para></entry> </row> + <row> + <entry><structfield>typsubshandler</structfield></entry> + <entry><type>regproc</type></entry> + <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry> + <entry>Custom subscripting function with type-specific logic for parsing + and validation, or 0 if this type doesn't support subscripting.</entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>typdefault</structfield> <type>text</type> diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml index 890ff97b7a..65cf5706b6 100644 --- a/doc/src/sgml/extend.sgml +++ b/doc/src/sgml/extend.sgml @@ -33,6 +33,11 @@ operators (starting in <xref linkend="xoper"/>) </para> </listitem> + <listitem> + <para> + subscripting procedure (starting in <xref linkend="xsubscripting"/>) + </para> + </listitem> <listitem> <para> operator classes for indexes (starting in <xref linkend="xindex"/>) @@ -481,6 +486,7 @@ RETURNS anycompatible AS ... &xaggr; &xtypes; &xoper; + &xsubscripting; &xindex; diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 64b5da0070..4c3fd37fc1 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -69,6 +69,7 @@ <!ENTITY xplang SYSTEM "xplang.sgml"> <!ENTITY xoper SYSTEM "xoper.sgml"> <!ENTITY xtypes SYSTEM "xtypes.sgml"> +<!ENTITY xsubscripting SYSTEM "xsubscripting.sgml"> <!ENTITY plperl SYSTEM "plperl.sgml"> <!ENTITY plpython SYSTEM "plpython.sgml"> <!ENTITY plsql SYSTEM "plpgsql.sgml"> diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml index c0a6554d4d..5c538dca05 100644 --- a/doc/src/sgml/json.sgml +++ b/doc/src/sgml/json.sgml @@ -602,6 +602,45 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu </para> </sect2> + <sect2 id="jsonb-subscripting"> + <title><type>jsonb</type> Subscripting</title> + <para> + <type>jsonb</type> data type supports array-style subscripting expressions + to extract or update particular elements. It's possible to use multiple + subscripting expressions to extract nested values. In this case, a chain of + subscripting expressions follows the same rules as the + <literal>path</literal> argument in <literal>jsonb_set</literal> function, + e.g. in case of arrays it is a 0-based operation or that negative integers + that appear in <literal>path</literal> count from the end of JSON arrays. + The result of subscripting expressions is always jsonb data type. An + example of subscripting syntax: +<programlisting> +-- Extract value by key +SELECT ('{"a": 1}'::jsonb)['a']; + +-- Extract nested value by key path +SELECT ('{"a": {"b": {"c": 1}}}'::jsonb)['a']['b']['c']; + +-- Extract element by index +SELECT ('[1, "2", null]'::jsonb)[1]; + +-- Update value by key +UPDATE table_name SET jsonb_field['key'] = 1; + +-- Select records using where clause with subscripting. Since the result of +-- subscripting is jsonb and we basically want to compare two jsonb objects, we +-- need to put the value in double quotes to be able to convert it to jsonb. +SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"'; +</programlisting> + + There is no special indexing support for such kind of expressions, but you + can always create a functional index that includes it +<programlisting> +CREATE INDEX idx ON table_name ((jsonb_field['key'])); +</programlisting> + </para> + </sect2> + <sect2> <title>Transforms</title> diff --git a/doc/src/sgml/ref/create_type.sgml b/doc/src/sgml/ref/create_type.sgml index 111f8e65d2..a34df4d247 100644 --- a/doc/src/sgml/ref/create_type.sgml +++ b/doc/src/sgml/ref/create_type.sgml @@ -54,6 +54,7 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> ( [ , ELEMENT = <replaceable class="parameter">element</replaceable> ] [ , DELIMITER = <replaceable class="parameter">delimiter</replaceable> ] [ , COLLATABLE = <replaceable class="parameter">collatable</replaceable> ] + [ , SUBSCRIPTING_HANDLER = <replaceable class="parameter">subscripting_handler_function</replaceable> ] ) CREATE TYPE <replaceable class="parameter">name</replaceable> @@ -196,8 +197,9 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> <replaceable class="parameter">receive_function</replaceable>, <replaceable class="parameter">send_function</replaceable>, <replaceable class="parameter">type_modifier_input_function</replaceable>, - <replaceable class="parameter">type_modifier_output_function</replaceable> and - <replaceable class="parameter">analyze_function</replaceable> + <replaceable class="parameter">type_modifier_output_function</replaceable>, + <replaceable class="parameter">analyze_function</replaceable>, + <replaceable class="parameter">subscripting_handler_function</replaceable> are optional. Generally these functions have to be coded in C or another low-level language. </para> @@ -454,6 +456,22 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> make use of the collation information; this does not happen automatically merely by marking the type collatable. </para> + + <para> + The optional + <replaceable class="parameter">subscripting_handler_function</replaceable> + contains type-specific logic for subscripting of the data type. + By default, there is no such function provided, which means that the data + type doesn't support subscripting. The subscripting function must be + declared to take a single argument of type <type>internal</type>, and return + a <type>internal</type> result. There are two examples of implementation for + subscripting functions in case of array + (<replaceable class="parameter">array_subscripting_handler</replaceable>) + and jsonb + (<replaceable class="parameter">jsonb_subscripting_handler</replaceable>) + types in <filename>src/backend/utils/adt/arrayfuncs.c</filename> and + <filename>src/backend/utils/adt/jsonfuncs.c</filename>, respectively. + </para> </refsect2> <refsect2> @@ -769,6 +787,17 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> </para> </listitem> </varlistentry> + + <varlistentry> + <term><replaceable class="parameter">subscripting_handler_function</replaceable></term> + <listitem> + <para> + The name of a function that returns list of type-specific callback functions to + support subscripting logic for the data type. + </para> + </listitem> + </varlistentry> + </variablelist> </refsect1> diff --git a/doc/src/sgml/xsubscripting.sgml b/doc/src/sgml/xsubscripting.sgml new file mode 100644 index 0000000000..7224e81fa2 --- /dev/null +++ b/doc/src/sgml/xsubscripting.sgml @@ -0,0 +1,111 @@ +<!-- doc/src/sgml/xsubscripting.sgml --> + + <sect1 id="xsubscripting"> + <title>User-defined subscripting procedure</title> + + <indexterm zone="xsubscripting"> + <primary>custom subscripting</primary> + </indexterm> + <para> + When you define a new base type, you can also specify a custom procedure to + handle subscripting expressions. It must contain logic for verification and + evaluation of this expression, i.e. fetching or updating some data in this + data type. For instance: +</para> +<programlisting><![CDATA[ +typedef struct Custom +{ + int first; + int second; +} Custom; + +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = someType; + sbsref->refassgntype = someType; + + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + // some validation and coercion logic + + return sbsref; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some assignment logic + + return newContainer; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some fetch logic based on sbstate +}]]> +</programlisting> + +<para> + Then you can define a subscripting procedure and a custom data type: +</para> +<programlisting> +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '<replaceable>filename</replaceable>' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 4, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler, +); +</programlisting> + +<para> + and use it as usual: +</para> +<programlisting> +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; +</programlisting> + + + <para> + The examples of custom subscripting implementation can be found in + <filename>subscripting.sql</filename> and <filename>subscripting.c</filename> + in the <filename>src/tutorial</filename> directory of the source distribution. + See the <filename>README</filename> file in that directory for instructions + about running the examples. + </para> + +</sect1> diff --git a/src/tutorial/Makefile b/src/tutorial/Makefile index 16dc390f71..0ead60c2d4 100644 --- a/src/tutorial/Makefile +++ b/src/tutorial/Makefile @@ -13,8 +13,8 @@ # #------------------------------------------------------------------------- -MODULES = complex funcs -DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql +MODULES = complex funcs subscripting +DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql subscripting.sql ifdef NO_PGXS subdir = src/tutorial diff --git a/src/tutorial/subscripting.c b/src/tutorial/subscripting.c new file mode 100644 index 0000000000..1eb8c45652 --- /dev/null +++ b/src/tutorial/subscripting.c @@ -0,0 +1,201 @@ +/* + * src/tutorial/subscripting.c + * + ****************************************************************************** + This file contains routines that can be bound to a Postgres backend and + called by the backend in the process of processing queries. The calling + format for these routines is dictated by Postgres architecture. +******************************************************************************/ + +#include "postgres.h" + +#include "catalog/pg_type.h" +#include "executor/executor.h" +#include "executor/execExpr.h" +#include "nodes/nodeFuncs.h" +#include "parser/parse_coerce.h" +#include "utils/builtins.h" +#include "utils/fmgrprotos.h" + +PG_MODULE_MAGIC; + +typedef struct Custom +{ + int first; + int second; +} Custom; + +SubscriptingRef * custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref); +SubscriptingRef * custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate); +Datum custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate); +Datum custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate); + +PG_FUNCTION_INFO_V1(custom_in); +PG_FUNCTION_INFO_V1(custom_out); +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +/***************************************************************************** + * Input/Output functions + *****************************************************************************/ + +Datum +custom_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + int firstValue, + secondValue; + Custom *result; + + if (sscanf(str, " ( %d , %d )", &firstValue, &secondValue) != 2) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for complex: \"%s\"", + str))); + + + result = (Custom *) palloc(sizeof(Custom)); + result->first = firstValue; + result->second = secondValue; + PG_RETURN_POINTER(result); +} + +Datum +custom_out(PG_FUNCTION_ARGS) +{ + Custom *custom = (Custom *) PG_GETARG_POINTER(0); + char *result; + + result = psprintf("(%d, %d)", custom->first, custom->second); + PG_RETURN_CSTRING(result); +} + +/***************************************************************************** + * Custom subscripting logic functions + *****************************************************************************/ + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = INT4OID; + sbsref->refassgntype = INT4OID; + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + List *upperIndexpr = NIL; + ListCell *l; + + if (sbsref->reflowerindexpr != NIL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *)lfirst(sbsref->reflowerindexpr->head)))))); + + foreach(l, sbsref->refupperindexpr) + { + Node *subexpr = (Node *) lfirst(l); + + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *) lfirst(sbsref->refupperindexpr->head)))))); + + subexpr = coerce_to_target_type(pstate, + subexpr, exprType(subexpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript must have integer type"), + parser_errposition(pstate, exprLocation(subexpr)))); + + upperIndexpr = lappend(upperIndexpr, subexpr); + + if (isAssignment) + { + Node *assignExpr = (Node *) sbsref->refassgnexpr; + Node *new_from; + + new_from = coerce_to_target_type(pstate, + assignExpr, exprType(assignExpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (new_from == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom assignment requires int type"), + errhint("You will need to rewrite or cast the expression."), + parser_errposition(pstate, exprLocation(assignExpr)))); + sbsref->refassgnexpr = (Expr *)new_from; + } + } + + sbsref->refupperindexpr = upperIndexpr; + + return sbsref; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + Custom *container= (Custom *) containerSource; + int index; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + return (Datum) container->first; + else + return (Datum) container->second; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + int index; + Custom *container = (Custom *) containerSource; + + if (sbstate->resnull) + return containerSource; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + container->first = DatumGetInt32(sbstate->replacevalue); + else + container->second = DatumGetInt32(sbstate->replacevalue); + + return (Datum) container; +} diff --git a/src/tutorial/subscripting.source b/src/tutorial/subscripting.source new file mode 100644 index 0000000000..837cf30612 --- /dev/null +++ b/src/tutorial/subscripting.source @@ -0,0 +1,71 @@ +--------------------------------------------------------------------------- +-- +-- subscripting.sql- +-- This file shows how to create a new subscripting procedure for +-- user-defined type. +-- +-- +-- Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group +-- Portions Copyright (c) 1994, Regents of the University of California +-- +-- src/tutorial/subscripting.source +-- +--------------------------------------------------------------------------- + +----------------------------- +-- Creating a new type: +-- We are going to create a new type called 'complex' which represents +-- complex numbers. +-- A user-defined type must have an input and an output function, and +-- optionally can have binary input and output functions. All of these +-- are usually user-defined C functions. +----------------------------- + +-- Assume the user defined functions are in /home/erthalion/programms/postgresql-master/src/tutorial/complex$DLSUFFIX +-- (we do not want to assume this is in the dynamic loader search path). +-- Look at $PWD/complex.c for the source. Note that we declare all of +-- them as STRICT, so we do not need to cope with NULL inputs in the +-- C code. We also mark them IMMUTABLE, since they always return the +-- same outputs given the same inputs. + +-- the input function 'complex_in' takes a null-terminated string (the +-- textual representation of the type) and turns it into the internal +-- (in memory) representation. You will get a message telling you 'complex' +-- does not exist yet but that's okay. + +CREATE FUNCTION custom_in(cstring) + RETURNS custom + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +-- the output function 'complex_out' takes the internal representation and +-- converts it into the textual representation. + +CREATE FUNCTION custom_out(custom) + RETURNS cstring + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 8, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler +); + +-- we can use it in a table + +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; -- 2.21.0 --u66dlpg6gaevsofr Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v33-0005-Filling-gaps-in-jsonb-arrays.patch" ^ permalink raw reply [nested|flat] 28+ messages in thread
* [PATCH v33 4/5] Subscripting documentation @ 2019-02-01 10:47 erthalion <[email protected]> 0 siblings, 0 replies; 28+ messages in thread From: erthalion @ 2019-02-01 10:47 UTC (permalink / raw) Supporting documentation for generalized subscripting. It includes the description of a new field in pg_type, the new section for jsonb documentation about subscripting feature on this data type, and also the tutorial about how to write subscripting operator for a custom data type. Reviewed-by: Tom Lane, Arthur Zakirov, Oleksandr Shulgin --- doc/src/sgml/catalogs.sgml | 8 ++ doc/src/sgml/extend.sgml | 6 + doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/json.sgml | 39 ++++++ doc/src/sgml/ref/create_type.sgml | 33 ++++- doc/src/sgml/xsubscripting.sgml | 111 +++++++++++++++++ src/tutorial/Makefile | 4 +- src/tutorial/subscripting.c | 201 ++++++++++++++++++++++++++++++ src/tutorial/subscripting.source | 71 +++++++++++ 9 files changed, 470 insertions(+), 4 deletions(-) create mode 100644 doc/src/sgml/xsubscripting.sgml create mode 100644 src/tutorial/subscripting.c create mode 100644 src/tutorial/subscripting.source diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 048ff284f7..b17e9ee382 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -8971,6 +8971,14 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l </para></entry> </row> + <row> + <entry><structfield>typsubshandler</structfield></entry> + <entry><type>regproc</type></entry> + <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry> + <entry>Custom subscripting function with type-specific logic for parsing + and validation, or 0 if this type doesn't support subscripting.</entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>typdefault</structfield> <type>text</type> diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml index 890ff97b7a..65cf5706b6 100644 --- a/doc/src/sgml/extend.sgml +++ b/doc/src/sgml/extend.sgml @@ -33,6 +33,11 @@ operators (starting in <xref linkend="xoper"/>) </para> </listitem> + <listitem> + <para> + subscripting procedure (starting in <xref linkend="xsubscripting"/>) + </para> + </listitem> <listitem> <para> operator classes for indexes (starting in <xref linkend="xindex"/>) @@ -481,6 +486,7 @@ RETURNS anycompatible AS ... &xaggr; &xtypes; &xoper; + &xsubscripting; &xindex; diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 64b5da0070..4c3fd37fc1 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -69,6 +69,7 @@ <!ENTITY xplang SYSTEM "xplang.sgml"> <!ENTITY xoper SYSTEM "xoper.sgml"> <!ENTITY xtypes SYSTEM "xtypes.sgml"> +<!ENTITY xsubscripting SYSTEM "xsubscripting.sgml"> <!ENTITY plperl SYSTEM "plperl.sgml"> <!ENTITY plpython SYSTEM "plpython.sgml"> <!ENTITY plsql SYSTEM "plpgsql.sgml"> diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml index c0a6554d4d..5c538dca05 100644 --- a/doc/src/sgml/json.sgml +++ b/doc/src/sgml/json.sgml @@ -602,6 +602,45 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu </para> </sect2> + <sect2 id="jsonb-subscripting"> + <title><type>jsonb</type> Subscripting</title> + <para> + <type>jsonb</type> data type supports array-style subscripting expressions + to extract or update particular elements. It's possible to use multiple + subscripting expressions to extract nested values. In this case, a chain of + subscripting expressions follows the same rules as the + <literal>path</literal> argument in <literal>jsonb_set</literal> function, + e.g. in case of arrays it is a 0-based operation or that negative integers + that appear in <literal>path</literal> count from the end of JSON arrays. + The result of subscripting expressions is always jsonb data type. An + example of subscripting syntax: +<programlisting> +-- Extract value by key +SELECT ('{"a": 1}'::jsonb)['a']; + +-- Extract nested value by key path +SELECT ('{"a": {"b": {"c": 1}}}'::jsonb)['a']['b']['c']; + +-- Extract element by index +SELECT ('[1, "2", null]'::jsonb)[1]; + +-- Update value by key +UPDATE table_name SET jsonb_field['key'] = 1; + +-- Select records using where clause with subscripting. Since the result of +-- subscripting is jsonb and we basically want to compare two jsonb objects, we +-- need to put the value in double quotes to be able to convert it to jsonb. +SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"'; +</programlisting> + + There is no special indexing support for such kind of expressions, but you + can always create a functional index that includes it +<programlisting> +CREATE INDEX idx ON table_name ((jsonb_field['key'])); +</programlisting> + </para> + </sect2> + <sect2> <title>Transforms</title> diff --git a/doc/src/sgml/ref/create_type.sgml b/doc/src/sgml/ref/create_type.sgml index 111f8e65d2..a34df4d247 100644 --- a/doc/src/sgml/ref/create_type.sgml +++ b/doc/src/sgml/ref/create_type.sgml @@ -54,6 +54,7 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> ( [ , ELEMENT = <replaceable class="parameter">element</replaceable> ] [ , DELIMITER = <replaceable class="parameter">delimiter</replaceable> ] [ , COLLATABLE = <replaceable class="parameter">collatable</replaceable> ] + [ , SUBSCRIPTING_HANDLER = <replaceable class="parameter">subscripting_handler_function</replaceable> ] ) CREATE TYPE <replaceable class="parameter">name</replaceable> @@ -196,8 +197,9 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> <replaceable class="parameter">receive_function</replaceable>, <replaceable class="parameter">send_function</replaceable>, <replaceable class="parameter">type_modifier_input_function</replaceable>, - <replaceable class="parameter">type_modifier_output_function</replaceable> and - <replaceable class="parameter">analyze_function</replaceable> + <replaceable class="parameter">type_modifier_output_function</replaceable>, + <replaceable class="parameter">analyze_function</replaceable>, + <replaceable class="parameter">subscripting_handler_function</replaceable> are optional. Generally these functions have to be coded in C or another low-level language. </para> @@ -454,6 +456,22 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> make use of the collation information; this does not happen automatically merely by marking the type collatable. </para> + + <para> + The optional + <replaceable class="parameter">subscripting_handler_function</replaceable> + contains type-specific logic for subscripting of the data type. + By default, there is no such function provided, which means that the data + type doesn't support subscripting. The subscripting function must be + declared to take a single argument of type <type>internal</type>, and return + a <type>internal</type> result. There are two examples of implementation for + subscripting functions in case of array + (<replaceable class="parameter">array_subscripting_handler</replaceable>) + and jsonb + (<replaceable class="parameter">jsonb_subscripting_handler</replaceable>) + types in <filename>src/backend/utils/adt/arrayfuncs.c</filename> and + <filename>src/backend/utils/adt/jsonfuncs.c</filename>, respectively. + </para> </refsect2> <refsect2> @@ -769,6 +787,17 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> </para> </listitem> </varlistentry> + + <varlistentry> + <term><replaceable class="parameter">subscripting_handler_function</replaceable></term> + <listitem> + <para> + The name of a function that returns list of type-specific callback functions to + support subscripting logic for the data type. + </para> + </listitem> + </varlistentry> + </variablelist> </refsect1> diff --git a/doc/src/sgml/xsubscripting.sgml b/doc/src/sgml/xsubscripting.sgml new file mode 100644 index 0000000000..7224e81fa2 --- /dev/null +++ b/doc/src/sgml/xsubscripting.sgml @@ -0,0 +1,111 @@ +<!-- doc/src/sgml/xsubscripting.sgml --> + + <sect1 id="xsubscripting"> + <title>User-defined subscripting procedure</title> + + <indexterm zone="xsubscripting"> + <primary>custom subscripting</primary> + </indexterm> + <para> + When you define a new base type, you can also specify a custom procedure to + handle subscripting expressions. It must contain logic for verification and + evaluation of this expression, i.e. fetching or updating some data in this + data type. For instance: +</para> +<programlisting><![CDATA[ +typedef struct Custom +{ + int first; + int second; +} Custom; + +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = someType; + sbsref->refassgntype = someType; + + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + // some validation and coercion logic + + return sbsref; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some assignment logic + + return newContainer; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some fetch logic based on sbstate +}]]> +</programlisting> + +<para> + Then you can define a subscripting procedure and a custom data type: +</para> +<programlisting> +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '<replaceable>filename</replaceable>' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 4, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler, +); +</programlisting> + +<para> + and use it as usual: +</para> +<programlisting> +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; +</programlisting> + + + <para> + The examples of custom subscripting implementation can be found in + <filename>subscripting.sql</filename> and <filename>subscripting.c</filename> + in the <filename>src/tutorial</filename> directory of the source distribution. + See the <filename>README</filename> file in that directory for instructions + about running the examples. + </para> + +</sect1> diff --git a/src/tutorial/Makefile b/src/tutorial/Makefile index 16dc390f71..0ead60c2d4 100644 --- a/src/tutorial/Makefile +++ b/src/tutorial/Makefile @@ -13,8 +13,8 @@ # #------------------------------------------------------------------------- -MODULES = complex funcs -DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql +MODULES = complex funcs subscripting +DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql subscripting.sql ifdef NO_PGXS subdir = src/tutorial diff --git a/src/tutorial/subscripting.c b/src/tutorial/subscripting.c new file mode 100644 index 0000000000..1eb8c45652 --- /dev/null +++ b/src/tutorial/subscripting.c @@ -0,0 +1,201 @@ +/* + * src/tutorial/subscripting.c + * + ****************************************************************************** + This file contains routines that can be bound to a Postgres backend and + called by the backend in the process of processing queries. The calling + format for these routines is dictated by Postgres architecture. +******************************************************************************/ + +#include "postgres.h" + +#include "catalog/pg_type.h" +#include "executor/executor.h" +#include "executor/execExpr.h" +#include "nodes/nodeFuncs.h" +#include "parser/parse_coerce.h" +#include "utils/builtins.h" +#include "utils/fmgrprotos.h" + +PG_MODULE_MAGIC; + +typedef struct Custom +{ + int first; + int second; +} Custom; + +SubscriptingRef * custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref); +SubscriptingRef * custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate); +Datum custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate); +Datum custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate); + +PG_FUNCTION_INFO_V1(custom_in); +PG_FUNCTION_INFO_V1(custom_out); +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +/***************************************************************************** + * Input/Output functions + *****************************************************************************/ + +Datum +custom_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + int firstValue, + secondValue; + Custom *result; + + if (sscanf(str, " ( %d , %d )", &firstValue, &secondValue) != 2) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for complex: \"%s\"", + str))); + + + result = (Custom *) palloc(sizeof(Custom)); + result->first = firstValue; + result->second = secondValue; + PG_RETURN_POINTER(result); +} + +Datum +custom_out(PG_FUNCTION_ARGS) +{ + Custom *custom = (Custom *) PG_GETARG_POINTER(0); + char *result; + + result = psprintf("(%d, %d)", custom->first, custom->second); + PG_RETURN_CSTRING(result); +} + +/***************************************************************************** + * Custom subscripting logic functions + *****************************************************************************/ + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = INT4OID; + sbsref->refassgntype = INT4OID; + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + List *upperIndexpr = NIL; + ListCell *l; + + if (sbsref->reflowerindexpr != NIL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *)lfirst(sbsref->reflowerindexpr->head)))))); + + foreach(l, sbsref->refupperindexpr) + { + Node *subexpr = (Node *) lfirst(l); + + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *) lfirst(sbsref->refupperindexpr->head)))))); + + subexpr = coerce_to_target_type(pstate, + subexpr, exprType(subexpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript must have integer type"), + parser_errposition(pstate, exprLocation(subexpr)))); + + upperIndexpr = lappend(upperIndexpr, subexpr); + + if (isAssignment) + { + Node *assignExpr = (Node *) sbsref->refassgnexpr; + Node *new_from; + + new_from = coerce_to_target_type(pstate, + assignExpr, exprType(assignExpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (new_from == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom assignment requires int type"), + errhint("You will need to rewrite or cast the expression."), + parser_errposition(pstate, exprLocation(assignExpr)))); + sbsref->refassgnexpr = (Expr *)new_from; + } + } + + sbsref->refupperindexpr = upperIndexpr; + + return sbsref; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + Custom *container= (Custom *) containerSource; + int index; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + return (Datum) container->first; + else + return (Datum) container->second; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + int index; + Custom *container = (Custom *) containerSource; + + if (sbstate->resnull) + return containerSource; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + container->first = DatumGetInt32(sbstate->replacevalue); + else + container->second = DatumGetInt32(sbstate->replacevalue); + + return (Datum) container; +} diff --git a/src/tutorial/subscripting.source b/src/tutorial/subscripting.source new file mode 100644 index 0000000000..837cf30612 --- /dev/null +++ b/src/tutorial/subscripting.source @@ -0,0 +1,71 @@ +--------------------------------------------------------------------------- +-- +-- subscripting.sql- +-- This file shows how to create a new subscripting procedure for +-- user-defined type. +-- +-- +-- Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group +-- Portions Copyright (c) 1994, Regents of the University of California +-- +-- src/tutorial/subscripting.source +-- +--------------------------------------------------------------------------- + +----------------------------- +-- Creating a new type: +-- We are going to create a new type called 'complex' which represents +-- complex numbers. +-- A user-defined type must have an input and an output function, and +-- optionally can have binary input and output functions. All of these +-- are usually user-defined C functions. +----------------------------- + +-- Assume the user defined functions are in /home/erthalion/programms/postgresql-master/src/tutorial/complex$DLSUFFIX +-- (we do not want to assume this is in the dynamic loader search path). +-- Look at $PWD/complex.c for the source. Note that we declare all of +-- them as STRICT, so we do not need to cope with NULL inputs in the +-- C code. We also mark them IMMUTABLE, since they always return the +-- same outputs given the same inputs. + +-- the input function 'complex_in' takes a null-terminated string (the +-- textual representation of the type) and turns it into the internal +-- (in memory) representation. You will get a message telling you 'complex' +-- does not exist yet but that's okay. + +CREATE FUNCTION custom_in(cstring) + RETURNS custom + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +-- the output function 'complex_out' takes the internal representation and +-- converts it into the textual representation. + +CREATE FUNCTION custom_out(custom) + RETURNS cstring + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 8, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler +); + +-- we can use it in a table + +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; -- 2.21.0 --u66dlpg6gaevsofr Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v33-0005-Filling-gaps-in-jsonb-arrays.patch" ^ permalink raw reply [nested|flat] 28+ messages in thread
* [PATCH v33 4/5] Subscripting documentation @ 2019-02-01 10:47 erthalion <[email protected]> 0 siblings, 0 replies; 28+ messages in thread From: erthalion @ 2019-02-01 10:47 UTC (permalink / raw) Supporting documentation for generalized subscripting. It includes the description of a new field in pg_type, the new section for jsonb documentation about subscripting feature on this data type, and also the tutorial about how to write subscripting operator for a custom data type. Reviewed-by: Tom Lane, Arthur Zakirov, Oleksandr Shulgin --- doc/src/sgml/catalogs.sgml | 8 ++ doc/src/sgml/extend.sgml | 6 + doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/json.sgml | 39 ++++++ doc/src/sgml/ref/create_type.sgml | 33 ++++- doc/src/sgml/xsubscripting.sgml | 111 +++++++++++++++++ src/tutorial/Makefile | 4 +- src/tutorial/subscripting.c | 201 ++++++++++++++++++++++++++++++ src/tutorial/subscripting.source | 71 +++++++++++ 9 files changed, 470 insertions(+), 4 deletions(-) create mode 100644 doc/src/sgml/xsubscripting.sgml create mode 100644 src/tutorial/subscripting.c create mode 100644 src/tutorial/subscripting.source diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 048ff284f7..b17e9ee382 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -8971,6 +8971,14 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l </para></entry> </row> + <row> + <entry><structfield>typsubshandler</structfield></entry> + <entry><type>regproc</type></entry> + <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry> + <entry>Custom subscripting function with type-specific logic for parsing + and validation, or 0 if this type doesn't support subscripting.</entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>typdefault</structfield> <type>text</type> diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml index 890ff97b7a..65cf5706b6 100644 --- a/doc/src/sgml/extend.sgml +++ b/doc/src/sgml/extend.sgml @@ -33,6 +33,11 @@ operators (starting in <xref linkend="xoper"/>) </para> </listitem> + <listitem> + <para> + subscripting procedure (starting in <xref linkend="xsubscripting"/>) + </para> + </listitem> <listitem> <para> operator classes for indexes (starting in <xref linkend="xindex"/>) @@ -481,6 +486,7 @@ RETURNS anycompatible AS ... &xaggr; &xtypes; &xoper; + &xsubscripting; &xindex; diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 64b5da0070..4c3fd37fc1 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -69,6 +69,7 @@ <!ENTITY xplang SYSTEM "xplang.sgml"> <!ENTITY xoper SYSTEM "xoper.sgml"> <!ENTITY xtypes SYSTEM "xtypes.sgml"> +<!ENTITY xsubscripting SYSTEM "xsubscripting.sgml"> <!ENTITY plperl SYSTEM "plperl.sgml"> <!ENTITY plpython SYSTEM "plpython.sgml"> <!ENTITY plsql SYSTEM "plpgsql.sgml"> diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml index c0a6554d4d..5c538dca05 100644 --- a/doc/src/sgml/json.sgml +++ b/doc/src/sgml/json.sgml @@ -602,6 +602,45 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu </para> </sect2> + <sect2 id="jsonb-subscripting"> + <title><type>jsonb</type> Subscripting</title> + <para> + <type>jsonb</type> data type supports array-style subscripting expressions + to extract or update particular elements. It's possible to use multiple + subscripting expressions to extract nested values. In this case, a chain of + subscripting expressions follows the same rules as the + <literal>path</literal> argument in <literal>jsonb_set</literal> function, + e.g. in case of arrays it is a 0-based operation or that negative integers + that appear in <literal>path</literal> count from the end of JSON arrays. + The result of subscripting expressions is always jsonb data type. An + example of subscripting syntax: +<programlisting> +-- Extract value by key +SELECT ('{"a": 1}'::jsonb)['a']; + +-- Extract nested value by key path +SELECT ('{"a": {"b": {"c": 1}}}'::jsonb)['a']['b']['c']; + +-- Extract element by index +SELECT ('[1, "2", null]'::jsonb)[1]; + +-- Update value by key +UPDATE table_name SET jsonb_field['key'] = 1; + +-- Select records using where clause with subscripting. Since the result of +-- subscripting is jsonb and we basically want to compare two jsonb objects, we +-- need to put the value in double quotes to be able to convert it to jsonb. +SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"'; +</programlisting> + + There is no special indexing support for such kind of expressions, but you + can always create a functional index that includes it +<programlisting> +CREATE INDEX idx ON table_name ((jsonb_field['key'])); +</programlisting> + </para> + </sect2> + <sect2> <title>Transforms</title> diff --git a/doc/src/sgml/ref/create_type.sgml b/doc/src/sgml/ref/create_type.sgml index 111f8e65d2..a34df4d247 100644 --- a/doc/src/sgml/ref/create_type.sgml +++ b/doc/src/sgml/ref/create_type.sgml @@ -54,6 +54,7 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> ( [ , ELEMENT = <replaceable class="parameter">element</replaceable> ] [ , DELIMITER = <replaceable class="parameter">delimiter</replaceable> ] [ , COLLATABLE = <replaceable class="parameter">collatable</replaceable> ] + [ , SUBSCRIPTING_HANDLER = <replaceable class="parameter">subscripting_handler_function</replaceable> ] ) CREATE TYPE <replaceable class="parameter">name</replaceable> @@ -196,8 +197,9 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> <replaceable class="parameter">receive_function</replaceable>, <replaceable class="parameter">send_function</replaceable>, <replaceable class="parameter">type_modifier_input_function</replaceable>, - <replaceable class="parameter">type_modifier_output_function</replaceable> and - <replaceable class="parameter">analyze_function</replaceable> + <replaceable class="parameter">type_modifier_output_function</replaceable>, + <replaceable class="parameter">analyze_function</replaceable>, + <replaceable class="parameter">subscripting_handler_function</replaceable> are optional. Generally these functions have to be coded in C or another low-level language. </para> @@ -454,6 +456,22 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> make use of the collation information; this does not happen automatically merely by marking the type collatable. </para> + + <para> + The optional + <replaceable class="parameter">subscripting_handler_function</replaceable> + contains type-specific logic for subscripting of the data type. + By default, there is no such function provided, which means that the data + type doesn't support subscripting. The subscripting function must be + declared to take a single argument of type <type>internal</type>, and return + a <type>internal</type> result. There are two examples of implementation for + subscripting functions in case of array + (<replaceable class="parameter">array_subscripting_handler</replaceable>) + and jsonb + (<replaceable class="parameter">jsonb_subscripting_handler</replaceable>) + types in <filename>src/backend/utils/adt/arrayfuncs.c</filename> and + <filename>src/backend/utils/adt/jsonfuncs.c</filename>, respectively. + </para> </refsect2> <refsect2> @@ -769,6 +787,17 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> </para> </listitem> </varlistentry> + + <varlistentry> + <term><replaceable class="parameter">subscripting_handler_function</replaceable></term> + <listitem> + <para> + The name of a function that returns list of type-specific callback functions to + support subscripting logic for the data type. + </para> + </listitem> + </varlistentry> + </variablelist> </refsect1> diff --git a/doc/src/sgml/xsubscripting.sgml b/doc/src/sgml/xsubscripting.sgml new file mode 100644 index 0000000000..7224e81fa2 --- /dev/null +++ b/doc/src/sgml/xsubscripting.sgml @@ -0,0 +1,111 @@ +<!-- doc/src/sgml/xsubscripting.sgml --> + + <sect1 id="xsubscripting"> + <title>User-defined subscripting procedure</title> + + <indexterm zone="xsubscripting"> + <primary>custom subscripting</primary> + </indexterm> + <para> + When you define a new base type, you can also specify a custom procedure to + handle subscripting expressions. It must contain logic for verification and + evaluation of this expression, i.e. fetching or updating some data in this + data type. For instance: +</para> +<programlisting><![CDATA[ +typedef struct Custom +{ + int first; + int second; +} Custom; + +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = someType; + sbsref->refassgntype = someType; + + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + // some validation and coercion logic + + return sbsref; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some assignment logic + + return newContainer; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some fetch logic based on sbstate +}]]> +</programlisting> + +<para> + Then you can define a subscripting procedure and a custom data type: +</para> +<programlisting> +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '<replaceable>filename</replaceable>' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 4, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler, +); +</programlisting> + +<para> + and use it as usual: +</para> +<programlisting> +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; +</programlisting> + + + <para> + The examples of custom subscripting implementation can be found in + <filename>subscripting.sql</filename> and <filename>subscripting.c</filename> + in the <filename>src/tutorial</filename> directory of the source distribution. + See the <filename>README</filename> file in that directory for instructions + about running the examples. + </para> + +</sect1> diff --git a/src/tutorial/Makefile b/src/tutorial/Makefile index 16dc390f71..0ead60c2d4 100644 --- a/src/tutorial/Makefile +++ b/src/tutorial/Makefile @@ -13,8 +13,8 @@ # #------------------------------------------------------------------------- -MODULES = complex funcs -DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql +MODULES = complex funcs subscripting +DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql subscripting.sql ifdef NO_PGXS subdir = src/tutorial diff --git a/src/tutorial/subscripting.c b/src/tutorial/subscripting.c new file mode 100644 index 0000000000..1eb8c45652 --- /dev/null +++ b/src/tutorial/subscripting.c @@ -0,0 +1,201 @@ +/* + * src/tutorial/subscripting.c + * + ****************************************************************************** + This file contains routines that can be bound to a Postgres backend and + called by the backend in the process of processing queries. The calling + format for these routines is dictated by Postgres architecture. +******************************************************************************/ + +#include "postgres.h" + +#include "catalog/pg_type.h" +#include "executor/executor.h" +#include "executor/execExpr.h" +#include "nodes/nodeFuncs.h" +#include "parser/parse_coerce.h" +#include "utils/builtins.h" +#include "utils/fmgrprotos.h" + +PG_MODULE_MAGIC; + +typedef struct Custom +{ + int first; + int second; +} Custom; + +SubscriptingRef * custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref); +SubscriptingRef * custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate); +Datum custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate); +Datum custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate); + +PG_FUNCTION_INFO_V1(custom_in); +PG_FUNCTION_INFO_V1(custom_out); +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +/***************************************************************************** + * Input/Output functions + *****************************************************************************/ + +Datum +custom_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + int firstValue, + secondValue; + Custom *result; + + if (sscanf(str, " ( %d , %d )", &firstValue, &secondValue) != 2) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for complex: \"%s\"", + str))); + + + result = (Custom *) palloc(sizeof(Custom)); + result->first = firstValue; + result->second = secondValue; + PG_RETURN_POINTER(result); +} + +Datum +custom_out(PG_FUNCTION_ARGS) +{ + Custom *custom = (Custom *) PG_GETARG_POINTER(0); + char *result; + + result = psprintf("(%d, %d)", custom->first, custom->second); + PG_RETURN_CSTRING(result); +} + +/***************************************************************************** + * Custom subscripting logic functions + *****************************************************************************/ + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = INT4OID; + sbsref->refassgntype = INT4OID; + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + List *upperIndexpr = NIL; + ListCell *l; + + if (sbsref->reflowerindexpr != NIL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *)lfirst(sbsref->reflowerindexpr->head)))))); + + foreach(l, sbsref->refupperindexpr) + { + Node *subexpr = (Node *) lfirst(l); + + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *) lfirst(sbsref->refupperindexpr->head)))))); + + subexpr = coerce_to_target_type(pstate, + subexpr, exprType(subexpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript must have integer type"), + parser_errposition(pstate, exprLocation(subexpr)))); + + upperIndexpr = lappend(upperIndexpr, subexpr); + + if (isAssignment) + { + Node *assignExpr = (Node *) sbsref->refassgnexpr; + Node *new_from; + + new_from = coerce_to_target_type(pstate, + assignExpr, exprType(assignExpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (new_from == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom assignment requires int type"), + errhint("You will need to rewrite or cast the expression."), + parser_errposition(pstate, exprLocation(assignExpr)))); + sbsref->refassgnexpr = (Expr *)new_from; + } + } + + sbsref->refupperindexpr = upperIndexpr; + + return sbsref; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + Custom *container= (Custom *) containerSource; + int index; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + return (Datum) container->first; + else + return (Datum) container->second; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + int index; + Custom *container = (Custom *) containerSource; + + if (sbstate->resnull) + return containerSource; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + container->first = DatumGetInt32(sbstate->replacevalue); + else + container->second = DatumGetInt32(sbstate->replacevalue); + + return (Datum) container; +} diff --git a/src/tutorial/subscripting.source b/src/tutorial/subscripting.source new file mode 100644 index 0000000000..837cf30612 --- /dev/null +++ b/src/tutorial/subscripting.source @@ -0,0 +1,71 @@ +--------------------------------------------------------------------------- +-- +-- subscripting.sql- +-- This file shows how to create a new subscripting procedure for +-- user-defined type. +-- +-- +-- Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group +-- Portions Copyright (c) 1994, Regents of the University of California +-- +-- src/tutorial/subscripting.source +-- +--------------------------------------------------------------------------- + +----------------------------- +-- Creating a new type: +-- We are going to create a new type called 'complex' which represents +-- complex numbers. +-- A user-defined type must have an input and an output function, and +-- optionally can have binary input and output functions. All of these +-- are usually user-defined C functions. +----------------------------- + +-- Assume the user defined functions are in /home/erthalion/programms/postgresql-master/src/tutorial/complex$DLSUFFIX +-- (we do not want to assume this is in the dynamic loader search path). +-- Look at $PWD/complex.c for the source. Note that we declare all of +-- them as STRICT, so we do not need to cope with NULL inputs in the +-- C code. We also mark them IMMUTABLE, since they always return the +-- same outputs given the same inputs. + +-- the input function 'complex_in' takes a null-terminated string (the +-- textual representation of the type) and turns it into the internal +-- (in memory) representation. You will get a message telling you 'complex' +-- does not exist yet but that's okay. + +CREATE FUNCTION custom_in(cstring) + RETURNS custom + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +-- the output function 'complex_out' takes the internal representation and +-- converts it into the textual representation. + +CREATE FUNCTION custom_out(custom) + RETURNS cstring + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 8, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler +); + +-- we can use it in a table + +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; -- 2.21.0 --u66dlpg6gaevsofr Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v33-0005-Filling-gaps-in-jsonb-arrays.patch" ^ permalink raw reply [nested|flat] 28+ messages in thread
* [PATCH v33 4/5] Subscripting documentation @ 2019-02-01 10:47 erthalion <[email protected]> 0 siblings, 0 replies; 28+ messages in thread From: erthalion @ 2019-02-01 10:47 UTC (permalink / raw) Supporting documentation for generalized subscripting. It includes the description of a new field in pg_type, the new section for jsonb documentation about subscripting feature on this data type, and also the tutorial about how to write subscripting operator for a custom data type. Reviewed-by: Tom Lane, Arthur Zakirov, Oleksandr Shulgin --- doc/src/sgml/catalogs.sgml | 8 ++ doc/src/sgml/extend.sgml | 6 + doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/json.sgml | 39 ++++++ doc/src/sgml/ref/create_type.sgml | 33 ++++- doc/src/sgml/xsubscripting.sgml | 111 +++++++++++++++++ src/tutorial/Makefile | 4 +- src/tutorial/subscripting.c | 201 ++++++++++++++++++++++++++++++ src/tutorial/subscripting.source | 71 +++++++++++ 9 files changed, 470 insertions(+), 4 deletions(-) create mode 100644 doc/src/sgml/xsubscripting.sgml create mode 100644 src/tutorial/subscripting.c create mode 100644 src/tutorial/subscripting.source diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 048ff284f7..b17e9ee382 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -8971,6 +8971,14 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l </para></entry> </row> + <row> + <entry><structfield>typsubshandler</structfield></entry> + <entry><type>regproc</type></entry> + <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry> + <entry>Custom subscripting function with type-specific logic for parsing + and validation, or 0 if this type doesn't support subscripting.</entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>typdefault</structfield> <type>text</type> diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml index 890ff97b7a..65cf5706b6 100644 --- a/doc/src/sgml/extend.sgml +++ b/doc/src/sgml/extend.sgml @@ -33,6 +33,11 @@ operators (starting in <xref linkend="xoper"/>) </para> </listitem> + <listitem> + <para> + subscripting procedure (starting in <xref linkend="xsubscripting"/>) + </para> + </listitem> <listitem> <para> operator classes for indexes (starting in <xref linkend="xindex"/>) @@ -481,6 +486,7 @@ RETURNS anycompatible AS ... &xaggr; &xtypes; &xoper; + &xsubscripting; &xindex; diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 64b5da0070..4c3fd37fc1 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -69,6 +69,7 @@ <!ENTITY xplang SYSTEM "xplang.sgml"> <!ENTITY xoper SYSTEM "xoper.sgml"> <!ENTITY xtypes SYSTEM "xtypes.sgml"> +<!ENTITY xsubscripting SYSTEM "xsubscripting.sgml"> <!ENTITY plperl SYSTEM "plperl.sgml"> <!ENTITY plpython SYSTEM "plpython.sgml"> <!ENTITY plsql SYSTEM "plpgsql.sgml"> diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml index c0a6554d4d..5c538dca05 100644 --- a/doc/src/sgml/json.sgml +++ b/doc/src/sgml/json.sgml @@ -602,6 +602,45 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu </para> </sect2> + <sect2 id="jsonb-subscripting"> + <title><type>jsonb</type> Subscripting</title> + <para> + <type>jsonb</type> data type supports array-style subscripting expressions + to extract or update particular elements. It's possible to use multiple + subscripting expressions to extract nested values. In this case, a chain of + subscripting expressions follows the same rules as the + <literal>path</literal> argument in <literal>jsonb_set</literal> function, + e.g. in case of arrays it is a 0-based operation or that negative integers + that appear in <literal>path</literal> count from the end of JSON arrays. + The result of subscripting expressions is always jsonb data type. An + example of subscripting syntax: +<programlisting> +-- Extract value by key +SELECT ('{"a": 1}'::jsonb)['a']; + +-- Extract nested value by key path +SELECT ('{"a": {"b": {"c": 1}}}'::jsonb)['a']['b']['c']; + +-- Extract element by index +SELECT ('[1, "2", null]'::jsonb)[1]; + +-- Update value by key +UPDATE table_name SET jsonb_field['key'] = 1; + +-- Select records using where clause with subscripting. Since the result of +-- subscripting is jsonb and we basically want to compare two jsonb objects, we +-- need to put the value in double quotes to be able to convert it to jsonb. +SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"'; +</programlisting> + + There is no special indexing support for such kind of expressions, but you + can always create a functional index that includes it +<programlisting> +CREATE INDEX idx ON table_name ((jsonb_field['key'])); +</programlisting> + </para> + </sect2> + <sect2> <title>Transforms</title> diff --git a/doc/src/sgml/ref/create_type.sgml b/doc/src/sgml/ref/create_type.sgml index 111f8e65d2..a34df4d247 100644 --- a/doc/src/sgml/ref/create_type.sgml +++ b/doc/src/sgml/ref/create_type.sgml @@ -54,6 +54,7 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> ( [ , ELEMENT = <replaceable class="parameter">element</replaceable> ] [ , DELIMITER = <replaceable class="parameter">delimiter</replaceable> ] [ , COLLATABLE = <replaceable class="parameter">collatable</replaceable> ] + [ , SUBSCRIPTING_HANDLER = <replaceable class="parameter">subscripting_handler_function</replaceable> ] ) CREATE TYPE <replaceable class="parameter">name</replaceable> @@ -196,8 +197,9 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> <replaceable class="parameter">receive_function</replaceable>, <replaceable class="parameter">send_function</replaceable>, <replaceable class="parameter">type_modifier_input_function</replaceable>, - <replaceable class="parameter">type_modifier_output_function</replaceable> and - <replaceable class="parameter">analyze_function</replaceable> + <replaceable class="parameter">type_modifier_output_function</replaceable>, + <replaceable class="parameter">analyze_function</replaceable>, + <replaceable class="parameter">subscripting_handler_function</replaceable> are optional. Generally these functions have to be coded in C or another low-level language. </para> @@ -454,6 +456,22 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> make use of the collation information; this does not happen automatically merely by marking the type collatable. </para> + + <para> + The optional + <replaceable class="parameter">subscripting_handler_function</replaceable> + contains type-specific logic for subscripting of the data type. + By default, there is no such function provided, which means that the data + type doesn't support subscripting. The subscripting function must be + declared to take a single argument of type <type>internal</type>, and return + a <type>internal</type> result. There are two examples of implementation for + subscripting functions in case of array + (<replaceable class="parameter">array_subscripting_handler</replaceable>) + and jsonb + (<replaceable class="parameter">jsonb_subscripting_handler</replaceable>) + types in <filename>src/backend/utils/adt/arrayfuncs.c</filename> and + <filename>src/backend/utils/adt/jsonfuncs.c</filename>, respectively. + </para> </refsect2> <refsect2> @@ -769,6 +787,17 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> </para> </listitem> </varlistentry> + + <varlistentry> + <term><replaceable class="parameter">subscripting_handler_function</replaceable></term> + <listitem> + <para> + The name of a function that returns list of type-specific callback functions to + support subscripting logic for the data type. + </para> + </listitem> + </varlistentry> + </variablelist> </refsect1> diff --git a/doc/src/sgml/xsubscripting.sgml b/doc/src/sgml/xsubscripting.sgml new file mode 100644 index 0000000000..7224e81fa2 --- /dev/null +++ b/doc/src/sgml/xsubscripting.sgml @@ -0,0 +1,111 @@ +<!-- doc/src/sgml/xsubscripting.sgml --> + + <sect1 id="xsubscripting"> + <title>User-defined subscripting procedure</title> + + <indexterm zone="xsubscripting"> + <primary>custom subscripting</primary> + </indexterm> + <para> + When you define a new base type, you can also specify a custom procedure to + handle subscripting expressions. It must contain logic for verification and + evaluation of this expression, i.e. fetching or updating some data in this + data type. For instance: +</para> +<programlisting><![CDATA[ +typedef struct Custom +{ + int first; + int second; +} Custom; + +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = someType; + sbsref->refassgntype = someType; + + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + // some validation and coercion logic + + return sbsref; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some assignment logic + + return newContainer; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some fetch logic based on sbstate +}]]> +</programlisting> + +<para> + Then you can define a subscripting procedure and a custom data type: +</para> +<programlisting> +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '<replaceable>filename</replaceable>' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 4, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler, +); +</programlisting> + +<para> + and use it as usual: +</para> +<programlisting> +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; +</programlisting> + + + <para> + The examples of custom subscripting implementation can be found in + <filename>subscripting.sql</filename> and <filename>subscripting.c</filename> + in the <filename>src/tutorial</filename> directory of the source distribution. + See the <filename>README</filename> file in that directory for instructions + about running the examples. + </para> + +</sect1> diff --git a/src/tutorial/Makefile b/src/tutorial/Makefile index 16dc390f71..0ead60c2d4 100644 --- a/src/tutorial/Makefile +++ b/src/tutorial/Makefile @@ -13,8 +13,8 @@ # #------------------------------------------------------------------------- -MODULES = complex funcs -DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql +MODULES = complex funcs subscripting +DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql subscripting.sql ifdef NO_PGXS subdir = src/tutorial diff --git a/src/tutorial/subscripting.c b/src/tutorial/subscripting.c new file mode 100644 index 0000000000..1eb8c45652 --- /dev/null +++ b/src/tutorial/subscripting.c @@ -0,0 +1,201 @@ +/* + * src/tutorial/subscripting.c + * + ****************************************************************************** + This file contains routines that can be bound to a Postgres backend and + called by the backend in the process of processing queries. The calling + format for these routines is dictated by Postgres architecture. +******************************************************************************/ + +#include "postgres.h" + +#include "catalog/pg_type.h" +#include "executor/executor.h" +#include "executor/execExpr.h" +#include "nodes/nodeFuncs.h" +#include "parser/parse_coerce.h" +#include "utils/builtins.h" +#include "utils/fmgrprotos.h" + +PG_MODULE_MAGIC; + +typedef struct Custom +{ + int first; + int second; +} Custom; + +SubscriptingRef * custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref); +SubscriptingRef * custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate); +Datum custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate); +Datum custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate); + +PG_FUNCTION_INFO_V1(custom_in); +PG_FUNCTION_INFO_V1(custom_out); +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +/***************************************************************************** + * Input/Output functions + *****************************************************************************/ + +Datum +custom_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + int firstValue, + secondValue; + Custom *result; + + if (sscanf(str, " ( %d , %d )", &firstValue, &secondValue) != 2) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for complex: \"%s\"", + str))); + + + result = (Custom *) palloc(sizeof(Custom)); + result->first = firstValue; + result->second = secondValue; + PG_RETURN_POINTER(result); +} + +Datum +custom_out(PG_FUNCTION_ARGS) +{ + Custom *custom = (Custom *) PG_GETARG_POINTER(0); + char *result; + + result = psprintf("(%d, %d)", custom->first, custom->second); + PG_RETURN_CSTRING(result); +} + +/***************************************************************************** + * Custom subscripting logic functions + *****************************************************************************/ + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = INT4OID; + sbsref->refassgntype = INT4OID; + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + List *upperIndexpr = NIL; + ListCell *l; + + if (sbsref->reflowerindexpr != NIL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *)lfirst(sbsref->reflowerindexpr->head)))))); + + foreach(l, sbsref->refupperindexpr) + { + Node *subexpr = (Node *) lfirst(l); + + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *) lfirst(sbsref->refupperindexpr->head)))))); + + subexpr = coerce_to_target_type(pstate, + subexpr, exprType(subexpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript must have integer type"), + parser_errposition(pstate, exprLocation(subexpr)))); + + upperIndexpr = lappend(upperIndexpr, subexpr); + + if (isAssignment) + { + Node *assignExpr = (Node *) sbsref->refassgnexpr; + Node *new_from; + + new_from = coerce_to_target_type(pstate, + assignExpr, exprType(assignExpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (new_from == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom assignment requires int type"), + errhint("You will need to rewrite or cast the expression."), + parser_errposition(pstate, exprLocation(assignExpr)))); + sbsref->refassgnexpr = (Expr *)new_from; + } + } + + sbsref->refupperindexpr = upperIndexpr; + + return sbsref; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + Custom *container= (Custom *) containerSource; + int index; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + return (Datum) container->first; + else + return (Datum) container->second; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + int index; + Custom *container = (Custom *) containerSource; + + if (sbstate->resnull) + return containerSource; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + container->first = DatumGetInt32(sbstate->replacevalue); + else + container->second = DatumGetInt32(sbstate->replacevalue); + + return (Datum) container; +} diff --git a/src/tutorial/subscripting.source b/src/tutorial/subscripting.source new file mode 100644 index 0000000000..837cf30612 --- /dev/null +++ b/src/tutorial/subscripting.source @@ -0,0 +1,71 @@ +--------------------------------------------------------------------------- +-- +-- subscripting.sql- +-- This file shows how to create a new subscripting procedure for +-- user-defined type. +-- +-- +-- Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group +-- Portions Copyright (c) 1994, Regents of the University of California +-- +-- src/tutorial/subscripting.source +-- +--------------------------------------------------------------------------- + +----------------------------- +-- Creating a new type: +-- We are going to create a new type called 'complex' which represents +-- complex numbers. +-- A user-defined type must have an input and an output function, and +-- optionally can have binary input and output functions. All of these +-- are usually user-defined C functions. +----------------------------- + +-- Assume the user defined functions are in /home/erthalion/programms/postgresql-master/src/tutorial/complex$DLSUFFIX +-- (we do not want to assume this is in the dynamic loader search path). +-- Look at $PWD/complex.c for the source. Note that we declare all of +-- them as STRICT, so we do not need to cope with NULL inputs in the +-- C code. We also mark them IMMUTABLE, since they always return the +-- same outputs given the same inputs. + +-- the input function 'complex_in' takes a null-terminated string (the +-- textual representation of the type) and turns it into the internal +-- (in memory) representation. You will get a message telling you 'complex' +-- does not exist yet but that's okay. + +CREATE FUNCTION custom_in(cstring) + RETURNS custom + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +-- the output function 'complex_out' takes the internal representation and +-- converts it into the textual representation. + +CREATE FUNCTION custom_out(custom) + RETURNS cstring + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 8, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler +); + +-- we can use it in a table + +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; -- 2.21.0 --u66dlpg6gaevsofr Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v33-0005-Filling-gaps-in-jsonb-arrays.patch" ^ permalink raw reply [nested|flat] 28+ messages in thread
* [PATCH v33 4/5] Subscripting documentation @ 2019-02-01 10:47 erthalion <[email protected]> 0 siblings, 0 replies; 28+ messages in thread From: erthalion @ 2019-02-01 10:47 UTC (permalink / raw) Supporting documentation for generalized subscripting. It includes the description of a new field in pg_type, the new section for jsonb documentation about subscripting feature on this data type, and also the tutorial about how to write subscripting operator for a custom data type. Reviewed-by: Tom Lane, Arthur Zakirov, Oleksandr Shulgin --- doc/src/sgml/catalogs.sgml | 8 ++ doc/src/sgml/extend.sgml | 6 + doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/json.sgml | 39 ++++++ doc/src/sgml/ref/create_type.sgml | 33 ++++- doc/src/sgml/xsubscripting.sgml | 111 +++++++++++++++++ src/tutorial/Makefile | 4 +- src/tutorial/subscripting.c | 201 ++++++++++++++++++++++++++++++ src/tutorial/subscripting.source | 71 +++++++++++ 9 files changed, 470 insertions(+), 4 deletions(-) create mode 100644 doc/src/sgml/xsubscripting.sgml create mode 100644 src/tutorial/subscripting.c create mode 100644 src/tutorial/subscripting.source diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 048ff284f7..b17e9ee382 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -8971,6 +8971,14 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l </para></entry> </row> + <row> + <entry><structfield>typsubshandler</structfield></entry> + <entry><type>regproc</type></entry> + <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry> + <entry>Custom subscripting function with type-specific logic for parsing + and validation, or 0 if this type doesn't support subscripting.</entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>typdefault</structfield> <type>text</type> diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml index 890ff97b7a..65cf5706b6 100644 --- a/doc/src/sgml/extend.sgml +++ b/doc/src/sgml/extend.sgml @@ -33,6 +33,11 @@ operators (starting in <xref linkend="xoper"/>) </para> </listitem> + <listitem> + <para> + subscripting procedure (starting in <xref linkend="xsubscripting"/>) + </para> + </listitem> <listitem> <para> operator classes for indexes (starting in <xref linkend="xindex"/>) @@ -481,6 +486,7 @@ RETURNS anycompatible AS ... &xaggr; &xtypes; &xoper; + &xsubscripting; &xindex; diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 64b5da0070..4c3fd37fc1 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -69,6 +69,7 @@ <!ENTITY xplang SYSTEM "xplang.sgml"> <!ENTITY xoper SYSTEM "xoper.sgml"> <!ENTITY xtypes SYSTEM "xtypes.sgml"> +<!ENTITY xsubscripting SYSTEM "xsubscripting.sgml"> <!ENTITY plperl SYSTEM "plperl.sgml"> <!ENTITY plpython SYSTEM "plpython.sgml"> <!ENTITY plsql SYSTEM "plpgsql.sgml"> diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml index c0a6554d4d..5c538dca05 100644 --- a/doc/src/sgml/json.sgml +++ b/doc/src/sgml/json.sgml @@ -602,6 +602,45 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu </para> </sect2> + <sect2 id="jsonb-subscripting"> + <title><type>jsonb</type> Subscripting</title> + <para> + <type>jsonb</type> data type supports array-style subscripting expressions + to extract or update particular elements. It's possible to use multiple + subscripting expressions to extract nested values. In this case, a chain of + subscripting expressions follows the same rules as the + <literal>path</literal> argument in <literal>jsonb_set</literal> function, + e.g. in case of arrays it is a 0-based operation or that negative integers + that appear in <literal>path</literal> count from the end of JSON arrays. + The result of subscripting expressions is always jsonb data type. An + example of subscripting syntax: +<programlisting> +-- Extract value by key +SELECT ('{"a": 1}'::jsonb)['a']; + +-- Extract nested value by key path +SELECT ('{"a": {"b": {"c": 1}}}'::jsonb)['a']['b']['c']; + +-- Extract element by index +SELECT ('[1, "2", null]'::jsonb)[1]; + +-- Update value by key +UPDATE table_name SET jsonb_field['key'] = 1; + +-- Select records using where clause with subscripting. Since the result of +-- subscripting is jsonb and we basically want to compare two jsonb objects, we +-- need to put the value in double quotes to be able to convert it to jsonb. +SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"'; +</programlisting> + + There is no special indexing support for such kind of expressions, but you + can always create a functional index that includes it +<programlisting> +CREATE INDEX idx ON table_name ((jsonb_field['key'])); +</programlisting> + </para> + </sect2> + <sect2> <title>Transforms</title> diff --git a/doc/src/sgml/ref/create_type.sgml b/doc/src/sgml/ref/create_type.sgml index 111f8e65d2..a34df4d247 100644 --- a/doc/src/sgml/ref/create_type.sgml +++ b/doc/src/sgml/ref/create_type.sgml @@ -54,6 +54,7 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> ( [ , ELEMENT = <replaceable class="parameter">element</replaceable> ] [ , DELIMITER = <replaceable class="parameter">delimiter</replaceable> ] [ , COLLATABLE = <replaceable class="parameter">collatable</replaceable> ] + [ , SUBSCRIPTING_HANDLER = <replaceable class="parameter">subscripting_handler_function</replaceable> ] ) CREATE TYPE <replaceable class="parameter">name</replaceable> @@ -196,8 +197,9 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> <replaceable class="parameter">receive_function</replaceable>, <replaceable class="parameter">send_function</replaceable>, <replaceable class="parameter">type_modifier_input_function</replaceable>, - <replaceable class="parameter">type_modifier_output_function</replaceable> and - <replaceable class="parameter">analyze_function</replaceable> + <replaceable class="parameter">type_modifier_output_function</replaceable>, + <replaceable class="parameter">analyze_function</replaceable>, + <replaceable class="parameter">subscripting_handler_function</replaceable> are optional. Generally these functions have to be coded in C or another low-level language. </para> @@ -454,6 +456,22 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> make use of the collation information; this does not happen automatically merely by marking the type collatable. </para> + + <para> + The optional + <replaceable class="parameter">subscripting_handler_function</replaceable> + contains type-specific logic for subscripting of the data type. + By default, there is no such function provided, which means that the data + type doesn't support subscripting. The subscripting function must be + declared to take a single argument of type <type>internal</type>, and return + a <type>internal</type> result. There are two examples of implementation for + subscripting functions in case of array + (<replaceable class="parameter">array_subscripting_handler</replaceable>) + and jsonb + (<replaceable class="parameter">jsonb_subscripting_handler</replaceable>) + types in <filename>src/backend/utils/adt/arrayfuncs.c</filename> and + <filename>src/backend/utils/adt/jsonfuncs.c</filename>, respectively. + </para> </refsect2> <refsect2> @@ -769,6 +787,17 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> </para> </listitem> </varlistentry> + + <varlistentry> + <term><replaceable class="parameter">subscripting_handler_function</replaceable></term> + <listitem> + <para> + The name of a function that returns list of type-specific callback functions to + support subscripting logic for the data type. + </para> + </listitem> + </varlistentry> + </variablelist> </refsect1> diff --git a/doc/src/sgml/xsubscripting.sgml b/doc/src/sgml/xsubscripting.sgml new file mode 100644 index 0000000000..7224e81fa2 --- /dev/null +++ b/doc/src/sgml/xsubscripting.sgml @@ -0,0 +1,111 @@ +<!-- doc/src/sgml/xsubscripting.sgml --> + + <sect1 id="xsubscripting"> + <title>User-defined subscripting procedure</title> + + <indexterm zone="xsubscripting"> + <primary>custom subscripting</primary> + </indexterm> + <para> + When you define a new base type, you can also specify a custom procedure to + handle subscripting expressions. It must contain logic for verification and + evaluation of this expression, i.e. fetching or updating some data in this + data type. For instance: +</para> +<programlisting><![CDATA[ +typedef struct Custom +{ + int first; + int second; +} Custom; + +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = someType; + sbsref->refassgntype = someType; + + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + // some validation and coercion logic + + return sbsref; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some assignment logic + + return newContainer; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some fetch logic based on sbstate +}]]> +</programlisting> + +<para> + Then you can define a subscripting procedure and a custom data type: +</para> +<programlisting> +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '<replaceable>filename</replaceable>' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 4, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler, +); +</programlisting> + +<para> + and use it as usual: +</para> +<programlisting> +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; +</programlisting> + + + <para> + The examples of custom subscripting implementation can be found in + <filename>subscripting.sql</filename> and <filename>subscripting.c</filename> + in the <filename>src/tutorial</filename> directory of the source distribution. + See the <filename>README</filename> file in that directory for instructions + about running the examples. + </para> + +</sect1> diff --git a/src/tutorial/Makefile b/src/tutorial/Makefile index 16dc390f71..0ead60c2d4 100644 --- a/src/tutorial/Makefile +++ b/src/tutorial/Makefile @@ -13,8 +13,8 @@ # #------------------------------------------------------------------------- -MODULES = complex funcs -DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql +MODULES = complex funcs subscripting +DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql subscripting.sql ifdef NO_PGXS subdir = src/tutorial diff --git a/src/tutorial/subscripting.c b/src/tutorial/subscripting.c new file mode 100644 index 0000000000..1eb8c45652 --- /dev/null +++ b/src/tutorial/subscripting.c @@ -0,0 +1,201 @@ +/* + * src/tutorial/subscripting.c + * + ****************************************************************************** + This file contains routines that can be bound to a Postgres backend and + called by the backend in the process of processing queries. The calling + format for these routines is dictated by Postgres architecture. +******************************************************************************/ + +#include "postgres.h" + +#include "catalog/pg_type.h" +#include "executor/executor.h" +#include "executor/execExpr.h" +#include "nodes/nodeFuncs.h" +#include "parser/parse_coerce.h" +#include "utils/builtins.h" +#include "utils/fmgrprotos.h" + +PG_MODULE_MAGIC; + +typedef struct Custom +{ + int first; + int second; +} Custom; + +SubscriptingRef * custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref); +SubscriptingRef * custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate); +Datum custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate); +Datum custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate); + +PG_FUNCTION_INFO_V1(custom_in); +PG_FUNCTION_INFO_V1(custom_out); +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +/***************************************************************************** + * Input/Output functions + *****************************************************************************/ + +Datum +custom_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + int firstValue, + secondValue; + Custom *result; + + if (sscanf(str, " ( %d , %d )", &firstValue, &secondValue) != 2) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for complex: \"%s\"", + str))); + + + result = (Custom *) palloc(sizeof(Custom)); + result->first = firstValue; + result->second = secondValue; + PG_RETURN_POINTER(result); +} + +Datum +custom_out(PG_FUNCTION_ARGS) +{ + Custom *custom = (Custom *) PG_GETARG_POINTER(0); + char *result; + + result = psprintf("(%d, %d)", custom->first, custom->second); + PG_RETURN_CSTRING(result); +} + +/***************************************************************************** + * Custom subscripting logic functions + *****************************************************************************/ + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = INT4OID; + sbsref->refassgntype = INT4OID; + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + List *upperIndexpr = NIL; + ListCell *l; + + if (sbsref->reflowerindexpr != NIL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *)lfirst(sbsref->reflowerindexpr->head)))))); + + foreach(l, sbsref->refupperindexpr) + { + Node *subexpr = (Node *) lfirst(l); + + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *) lfirst(sbsref->refupperindexpr->head)))))); + + subexpr = coerce_to_target_type(pstate, + subexpr, exprType(subexpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript must have integer type"), + parser_errposition(pstate, exprLocation(subexpr)))); + + upperIndexpr = lappend(upperIndexpr, subexpr); + + if (isAssignment) + { + Node *assignExpr = (Node *) sbsref->refassgnexpr; + Node *new_from; + + new_from = coerce_to_target_type(pstate, + assignExpr, exprType(assignExpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (new_from == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom assignment requires int type"), + errhint("You will need to rewrite or cast the expression."), + parser_errposition(pstate, exprLocation(assignExpr)))); + sbsref->refassgnexpr = (Expr *)new_from; + } + } + + sbsref->refupperindexpr = upperIndexpr; + + return sbsref; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + Custom *container= (Custom *) containerSource; + int index; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + return (Datum) container->first; + else + return (Datum) container->second; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + int index; + Custom *container = (Custom *) containerSource; + + if (sbstate->resnull) + return containerSource; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + container->first = DatumGetInt32(sbstate->replacevalue); + else + container->second = DatumGetInt32(sbstate->replacevalue); + + return (Datum) container; +} diff --git a/src/tutorial/subscripting.source b/src/tutorial/subscripting.source new file mode 100644 index 0000000000..837cf30612 --- /dev/null +++ b/src/tutorial/subscripting.source @@ -0,0 +1,71 @@ +--------------------------------------------------------------------------- +-- +-- subscripting.sql- +-- This file shows how to create a new subscripting procedure for +-- user-defined type. +-- +-- +-- Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group +-- Portions Copyright (c) 1994, Regents of the University of California +-- +-- src/tutorial/subscripting.source +-- +--------------------------------------------------------------------------- + +----------------------------- +-- Creating a new type: +-- We are going to create a new type called 'complex' which represents +-- complex numbers. +-- A user-defined type must have an input and an output function, and +-- optionally can have binary input and output functions. All of these +-- are usually user-defined C functions. +----------------------------- + +-- Assume the user defined functions are in /home/erthalion/programms/postgresql-master/src/tutorial/complex$DLSUFFIX +-- (we do not want to assume this is in the dynamic loader search path). +-- Look at $PWD/complex.c for the source. Note that we declare all of +-- them as STRICT, so we do not need to cope with NULL inputs in the +-- C code. We also mark them IMMUTABLE, since they always return the +-- same outputs given the same inputs. + +-- the input function 'complex_in' takes a null-terminated string (the +-- textual representation of the type) and turns it into the internal +-- (in memory) representation. You will get a message telling you 'complex' +-- does not exist yet but that's okay. + +CREATE FUNCTION custom_in(cstring) + RETURNS custom + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +-- the output function 'complex_out' takes the internal representation and +-- converts it into the textual representation. + +CREATE FUNCTION custom_out(custom) + RETURNS cstring + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 8, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler +); + +-- we can use it in a table + +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; -- 2.21.0 --u66dlpg6gaevsofr Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v33-0005-Filling-gaps-in-jsonb-arrays.patch" ^ permalink raw reply [nested|flat] 28+ messages in thread
* [PATCH v33 4/5] Subscripting documentation @ 2019-02-01 10:47 erthalion <[email protected]> 0 siblings, 0 replies; 28+ messages in thread From: erthalion @ 2019-02-01 10:47 UTC (permalink / raw) Supporting documentation for generalized subscripting. It includes the description of a new field in pg_type, the new section for jsonb documentation about subscripting feature on this data type, and also the tutorial about how to write subscripting operator for a custom data type. Reviewed-by: Tom Lane, Arthur Zakirov, Oleksandr Shulgin --- doc/src/sgml/catalogs.sgml | 8 ++ doc/src/sgml/extend.sgml | 6 + doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/json.sgml | 39 ++++++ doc/src/sgml/ref/create_type.sgml | 33 ++++- doc/src/sgml/xsubscripting.sgml | 111 +++++++++++++++++ src/tutorial/Makefile | 4 +- src/tutorial/subscripting.c | 201 ++++++++++++++++++++++++++++++ src/tutorial/subscripting.source | 71 +++++++++++ 9 files changed, 470 insertions(+), 4 deletions(-) create mode 100644 doc/src/sgml/xsubscripting.sgml create mode 100644 src/tutorial/subscripting.c create mode 100644 src/tutorial/subscripting.source diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 048ff284f7..b17e9ee382 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -8971,6 +8971,14 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l </para></entry> </row> + <row> + <entry><structfield>typsubshandler</structfield></entry> + <entry><type>regproc</type></entry> + <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry> + <entry>Custom subscripting function with type-specific logic for parsing + and validation, or 0 if this type doesn't support subscripting.</entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>typdefault</structfield> <type>text</type> diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml index 890ff97b7a..65cf5706b6 100644 --- a/doc/src/sgml/extend.sgml +++ b/doc/src/sgml/extend.sgml @@ -33,6 +33,11 @@ operators (starting in <xref linkend="xoper"/>) </para> </listitem> + <listitem> + <para> + subscripting procedure (starting in <xref linkend="xsubscripting"/>) + </para> + </listitem> <listitem> <para> operator classes for indexes (starting in <xref linkend="xindex"/>) @@ -481,6 +486,7 @@ RETURNS anycompatible AS ... &xaggr; &xtypes; &xoper; + &xsubscripting; &xindex; diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 64b5da0070..4c3fd37fc1 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -69,6 +69,7 @@ <!ENTITY xplang SYSTEM "xplang.sgml"> <!ENTITY xoper SYSTEM "xoper.sgml"> <!ENTITY xtypes SYSTEM "xtypes.sgml"> +<!ENTITY xsubscripting SYSTEM "xsubscripting.sgml"> <!ENTITY plperl SYSTEM "plperl.sgml"> <!ENTITY plpython SYSTEM "plpython.sgml"> <!ENTITY plsql SYSTEM "plpgsql.sgml"> diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml index c0a6554d4d..5c538dca05 100644 --- a/doc/src/sgml/json.sgml +++ b/doc/src/sgml/json.sgml @@ -602,6 +602,45 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu </para> </sect2> + <sect2 id="jsonb-subscripting"> + <title><type>jsonb</type> Subscripting</title> + <para> + <type>jsonb</type> data type supports array-style subscripting expressions + to extract or update particular elements. It's possible to use multiple + subscripting expressions to extract nested values. In this case, a chain of + subscripting expressions follows the same rules as the + <literal>path</literal> argument in <literal>jsonb_set</literal> function, + e.g. in case of arrays it is a 0-based operation or that negative integers + that appear in <literal>path</literal> count from the end of JSON arrays. + The result of subscripting expressions is always jsonb data type. An + example of subscripting syntax: +<programlisting> +-- Extract value by key +SELECT ('{"a": 1}'::jsonb)['a']; + +-- Extract nested value by key path +SELECT ('{"a": {"b": {"c": 1}}}'::jsonb)['a']['b']['c']; + +-- Extract element by index +SELECT ('[1, "2", null]'::jsonb)[1]; + +-- Update value by key +UPDATE table_name SET jsonb_field['key'] = 1; + +-- Select records using where clause with subscripting. Since the result of +-- subscripting is jsonb and we basically want to compare two jsonb objects, we +-- need to put the value in double quotes to be able to convert it to jsonb. +SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"'; +</programlisting> + + There is no special indexing support for such kind of expressions, but you + can always create a functional index that includes it +<programlisting> +CREATE INDEX idx ON table_name ((jsonb_field['key'])); +</programlisting> + </para> + </sect2> + <sect2> <title>Transforms</title> diff --git a/doc/src/sgml/ref/create_type.sgml b/doc/src/sgml/ref/create_type.sgml index 111f8e65d2..a34df4d247 100644 --- a/doc/src/sgml/ref/create_type.sgml +++ b/doc/src/sgml/ref/create_type.sgml @@ -54,6 +54,7 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> ( [ , ELEMENT = <replaceable class="parameter">element</replaceable> ] [ , DELIMITER = <replaceable class="parameter">delimiter</replaceable> ] [ , COLLATABLE = <replaceable class="parameter">collatable</replaceable> ] + [ , SUBSCRIPTING_HANDLER = <replaceable class="parameter">subscripting_handler_function</replaceable> ] ) CREATE TYPE <replaceable class="parameter">name</replaceable> @@ -196,8 +197,9 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> <replaceable class="parameter">receive_function</replaceable>, <replaceable class="parameter">send_function</replaceable>, <replaceable class="parameter">type_modifier_input_function</replaceable>, - <replaceable class="parameter">type_modifier_output_function</replaceable> and - <replaceable class="parameter">analyze_function</replaceable> + <replaceable class="parameter">type_modifier_output_function</replaceable>, + <replaceable class="parameter">analyze_function</replaceable>, + <replaceable class="parameter">subscripting_handler_function</replaceable> are optional. Generally these functions have to be coded in C or another low-level language. </para> @@ -454,6 +456,22 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> make use of the collation information; this does not happen automatically merely by marking the type collatable. </para> + + <para> + The optional + <replaceable class="parameter">subscripting_handler_function</replaceable> + contains type-specific logic for subscripting of the data type. + By default, there is no such function provided, which means that the data + type doesn't support subscripting. The subscripting function must be + declared to take a single argument of type <type>internal</type>, and return + a <type>internal</type> result. There are two examples of implementation for + subscripting functions in case of array + (<replaceable class="parameter">array_subscripting_handler</replaceable>) + and jsonb + (<replaceable class="parameter">jsonb_subscripting_handler</replaceable>) + types in <filename>src/backend/utils/adt/arrayfuncs.c</filename> and + <filename>src/backend/utils/adt/jsonfuncs.c</filename>, respectively. + </para> </refsect2> <refsect2> @@ -769,6 +787,17 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> </para> </listitem> </varlistentry> + + <varlistentry> + <term><replaceable class="parameter">subscripting_handler_function</replaceable></term> + <listitem> + <para> + The name of a function that returns list of type-specific callback functions to + support subscripting logic for the data type. + </para> + </listitem> + </varlistentry> + </variablelist> </refsect1> diff --git a/doc/src/sgml/xsubscripting.sgml b/doc/src/sgml/xsubscripting.sgml new file mode 100644 index 0000000000..7224e81fa2 --- /dev/null +++ b/doc/src/sgml/xsubscripting.sgml @@ -0,0 +1,111 @@ +<!-- doc/src/sgml/xsubscripting.sgml --> + + <sect1 id="xsubscripting"> + <title>User-defined subscripting procedure</title> + + <indexterm zone="xsubscripting"> + <primary>custom subscripting</primary> + </indexterm> + <para> + When you define a new base type, you can also specify a custom procedure to + handle subscripting expressions. It must contain logic for verification and + evaluation of this expression, i.e. fetching or updating some data in this + data type. For instance: +</para> +<programlisting><![CDATA[ +typedef struct Custom +{ + int first; + int second; +} Custom; + +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = someType; + sbsref->refassgntype = someType; + + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + // some validation and coercion logic + + return sbsref; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some assignment logic + + return newContainer; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some fetch logic based on sbstate +}]]> +</programlisting> + +<para> + Then you can define a subscripting procedure and a custom data type: +</para> +<programlisting> +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '<replaceable>filename</replaceable>' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 4, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler, +); +</programlisting> + +<para> + and use it as usual: +</para> +<programlisting> +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; +</programlisting> + + + <para> + The examples of custom subscripting implementation can be found in + <filename>subscripting.sql</filename> and <filename>subscripting.c</filename> + in the <filename>src/tutorial</filename> directory of the source distribution. + See the <filename>README</filename> file in that directory for instructions + about running the examples. + </para> + +</sect1> diff --git a/src/tutorial/Makefile b/src/tutorial/Makefile index 16dc390f71..0ead60c2d4 100644 --- a/src/tutorial/Makefile +++ b/src/tutorial/Makefile @@ -13,8 +13,8 @@ # #------------------------------------------------------------------------- -MODULES = complex funcs -DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql +MODULES = complex funcs subscripting +DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql subscripting.sql ifdef NO_PGXS subdir = src/tutorial diff --git a/src/tutorial/subscripting.c b/src/tutorial/subscripting.c new file mode 100644 index 0000000000..1eb8c45652 --- /dev/null +++ b/src/tutorial/subscripting.c @@ -0,0 +1,201 @@ +/* + * src/tutorial/subscripting.c + * + ****************************************************************************** + This file contains routines that can be bound to a Postgres backend and + called by the backend in the process of processing queries. The calling + format for these routines is dictated by Postgres architecture. +******************************************************************************/ + +#include "postgres.h" + +#include "catalog/pg_type.h" +#include "executor/executor.h" +#include "executor/execExpr.h" +#include "nodes/nodeFuncs.h" +#include "parser/parse_coerce.h" +#include "utils/builtins.h" +#include "utils/fmgrprotos.h" + +PG_MODULE_MAGIC; + +typedef struct Custom +{ + int first; + int second; +} Custom; + +SubscriptingRef * custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref); +SubscriptingRef * custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate); +Datum custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate); +Datum custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate); + +PG_FUNCTION_INFO_V1(custom_in); +PG_FUNCTION_INFO_V1(custom_out); +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +/***************************************************************************** + * Input/Output functions + *****************************************************************************/ + +Datum +custom_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + int firstValue, + secondValue; + Custom *result; + + if (sscanf(str, " ( %d , %d )", &firstValue, &secondValue) != 2) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for complex: \"%s\"", + str))); + + + result = (Custom *) palloc(sizeof(Custom)); + result->first = firstValue; + result->second = secondValue; + PG_RETURN_POINTER(result); +} + +Datum +custom_out(PG_FUNCTION_ARGS) +{ + Custom *custom = (Custom *) PG_GETARG_POINTER(0); + char *result; + + result = psprintf("(%d, %d)", custom->first, custom->second); + PG_RETURN_CSTRING(result); +} + +/***************************************************************************** + * Custom subscripting logic functions + *****************************************************************************/ + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = INT4OID; + sbsref->refassgntype = INT4OID; + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + List *upperIndexpr = NIL; + ListCell *l; + + if (sbsref->reflowerindexpr != NIL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *)lfirst(sbsref->reflowerindexpr->head)))))); + + foreach(l, sbsref->refupperindexpr) + { + Node *subexpr = (Node *) lfirst(l); + + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *) lfirst(sbsref->refupperindexpr->head)))))); + + subexpr = coerce_to_target_type(pstate, + subexpr, exprType(subexpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript must have integer type"), + parser_errposition(pstate, exprLocation(subexpr)))); + + upperIndexpr = lappend(upperIndexpr, subexpr); + + if (isAssignment) + { + Node *assignExpr = (Node *) sbsref->refassgnexpr; + Node *new_from; + + new_from = coerce_to_target_type(pstate, + assignExpr, exprType(assignExpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (new_from == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom assignment requires int type"), + errhint("You will need to rewrite or cast the expression."), + parser_errposition(pstate, exprLocation(assignExpr)))); + sbsref->refassgnexpr = (Expr *)new_from; + } + } + + sbsref->refupperindexpr = upperIndexpr; + + return sbsref; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + Custom *container= (Custom *) containerSource; + int index; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + return (Datum) container->first; + else + return (Datum) container->second; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + int index; + Custom *container = (Custom *) containerSource; + + if (sbstate->resnull) + return containerSource; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + container->first = DatumGetInt32(sbstate->replacevalue); + else + container->second = DatumGetInt32(sbstate->replacevalue); + + return (Datum) container; +} diff --git a/src/tutorial/subscripting.source b/src/tutorial/subscripting.source new file mode 100644 index 0000000000..837cf30612 --- /dev/null +++ b/src/tutorial/subscripting.source @@ -0,0 +1,71 @@ +--------------------------------------------------------------------------- +-- +-- subscripting.sql- +-- This file shows how to create a new subscripting procedure for +-- user-defined type. +-- +-- +-- Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group +-- Portions Copyright (c) 1994, Regents of the University of California +-- +-- src/tutorial/subscripting.source +-- +--------------------------------------------------------------------------- + +----------------------------- +-- Creating a new type: +-- We are going to create a new type called 'complex' which represents +-- complex numbers. +-- A user-defined type must have an input and an output function, and +-- optionally can have binary input and output functions. All of these +-- are usually user-defined C functions. +----------------------------- + +-- Assume the user defined functions are in /home/erthalion/programms/postgresql-master/src/tutorial/complex$DLSUFFIX +-- (we do not want to assume this is in the dynamic loader search path). +-- Look at $PWD/complex.c for the source. Note that we declare all of +-- them as STRICT, so we do not need to cope with NULL inputs in the +-- C code. We also mark them IMMUTABLE, since they always return the +-- same outputs given the same inputs. + +-- the input function 'complex_in' takes a null-terminated string (the +-- textual representation of the type) and turns it into the internal +-- (in memory) representation. You will get a message telling you 'complex' +-- does not exist yet but that's okay. + +CREATE FUNCTION custom_in(cstring) + RETURNS custom + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +-- the output function 'complex_out' takes the internal representation and +-- converts it into the textual representation. + +CREATE FUNCTION custom_out(custom) + RETURNS cstring + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 8, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler +); + +-- we can use it in a table + +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; -- 2.21.0 --u66dlpg6gaevsofr Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v33-0005-Filling-gaps-in-jsonb-arrays.patch" ^ permalink raw reply [nested|flat] 28+ messages in thread
* [PATCH v33 4/5] Subscripting documentation @ 2019-02-01 10:47 erthalion <[email protected]> 0 siblings, 0 replies; 28+ messages in thread From: erthalion @ 2019-02-01 10:47 UTC (permalink / raw) Supporting documentation for generalized subscripting. It includes the description of a new field in pg_type, the new section for jsonb documentation about subscripting feature on this data type, and also the tutorial about how to write subscripting operator for a custom data type. Reviewed-by: Tom Lane, Arthur Zakirov, Oleksandr Shulgin --- doc/src/sgml/catalogs.sgml | 8 ++ doc/src/sgml/extend.sgml | 6 + doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/json.sgml | 39 ++++++ doc/src/sgml/ref/create_type.sgml | 33 ++++- doc/src/sgml/xsubscripting.sgml | 111 +++++++++++++++++ src/tutorial/Makefile | 4 +- src/tutorial/subscripting.c | 201 ++++++++++++++++++++++++++++++ src/tutorial/subscripting.source | 71 +++++++++++ 9 files changed, 470 insertions(+), 4 deletions(-) create mode 100644 doc/src/sgml/xsubscripting.sgml create mode 100644 src/tutorial/subscripting.c create mode 100644 src/tutorial/subscripting.source diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 048ff284f7..b17e9ee382 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -8971,6 +8971,14 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l </para></entry> </row> + <row> + <entry><structfield>typsubshandler</structfield></entry> + <entry><type>regproc</type></entry> + <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry> + <entry>Custom subscripting function with type-specific logic for parsing + and validation, or 0 if this type doesn't support subscripting.</entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>typdefault</structfield> <type>text</type> diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml index 890ff97b7a..65cf5706b6 100644 --- a/doc/src/sgml/extend.sgml +++ b/doc/src/sgml/extend.sgml @@ -33,6 +33,11 @@ operators (starting in <xref linkend="xoper"/>) </para> </listitem> + <listitem> + <para> + subscripting procedure (starting in <xref linkend="xsubscripting"/>) + </para> + </listitem> <listitem> <para> operator classes for indexes (starting in <xref linkend="xindex"/>) @@ -481,6 +486,7 @@ RETURNS anycompatible AS ... &xaggr; &xtypes; &xoper; + &xsubscripting; &xindex; diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 64b5da0070..4c3fd37fc1 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -69,6 +69,7 @@ <!ENTITY xplang SYSTEM "xplang.sgml"> <!ENTITY xoper SYSTEM "xoper.sgml"> <!ENTITY xtypes SYSTEM "xtypes.sgml"> +<!ENTITY xsubscripting SYSTEM "xsubscripting.sgml"> <!ENTITY plperl SYSTEM "plperl.sgml"> <!ENTITY plpython SYSTEM "plpython.sgml"> <!ENTITY plsql SYSTEM "plpgsql.sgml"> diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml index c0a6554d4d..5c538dca05 100644 --- a/doc/src/sgml/json.sgml +++ b/doc/src/sgml/json.sgml @@ -602,6 +602,45 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu </para> </sect2> + <sect2 id="jsonb-subscripting"> + <title><type>jsonb</type> Subscripting</title> + <para> + <type>jsonb</type> data type supports array-style subscripting expressions + to extract or update particular elements. It's possible to use multiple + subscripting expressions to extract nested values. In this case, a chain of + subscripting expressions follows the same rules as the + <literal>path</literal> argument in <literal>jsonb_set</literal> function, + e.g. in case of arrays it is a 0-based operation or that negative integers + that appear in <literal>path</literal> count from the end of JSON arrays. + The result of subscripting expressions is always jsonb data type. An + example of subscripting syntax: +<programlisting> +-- Extract value by key +SELECT ('{"a": 1}'::jsonb)['a']; + +-- Extract nested value by key path +SELECT ('{"a": {"b": {"c": 1}}}'::jsonb)['a']['b']['c']; + +-- Extract element by index +SELECT ('[1, "2", null]'::jsonb)[1]; + +-- Update value by key +UPDATE table_name SET jsonb_field['key'] = 1; + +-- Select records using where clause with subscripting. Since the result of +-- subscripting is jsonb and we basically want to compare two jsonb objects, we +-- need to put the value in double quotes to be able to convert it to jsonb. +SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"'; +</programlisting> + + There is no special indexing support for such kind of expressions, but you + can always create a functional index that includes it +<programlisting> +CREATE INDEX idx ON table_name ((jsonb_field['key'])); +</programlisting> + </para> + </sect2> + <sect2> <title>Transforms</title> diff --git a/doc/src/sgml/ref/create_type.sgml b/doc/src/sgml/ref/create_type.sgml index 111f8e65d2..a34df4d247 100644 --- a/doc/src/sgml/ref/create_type.sgml +++ b/doc/src/sgml/ref/create_type.sgml @@ -54,6 +54,7 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> ( [ , ELEMENT = <replaceable class="parameter">element</replaceable> ] [ , DELIMITER = <replaceable class="parameter">delimiter</replaceable> ] [ , COLLATABLE = <replaceable class="parameter">collatable</replaceable> ] + [ , SUBSCRIPTING_HANDLER = <replaceable class="parameter">subscripting_handler_function</replaceable> ] ) CREATE TYPE <replaceable class="parameter">name</replaceable> @@ -196,8 +197,9 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> <replaceable class="parameter">receive_function</replaceable>, <replaceable class="parameter">send_function</replaceable>, <replaceable class="parameter">type_modifier_input_function</replaceable>, - <replaceable class="parameter">type_modifier_output_function</replaceable> and - <replaceable class="parameter">analyze_function</replaceable> + <replaceable class="parameter">type_modifier_output_function</replaceable>, + <replaceable class="parameter">analyze_function</replaceable>, + <replaceable class="parameter">subscripting_handler_function</replaceable> are optional. Generally these functions have to be coded in C or another low-level language. </para> @@ -454,6 +456,22 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> make use of the collation information; this does not happen automatically merely by marking the type collatable. </para> + + <para> + The optional + <replaceable class="parameter">subscripting_handler_function</replaceable> + contains type-specific logic for subscripting of the data type. + By default, there is no such function provided, which means that the data + type doesn't support subscripting. The subscripting function must be + declared to take a single argument of type <type>internal</type>, and return + a <type>internal</type> result. There are two examples of implementation for + subscripting functions in case of array + (<replaceable class="parameter">array_subscripting_handler</replaceable>) + and jsonb + (<replaceable class="parameter">jsonb_subscripting_handler</replaceable>) + types in <filename>src/backend/utils/adt/arrayfuncs.c</filename> and + <filename>src/backend/utils/adt/jsonfuncs.c</filename>, respectively. + </para> </refsect2> <refsect2> @@ -769,6 +787,17 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> </para> </listitem> </varlistentry> + + <varlistentry> + <term><replaceable class="parameter">subscripting_handler_function</replaceable></term> + <listitem> + <para> + The name of a function that returns list of type-specific callback functions to + support subscripting logic for the data type. + </para> + </listitem> + </varlistentry> + </variablelist> </refsect1> diff --git a/doc/src/sgml/xsubscripting.sgml b/doc/src/sgml/xsubscripting.sgml new file mode 100644 index 0000000000..7224e81fa2 --- /dev/null +++ b/doc/src/sgml/xsubscripting.sgml @@ -0,0 +1,111 @@ +<!-- doc/src/sgml/xsubscripting.sgml --> + + <sect1 id="xsubscripting"> + <title>User-defined subscripting procedure</title> + + <indexterm zone="xsubscripting"> + <primary>custom subscripting</primary> + </indexterm> + <para> + When you define a new base type, you can also specify a custom procedure to + handle subscripting expressions. It must contain logic for verification and + evaluation of this expression, i.e. fetching or updating some data in this + data type. For instance: +</para> +<programlisting><![CDATA[ +typedef struct Custom +{ + int first; + int second; +} Custom; + +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = someType; + sbsref->refassgntype = someType; + + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + // some validation and coercion logic + + return sbsref; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some assignment logic + + return newContainer; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some fetch logic based on sbstate +}]]> +</programlisting> + +<para> + Then you can define a subscripting procedure and a custom data type: +</para> +<programlisting> +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '<replaceable>filename</replaceable>' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 4, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler, +); +</programlisting> + +<para> + and use it as usual: +</para> +<programlisting> +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; +</programlisting> + + + <para> + The examples of custom subscripting implementation can be found in + <filename>subscripting.sql</filename> and <filename>subscripting.c</filename> + in the <filename>src/tutorial</filename> directory of the source distribution. + See the <filename>README</filename> file in that directory for instructions + about running the examples. + </para> + +</sect1> diff --git a/src/tutorial/Makefile b/src/tutorial/Makefile index 16dc390f71..0ead60c2d4 100644 --- a/src/tutorial/Makefile +++ b/src/tutorial/Makefile @@ -13,8 +13,8 @@ # #------------------------------------------------------------------------- -MODULES = complex funcs -DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql +MODULES = complex funcs subscripting +DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql subscripting.sql ifdef NO_PGXS subdir = src/tutorial diff --git a/src/tutorial/subscripting.c b/src/tutorial/subscripting.c new file mode 100644 index 0000000000..1eb8c45652 --- /dev/null +++ b/src/tutorial/subscripting.c @@ -0,0 +1,201 @@ +/* + * src/tutorial/subscripting.c + * + ****************************************************************************** + This file contains routines that can be bound to a Postgres backend and + called by the backend in the process of processing queries. The calling + format for these routines is dictated by Postgres architecture. +******************************************************************************/ + +#include "postgres.h" + +#include "catalog/pg_type.h" +#include "executor/executor.h" +#include "executor/execExpr.h" +#include "nodes/nodeFuncs.h" +#include "parser/parse_coerce.h" +#include "utils/builtins.h" +#include "utils/fmgrprotos.h" + +PG_MODULE_MAGIC; + +typedef struct Custom +{ + int first; + int second; +} Custom; + +SubscriptingRef * custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref); +SubscriptingRef * custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate); +Datum custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate); +Datum custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate); + +PG_FUNCTION_INFO_V1(custom_in); +PG_FUNCTION_INFO_V1(custom_out); +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +/***************************************************************************** + * Input/Output functions + *****************************************************************************/ + +Datum +custom_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + int firstValue, + secondValue; + Custom *result; + + if (sscanf(str, " ( %d , %d )", &firstValue, &secondValue) != 2) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for complex: \"%s\"", + str))); + + + result = (Custom *) palloc(sizeof(Custom)); + result->first = firstValue; + result->second = secondValue; + PG_RETURN_POINTER(result); +} + +Datum +custom_out(PG_FUNCTION_ARGS) +{ + Custom *custom = (Custom *) PG_GETARG_POINTER(0); + char *result; + + result = psprintf("(%d, %d)", custom->first, custom->second); + PG_RETURN_CSTRING(result); +} + +/***************************************************************************** + * Custom subscripting logic functions + *****************************************************************************/ + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = INT4OID; + sbsref->refassgntype = INT4OID; + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + List *upperIndexpr = NIL; + ListCell *l; + + if (sbsref->reflowerindexpr != NIL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *)lfirst(sbsref->reflowerindexpr->head)))))); + + foreach(l, sbsref->refupperindexpr) + { + Node *subexpr = (Node *) lfirst(l); + + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *) lfirst(sbsref->refupperindexpr->head)))))); + + subexpr = coerce_to_target_type(pstate, + subexpr, exprType(subexpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript must have integer type"), + parser_errposition(pstate, exprLocation(subexpr)))); + + upperIndexpr = lappend(upperIndexpr, subexpr); + + if (isAssignment) + { + Node *assignExpr = (Node *) sbsref->refassgnexpr; + Node *new_from; + + new_from = coerce_to_target_type(pstate, + assignExpr, exprType(assignExpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (new_from == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom assignment requires int type"), + errhint("You will need to rewrite or cast the expression."), + parser_errposition(pstate, exprLocation(assignExpr)))); + sbsref->refassgnexpr = (Expr *)new_from; + } + } + + sbsref->refupperindexpr = upperIndexpr; + + return sbsref; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + Custom *container= (Custom *) containerSource; + int index; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + return (Datum) container->first; + else + return (Datum) container->second; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + int index; + Custom *container = (Custom *) containerSource; + + if (sbstate->resnull) + return containerSource; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + container->first = DatumGetInt32(sbstate->replacevalue); + else + container->second = DatumGetInt32(sbstate->replacevalue); + + return (Datum) container; +} diff --git a/src/tutorial/subscripting.source b/src/tutorial/subscripting.source new file mode 100644 index 0000000000..837cf30612 --- /dev/null +++ b/src/tutorial/subscripting.source @@ -0,0 +1,71 @@ +--------------------------------------------------------------------------- +-- +-- subscripting.sql- +-- This file shows how to create a new subscripting procedure for +-- user-defined type. +-- +-- +-- Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group +-- Portions Copyright (c) 1994, Regents of the University of California +-- +-- src/tutorial/subscripting.source +-- +--------------------------------------------------------------------------- + +----------------------------- +-- Creating a new type: +-- We are going to create a new type called 'complex' which represents +-- complex numbers. +-- A user-defined type must have an input and an output function, and +-- optionally can have binary input and output functions. All of these +-- are usually user-defined C functions. +----------------------------- + +-- Assume the user defined functions are in /home/erthalion/programms/postgresql-master/src/tutorial/complex$DLSUFFIX +-- (we do not want to assume this is in the dynamic loader search path). +-- Look at $PWD/complex.c for the source. Note that we declare all of +-- them as STRICT, so we do not need to cope with NULL inputs in the +-- C code. We also mark them IMMUTABLE, since they always return the +-- same outputs given the same inputs. + +-- the input function 'complex_in' takes a null-terminated string (the +-- textual representation of the type) and turns it into the internal +-- (in memory) representation. You will get a message telling you 'complex' +-- does not exist yet but that's okay. + +CREATE FUNCTION custom_in(cstring) + RETURNS custom + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +-- the output function 'complex_out' takes the internal representation and +-- converts it into the textual representation. + +CREATE FUNCTION custom_out(custom) + RETURNS cstring + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 8, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler +); + +-- we can use it in a table + +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; -- 2.21.0 --u66dlpg6gaevsofr Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v33-0005-Filling-gaps-in-jsonb-arrays.patch" ^ permalink raw reply [nested|flat] 28+ messages in thread
* [PATCH v33 4/5] Subscripting documentation @ 2019-02-01 10:47 erthalion <[email protected]> 0 siblings, 0 replies; 28+ messages in thread From: erthalion @ 2019-02-01 10:47 UTC (permalink / raw) Supporting documentation for generalized subscripting. It includes the description of a new field in pg_type, the new section for jsonb documentation about subscripting feature on this data type, and also the tutorial about how to write subscripting operator for a custom data type. Reviewed-by: Tom Lane, Arthur Zakirov, Oleksandr Shulgin --- doc/src/sgml/catalogs.sgml | 8 ++ doc/src/sgml/extend.sgml | 6 + doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/json.sgml | 39 ++++++ doc/src/sgml/ref/create_type.sgml | 33 ++++- doc/src/sgml/xsubscripting.sgml | 111 +++++++++++++++++ src/tutorial/Makefile | 4 +- src/tutorial/subscripting.c | 201 ++++++++++++++++++++++++++++++ src/tutorial/subscripting.source | 71 +++++++++++ 9 files changed, 470 insertions(+), 4 deletions(-) create mode 100644 doc/src/sgml/xsubscripting.sgml create mode 100644 src/tutorial/subscripting.c create mode 100644 src/tutorial/subscripting.source diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 048ff284f7..b17e9ee382 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -8971,6 +8971,14 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l </para></entry> </row> + <row> + <entry><structfield>typsubshandler</structfield></entry> + <entry><type>regproc</type></entry> + <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry> + <entry>Custom subscripting function with type-specific logic for parsing + and validation, or 0 if this type doesn't support subscripting.</entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>typdefault</structfield> <type>text</type> diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml index 890ff97b7a..65cf5706b6 100644 --- a/doc/src/sgml/extend.sgml +++ b/doc/src/sgml/extend.sgml @@ -33,6 +33,11 @@ operators (starting in <xref linkend="xoper"/>) </para> </listitem> + <listitem> + <para> + subscripting procedure (starting in <xref linkend="xsubscripting"/>) + </para> + </listitem> <listitem> <para> operator classes for indexes (starting in <xref linkend="xindex"/>) @@ -481,6 +486,7 @@ RETURNS anycompatible AS ... &xaggr; &xtypes; &xoper; + &xsubscripting; &xindex; diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 64b5da0070..4c3fd37fc1 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -69,6 +69,7 @@ <!ENTITY xplang SYSTEM "xplang.sgml"> <!ENTITY xoper SYSTEM "xoper.sgml"> <!ENTITY xtypes SYSTEM "xtypes.sgml"> +<!ENTITY xsubscripting SYSTEM "xsubscripting.sgml"> <!ENTITY plperl SYSTEM "plperl.sgml"> <!ENTITY plpython SYSTEM "plpython.sgml"> <!ENTITY plsql SYSTEM "plpgsql.sgml"> diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml index c0a6554d4d..5c538dca05 100644 --- a/doc/src/sgml/json.sgml +++ b/doc/src/sgml/json.sgml @@ -602,6 +602,45 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu </para> </sect2> + <sect2 id="jsonb-subscripting"> + <title><type>jsonb</type> Subscripting</title> + <para> + <type>jsonb</type> data type supports array-style subscripting expressions + to extract or update particular elements. It's possible to use multiple + subscripting expressions to extract nested values. In this case, a chain of + subscripting expressions follows the same rules as the + <literal>path</literal> argument in <literal>jsonb_set</literal> function, + e.g. in case of arrays it is a 0-based operation or that negative integers + that appear in <literal>path</literal> count from the end of JSON arrays. + The result of subscripting expressions is always jsonb data type. An + example of subscripting syntax: +<programlisting> +-- Extract value by key +SELECT ('{"a": 1}'::jsonb)['a']; + +-- Extract nested value by key path +SELECT ('{"a": {"b": {"c": 1}}}'::jsonb)['a']['b']['c']; + +-- Extract element by index +SELECT ('[1, "2", null]'::jsonb)[1]; + +-- Update value by key +UPDATE table_name SET jsonb_field['key'] = 1; + +-- Select records using where clause with subscripting. Since the result of +-- subscripting is jsonb and we basically want to compare two jsonb objects, we +-- need to put the value in double quotes to be able to convert it to jsonb. +SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"'; +</programlisting> + + There is no special indexing support for such kind of expressions, but you + can always create a functional index that includes it +<programlisting> +CREATE INDEX idx ON table_name ((jsonb_field['key'])); +</programlisting> + </para> + </sect2> + <sect2> <title>Transforms</title> diff --git a/doc/src/sgml/ref/create_type.sgml b/doc/src/sgml/ref/create_type.sgml index 111f8e65d2..a34df4d247 100644 --- a/doc/src/sgml/ref/create_type.sgml +++ b/doc/src/sgml/ref/create_type.sgml @@ -54,6 +54,7 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> ( [ , ELEMENT = <replaceable class="parameter">element</replaceable> ] [ , DELIMITER = <replaceable class="parameter">delimiter</replaceable> ] [ , COLLATABLE = <replaceable class="parameter">collatable</replaceable> ] + [ , SUBSCRIPTING_HANDLER = <replaceable class="parameter">subscripting_handler_function</replaceable> ] ) CREATE TYPE <replaceable class="parameter">name</replaceable> @@ -196,8 +197,9 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> <replaceable class="parameter">receive_function</replaceable>, <replaceable class="parameter">send_function</replaceable>, <replaceable class="parameter">type_modifier_input_function</replaceable>, - <replaceable class="parameter">type_modifier_output_function</replaceable> and - <replaceable class="parameter">analyze_function</replaceable> + <replaceable class="parameter">type_modifier_output_function</replaceable>, + <replaceable class="parameter">analyze_function</replaceable>, + <replaceable class="parameter">subscripting_handler_function</replaceable> are optional. Generally these functions have to be coded in C or another low-level language. </para> @@ -454,6 +456,22 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> make use of the collation information; this does not happen automatically merely by marking the type collatable. </para> + + <para> + The optional + <replaceable class="parameter">subscripting_handler_function</replaceable> + contains type-specific logic for subscripting of the data type. + By default, there is no such function provided, which means that the data + type doesn't support subscripting. The subscripting function must be + declared to take a single argument of type <type>internal</type>, and return + a <type>internal</type> result. There are two examples of implementation for + subscripting functions in case of array + (<replaceable class="parameter">array_subscripting_handler</replaceable>) + and jsonb + (<replaceable class="parameter">jsonb_subscripting_handler</replaceable>) + types in <filename>src/backend/utils/adt/arrayfuncs.c</filename> and + <filename>src/backend/utils/adt/jsonfuncs.c</filename>, respectively. + </para> </refsect2> <refsect2> @@ -769,6 +787,17 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> </para> </listitem> </varlistentry> + + <varlistentry> + <term><replaceable class="parameter">subscripting_handler_function</replaceable></term> + <listitem> + <para> + The name of a function that returns list of type-specific callback functions to + support subscripting logic for the data type. + </para> + </listitem> + </varlistentry> + </variablelist> </refsect1> diff --git a/doc/src/sgml/xsubscripting.sgml b/doc/src/sgml/xsubscripting.sgml new file mode 100644 index 0000000000..7224e81fa2 --- /dev/null +++ b/doc/src/sgml/xsubscripting.sgml @@ -0,0 +1,111 @@ +<!-- doc/src/sgml/xsubscripting.sgml --> + + <sect1 id="xsubscripting"> + <title>User-defined subscripting procedure</title> + + <indexterm zone="xsubscripting"> + <primary>custom subscripting</primary> + </indexterm> + <para> + When you define a new base type, you can also specify a custom procedure to + handle subscripting expressions. It must contain logic for verification and + evaluation of this expression, i.e. fetching or updating some data in this + data type. For instance: +</para> +<programlisting><![CDATA[ +typedef struct Custom +{ + int first; + int second; +} Custom; + +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = someType; + sbsref->refassgntype = someType; + + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + // some validation and coercion logic + + return sbsref; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some assignment logic + + return newContainer; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some fetch logic based on sbstate +}]]> +</programlisting> + +<para> + Then you can define a subscripting procedure and a custom data type: +</para> +<programlisting> +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '<replaceable>filename</replaceable>' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 4, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler, +); +</programlisting> + +<para> + and use it as usual: +</para> +<programlisting> +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; +</programlisting> + + + <para> + The examples of custom subscripting implementation can be found in + <filename>subscripting.sql</filename> and <filename>subscripting.c</filename> + in the <filename>src/tutorial</filename> directory of the source distribution. + See the <filename>README</filename> file in that directory for instructions + about running the examples. + </para> + +</sect1> diff --git a/src/tutorial/Makefile b/src/tutorial/Makefile index 16dc390f71..0ead60c2d4 100644 --- a/src/tutorial/Makefile +++ b/src/tutorial/Makefile @@ -13,8 +13,8 @@ # #------------------------------------------------------------------------- -MODULES = complex funcs -DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql +MODULES = complex funcs subscripting +DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql subscripting.sql ifdef NO_PGXS subdir = src/tutorial diff --git a/src/tutorial/subscripting.c b/src/tutorial/subscripting.c new file mode 100644 index 0000000000..1eb8c45652 --- /dev/null +++ b/src/tutorial/subscripting.c @@ -0,0 +1,201 @@ +/* + * src/tutorial/subscripting.c + * + ****************************************************************************** + This file contains routines that can be bound to a Postgres backend and + called by the backend in the process of processing queries. The calling + format for these routines is dictated by Postgres architecture. +******************************************************************************/ + +#include "postgres.h" + +#include "catalog/pg_type.h" +#include "executor/executor.h" +#include "executor/execExpr.h" +#include "nodes/nodeFuncs.h" +#include "parser/parse_coerce.h" +#include "utils/builtins.h" +#include "utils/fmgrprotos.h" + +PG_MODULE_MAGIC; + +typedef struct Custom +{ + int first; + int second; +} Custom; + +SubscriptingRef * custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref); +SubscriptingRef * custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate); +Datum custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate); +Datum custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate); + +PG_FUNCTION_INFO_V1(custom_in); +PG_FUNCTION_INFO_V1(custom_out); +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +/***************************************************************************** + * Input/Output functions + *****************************************************************************/ + +Datum +custom_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + int firstValue, + secondValue; + Custom *result; + + if (sscanf(str, " ( %d , %d )", &firstValue, &secondValue) != 2) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for complex: \"%s\"", + str))); + + + result = (Custom *) palloc(sizeof(Custom)); + result->first = firstValue; + result->second = secondValue; + PG_RETURN_POINTER(result); +} + +Datum +custom_out(PG_FUNCTION_ARGS) +{ + Custom *custom = (Custom *) PG_GETARG_POINTER(0); + char *result; + + result = psprintf("(%d, %d)", custom->first, custom->second); + PG_RETURN_CSTRING(result); +} + +/***************************************************************************** + * Custom subscripting logic functions + *****************************************************************************/ + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = INT4OID; + sbsref->refassgntype = INT4OID; + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + List *upperIndexpr = NIL; + ListCell *l; + + if (sbsref->reflowerindexpr != NIL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *)lfirst(sbsref->reflowerindexpr->head)))))); + + foreach(l, sbsref->refupperindexpr) + { + Node *subexpr = (Node *) lfirst(l); + + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *) lfirst(sbsref->refupperindexpr->head)))))); + + subexpr = coerce_to_target_type(pstate, + subexpr, exprType(subexpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript must have integer type"), + parser_errposition(pstate, exprLocation(subexpr)))); + + upperIndexpr = lappend(upperIndexpr, subexpr); + + if (isAssignment) + { + Node *assignExpr = (Node *) sbsref->refassgnexpr; + Node *new_from; + + new_from = coerce_to_target_type(pstate, + assignExpr, exprType(assignExpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (new_from == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom assignment requires int type"), + errhint("You will need to rewrite or cast the expression."), + parser_errposition(pstate, exprLocation(assignExpr)))); + sbsref->refassgnexpr = (Expr *)new_from; + } + } + + sbsref->refupperindexpr = upperIndexpr; + + return sbsref; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + Custom *container= (Custom *) containerSource; + int index; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + return (Datum) container->first; + else + return (Datum) container->second; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + int index; + Custom *container = (Custom *) containerSource; + + if (sbstate->resnull) + return containerSource; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + container->first = DatumGetInt32(sbstate->replacevalue); + else + container->second = DatumGetInt32(sbstate->replacevalue); + + return (Datum) container; +} diff --git a/src/tutorial/subscripting.source b/src/tutorial/subscripting.source new file mode 100644 index 0000000000..837cf30612 --- /dev/null +++ b/src/tutorial/subscripting.source @@ -0,0 +1,71 @@ +--------------------------------------------------------------------------- +-- +-- subscripting.sql- +-- This file shows how to create a new subscripting procedure for +-- user-defined type. +-- +-- +-- Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group +-- Portions Copyright (c) 1994, Regents of the University of California +-- +-- src/tutorial/subscripting.source +-- +--------------------------------------------------------------------------- + +----------------------------- +-- Creating a new type: +-- We are going to create a new type called 'complex' which represents +-- complex numbers. +-- A user-defined type must have an input and an output function, and +-- optionally can have binary input and output functions. All of these +-- are usually user-defined C functions. +----------------------------- + +-- Assume the user defined functions are in /home/erthalion/programms/postgresql-master/src/tutorial/complex$DLSUFFIX +-- (we do not want to assume this is in the dynamic loader search path). +-- Look at $PWD/complex.c for the source. Note that we declare all of +-- them as STRICT, so we do not need to cope with NULL inputs in the +-- C code. We also mark them IMMUTABLE, since they always return the +-- same outputs given the same inputs. + +-- the input function 'complex_in' takes a null-terminated string (the +-- textual representation of the type) and turns it into the internal +-- (in memory) representation. You will get a message telling you 'complex' +-- does not exist yet but that's okay. + +CREATE FUNCTION custom_in(cstring) + RETURNS custom + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +-- the output function 'complex_out' takes the internal representation and +-- converts it into the textual representation. + +CREATE FUNCTION custom_out(custom) + RETURNS cstring + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 8, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler +); + +-- we can use it in a table + +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; -- 2.21.0 --u66dlpg6gaevsofr Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v33-0005-Filling-gaps-in-jsonb-arrays.patch" ^ permalink raw reply [nested|flat] 28+ messages in thread
* [PATCH v33 4/5] Subscripting documentation @ 2019-02-01 10:47 erthalion <[email protected]> 0 siblings, 0 replies; 28+ messages in thread From: erthalion @ 2019-02-01 10:47 UTC (permalink / raw) Supporting documentation for generalized subscripting. It includes the description of a new field in pg_type, the new section for jsonb documentation about subscripting feature on this data type, and also the tutorial about how to write subscripting operator for a custom data type. Reviewed-by: Tom Lane, Arthur Zakirov, Oleksandr Shulgin --- doc/src/sgml/catalogs.sgml | 8 ++ doc/src/sgml/extend.sgml | 6 + doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/json.sgml | 39 ++++++ doc/src/sgml/ref/create_type.sgml | 33 ++++- doc/src/sgml/xsubscripting.sgml | 111 +++++++++++++++++ src/tutorial/Makefile | 4 +- src/tutorial/subscripting.c | 201 ++++++++++++++++++++++++++++++ src/tutorial/subscripting.source | 71 +++++++++++ 9 files changed, 470 insertions(+), 4 deletions(-) create mode 100644 doc/src/sgml/xsubscripting.sgml create mode 100644 src/tutorial/subscripting.c create mode 100644 src/tutorial/subscripting.source diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 048ff284f7..b17e9ee382 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -8971,6 +8971,14 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l </para></entry> </row> + <row> + <entry><structfield>typsubshandler</structfield></entry> + <entry><type>regproc</type></entry> + <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry> + <entry>Custom subscripting function with type-specific logic for parsing + and validation, or 0 if this type doesn't support subscripting.</entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>typdefault</structfield> <type>text</type> diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml index 890ff97b7a..65cf5706b6 100644 --- a/doc/src/sgml/extend.sgml +++ b/doc/src/sgml/extend.sgml @@ -33,6 +33,11 @@ operators (starting in <xref linkend="xoper"/>) </para> </listitem> + <listitem> + <para> + subscripting procedure (starting in <xref linkend="xsubscripting"/>) + </para> + </listitem> <listitem> <para> operator classes for indexes (starting in <xref linkend="xindex"/>) @@ -481,6 +486,7 @@ RETURNS anycompatible AS ... &xaggr; &xtypes; &xoper; + &xsubscripting; &xindex; diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 64b5da0070..4c3fd37fc1 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -69,6 +69,7 @@ <!ENTITY xplang SYSTEM "xplang.sgml"> <!ENTITY xoper SYSTEM "xoper.sgml"> <!ENTITY xtypes SYSTEM "xtypes.sgml"> +<!ENTITY xsubscripting SYSTEM "xsubscripting.sgml"> <!ENTITY plperl SYSTEM "plperl.sgml"> <!ENTITY plpython SYSTEM "plpython.sgml"> <!ENTITY plsql SYSTEM "plpgsql.sgml"> diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml index c0a6554d4d..5c538dca05 100644 --- a/doc/src/sgml/json.sgml +++ b/doc/src/sgml/json.sgml @@ -602,6 +602,45 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu </para> </sect2> + <sect2 id="jsonb-subscripting"> + <title><type>jsonb</type> Subscripting</title> + <para> + <type>jsonb</type> data type supports array-style subscripting expressions + to extract or update particular elements. It's possible to use multiple + subscripting expressions to extract nested values. In this case, a chain of + subscripting expressions follows the same rules as the + <literal>path</literal> argument in <literal>jsonb_set</literal> function, + e.g. in case of arrays it is a 0-based operation or that negative integers + that appear in <literal>path</literal> count from the end of JSON arrays. + The result of subscripting expressions is always jsonb data type. An + example of subscripting syntax: +<programlisting> +-- Extract value by key +SELECT ('{"a": 1}'::jsonb)['a']; + +-- Extract nested value by key path +SELECT ('{"a": {"b": {"c": 1}}}'::jsonb)['a']['b']['c']; + +-- Extract element by index +SELECT ('[1, "2", null]'::jsonb)[1]; + +-- Update value by key +UPDATE table_name SET jsonb_field['key'] = 1; + +-- Select records using where clause with subscripting. Since the result of +-- subscripting is jsonb and we basically want to compare two jsonb objects, we +-- need to put the value in double quotes to be able to convert it to jsonb. +SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"'; +</programlisting> + + There is no special indexing support for such kind of expressions, but you + can always create a functional index that includes it +<programlisting> +CREATE INDEX idx ON table_name ((jsonb_field['key'])); +</programlisting> + </para> + </sect2> + <sect2> <title>Transforms</title> diff --git a/doc/src/sgml/ref/create_type.sgml b/doc/src/sgml/ref/create_type.sgml index 111f8e65d2..a34df4d247 100644 --- a/doc/src/sgml/ref/create_type.sgml +++ b/doc/src/sgml/ref/create_type.sgml @@ -54,6 +54,7 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> ( [ , ELEMENT = <replaceable class="parameter">element</replaceable> ] [ , DELIMITER = <replaceable class="parameter">delimiter</replaceable> ] [ , COLLATABLE = <replaceable class="parameter">collatable</replaceable> ] + [ , SUBSCRIPTING_HANDLER = <replaceable class="parameter">subscripting_handler_function</replaceable> ] ) CREATE TYPE <replaceable class="parameter">name</replaceable> @@ -196,8 +197,9 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> <replaceable class="parameter">receive_function</replaceable>, <replaceable class="parameter">send_function</replaceable>, <replaceable class="parameter">type_modifier_input_function</replaceable>, - <replaceable class="parameter">type_modifier_output_function</replaceable> and - <replaceable class="parameter">analyze_function</replaceable> + <replaceable class="parameter">type_modifier_output_function</replaceable>, + <replaceable class="parameter">analyze_function</replaceable>, + <replaceable class="parameter">subscripting_handler_function</replaceable> are optional. Generally these functions have to be coded in C or another low-level language. </para> @@ -454,6 +456,22 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> make use of the collation information; this does not happen automatically merely by marking the type collatable. </para> + + <para> + The optional + <replaceable class="parameter">subscripting_handler_function</replaceable> + contains type-specific logic for subscripting of the data type. + By default, there is no such function provided, which means that the data + type doesn't support subscripting. The subscripting function must be + declared to take a single argument of type <type>internal</type>, and return + a <type>internal</type> result. There are two examples of implementation for + subscripting functions in case of array + (<replaceable class="parameter">array_subscripting_handler</replaceable>) + and jsonb + (<replaceable class="parameter">jsonb_subscripting_handler</replaceable>) + types in <filename>src/backend/utils/adt/arrayfuncs.c</filename> and + <filename>src/backend/utils/adt/jsonfuncs.c</filename>, respectively. + </para> </refsect2> <refsect2> @@ -769,6 +787,17 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> </para> </listitem> </varlistentry> + + <varlistentry> + <term><replaceable class="parameter">subscripting_handler_function</replaceable></term> + <listitem> + <para> + The name of a function that returns list of type-specific callback functions to + support subscripting logic for the data type. + </para> + </listitem> + </varlistentry> + </variablelist> </refsect1> diff --git a/doc/src/sgml/xsubscripting.sgml b/doc/src/sgml/xsubscripting.sgml new file mode 100644 index 0000000000..7224e81fa2 --- /dev/null +++ b/doc/src/sgml/xsubscripting.sgml @@ -0,0 +1,111 @@ +<!-- doc/src/sgml/xsubscripting.sgml --> + + <sect1 id="xsubscripting"> + <title>User-defined subscripting procedure</title> + + <indexterm zone="xsubscripting"> + <primary>custom subscripting</primary> + </indexterm> + <para> + When you define a new base type, you can also specify a custom procedure to + handle subscripting expressions. It must contain logic for verification and + evaluation of this expression, i.e. fetching or updating some data in this + data type. For instance: +</para> +<programlisting><![CDATA[ +typedef struct Custom +{ + int first; + int second; +} Custom; + +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = someType; + sbsref->refassgntype = someType; + + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + // some validation and coercion logic + + return sbsref; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some assignment logic + + return newContainer; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some fetch logic based on sbstate +}]]> +</programlisting> + +<para> + Then you can define a subscripting procedure and a custom data type: +</para> +<programlisting> +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '<replaceable>filename</replaceable>' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 4, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler, +); +</programlisting> + +<para> + and use it as usual: +</para> +<programlisting> +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; +</programlisting> + + + <para> + The examples of custom subscripting implementation can be found in + <filename>subscripting.sql</filename> and <filename>subscripting.c</filename> + in the <filename>src/tutorial</filename> directory of the source distribution. + See the <filename>README</filename> file in that directory for instructions + about running the examples. + </para> + +</sect1> diff --git a/src/tutorial/Makefile b/src/tutorial/Makefile index 16dc390f71..0ead60c2d4 100644 --- a/src/tutorial/Makefile +++ b/src/tutorial/Makefile @@ -13,8 +13,8 @@ # #------------------------------------------------------------------------- -MODULES = complex funcs -DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql +MODULES = complex funcs subscripting +DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql subscripting.sql ifdef NO_PGXS subdir = src/tutorial diff --git a/src/tutorial/subscripting.c b/src/tutorial/subscripting.c new file mode 100644 index 0000000000..1eb8c45652 --- /dev/null +++ b/src/tutorial/subscripting.c @@ -0,0 +1,201 @@ +/* + * src/tutorial/subscripting.c + * + ****************************************************************************** + This file contains routines that can be bound to a Postgres backend and + called by the backend in the process of processing queries. The calling + format for these routines is dictated by Postgres architecture. +******************************************************************************/ + +#include "postgres.h" + +#include "catalog/pg_type.h" +#include "executor/executor.h" +#include "executor/execExpr.h" +#include "nodes/nodeFuncs.h" +#include "parser/parse_coerce.h" +#include "utils/builtins.h" +#include "utils/fmgrprotos.h" + +PG_MODULE_MAGIC; + +typedef struct Custom +{ + int first; + int second; +} Custom; + +SubscriptingRef * custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref); +SubscriptingRef * custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate); +Datum custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate); +Datum custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate); + +PG_FUNCTION_INFO_V1(custom_in); +PG_FUNCTION_INFO_V1(custom_out); +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +/***************************************************************************** + * Input/Output functions + *****************************************************************************/ + +Datum +custom_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + int firstValue, + secondValue; + Custom *result; + + if (sscanf(str, " ( %d , %d )", &firstValue, &secondValue) != 2) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for complex: \"%s\"", + str))); + + + result = (Custom *) palloc(sizeof(Custom)); + result->first = firstValue; + result->second = secondValue; + PG_RETURN_POINTER(result); +} + +Datum +custom_out(PG_FUNCTION_ARGS) +{ + Custom *custom = (Custom *) PG_GETARG_POINTER(0); + char *result; + + result = psprintf("(%d, %d)", custom->first, custom->second); + PG_RETURN_CSTRING(result); +} + +/***************************************************************************** + * Custom subscripting logic functions + *****************************************************************************/ + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = INT4OID; + sbsref->refassgntype = INT4OID; + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + List *upperIndexpr = NIL; + ListCell *l; + + if (sbsref->reflowerindexpr != NIL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *)lfirst(sbsref->reflowerindexpr->head)))))); + + foreach(l, sbsref->refupperindexpr) + { + Node *subexpr = (Node *) lfirst(l); + + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *) lfirst(sbsref->refupperindexpr->head)))))); + + subexpr = coerce_to_target_type(pstate, + subexpr, exprType(subexpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript must have integer type"), + parser_errposition(pstate, exprLocation(subexpr)))); + + upperIndexpr = lappend(upperIndexpr, subexpr); + + if (isAssignment) + { + Node *assignExpr = (Node *) sbsref->refassgnexpr; + Node *new_from; + + new_from = coerce_to_target_type(pstate, + assignExpr, exprType(assignExpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (new_from == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom assignment requires int type"), + errhint("You will need to rewrite or cast the expression."), + parser_errposition(pstate, exprLocation(assignExpr)))); + sbsref->refassgnexpr = (Expr *)new_from; + } + } + + sbsref->refupperindexpr = upperIndexpr; + + return sbsref; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + Custom *container= (Custom *) containerSource; + int index; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + return (Datum) container->first; + else + return (Datum) container->second; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + int index; + Custom *container = (Custom *) containerSource; + + if (sbstate->resnull) + return containerSource; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + container->first = DatumGetInt32(sbstate->replacevalue); + else + container->second = DatumGetInt32(sbstate->replacevalue); + + return (Datum) container; +} diff --git a/src/tutorial/subscripting.source b/src/tutorial/subscripting.source new file mode 100644 index 0000000000..837cf30612 --- /dev/null +++ b/src/tutorial/subscripting.source @@ -0,0 +1,71 @@ +--------------------------------------------------------------------------- +-- +-- subscripting.sql- +-- This file shows how to create a new subscripting procedure for +-- user-defined type. +-- +-- +-- Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group +-- Portions Copyright (c) 1994, Regents of the University of California +-- +-- src/tutorial/subscripting.source +-- +--------------------------------------------------------------------------- + +----------------------------- +-- Creating a new type: +-- We are going to create a new type called 'complex' which represents +-- complex numbers. +-- A user-defined type must have an input and an output function, and +-- optionally can have binary input and output functions. All of these +-- are usually user-defined C functions. +----------------------------- + +-- Assume the user defined functions are in /home/erthalion/programms/postgresql-master/src/tutorial/complex$DLSUFFIX +-- (we do not want to assume this is in the dynamic loader search path). +-- Look at $PWD/complex.c for the source. Note that we declare all of +-- them as STRICT, so we do not need to cope with NULL inputs in the +-- C code. We also mark them IMMUTABLE, since they always return the +-- same outputs given the same inputs. + +-- the input function 'complex_in' takes a null-terminated string (the +-- textual representation of the type) and turns it into the internal +-- (in memory) representation. You will get a message telling you 'complex' +-- does not exist yet but that's okay. + +CREATE FUNCTION custom_in(cstring) + RETURNS custom + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +-- the output function 'complex_out' takes the internal representation and +-- converts it into the textual representation. + +CREATE FUNCTION custom_out(custom) + RETURNS cstring + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 8, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler +); + +-- we can use it in a table + +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; -- 2.21.0 --u66dlpg6gaevsofr Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v33-0005-Filling-gaps-in-jsonb-arrays.patch" ^ permalink raw reply [nested|flat] 28+ messages in thread
* [PATCH v33 4/5] Subscripting documentation @ 2019-02-01 10:47 erthalion <[email protected]> 0 siblings, 0 replies; 28+ messages in thread From: erthalion @ 2019-02-01 10:47 UTC (permalink / raw) Supporting documentation for generalized subscripting. It includes the description of a new field in pg_type, the new section for jsonb documentation about subscripting feature on this data type, and also the tutorial about how to write subscripting operator for a custom data type. Reviewed-by: Tom Lane, Arthur Zakirov, Oleksandr Shulgin --- doc/src/sgml/catalogs.sgml | 8 ++ doc/src/sgml/extend.sgml | 6 + doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/json.sgml | 39 ++++++ doc/src/sgml/ref/create_type.sgml | 33 ++++- doc/src/sgml/xsubscripting.sgml | 111 +++++++++++++++++ src/tutorial/Makefile | 4 +- src/tutorial/subscripting.c | 201 ++++++++++++++++++++++++++++++ src/tutorial/subscripting.source | 71 +++++++++++ 9 files changed, 470 insertions(+), 4 deletions(-) create mode 100644 doc/src/sgml/xsubscripting.sgml create mode 100644 src/tutorial/subscripting.c create mode 100644 src/tutorial/subscripting.source diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 048ff284f7..b17e9ee382 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -8971,6 +8971,14 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l </para></entry> </row> + <row> + <entry><structfield>typsubshandler</structfield></entry> + <entry><type>regproc</type></entry> + <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry> + <entry>Custom subscripting function with type-specific logic for parsing + and validation, or 0 if this type doesn't support subscripting.</entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>typdefault</structfield> <type>text</type> diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml index 890ff97b7a..65cf5706b6 100644 --- a/doc/src/sgml/extend.sgml +++ b/doc/src/sgml/extend.sgml @@ -33,6 +33,11 @@ operators (starting in <xref linkend="xoper"/>) </para> </listitem> + <listitem> + <para> + subscripting procedure (starting in <xref linkend="xsubscripting"/>) + </para> + </listitem> <listitem> <para> operator classes for indexes (starting in <xref linkend="xindex"/>) @@ -481,6 +486,7 @@ RETURNS anycompatible AS ... &xaggr; &xtypes; &xoper; + &xsubscripting; &xindex; diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 64b5da0070..4c3fd37fc1 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -69,6 +69,7 @@ <!ENTITY xplang SYSTEM "xplang.sgml"> <!ENTITY xoper SYSTEM "xoper.sgml"> <!ENTITY xtypes SYSTEM "xtypes.sgml"> +<!ENTITY xsubscripting SYSTEM "xsubscripting.sgml"> <!ENTITY plperl SYSTEM "plperl.sgml"> <!ENTITY plpython SYSTEM "plpython.sgml"> <!ENTITY plsql SYSTEM "plpgsql.sgml"> diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml index c0a6554d4d..5c538dca05 100644 --- a/doc/src/sgml/json.sgml +++ b/doc/src/sgml/json.sgml @@ -602,6 +602,45 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu </para> </sect2> + <sect2 id="jsonb-subscripting"> + <title><type>jsonb</type> Subscripting</title> + <para> + <type>jsonb</type> data type supports array-style subscripting expressions + to extract or update particular elements. It's possible to use multiple + subscripting expressions to extract nested values. In this case, a chain of + subscripting expressions follows the same rules as the + <literal>path</literal> argument in <literal>jsonb_set</literal> function, + e.g. in case of arrays it is a 0-based operation or that negative integers + that appear in <literal>path</literal> count from the end of JSON arrays. + The result of subscripting expressions is always jsonb data type. An + example of subscripting syntax: +<programlisting> +-- Extract value by key +SELECT ('{"a": 1}'::jsonb)['a']; + +-- Extract nested value by key path +SELECT ('{"a": {"b": {"c": 1}}}'::jsonb)['a']['b']['c']; + +-- Extract element by index +SELECT ('[1, "2", null]'::jsonb)[1]; + +-- Update value by key +UPDATE table_name SET jsonb_field['key'] = 1; + +-- Select records using where clause with subscripting. Since the result of +-- subscripting is jsonb and we basically want to compare two jsonb objects, we +-- need to put the value in double quotes to be able to convert it to jsonb. +SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"'; +</programlisting> + + There is no special indexing support for such kind of expressions, but you + can always create a functional index that includes it +<programlisting> +CREATE INDEX idx ON table_name ((jsonb_field['key'])); +</programlisting> + </para> + </sect2> + <sect2> <title>Transforms</title> diff --git a/doc/src/sgml/ref/create_type.sgml b/doc/src/sgml/ref/create_type.sgml index 111f8e65d2..a34df4d247 100644 --- a/doc/src/sgml/ref/create_type.sgml +++ b/doc/src/sgml/ref/create_type.sgml @@ -54,6 +54,7 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> ( [ , ELEMENT = <replaceable class="parameter">element</replaceable> ] [ , DELIMITER = <replaceable class="parameter">delimiter</replaceable> ] [ , COLLATABLE = <replaceable class="parameter">collatable</replaceable> ] + [ , SUBSCRIPTING_HANDLER = <replaceable class="parameter">subscripting_handler_function</replaceable> ] ) CREATE TYPE <replaceable class="parameter">name</replaceable> @@ -196,8 +197,9 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> <replaceable class="parameter">receive_function</replaceable>, <replaceable class="parameter">send_function</replaceable>, <replaceable class="parameter">type_modifier_input_function</replaceable>, - <replaceable class="parameter">type_modifier_output_function</replaceable> and - <replaceable class="parameter">analyze_function</replaceable> + <replaceable class="parameter">type_modifier_output_function</replaceable>, + <replaceable class="parameter">analyze_function</replaceable>, + <replaceable class="parameter">subscripting_handler_function</replaceable> are optional. Generally these functions have to be coded in C or another low-level language. </para> @@ -454,6 +456,22 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> make use of the collation information; this does not happen automatically merely by marking the type collatable. </para> + + <para> + The optional + <replaceable class="parameter">subscripting_handler_function</replaceable> + contains type-specific logic for subscripting of the data type. + By default, there is no such function provided, which means that the data + type doesn't support subscripting. The subscripting function must be + declared to take a single argument of type <type>internal</type>, and return + a <type>internal</type> result. There are two examples of implementation for + subscripting functions in case of array + (<replaceable class="parameter">array_subscripting_handler</replaceable>) + and jsonb + (<replaceable class="parameter">jsonb_subscripting_handler</replaceable>) + types in <filename>src/backend/utils/adt/arrayfuncs.c</filename> and + <filename>src/backend/utils/adt/jsonfuncs.c</filename>, respectively. + </para> </refsect2> <refsect2> @@ -769,6 +787,17 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> </para> </listitem> </varlistentry> + + <varlistentry> + <term><replaceable class="parameter">subscripting_handler_function</replaceable></term> + <listitem> + <para> + The name of a function that returns list of type-specific callback functions to + support subscripting logic for the data type. + </para> + </listitem> + </varlistentry> + </variablelist> </refsect1> diff --git a/doc/src/sgml/xsubscripting.sgml b/doc/src/sgml/xsubscripting.sgml new file mode 100644 index 0000000000..7224e81fa2 --- /dev/null +++ b/doc/src/sgml/xsubscripting.sgml @@ -0,0 +1,111 @@ +<!-- doc/src/sgml/xsubscripting.sgml --> + + <sect1 id="xsubscripting"> + <title>User-defined subscripting procedure</title> + + <indexterm zone="xsubscripting"> + <primary>custom subscripting</primary> + </indexterm> + <para> + When you define a new base type, you can also specify a custom procedure to + handle subscripting expressions. It must contain logic for verification and + evaluation of this expression, i.e. fetching or updating some data in this + data type. For instance: +</para> +<programlisting><![CDATA[ +typedef struct Custom +{ + int first; + int second; +} Custom; + +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = someType; + sbsref->refassgntype = someType; + + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + // some validation and coercion logic + + return sbsref; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some assignment logic + + return newContainer; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some fetch logic based on sbstate +}]]> +</programlisting> + +<para> + Then you can define a subscripting procedure and a custom data type: +</para> +<programlisting> +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '<replaceable>filename</replaceable>' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 4, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler, +); +</programlisting> + +<para> + and use it as usual: +</para> +<programlisting> +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; +</programlisting> + + + <para> + The examples of custom subscripting implementation can be found in + <filename>subscripting.sql</filename> and <filename>subscripting.c</filename> + in the <filename>src/tutorial</filename> directory of the source distribution. + See the <filename>README</filename> file in that directory for instructions + about running the examples. + </para> + +</sect1> diff --git a/src/tutorial/Makefile b/src/tutorial/Makefile index 16dc390f71..0ead60c2d4 100644 --- a/src/tutorial/Makefile +++ b/src/tutorial/Makefile @@ -13,8 +13,8 @@ # #------------------------------------------------------------------------- -MODULES = complex funcs -DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql +MODULES = complex funcs subscripting +DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql subscripting.sql ifdef NO_PGXS subdir = src/tutorial diff --git a/src/tutorial/subscripting.c b/src/tutorial/subscripting.c new file mode 100644 index 0000000000..1eb8c45652 --- /dev/null +++ b/src/tutorial/subscripting.c @@ -0,0 +1,201 @@ +/* + * src/tutorial/subscripting.c + * + ****************************************************************************** + This file contains routines that can be bound to a Postgres backend and + called by the backend in the process of processing queries. The calling + format for these routines is dictated by Postgres architecture. +******************************************************************************/ + +#include "postgres.h" + +#include "catalog/pg_type.h" +#include "executor/executor.h" +#include "executor/execExpr.h" +#include "nodes/nodeFuncs.h" +#include "parser/parse_coerce.h" +#include "utils/builtins.h" +#include "utils/fmgrprotos.h" + +PG_MODULE_MAGIC; + +typedef struct Custom +{ + int first; + int second; +} Custom; + +SubscriptingRef * custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref); +SubscriptingRef * custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate); +Datum custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate); +Datum custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate); + +PG_FUNCTION_INFO_V1(custom_in); +PG_FUNCTION_INFO_V1(custom_out); +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +/***************************************************************************** + * Input/Output functions + *****************************************************************************/ + +Datum +custom_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + int firstValue, + secondValue; + Custom *result; + + if (sscanf(str, " ( %d , %d )", &firstValue, &secondValue) != 2) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for complex: \"%s\"", + str))); + + + result = (Custom *) palloc(sizeof(Custom)); + result->first = firstValue; + result->second = secondValue; + PG_RETURN_POINTER(result); +} + +Datum +custom_out(PG_FUNCTION_ARGS) +{ + Custom *custom = (Custom *) PG_GETARG_POINTER(0); + char *result; + + result = psprintf("(%d, %d)", custom->first, custom->second); + PG_RETURN_CSTRING(result); +} + +/***************************************************************************** + * Custom subscripting logic functions + *****************************************************************************/ + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = INT4OID; + sbsref->refassgntype = INT4OID; + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + List *upperIndexpr = NIL; + ListCell *l; + + if (sbsref->reflowerindexpr != NIL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *)lfirst(sbsref->reflowerindexpr->head)))))); + + foreach(l, sbsref->refupperindexpr) + { + Node *subexpr = (Node *) lfirst(l); + + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *) lfirst(sbsref->refupperindexpr->head)))))); + + subexpr = coerce_to_target_type(pstate, + subexpr, exprType(subexpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript must have integer type"), + parser_errposition(pstate, exprLocation(subexpr)))); + + upperIndexpr = lappend(upperIndexpr, subexpr); + + if (isAssignment) + { + Node *assignExpr = (Node *) sbsref->refassgnexpr; + Node *new_from; + + new_from = coerce_to_target_type(pstate, + assignExpr, exprType(assignExpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (new_from == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom assignment requires int type"), + errhint("You will need to rewrite or cast the expression."), + parser_errposition(pstate, exprLocation(assignExpr)))); + sbsref->refassgnexpr = (Expr *)new_from; + } + } + + sbsref->refupperindexpr = upperIndexpr; + + return sbsref; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + Custom *container= (Custom *) containerSource; + int index; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + return (Datum) container->first; + else + return (Datum) container->second; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + int index; + Custom *container = (Custom *) containerSource; + + if (sbstate->resnull) + return containerSource; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + container->first = DatumGetInt32(sbstate->replacevalue); + else + container->second = DatumGetInt32(sbstate->replacevalue); + + return (Datum) container; +} diff --git a/src/tutorial/subscripting.source b/src/tutorial/subscripting.source new file mode 100644 index 0000000000..837cf30612 --- /dev/null +++ b/src/tutorial/subscripting.source @@ -0,0 +1,71 @@ +--------------------------------------------------------------------------- +-- +-- subscripting.sql- +-- This file shows how to create a new subscripting procedure for +-- user-defined type. +-- +-- +-- Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group +-- Portions Copyright (c) 1994, Regents of the University of California +-- +-- src/tutorial/subscripting.source +-- +--------------------------------------------------------------------------- + +----------------------------- +-- Creating a new type: +-- We are going to create a new type called 'complex' which represents +-- complex numbers. +-- A user-defined type must have an input and an output function, and +-- optionally can have binary input and output functions. All of these +-- are usually user-defined C functions. +----------------------------- + +-- Assume the user defined functions are in /home/erthalion/programms/postgresql-master/src/tutorial/complex$DLSUFFIX +-- (we do not want to assume this is in the dynamic loader search path). +-- Look at $PWD/complex.c for the source. Note that we declare all of +-- them as STRICT, so we do not need to cope with NULL inputs in the +-- C code. We also mark them IMMUTABLE, since they always return the +-- same outputs given the same inputs. + +-- the input function 'complex_in' takes a null-terminated string (the +-- textual representation of the type) and turns it into the internal +-- (in memory) representation. You will get a message telling you 'complex' +-- does not exist yet but that's okay. + +CREATE FUNCTION custom_in(cstring) + RETURNS custom + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +-- the output function 'complex_out' takes the internal representation and +-- converts it into the textual representation. + +CREATE FUNCTION custom_out(custom) + RETURNS cstring + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 8, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler +); + +-- we can use it in a table + +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; -- 2.21.0 --u66dlpg6gaevsofr Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v33-0005-Filling-gaps-in-jsonb-arrays.patch" ^ permalink raw reply [nested|flat] 28+ messages in thread
* [PATCH v33 4/5] Subscripting documentation @ 2019-02-01 10:47 erthalion <[email protected]> 0 siblings, 0 replies; 28+ messages in thread From: erthalion @ 2019-02-01 10:47 UTC (permalink / raw) Supporting documentation for generalized subscripting. It includes the description of a new field in pg_type, the new section for jsonb documentation about subscripting feature on this data type, and also the tutorial about how to write subscripting operator for a custom data type. Reviewed-by: Tom Lane, Arthur Zakirov, Oleksandr Shulgin --- doc/src/sgml/catalogs.sgml | 8 ++ doc/src/sgml/extend.sgml | 6 + doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/json.sgml | 39 ++++++ doc/src/sgml/ref/create_type.sgml | 33 ++++- doc/src/sgml/xsubscripting.sgml | 111 +++++++++++++++++ src/tutorial/Makefile | 4 +- src/tutorial/subscripting.c | 201 ++++++++++++++++++++++++++++++ src/tutorial/subscripting.source | 71 +++++++++++ 9 files changed, 470 insertions(+), 4 deletions(-) create mode 100644 doc/src/sgml/xsubscripting.sgml create mode 100644 src/tutorial/subscripting.c create mode 100644 src/tutorial/subscripting.source diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 048ff284f7..b17e9ee382 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -8971,6 +8971,14 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l </para></entry> </row> + <row> + <entry><structfield>typsubshandler</structfield></entry> + <entry><type>regproc</type></entry> + <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry> + <entry>Custom subscripting function with type-specific logic for parsing + and validation, or 0 if this type doesn't support subscripting.</entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>typdefault</structfield> <type>text</type> diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml index 890ff97b7a..65cf5706b6 100644 --- a/doc/src/sgml/extend.sgml +++ b/doc/src/sgml/extend.sgml @@ -33,6 +33,11 @@ operators (starting in <xref linkend="xoper"/>) </para> </listitem> + <listitem> + <para> + subscripting procedure (starting in <xref linkend="xsubscripting"/>) + </para> + </listitem> <listitem> <para> operator classes for indexes (starting in <xref linkend="xindex"/>) @@ -481,6 +486,7 @@ RETURNS anycompatible AS ... &xaggr; &xtypes; &xoper; + &xsubscripting; &xindex; diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 64b5da0070..4c3fd37fc1 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -69,6 +69,7 @@ <!ENTITY xplang SYSTEM "xplang.sgml"> <!ENTITY xoper SYSTEM "xoper.sgml"> <!ENTITY xtypes SYSTEM "xtypes.sgml"> +<!ENTITY xsubscripting SYSTEM "xsubscripting.sgml"> <!ENTITY plperl SYSTEM "plperl.sgml"> <!ENTITY plpython SYSTEM "plpython.sgml"> <!ENTITY plsql SYSTEM "plpgsql.sgml"> diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml index c0a6554d4d..5c538dca05 100644 --- a/doc/src/sgml/json.sgml +++ b/doc/src/sgml/json.sgml @@ -602,6 +602,45 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu </para> </sect2> + <sect2 id="jsonb-subscripting"> + <title><type>jsonb</type> Subscripting</title> + <para> + <type>jsonb</type> data type supports array-style subscripting expressions + to extract or update particular elements. It's possible to use multiple + subscripting expressions to extract nested values. In this case, a chain of + subscripting expressions follows the same rules as the + <literal>path</literal> argument in <literal>jsonb_set</literal> function, + e.g. in case of arrays it is a 0-based operation or that negative integers + that appear in <literal>path</literal> count from the end of JSON arrays. + The result of subscripting expressions is always jsonb data type. An + example of subscripting syntax: +<programlisting> +-- Extract value by key +SELECT ('{"a": 1}'::jsonb)['a']; + +-- Extract nested value by key path +SELECT ('{"a": {"b": {"c": 1}}}'::jsonb)['a']['b']['c']; + +-- Extract element by index +SELECT ('[1, "2", null]'::jsonb)[1]; + +-- Update value by key +UPDATE table_name SET jsonb_field['key'] = 1; + +-- Select records using where clause with subscripting. Since the result of +-- subscripting is jsonb and we basically want to compare two jsonb objects, we +-- need to put the value in double quotes to be able to convert it to jsonb. +SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"'; +</programlisting> + + There is no special indexing support for such kind of expressions, but you + can always create a functional index that includes it +<programlisting> +CREATE INDEX idx ON table_name ((jsonb_field['key'])); +</programlisting> + </para> + </sect2> + <sect2> <title>Transforms</title> diff --git a/doc/src/sgml/ref/create_type.sgml b/doc/src/sgml/ref/create_type.sgml index 111f8e65d2..a34df4d247 100644 --- a/doc/src/sgml/ref/create_type.sgml +++ b/doc/src/sgml/ref/create_type.sgml @@ -54,6 +54,7 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> ( [ , ELEMENT = <replaceable class="parameter">element</replaceable> ] [ , DELIMITER = <replaceable class="parameter">delimiter</replaceable> ] [ , COLLATABLE = <replaceable class="parameter">collatable</replaceable> ] + [ , SUBSCRIPTING_HANDLER = <replaceable class="parameter">subscripting_handler_function</replaceable> ] ) CREATE TYPE <replaceable class="parameter">name</replaceable> @@ -196,8 +197,9 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> <replaceable class="parameter">receive_function</replaceable>, <replaceable class="parameter">send_function</replaceable>, <replaceable class="parameter">type_modifier_input_function</replaceable>, - <replaceable class="parameter">type_modifier_output_function</replaceable> and - <replaceable class="parameter">analyze_function</replaceable> + <replaceable class="parameter">type_modifier_output_function</replaceable>, + <replaceable class="parameter">analyze_function</replaceable>, + <replaceable class="parameter">subscripting_handler_function</replaceable> are optional. Generally these functions have to be coded in C or another low-level language. </para> @@ -454,6 +456,22 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> make use of the collation information; this does not happen automatically merely by marking the type collatable. </para> + + <para> + The optional + <replaceable class="parameter">subscripting_handler_function</replaceable> + contains type-specific logic for subscripting of the data type. + By default, there is no such function provided, which means that the data + type doesn't support subscripting. The subscripting function must be + declared to take a single argument of type <type>internal</type>, and return + a <type>internal</type> result. There are two examples of implementation for + subscripting functions in case of array + (<replaceable class="parameter">array_subscripting_handler</replaceable>) + and jsonb + (<replaceable class="parameter">jsonb_subscripting_handler</replaceable>) + types in <filename>src/backend/utils/adt/arrayfuncs.c</filename> and + <filename>src/backend/utils/adt/jsonfuncs.c</filename>, respectively. + </para> </refsect2> <refsect2> @@ -769,6 +787,17 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> </para> </listitem> </varlistentry> + + <varlistentry> + <term><replaceable class="parameter">subscripting_handler_function</replaceable></term> + <listitem> + <para> + The name of a function that returns list of type-specific callback functions to + support subscripting logic for the data type. + </para> + </listitem> + </varlistentry> + </variablelist> </refsect1> diff --git a/doc/src/sgml/xsubscripting.sgml b/doc/src/sgml/xsubscripting.sgml new file mode 100644 index 0000000000..7224e81fa2 --- /dev/null +++ b/doc/src/sgml/xsubscripting.sgml @@ -0,0 +1,111 @@ +<!-- doc/src/sgml/xsubscripting.sgml --> + + <sect1 id="xsubscripting"> + <title>User-defined subscripting procedure</title> + + <indexterm zone="xsubscripting"> + <primary>custom subscripting</primary> + </indexterm> + <para> + When you define a new base type, you can also specify a custom procedure to + handle subscripting expressions. It must contain logic for verification and + evaluation of this expression, i.e. fetching or updating some data in this + data type. For instance: +</para> +<programlisting><![CDATA[ +typedef struct Custom +{ + int first; + int second; +} Custom; + +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = someType; + sbsref->refassgntype = someType; + + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + // some validation and coercion logic + + return sbsref; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some assignment logic + + return newContainer; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some fetch logic based on sbstate +}]]> +</programlisting> + +<para> + Then you can define a subscripting procedure and a custom data type: +</para> +<programlisting> +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '<replaceable>filename</replaceable>' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 4, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler, +); +</programlisting> + +<para> + and use it as usual: +</para> +<programlisting> +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; +</programlisting> + + + <para> + The examples of custom subscripting implementation can be found in + <filename>subscripting.sql</filename> and <filename>subscripting.c</filename> + in the <filename>src/tutorial</filename> directory of the source distribution. + See the <filename>README</filename> file in that directory for instructions + about running the examples. + </para> + +</sect1> diff --git a/src/tutorial/Makefile b/src/tutorial/Makefile index 16dc390f71..0ead60c2d4 100644 --- a/src/tutorial/Makefile +++ b/src/tutorial/Makefile @@ -13,8 +13,8 @@ # #------------------------------------------------------------------------- -MODULES = complex funcs -DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql +MODULES = complex funcs subscripting +DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql subscripting.sql ifdef NO_PGXS subdir = src/tutorial diff --git a/src/tutorial/subscripting.c b/src/tutorial/subscripting.c new file mode 100644 index 0000000000..1eb8c45652 --- /dev/null +++ b/src/tutorial/subscripting.c @@ -0,0 +1,201 @@ +/* + * src/tutorial/subscripting.c + * + ****************************************************************************** + This file contains routines that can be bound to a Postgres backend and + called by the backend in the process of processing queries. The calling + format for these routines is dictated by Postgres architecture. +******************************************************************************/ + +#include "postgres.h" + +#include "catalog/pg_type.h" +#include "executor/executor.h" +#include "executor/execExpr.h" +#include "nodes/nodeFuncs.h" +#include "parser/parse_coerce.h" +#include "utils/builtins.h" +#include "utils/fmgrprotos.h" + +PG_MODULE_MAGIC; + +typedef struct Custom +{ + int first; + int second; +} Custom; + +SubscriptingRef * custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref); +SubscriptingRef * custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate); +Datum custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate); +Datum custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate); + +PG_FUNCTION_INFO_V1(custom_in); +PG_FUNCTION_INFO_V1(custom_out); +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +/***************************************************************************** + * Input/Output functions + *****************************************************************************/ + +Datum +custom_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + int firstValue, + secondValue; + Custom *result; + + if (sscanf(str, " ( %d , %d )", &firstValue, &secondValue) != 2) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for complex: \"%s\"", + str))); + + + result = (Custom *) palloc(sizeof(Custom)); + result->first = firstValue; + result->second = secondValue; + PG_RETURN_POINTER(result); +} + +Datum +custom_out(PG_FUNCTION_ARGS) +{ + Custom *custom = (Custom *) PG_GETARG_POINTER(0); + char *result; + + result = psprintf("(%d, %d)", custom->first, custom->second); + PG_RETURN_CSTRING(result); +} + +/***************************************************************************** + * Custom subscripting logic functions + *****************************************************************************/ + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = INT4OID; + sbsref->refassgntype = INT4OID; + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + List *upperIndexpr = NIL; + ListCell *l; + + if (sbsref->reflowerindexpr != NIL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *)lfirst(sbsref->reflowerindexpr->head)))))); + + foreach(l, sbsref->refupperindexpr) + { + Node *subexpr = (Node *) lfirst(l); + + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *) lfirst(sbsref->refupperindexpr->head)))))); + + subexpr = coerce_to_target_type(pstate, + subexpr, exprType(subexpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript must have integer type"), + parser_errposition(pstate, exprLocation(subexpr)))); + + upperIndexpr = lappend(upperIndexpr, subexpr); + + if (isAssignment) + { + Node *assignExpr = (Node *) sbsref->refassgnexpr; + Node *new_from; + + new_from = coerce_to_target_type(pstate, + assignExpr, exprType(assignExpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (new_from == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom assignment requires int type"), + errhint("You will need to rewrite or cast the expression."), + parser_errposition(pstate, exprLocation(assignExpr)))); + sbsref->refassgnexpr = (Expr *)new_from; + } + } + + sbsref->refupperindexpr = upperIndexpr; + + return sbsref; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + Custom *container= (Custom *) containerSource; + int index; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + return (Datum) container->first; + else + return (Datum) container->second; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + int index; + Custom *container = (Custom *) containerSource; + + if (sbstate->resnull) + return containerSource; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + container->first = DatumGetInt32(sbstate->replacevalue); + else + container->second = DatumGetInt32(sbstate->replacevalue); + + return (Datum) container; +} diff --git a/src/tutorial/subscripting.source b/src/tutorial/subscripting.source new file mode 100644 index 0000000000..837cf30612 --- /dev/null +++ b/src/tutorial/subscripting.source @@ -0,0 +1,71 @@ +--------------------------------------------------------------------------- +-- +-- subscripting.sql- +-- This file shows how to create a new subscripting procedure for +-- user-defined type. +-- +-- +-- Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group +-- Portions Copyright (c) 1994, Regents of the University of California +-- +-- src/tutorial/subscripting.source +-- +--------------------------------------------------------------------------- + +----------------------------- +-- Creating a new type: +-- We are going to create a new type called 'complex' which represents +-- complex numbers. +-- A user-defined type must have an input and an output function, and +-- optionally can have binary input and output functions. All of these +-- are usually user-defined C functions. +----------------------------- + +-- Assume the user defined functions are in /home/erthalion/programms/postgresql-master/src/tutorial/complex$DLSUFFIX +-- (we do not want to assume this is in the dynamic loader search path). +-- Look at $PWD/complex.c for the source. Note that we declare all of +-- them as STRICT, so we do not need to cope with NULL inputs in the +-- C code. We also mark them IMMUTABLE, since they always return the +-- same outputs given the same inputs. + +-- the input function 'complex_in' takes a null-terminated string (the +-- textual representation of the type) and turns it into the internal +-- (in memory) representation. You will get a message telling you 'complex' +-- does not exist yet but that's okay. + +CREATE FUNCTION custom_in(cstring) + RETURNS custom + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +-- the output function 'complex_out' takes the internal representation and +-- converts it into the textual representation. + +CREATE FUNCTION custom_out(custom) + RETURNS cstring + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 8, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler +); + +-- we can use it in a table + +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; -- 2.21.0 --u66dlpg6gaevsofr Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v33-0005-Filling-gaps-in-jsonb-arrays.patch" ^ permalink raw reply [nested|flat] 28+ messages in thread
* [PATCH v33 4/5] Subscripting documentation @ 2019-02-01 10:47 erthalion <[email protected]> 0 siblings, 0 replies; 28+ messages in thread From: erthalion @ 2019-02-01 10:47 UTC (permalink / raw) Supporting documentation for generalized subscripting. It includes the description of a new field in pg_type, the new section for jsonb documentation about subscripting feature on this data type, and also the tutorial about how to write subscripting operator for a custom data type. Reviewed-by: Tom Lane, Arthur Zakirov, Oleksandr Shulgin --- doc/src/sgml/catalogs.sgml | 8 ++ doc/src/sgml/extend.sgml | 6 + doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/json.sgml | 39 ++++++ doc/src/sgml/ref/create_type.sgml | 33 ++++- doc/src/sgml/xsubscripting.sgml | 111 +++++++++++++++++ src/tutorial/Makefile | 4 +- src/tutorial/subscripting.c | 201 ++++++++++++++++++++++++++++++ src/tutorial/subscripting.source | 71 +++++++++++ 9 files changed, 470 insertions(+), 4 deletions(-) create mode 100644 doc/src/sgml/xsubscripting.sgml create mode 100644 src/tutorial/subscripting.c create mode 100644 src/tutorial/subscripting.source diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 048ff284f7..b17e9ee382 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -8971,6 +8971,14 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l </para></entry> </row> + <row> + <entry><structfield>typsubshandler</structfield></entry> + <entry><type>regproc</type></entry> + <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry> + <entry>Custom subscripting function with type-specific logic for parsing + and validation, or 0 if this type doesn't support subscripting.</entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>typdefault</structfield> <type>text</type> diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml index 890ff97b7a..65cf5706b6 100644 --- a/doc/src/sgml/extend.sgml +++ b/doc/src/sgml/extend.sgml @@ -33,6 +33,11 @@ operators (starting in <xref linkend="xoper"/>) </para> </listitem> + <listitem> + <para> + subscripting procedure (starting in <xref linkend="xsubscripting"/>) + </para> + </listitem> <listitem> <para> operator classes for indexes (starting in <xref linkend="xindex"/>) @@ -481,6 +486,7 @@ RETURNS anycompatible AS ... &xaggr; &xtypes; &xoper; + &xsubscripting; &xindex; diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 64b5da0070..4c3fd37fc1 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -69,6 +69,7 @@ <!ENTITY xplang SYSTEM "xplang.sgml"> <!ENTITY xoper SYSTEM "xoper.sgml"> <!ENTITY xtypes SYSTEM "xtypes.sgml"> +<!ENTITY xsubscripting SYSTEM "xsubscripting.sgml"> <!ENTITY plperl SYSTEM "plperl.sgml"> <!ENTITY plpython SYSTEM "plpython.sgml"> <!ENTITY plsql SYSTEM "plpgsql.sgml"> diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml index c0a6554d4d..5c538dca05 100644 --- a/doc/src/sgml/json.sgml +++ b/doc/src/sgml/json.sgml @@ -602,6 +602,45 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu </para> </sect2> + <sect2 id="jsonb-subscripting"> + <title><type>jsonb</type> Subscripting</title> + <para> + <type>jsonb</type> data type supports array-style subscripting expressions + to extract or update particular elements. It's possible to use multiple + subscripting expressions to extract nested values. In this case, a chain of + subscripting expressions follows the same rules as the + <literal>path</literal> argument in <literal>jsonb_set</literal> function, + e.g. in case of arrays it is a 0-based operation or that negative integers + that appear in <literal>path</literal> count from the end of JSON arrays. + The result of subscripting expressions is always jsonb data type. An + example of subscripting syntax: +<programlisting> +-- Extract value by key +SELECT ('{"a": 1}'::jsonb)['a']; + +-- Extract nested value by key path +SELECT ('{"a": {"b": {"c": 1}}}'::jsonb)['a']['b']['c']; + +-- Extract element by index +SELECT ('[1, "2", null]'::jsonb)[1]; + +-- Update value by key +UPDATE table_name SET jsonb_field['key'] = 1; + +-- Select records using where clause with subscripting. Since the result of +-- subscripting is jsonb and we basically want to compare two jsonb objects, we +-- need to put the value in double quotes to be able to convert it to jsonb. +SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"'; +</programlisting> + + There is no special indexing support for such kind of expressions, but you + can always create a functional index that includes it +<programlisting> +CREATE INDEX idx ON table_name ((jsonb_field['key'])); +</programlisting> + </para> + </sect2> + <sect2> <title>Transforms</title> diff --git a/doc/src/sgml/ref/create_type.sgml b/doc/src/sgml/ref/create_type.sgml index 111f8e65d2..a34df4d247 100644 --- a/doc/src/sgml/ref/create_type.sgml +++ b/doc/src/sgml/ref/create_type.sgml @@ -54,6 +54,7 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> ( [ , ELEMENT = <replaceable class="parameter">element</replaceable> ] [ , DELIMITER = <replaceable class="parameter">delimiter</replaceable> ] [ , COLLATABLE = <replaceable class="parameter">collatable</replaceable> ] + [ , SUBSCRIPTING_HANDLER = <replaceable class="parameter">subscripting_handler_function</replaceable> ] ) CREATE TYPE <replaceable class="parameter">name</replaceable> @@ -196,8 +197,9 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> <replaceable class="parameter">receive_function</replaceable>, <replaceable class="parameter">send_function</replaceable>, <replaceable class="parameter">type_modifier_input_function</replaceable>, - <replaceable class="parameter">type_modifier_output_function</replaceable> and - <replaceable class="parameter">analyze_function</replaceable> + <replaceable class="parameter">type_modifier_output_function</replaceable>, + <replaceable class="parameter">analyze_function</replaceable>, + <replaceable class="parameter">subscripting_handler_function</replaceable> are optional. Generally these functions have to be coded in C or another low-level language. </para> @@ -454,6 +456,22 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> make use of the collation information; this does not happen automatically merely by marking the type collatable. </para> + + <para> + The optional + <replaceable class="parameter">subscripting_handler_function</replaceable> + contains type-specific logic for subscripting of the data type. + By default, there is no such function provided, which means that the data + type doesn't support subscripting. The subscripting function must be + declared to take a single argument of type <type>internal</type>, and return + a <type>internal</type> result. There are two examples of implementation for + subscripting functions in case of array + (<replaceable class="parameter">array_subscripting_handler</replaceable>) + and jsonb + (<replaceable class="parameter">jsonb_subscripting_handler</replaceable>) + types in <filename>src/backend/utils/adt/arrayfuncs.c</filename> and + <filename>src/backend/utils/adt/jsonfuncs.c</filename>, respectively. + </para> </refsect2> <refsect2> @@ -769,6 +787,17 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> </para> </listitem> </varlistentry> + + <varlistentry> + <term><replaceable class="parameter">subscripting_handler_function</replaceable></term> + <listitem> + <para> + The name of a function that returns list of type-specific callback functions to + support subscripting logic for the data type. + </para> + </listitem> + </varlistentry> + </variablelist> </refsect1> diff --git a/doc/src/sgml/xsubscripting.sgml b/doc/src/sgml/xsubscripting.sgml new file mode 100644 index 0000000000..7224e81fa2 --- /dev/null +++ b/doc/src/sgml/xsubscripting.sgml @@ -0,0 +1,111 @@ +<!-- doc/src/sgml/xsubscripting.sgml --> + + <sect1 id="xsubscripting"> + <title>User-defined subscripting procedure</title> + + <indexterm zone="xsubscripting"> + <primary>custom subscripting</primary> + </indexterm> + <para> + When you define a new base type, you can also specify a custom procedure to + handle subscripting expressions. It must contain logic for verification and + evaluation of this expression, i.e. fetching or updating some data in this + data type. For instance: +</para> +<programlisting><![CDATA[ +typedef struct Custom +{ + int first; + int second; +} Custom; + +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = someType; + sbsref->refassgntype = someType; + + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + // some validation and coercion logic + + return sbsref; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some assignment logic + + return newContainer; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some fetch logic based on sbstate +}]]> +</programlisting> + +<para> + Then you can define a subscripting procedure and a custom data type: +</para> +<programlisting> +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '<replaceable>filename</replaceable>' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 4, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler, +); +</programlisting> + +<para> + and use it as usual: +</para> +<programlisting> +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; +</programlisting> + + + <para> + The examples of custom subscripting implementation can be found in + <filename>subscripting.sql</filename> and <filename>subscripting.c</filename> + in the <filename>src/tutorial</filename> directory of the source distribution. + See the <filename>README</filename> file in that directory for instructions + about running the examples. + </para> + +</sect1> diff --git a/src/tutorial/Makefile b/src/tutorial/Makefile index 16dc390f71..0ead60c2d4 100644 --- a/src/tutorial/Makefile +++ b/src/tutorial/Makefile @@ -13,8 +13,8 @@ # #------------------------------------------------------------------------- -MODULES = complex funcs -DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql +MODULES = complex funcs subscripting +DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql subscripting.sql ifdef NO_PGXS subdir = src/tutorial diff --git a/src/tutorial/subscripting.c b/src/tutorial/subscripting.c new file mode 100644 index 0000000000..1eb8c45652 --- /dev/null +++ b/src/tutorial/subscripting.c @@ -0,0 +1,201 @@ +/* + * src/tutorial/subscripting.c + * + ****************************************************************************** + This file contains routines that can be bound to a Postgres backend and + called by the backend in the process of processing queries. The calling + format for these routines is dictated by Postgres architecture. +******************************************************************************/ + +#include "postgres.h" + +#include "catalog/pg_type.h" +#include "executor/executor.h" +#include "executor/execExpr.h" +#include "nodes/nodeFuncs.h" +#include "parser/parse_coerce.h" +#include "utils/builtins.h" +#include "utils/fmgrprotos.h" + +PG_MODULE_MAGIC; + +typedef struct Custom +{ + int first; + int second; +} Custom; + +SubscriptingRef * custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref); +SubscriptingRef * custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate); +Datum custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate); +Datum custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate); + +PG_FUNCTION_INFO_V1(custom_in); +PG_FUNCTION_INFO_V1(custom_out); +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +/***************************************************************************** + * Input/Output functions + *****************************************************************************/ + +Datum +custom_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + int firstValue, + secondValue; + Custom *result; + + if (sscanf(str, " ( %d , %d )", &firstValue, &secondValue) != 2) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for complex: \"%s\"", + str))); + + + result = (Custom *) palloc(sizeof(Custom)); + result->first = firstValue; + result->second = secondValue; + PG_RETURN_POINTER(result); +} + +Datum +custom_out(PG_FUNCTION_ARGS) +{ + Custom *custom = (Custom *) PG_GETARG_POINTER(0); + char *result; + + result = psprintf("(%d, %d)", custom->first, custom->second); + PG_RETURN_CSTRING(result); +} + +/***************************************************************************** + * Custom subscripting logic functions + *****************************************************************************/ + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = INT4OID; + sbsref->refassgntype = INT4OID; + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + List *upperIndexpr = NIL; + ListCell *l; + + if (sbsref->reflowerindexpr != NIL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *)lfirst(sbsref->reflowerindexpr->head)))))); + + foreach(l, sbsref->refupperindexpr) + { + Node *subexpr = (Node *) lfirst(l); + + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *) lfirst(sbsref->refupperindexpr->head)))))); + + subexpr = coerce_to_target_type(pstate, + subexpr, exprType(subexpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript must have integer type"), + parser_errposition(pstate, exprLocation(subexpr)))); + + upperIndexpr = lappend(upperIndexpr, subexpr); + + if (isAssignment) + { + Node *assignExpr = (Node *) sbsref->refassgnexpr; + Node *new_from; + + new_from = coerce_to_target_type(pstate, + assignExpr, exprType(assignExpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (new_from == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom assignment requires int type"), + errhint("You will need to rewrite or cast the expression."), + parser_errposition(pstate, exprLocation(assignExpr)))); + sbsref->refassgnexpr = (Expr *)new_from; + } + } + + sbsref->refupperindexpr = upperIndexpr; + + return sbsref; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + Custom *container= (Custom *) containerSource; + int index; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + return (Datum) container->first; + else + return (Datum) container->second; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + int index; + Custom *container = (Custom *) containerSource; + + if (sbstate->resnull) + return containerSource; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + container->first = DatumGetInt32(sbstate->replacevalue); + else + container->second = DatumGetInt32(sbstate->replacevalue); + + return (Datum) container; +} diff --git a/src/tutorial/subscripting.source b/src/tutorial/subscripting.source new file mode 100644 index 0000000000..837cf30612 --- /dev/null +++ b/src/tutorial/subscripting.source @@ -0,0 +1,71 @@ +--------------------------------------------------------------------------- +-- +-- subscripting.sql- +-- This file shows how to create a new subscripting procedure for +-- user-defined type. +-- +-- +-- Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group +-- Portions Copyright (c) 1994, Regents of the University of California +-- +-- src/tutorial/subscripting.source +-- +--------------------------------------------------------------------------- + +----------------------------- +-- Creating a new type: +-- We are going to create a new type called 'complex' which represents +-- complex numbers. +-- A user-defined type must have an input and an output function, and +-- optionally can have binary input and output functions. All of these +-- are usually user-defined C functions. +----------------------------- + +-- Assume the user defined functions are in /home/erthalion/programms/postgresql-master/src/tutorial/complex$DLSUFFIX +-- (we do not want to assume this is in the dynamic loader search path). +-- Look at $PWD/complex.c for the source. Note that we declare all of +-- them as STRICT, so we do not need to cope with NULL inputs in the +-- C code. We also mark them IMMUTABLE, since they always return the +-- same outputs given the same inputs. + +-- the input function 'complex_in' takes a null-terminated string (the +-- textual representation of the type) and turns it into the internal +-- (in memory) representation. You will get a message telling you 'complex' +-- does not exist yet but that's okay. + +CREATE FUNCTION custom_in(cstring) + RETURNS custom + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +-- the output function 'complex_out' takes the internal representation and +-- converts it into the textual representation. + +CREATE FUNCTION custom_out(custom) + RETURNS cstring + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 8, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler +); + +-- we can use it in a table + +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; -- 2.21.0 --u66dlpg6gaevsofr Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v33-0005-Filling-gaps-in-jsonb-arrays.patch" ^ permalink raw reply [nested|flat] 28+ messages in thread
* [PATCH v33 4/5] Subscripting documentation @ 2019-02-01 10:47 erthalion <[email protected]> 0 siblings, 0 replies; 28+ messages in thread From: erthalion @ 2019-02-01 10:47 UTC (permalink / raw) Supporting documentation for generalized subscripting. It includes the description of a new field in pg_type, the new section for jsonb documentation about subscripting feature on this data type, and also the tutorial about how to write subscripting operator for a custom data type. Reviewed-by: Tom Lane, Arthur Zakirov, Oleksandr Shulgin --- doc/src/sgml/catalogs.sgml | 8 ++ doc/src/sgml/extend.sgml | 6 + doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/json.sgml | 39 ++++++ doc/src/sgml/ref/create_type.sgml | 33 ++++- doc/src/sgml/xsubscripting.sgml | 111 +++++++++++++++++ src/tutorial/Makefile | 4 +- src/tutorial/subscripting.c | 201 ++++++++++++++++++++++++++++++ src/tutorial/subscripting.source | 71 +++++++++++ 9 files changed, 470 insertions(+), 4 deletions(-) create mode 100644 doc/src/sgml/xsubscripting.sgml create mode 100644 src/tutorial/subscripting.c create mode 100644 src/tutorial/subscripting.source diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 048ff284f7..b17e9ee382 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -8971,6 +8971,14 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l </para></entry> </row> + <row> + <entry><structfield>typsubshandler</structfield></entry> + <entry><type>regproc</type></entry> + <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry> + <entry>Custom subscripting function with type-specific logic for parsing + and validation, or 0 if this type doesn't support subscripting.</entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>typdefault</structfield> <type>text</type> diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml index 890ff97b7a..65cf5706b6 100644 --- a/doc/src/sgml/extend.sgml +++ b/doc/src/sgml/extend.sgml @@ -33,6 +33,11 @@ operators (starting in <xref linkend="xoper"/>) </para> </listitem> + <listitem> + <para> + subscripting procedure (starting in <xref linkend="xsubscripting"/>) + </para> + </listitem> <listitem> <para> operator classes for indexes (starting in <xref linkend="xindex"/>) @@ -481,6 +486,7 @@ RETURNS anycompatible AS ... &xaggr; &xtypes; &xoper; + &xsubscripting; &xindex; diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 64b5da0070..4c3fd37fc1 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -69,6 +69,7 @@ <!ENTITY xplang SYSTEM "xplang.sgml"> <!ENTITY xoper SYSTEM "xoper.sgml"> <!ENTITY xtypes SYSTEM "xtypes.sgml"> +<!ENTITY xsubscripting SYSTEM "xsubscripting.sgml"> <!ENTITY plperl SYSTEM "plperl.sgml"> <!ENTITY plpython SYSTEM "plpython.sgml"> <!ENTITY plsql SYSTEM "plpgsql.sgml"> diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml index c0a6554d4d..5c538dca05 100644 --- a/doc/src/sgml/json.sgml +++ b/doc/src/sgml/json.sgml @@ -602,6 +602,45 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu </para> </sect2> + <sect2 id="jsonb-subscripting"> + <title><type>jsonb</type> Subscripting</title> + <para> + <type>jsonb</type> data type supports array-style subscripting expressions + to extract or update particular elements. It's possible to use multiple + subscripting expressions to extract nested values. In this case, a chain of + subscripting expressions follows the same rules as the + <literal>path</literal> argument in <literal>jsonb_set</literal> function, + e.g. in case of arrays it is a 0-based operation or that negative integers + that appear in <literal>path</literal> count from the end of JSON arrays. + The result of subscripting expressions is always jsonb data type. An + example of subscripting syntax: +<programlisting> +-- Extract value by key +SELECT ('{"a": 1}'::jsonb)['a']; + +-- Extract nested value by key path +SELECT ('{"a": {"b": {"c": 1}}}'::jsonb)['a']['b']['c']; + +-- Extract element by index +SELECT ('[1, "2", null]'::jsonb)[1]; + +-- Update value by key +UPDATE table_name SET jsonb_field['key'] = 1; + +-- Select records using where clause with subscripting. Since the result of +-- subscripting is jsonb and we basically want to compare two jsonb objects, we +-- need to put the value in double quotes to be able to convert it to jsonb. +SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"'; +</programlisting> + + There is no special indexing support for such kind of expressions, but you + can always create a functional index that includes it +<programlisting> +CREATE INDEX idx ON table_name ((jsonb_field['key'])); +</programlisting> + </para> + </sect2> + <sect2> <title>Transforms</title> diff --git a/doc/src/sgml/ref/create_type.sgml b/doc/src/sgml/ref/create_type.sgml index 111f8e65d2..a34df4d247 100644 --- a/doc/src/sgml/ref/create_type.sgml +++ b/doc/src/sgml/ref/create_type.sgml @@ -54,6 +54,7 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> ( [ , ELEMENT = <replaceable class="parameter">element</replaceable> ] [ , DELIMITER = <replaceable class="parameter">delimiter</replaceable> ] [ , COLLATABLE = <replaceable class="parameter">collatable</replaceable> ] + [ , SUBSCRIPTING_HANDLER = <replaceable class="parameter">subscripting_handler_function</replaceable> ] ) CREATE TYPE <replaceable class="parameter">name</replaceable> @@ -196,8 +197,9 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> <replaceable class="parameter">receive_function</replaceable>, <replaceable class="parameter">send_function</replaceable>, <replaceable class="parameter">type_modifier_input_function</replaceable>, - <replaceable class="parameter">type_modifier_output_function</replaceable> and - <replaceable class="parameter">analyze_function</replaceable> + <replaceable class="parameter">type_modifier_output_function</replaceable>, + <replaceable class="parameter">analyze_function</replaceable>, + <replaceable class="parameter">subscripting_handler_function</replaceable> are optional. Generally these functions have to be coded in C or another low-level language. </para> @@ -454,6 +456,22 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> make use of the collation information; this does not happen automatically merely by marking the type collatable. </para> + + <para> + The optional + <replaceable class="parameter">subscripting_handler_function</replaceable> + contains type-specific logic for subscripting of the data type. + By default, there is no such function provided, which means that the data + type doesn't support subscripting. The subscripting function must be + declared to take a single argument of type <type>internal</type>, and return + a <type>internal</type> result. There are two examples of implementation for + subscripting functions in case of array + (<replaceable class="parameter">array_subscripting_handler</replaceable>) + and jsonb + (<replaceable class="parameter">jsonb_subscripting_handler</replaceable>) + types in <filename>src/backend/utils/adt/arrayfuncs.c</filename> and + <filename>src/backend/utils/adt/jsonfuncs.c</filename>, respectively. + </para> </refsect2> <refsect2> @@ -769,6 +787,17 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> </para> </listitem> </varlistentry> + + <varlistentry> + <term><replaceable class="parameter">subscripting_handler_function</replaceable></term> + <listitem> + <para> + The name of a function that returns list of type-specific callback functions to + support subscripting logic for the data type. + </para> + </listitem> + </varlistentry> + </variablelist> </refsect1> diff --git a/doc/src/sgml/xsubscripting.sgml b/doc/src/sgml/xsubscripting.sgml new file mode 100644 index 0000000000..7224e81fa2 --- /dev/null +++ b/doc/src/sgml/xsubscripting.sgml @@ -0,0 +1,111 @@ +<!-- doc/src/sgml/xsubscripting.sgml --> + + <sect1 id="xsubscripting"> + <title>User-defined subscripting procedure</title> + + <indexterm zone="xsubscripting"> + <primary>custom subscripting</primary> + </indexterm> + <para> + When you define a new base type, you can also specify a custom procedure to + handle subscripting expressions. It must contain logic for verification and + evaluation of this expression, i.e. fetching or updating some data in this + data type. For instance: +</para> +<programlisting><![CDATA[ +typedef struct Custom +{ + int first; + int second; +} Custom; + +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = someType; + sbsref->refassgntype = someType; + + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + // some validation and coercion logic + + return sbsref; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some assignment logic + + return newContainer; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some fetch logic based on sbstate +}]]> +</programlisting> + +<para> + Then you can define a subscripting procedure and a custom data type: +</para> +<programlisting> +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '<replaceable>filename</replaceable>' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 4, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler, +); +</programlisting> + +<para> + and use it as usual: +</para> +<programlisting> +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; +</programlisting> + + + <para> + The examples of custom subscripting implementation can be found in + <filename>subscripting.sql</filename> and <filename>subscripting.c</filename> + in the <filename>src/tutorial</filename> directory of the source distribution. + See the <filename>README</filename> file in that directory for instructions + about running the examples. + </para> + +</sect1> diff --git a/src/tutorial/Makefile b/src/tutorial/Makefile index 16dc390f71..0ead60c2d4 100644 --- a/src/tutorial/Makefile +++ b/src/tutorial/Makefile @@ -13,8 +13,8 @@ # #------------------------------------------------------------------------- -MODULES = complex funcs -DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql +MODULES = complex funcs subscripting +DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql subscripting.sql ifdef NO_PGXS subdir = src/tutorial diff --git a/src/tutorial/subscripting.c b/src/tutorial/subscripting.c new file mode 100644 index 0000000000..1eb8c45652 --- /dev/null +++ b/src/tutorial/subscripting.c @@ -0,0 +1,201 @@ +/* + * src/tutorial/subscripting.c + * + ****************************************************************************** + This file contains routines that can be bound to a Postgres backend and + called by the backend in the process of processing queries. The calling + format for these routines is dictated by Postgres architecture. +******************************************************************************/ + +#include "postgres.h" + +#include "catalog/pg_type.h" +#include "executor/executor.h" +#include "executor/execExpr.h" +#include "nodes/nodeFuncs.h" +#include "parser/parse_coerce.h" +#include "utils/builtins.h" +#include "utils/fmgrprotos.h" + +PG_MODULE_MAGIC; + +typedef struct Custom +{ + int first; + int second; +} Custom; + +SubscriptingRef * custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref); +SubscriptingRef * custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate); +Datum custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate); +Datum custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate); + +PG_FUNCTION_INFO_V1(custom_in); +PG_FUNCTION_INFO_V1(custom_out); +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +/***************************************************************************** + * Input/Output functions + *****************************************************************************/ + +Datum +custom_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + int firstValue, + secondValue; + Custom *result; + + if (sscanf(str, " ( %d , %d )", &firstValue, &secondValue) != 2) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for complex: \"%s\"", + str))); + + + result = (Custom *) palloc(sizeof(Custom)); + result->first = firstValue; + result->second = secondValue; + PG_RETURN_POINTER(result); +} + +Datum +custom_out(PG_FUNCTION_ARGS) +{ + Custom *custom = (Custom *) PG_GETARG_POINTER(0); + char *result; + + result = psprintf("(%d, %d)", custom->first, custom->second); + PG_RETURN_CSTRING(result); +} + +/***************************************************************************** + * Custom subscripting logic functions + *****************************************************************************/ + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = INT4OID; + sbsref->refassgntype = INT4OID; + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + List *upperIndexpr = NIL; + ListCell *l; + + if (sbsref->reflowerindexpr != NIL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *)lfirst(sbsref->reflowerindexpr->head)))))); + + foreach(l, sbsref->refupperindexpr) + { + Node *subexpr = (Node *) lfirst(l); + + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *) lfirst(sbsref->refupperindexpr->head)))))); + + subexpr = coerce_to_target_type(pstate, + subexpr, exprType(subexpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript must have integer type"), + parser_errposition(pstate, exprLocation(subexpr)))); + + upperIndexpr = lappend(upperIndexpr, subexpr); + + if (isAssignment) + { + Node *assignExpr = (Node *) sbsref->refassgnexpr; + Node *new_from; + + new_from = coerce_to_target_type(pstate, + assignExpr, exprType(assignExpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (new_from == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom assignment requires int type"), + errhint("You will need to rewrite or cast the expression."), + parser_errposition(pstate, exprLocation(assignExpr)))); + sbsref->refassgnexpr = (Expr *)new_from; + } + } + + sbsref->refupperindexpr = upperIndexpr; + + return sbsref; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + Custom *container= (Custom *) containerSource; + int index; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + return (Datum) container->first; + else + return (Datum) container->second; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + int index; + Custom *container = (Custom *) containerSource; + + if (sbstate->resnull) + return containerSource; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + container->first = DatumGetInt32(sbstate->replacevalue); + else + container->second = DatumGetInt32(sbstate->replacevalue); + + return (Datum) container; +} diff --git a/src/tutorial/subscripting.source b/src/tutorial/subscripting.source new file mode 100644 index 0000000000..837cf30612 --- /dev/null +++ b/src/tutorial/subscripting.source @@ -0,0 +1,71 @@ +--------------------------------------------------------------------------- +-- +-- subscripting.sql- +-- This file shows how to create a new subscripting procedure for +-- user-defined type. +-- +-- +-- Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group +-- Portions Copyright (c) 1994, Regents of the University of California +-- +-- src/tutorial/subscripting.source +-- +--------------------------------------------------------------------------- + +----------------------------- +-- Creating a new type: +-- We are going to create a new type called 'complex' which represents +-- complex numbers. +-- A user-defined type must have an input and an output function, and +-- optionally can have binary input and output functions. All of these +-- are usually user-defined C functions. +----------------------------- + +-- Assume the user defined functions are in /home/erthalion/programms/postgresql-master/src/tutorial/complex$DLSUFFIX +-- (we do not want to assume this is in the dynamic loader search path). +-- Look at $PWD/complex.c for the source. Note that we declare all of +-- them as STRICT, so we do not need to cope with NULL inputs in the +-- C code. We also mark them IMMUTABLE, since they always return the +-- same outputs given the same inputs. + +-- the input function 'complex_in' takes a null-terminated string (the +-- textual representation of the type) and turns it into the internal +-- (in memory) representation. You will get a message telling you 'complex' +-- does not exist yet but that's okay. + +CREATE FUNCTION custom_in(cstring) + RETURNS custom + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +-- the output function 'complex_out' takes the internal representation and +-- converts it into the textual representation. + +CREATE FUNCTION custom_out(custom) + RETURNS cstring + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 8, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler +); + +-- we can use it in a table + +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; -- 2.21.0 --u66dlpg6gaevsofr Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v33-0005-Filling-gaps-in-jsonb-arrays.patch" ^ permalink raw reply [nested|flat] 28+ messages in thread
* [PATCH v31 4/6] Subscripting documentation @ 2019-02-01 10:47 erthalion <[email protected]> 0 siblings, 0 replies; 28+ messages in thread From: erthalion @ 2019-02-01 10:47 UTC (permalink / raw) Supporting documentation for generalized subscripting. It includes the description of a new field in pg_type, the new section for jsonb documentation about subscripting feature on this data type, and also the tutorial about how to write subscripting operator for a custom data type. Reviewed-by: Tom Lane, Arthur Zakirov, Oleksandr Shulgin --- doc/src/sgml/catalogs.sgml | 8 ++ doc/src/sgml/extend.sgml | 6 + doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/json.sgml | 39 ++++++ doc/src/sgml/ref/create_type.sgml | 33 ++++- doc/src/sgml/xsubscripting.sgml | 111 +++++++++++++++++ src/tutorial/Makefile | 4 +- src/tutorial/subscripting.c | 201 ++++++++++++++++++++++++++++++ src/tutorial/subscripting.source | 71 +++++++++++ 9 files changed, 470 insertions(+), 4 deletions(-) create mode 100644 doc/src/sgml/xsubscripting.sgml create mode 100644 src/tutorial/subscripting.c create mode 100644 src/tutorial/subscripting.source diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 9d8fa0bec3..439bee0678 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -8957,6 +8957,14 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l </para></entry> </row> + <row> + <entry><structfield>typsubshandler</structfield></entry> + <entry><type>regproc</type></entry> + <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry> + <entry>Custom subscripting function with type-specific logic for parsing + and validation, or 0 if this type doesn't support subscripting.</entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>typdefault</structfield> <type>text</type> diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml index c1ffb14571..5d3cf479b4 100644 --- a/doc/src/sgml/extend.sgml +++ b/doc/src/sgml/extend.sgml @@ -33,6 +33,11 @@ operators (starting in <xref linkend="xoper"/>) </para> </listitem> + <listitem> + <para> + subscripting procedure (starting in <xref linkend="xsubscripting"/>) + </para> + </listitem> <listitem> <para> operator classes for indexes (starting in <xref linkend="xindex"/>) @@ -481,6 +486,7 @@ RETURNS anycompatible AS ... &xaggr; &xtypes; &xoper; + &xsubscripting; &xindex; diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 68179f71cd..34249f8c60 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -69,6 +69,7 @@ <!ENTITY xplang SYSTEM "xplang.sgml"> <!ENTITY xoper SYSTEM "xoper.sgml"> <!ENTITY xtypes SYSTEM "xtypes.sgml"> +<!ENTITY xsubscripting SYSTEM "xsubscripting.sgml"> <!ENTITY plperl SYSTEM "plperl.sgml"> <!ENTITY plpython SYSTEM "plpython.sgml"> <!ENTITY plsql SYSTEM "plpgsql.sgml"> diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml index c0a6554d4d..3bffe8049b 100644 --- a/doc/src/sgml/json.sgml +++ b/doc/src/sgml/json.sgml @@ -602,6 +602,45 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu </para> </sect2> + <sect2 id="jsonb-subscripting"> + <title><type>jsonb</type> Subscripting</title> + <para> + <type>jsonb</type> data type supports array-style subscripting expressions + to extract or update particular element. It's possible to use multiple + subscripting expressions to extract nested values. In this case a chain of + subscripting expressions follows the same rules as the + <literal>path</literal> argument in <literal>jsonb_set</literal> function, + e.g. in case of arrays it is a 0-based operation or that negative integers + that appear in <literal>path</literal> count from the end of JSON arrays. + The result of subscripting expressions is always jsonb data type. An + example of subscripting syntax: +<programlisting> +-- Extract value by key +SELECT ('{"a": 1}'::jsonb)['a']; + +-- Extract nested value by key path +SELECT ('{"a": {"b": {"c": 1}}}'::jsonb)['a']['b']['c']; + +-- Extract element by index +SELECT ('[1, "2", null]'::jsonb)[1]; + +-- Update value by key +UPDATE table_name SET jsonb_field['key'] = 1; + +-- Select records using where clause with subscripting. Since the result of +-- subscripting is jsonb and we basically want to compare two jsonb objects, we +-- need to put the value in double quotes to be able to convert it to jsonb. +SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"'; +</programlisting> + + There is no special indexing support for such kind of expressions, but you + always can create a functional index that includes it +<programlisting> +CREATE INDEX idx ON table_name ((jsonb_field['key'])); +</programlisting> + </para> + </sect2> + <sect2> <title>Transforms</title> diff --git a/doc/src/sgml/ref/create_type.sgml b/doc/src/sgml/ref/create_type.sgml index 111f8e65d2..ec67761c66 100644 --- a/doc/src/sgml/ref/create_type.sgml +++ b/doc/src/sgml/ref/create_type.sgml @@ -54,6 +54,7 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> ( [ , ELEMENT = <replaceable class="parameter">element</replaceable> ] [ , DELIMITER = <replaceable class="parameter">delimiter</replaceable> ] [ , COLLATABLE = <replaceable class="parameter">collatable</replaceable> ] + [ , SUBSCRIPTING_HANDLER = <replaceable class="parameter">subscripting_handler_function</replaceable> ] ) CREATE TYPE <replaceable class="parameter">name</replaceable> @@ -196,8 +197,9 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> <replaceable class="parameter">receive_function</replaceable>, <replaceable class="parameter">send_function</replaceable>, <replaceable class="parameter">type_modifier_input_function</replaceable>, - <replaceable class="parameter">type_modifier_output_function</replaceable> and - <replaceable class="parameter">analyze_function</replaceable> + <replaceable class="parameter">type_modifier_output_function</replaceable>, + <replaceable class="parameter">analyze_function</replaceable>, + <replaceable class="parameter">subscripting_handler_function</replaceable> are optional. Generally these functions have to be coded in C or another low-level language. </para> @@ -454,6 +456,22 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> make use of the collation information; this does not happen automatically merely by marking the type collatable. </para> + + <para> + The optional + <replaceable class="parameter">subscripting_handler_function</replaceable> + contains type-specific logic for subscripting of the data type. + By default, there is no such function provided, which means that the data + type doesn't support subscripting. The subscripting function must be + declared to take a single argument of type <type>internal</type>, and return + a <type>internal</type> result. There are two examples of implementation for + subscripting functions in case of array + (<replaceable class="parameter">array_subscripting_handler</replaceable>) + and jsonb + (<replaceable class="parameter">jsonb_subscripting_handler</replaceable>) + types in <filename>src/backend/utils/adt/arrayfuncs.c</filename> and + <filename>src/backend/utils/adt/jsonfuncs.c</filename> corresponding. + </para> </refsect2> <refsect2> @@ -769,6 +787,17 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> </para> </listitem> </varlistentry> + + <varlistentry> + <term><replaceable class="parameter">subscripting_handler_function</replaceable></term> + <listitem> + <para> + The name of a function that returns list of type-specific callback functions to + support subscripting logic for the data type. + </para> + </listitem> + </varlistentry> + </variablelist> </refsect1> diff --git a/doc/src/sgml/xsubscripting.sgml b/doc/src/sgml/xsubscripting.sgml new file mode 100644 index 0000000000..d701631223 --- /dev/null +++ b/doc/src/sgml/xsubscripting.sgml @@ -0,0 +1,111 @@ +<!-- doc/src/sgml/xsubscripting.sgml --> + + <sect1 id="xsubscripting"> + <title>User-defined subscripting procedure</title> + + <indexterm zone="xsubscripting"> + <primary>custom subscripting</primary> + </indexterm> + <para> + When you define a new base type, you can also specify a custom procedures to + handle subscripting expressions. They must contain logic for verification and + evaluation of this expression, i.e. fetching or updating some data in this + data type. For instance: +</para> +<programlisting><![CDATA[ +typedef struct Custom +{ + int first; + int second; +} Custom; + +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = someType; + sbsref->refassgntype = someType; + + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + // some validation and coercion logic + + return sbsref; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some assignment logic + + return newContainer; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some fetch logic based on sbsdata +}]]> +</programlisting> + +<para> + Then you can define a subscripting procedures and a custom data type: +</para> +<programlisting> +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '<replaceable>filename</replaceable>' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 4, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler, +); +</programlisting> + +<para> + and use it as usual: +</para> +<programlisting> +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; +</programlisting> + + + <para> + The examples of custom subscripting implementation can be found in + <filename>subscripting.sql</filename> and <filename>subscripting.c</filename> + in the <filename>src/tutorial</filename> directory of the source distribution. + See the <filename>README</filename> file in that directory for instructions + about running the examples. + </para> + +</sect1> diff --git a/src/tutorial/Makefile b/src/tutorial/Makefile index 16dc390f71..0ead60c2d4 100644 --- a/src/tutorial/Makefile +++ b/src/tutorial/Makefile @@ -13,8 +13,8 @@ # #------------------------------------------------------------------------- -MODULES = complex funcs -DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql +MODULES = complex funcs subscripting +DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql subscripting.sql ifdef NO_PGXS subdir = src/tutorial diff --git a/src/tutorial/subscripting.c b/src/tutorial/subscripting.c new file mode 100644 index 0000000000..1eb8c45652 --- /dev/null +++ b/src/tutorial/subscripting.c @@ -0,0 +1,201 @@ +/* + * src/tutorial/subscripting.c + * + ****************************************************************************** + This file contains routines that can be bound to a Postgres backend and + called by the backend in the process of processing queries. The calling + format for these routines is dictated by Postgres architecture. +******************************************************************************/ + +#include "postgres.h" + +#include "catalog/pg_type.h" +#include "executor/executor.h" +#include "executor/execExpr.h" +#include "nodes/nodeFuncs.h" +#include "parser/parse_coerce.h" +#include "utils/builtins.h" +#include "utils/fmgrprotos.h" + +PG_MODULE_MAGIC; + +typedef struct Custom +{ + int first; + int second; +} Custom; + +SubscriptingRef * custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref); +SubscriptingRef * custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate); +Datum custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate); +Datum custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate); + +PG_FUNCTION_INFO_V1(custom_in); +PG_FUNCTION_INFO_V1(custom_out); +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +/***************************************************************************** + * Input/Output functions + *****************************************************************************/ + +Datum +custom_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + int firstValue, + secondValue; + Custom *result; + + if (sscanf(str, " ( %d , %d )", &firstValue, &secondValue) != 2) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for complex: \"%s\"", + str))); + + + result = (Custom *) palloc(sizeof(Custom)); + result->first = firstValue; + result->second = secondValue; + PG_RETURN_POINTER(result); +} + +Datum +custom_out(PG_FUNCTION_ARGS) +{ + Custom *custom = (Custom *) PG_GETARG_POINTER(0); + char *result; + + result = psprintf("(%d, %d)", custom->first, custom->second); + PG_RETURN_CSTRING(result); +} + +/***************************************************************************** + * Custom subscripting logic functions + *****************************************************************************/ + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = INT4OID; + sbsref->refassgntype = INT4OID; + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + List *upperIndexpr = NIL; + ListCell *l; + + if (sbsref->reflowerindexpr != NIL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *)lfirst(sbsref->reflowerindexpr->head)))))); + + foreach(l, sbsref->refupperindexpr) + { + Node *subexpr = (Node *) lfirst(l); + + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *) lfirst(sbsref->refupperindexpr->head)))))); + + subexpr = coerce_to_target_type(pstate, + subexpr, exprType(subexpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript must have integer type"), + parser_errposition(pstate, exprLocation(subexpr)))); + + upperIndexpr = lappend(upperIndexpr, subexpr); + + if (isAssignment) + { + Node *assignExpr = (Node *) sbsref->refassgnexpr; + Node *new_from; + + new_from = coerce_to_target_type(pstate, + assignExpr, exprType(assignExpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (new_from == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom assignment requires int type"), + errhint("You will need to rewrite or cast the expression."), + parser_errposition(pstate, exprLocation(assignExpr)))); + sbsref->refassgnexpr = (Expr *)new_from; + } + } + + sbsref->refupperindexpr = upperIndexpr; + + return sbsref; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + Custom *container= (Custom *) containerSource; + int index; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + return (Datum) container->first; + else + return (Datum) container->second; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + int index; + Custom *container = (Custom *) containerSource; + + if (sbstate->resnull) + return containerSource; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + container->first = DatumGetInt32(sbstate->replacevalue); + else + container->second = DatumGetInt32(sbstate->replacevalue); + + return (Datum) container; +} diff --git a/src/tutorial/subscripting.source b/src/tutorial/subscripting.source new file mode 100644 index 0000000000..837cf30612 --- /dev/null +++ b/src/tutorial/subscripting.source @@ -0,0 +1,71 @@ +--------------------------------------------------------------------------- +-- +-- subscripting.sql- +-- This file shows how to create a new subscripting procedure for +-- user-defined type. +-- +-- +-- Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group +-- Portions Copyright (c) 1994, Regents of the University of California +-- +-- src/tutorial/subscripting.source +-- +--------------------------------------------------------------------------- + +----------------------------- +-- Creating a new type: +-- We are going to create a new type called 'complex' which represents +-- complex numbers. +-- A user-defined type must have an input and an output function, and +-- optionally can have binary input and output functions. All of these +-- are usually user-defined C functions. +----------------------------- + +-- Assume the user defined functions are in /home/erthalion/programms/postgresql-master/src/tutorial/complex$DLSUFFIX +-- (we do not want to assume this is in the dynamic loader search path). +-- Look at $PWD/complex.c for the source. Note that we declare all of +-- them as STRICT, so we do not need to cope with NULL inputs in the +-- C code. We also mark them IMMUTABLE, since they always return the +-- same outputs given the same inputs. + +-- the input function 'complex_in' takes a null-terminated string (the +-- textual representation of the type) and turns it into the internal +-- (in memory) representation. You will get a message telling you 'complex' +-- does not exist yet but that's okay. + +CREATE FUNCTION custom_in(cstring) + RETURNS custom + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +-- the output function 'complex_out' takes the internal representation and +-- converts it into the textual representation. + +CREATE FUNCTION custom_out(custom) + RETURNS cstring + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 8, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler +); + +-- we can use it in a table + +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; -- 2.21.0 --bvn7grlx53cj2mie Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v31-0005-Polymorphic-subscripting.patch" ^ permalink raw reply [nested|flat] 28+ messages in thread
* [PATCH v33 4/5] Subscripting documentation @ 2019-02-01 10:47 erthalion <[email protected]> 0 siblings, 0 replies; 28+ messages in thread From: erthalion @ 2019-02-01 10:47 UTC (permalink / raw) Supporting documentation for generalized subscripting. It includes the description of a new field in pg_type, the new section for jsonb documentation about subscripting feature on this data type, and also the tutorial about how to write subscripting operator for a custom data type. Reviewed-by: Tom Lane, Arthur Zakirov, Oleksandr Shulgin --- doc/src/sgml/catalogs.sgml | 8 ++ doc/src/sgml/extend.sgml | 6 + doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/json.sgml | 39 ++++++ doc/src/sgml/ref/create_type.sgml | 33 ++++- doc/src/sgml/xsubscripting.sgml | 111 +++++++++++++++++ src/tutorial/Makefile | 4 +- src/tutorial/subscripting.c | 201 ++++++++++++++++++++++++++++++ src/tutorial/subscripting.source | 71 +++++++++++ 9 files changed, 470 insertions(+), 4 deletions(-) create mode 100644 doc/src/sgml/xsubscripting.sgml create mode 100644 src/tutorial/subscripting.c create mode 100644 src/tutorial/subscripting.source diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 048ff284f7..b17e9ee382 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -8971,6 +8971,14 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l </para></entry> </row> + <row> + <entry><structfield>typsubshandler</structfield></entry> + <entry><type>regproc</type></entry> + <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry> + <entry>Custom subscripting function with type-specific logic for parsing + and validation, or 0 if this type doesn't support subscripting.</entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>typdefault</structfield> <type>text</type> diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml index 890ff97b7a..65cf5706b6 100644 --- a/doc/src/sgml/extend.sgml +++ b/doc/src/sgml/extend.sgml @@ -33,6 +33,11 @@ operators (starting in <xref linkend="xoper"/>) </para> </listitem> + <listitem> + <para> + subscripting procedure (starting in <xref linkend="xsubscripting"/>) + </para> + </listitem> <listitem> <para> operator classes for indexes (starting in <xref linkend="xindex"/>) @@ -481,6 +486,7 @@ RETURNS anycompatible AS ... &xaggr; &xtypes; &xoper; + &xsubscripting; &xindex; diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 64b5da0070..4c3fd37fc1 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -69,6 +69,7 @@ <!ENTITY xplang SYSTEM "xplang.sgml"> <!ENTITY xoper SYSTEM "xoper.sgml"> <!ENTITY xtypes SYSTEM "xtypes.sgml"> +<!ENTITY xsubscripting SYSTEM "xsubscripting.sgml"> <!ENTITY plperl SYSTEM "plperl.sgml"> <!ENTITY plpython SYSTEM "plpython.sgml"> <!ENTITY plsql SYSTEM "plpgsql.sgml"> diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml index c0a6554d4d..5c538dca05 100644 --- a/doc/src/sgml/json.sgml +++ b/doc/src/sgml/json.sgml @@ -602,6 +602,45 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu </para> </sect2> + <sect2 id="jsonb-subscripting"> + <title><type>jsonb</type> Subscripting</title> + <para> + <type>jsonb</type> data type supports array-style subscripting expressions + to extract or update particular elements. It's possible to use multiple + subscripting expressions to extract nested values. In this case, a chain of + subscripting expressions follows the same rules as the + <literal>path</literal> argument in <literal>jsonb_set</literal> function, + e.g. in case of arrays it is a 0-based operation or that negative integers + that appear in <literal>path</literal> count from the end of JSON arrays. + The result of subscripting expressions is always jsonb data type. An + example of subscripting syntax: +<programlisting> +-- Extract value by key +SELECT ('{"a": 1}'::jsonb)['a']; + +-- Extract nested value by key path +SELECT ('{"a": {"b": {"c": 1}}}'::jsonb)['a']['b']['c']; + +-- Extract element by index +SELECT ('[1, "2", null]'::jsonb)[1]; + +-- Update value by key +UPDATE table_name SET jsonb_field['key'] = 1; + +-- Select records using where clause with subscripting. Since the result of +-- subscripting is jsonb and we basically want to compare two jsonb objects, we +-- need to put the value in double quotes to be able to convert it to jsonb. +SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"'; +</programlisting> + + There is no special indexing support for such kind of expressions, but you + can always create a functional index that includes it +<programlisting> +CREATE INDEX idx ON table_name ((jsonb_field['key'])); +</programlisting> + </para> + </sect2> + <sect2> <title>Transforms</title> diff --git a/doc/src/sgml/ref/create_type.sgml b/doc/src/sgml/ref/create_type.sgml index 111f8e65d2..a34df4d247 100644 --- a/doc/src/sgml/ref/create_type.sgml +++ b/doc/src/sgml/ref/create_type.sgml @@ -54,6 +54,7 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> ( [ , ELEMENT = <replaceable class="parameter">element</replaceable> ] [ , DELIMITER = <replaceable class="parameter">delimiter</replaceable> ] [ , COLLATABLE = <replaceable class="parameter">collatable</replaceable> ] + [ , SUBSCRIPTING_HANDLER = <replaceable class="parameter">subscripting_handler_function</replaceable> ] ) CREATE TYPE <replaceable class="parameter">name</replaceable> @@ -196,8 +197,9 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> <replaceable class="parameter">receive_function</replaceable>, <replaceable class="parameter">send_function</replaceable>, <replaceable class="parameter">type_modifier_input_function</replaceable>, - <replaceable class="parameter">type_modifier_output_function</replaceable> and - <replaceable class="parameter">analyze_function</replaceable> + <replaceable class="parameter">type_modifier_output_function</replaceable>, + <replaceable class="parameter">analyze_function</replaceable>, + <replaceable class="parameter">subscripting_handler_function</replaceable> are optional. Generally these functions have to be coded in C or another low-level language. </para> @@ -454,6 +456,22 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> make use of the collation information; this does not happen automatically merely by marking the type collatable. </para> + + <para> + The optional + <replaceable class="parameter">subscripting_handler_function</replaceable> + contains type-specific logic for subscripting of the data type. + By default, there is no such function provided, which means that the data + type doesn't support subscripting. The subscripting function must be + declared to take a single argument of type <type>internal</type>, and return + a <type>internal</type> result. There are two examples of implementation for + subscripting functions in case of array + (<replaceable class="parameter">array_subscripting_handler</replaceable>) + and jsonb + (<replaceable class="parameter">jsonb_subscripting_handler</replaceable>) + types in <filename>src/backend/utils/adt/arrayfuncs.c</filename> and + <filename>src/backend/utils/adt/jsonfuncs.c</filename>, respectively. + </para> </refsect2> <refsect2> @@ -769,6 +787,17 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> </para> </listitem> </varlistentry> + + <varlistentry> + <term><replaceable class="parameter">subscripting_handler_function</replaceable></term> + <listitem> + <para> + The name of a function that returns list of type-specific callback functions to + support subscripting logic for the data type. + </para> + </listitem> + </varlistentry> + </variablelist> </refsect1> diff --git a/doc/src/sgml/xsubscripting.sgml b/doc/src/sgml/xsubscripting.sgml new file mode 100644 index 0000000000..7224e81fa2 --- /dev/null +++ b/doc/src/sgml/xsubscripting.sgml @@ -0,0 +1,111 @@ +<!-- doc/src/sgml/xsubscripting.sgml --> + + <sect1 id="xsubscripting"> + <title>User-defined subscripting procedure</title> + + <indexterm zone="xsubscripting"> + <primary>custom subscripting</primary> + </indexterm> + <para> + When you define a new base type, you can also specify a custom procedure to + handle subscripting expressions. It must contain logic for verification and + evaluation of this expression, i.e. fetching or updating some data in this + data type. For instance: +</para> +<programlisting><![CDATA[ +typedef struct Custom +{ + int first; + int second; +} Custom; + +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = someType; + sbsref->refassgntype = someType; + + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + // some validation and coercion logic + + return sbsref; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some assignment logic + + return newContainer; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some fetch logic based on sbstate +}]]> +</programlisting> + +<para> + Then you can define a subscripting procedure and a custom data type: +</para> +<programlisting> +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '<replaceable>filename</replaceable>' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 4, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler, +); +</programlisting> + +<para> + and use it as usual: +</para> +<programlisting> +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; +</programlisting> + + + <para> + The examples of custom subscripting implementation can be found in + <filename>subscripting.sql</filename> and <filename>subscripting.c</filename> + in the <filename>src/tutorial</filename> directory of the source distribution. + See the <filename>README</filename> file in that directory for instructions + about running the examples. + </para> + +</sect1> diff --git a/src/tutorial/Makefile b/src/tutorial/Makefile index 16dc390f71..0ead60c2d4 100644 --- a/src/tutorial/Makefile +++ b/src/tutorial/Makefile @@ -13,8 +13,8 @@ # #------------------------------------------------------------------------- -MODULES = complex funcs -DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql +MODULES = complex funcs subscripting +DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql subscripting.sql ifdef NO_PGXS subdir = src/tutorial diff --git a/src/tutorial/subscripting.c b/src/tutorial/subscripting.c new file mode 100644 index 0000000000..1eb8c45652 --- /dev/null +++ b/src/tutorial/subscripting.c @@ -0,0 +1,201 @@ +/* + * src/tutorial/subscripting.c + * + ****************************************************************************** + This file contains routines that can be bound to a Postgres backend and + called by the backend in the process of processing queries. The calling + format for these routines is dictated by Postgres architecture. +******************************************************************************/ + +#include "postgres.h" + +#include "catalog/pg_type.h" +#include "executor/executor.h" +#include "executor/execExpr.h" +#include "nodes/nodeFuncs.h" +#include "parser/parse_coerce.h" +#include "utils/builtins.h" +#include "utils/fmgrprotos.h" + +PG_MODULE_MAGIC; + +typedef struct Custom +{ + int first; + int second; +} Custom; + +SubscriptingRef * custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref); +SubscriptingRef * custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate); +Datum custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate); +Datum custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate); + +PG_FUNCTION_INFO_V1(custom_in); +PG_FUNCTION_INFO_V1(custom_out); +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +/***************************************************************************** + * Input/Output functions + *****************************************************************************/ + +Datum +custom_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + int firstValue, + secondValue; + Custom *result; + + if (sscanf(str, " ( %d , %d )", &firstValue, &secondValue) != 2) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for complex: \"%s\"", + str))); + + + result = (Custom *) palloc(sizeof(Custom)); + result->first = firstValue; + result->second = secondValue; + PG_RETURN_POINTER(result); +} + +Datum +custom_out(PG_FUNCTION_ARGS) +{ + Custom *custom = (Custom *) PG_GETARG_POINTER(0); + char *result; + + result = psprintf("(%d, %d)", custom->first, custom->second); + PG_RETURN_CSTRING(result); +} + +/***************************************************************************** + * Custom subscripting logic functions + *****************************************************************************/ + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = INT4OID; + sbsref->refassgntype = INT4OID; + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + List *upperIndexpr = NIL; + ListCell *l; + + if (sbsref->reflowerindexpr != NIL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *)lfirst(sbsref->reflowerindexpr->head)))))); + + foreach(l, sbsref->refupperindexpr) + { + Node *subexpr = (Node *) lfirst(l); + + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *) lfirst(sbsref->refupperindexpr->head)))))); + + subexpr = coerce_to_target_type(pstate, + subexpr, exprType(subexpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript must have integer type"), + parser_errposition(pstate, exprLocation(subexpr)))); + + upperIndexpr = lappend(upperIndexpr, subexpr); + + if (isAssignment) + { + Node *assignExpr = (Node *) sbsref->refassgnexpr; + Node *new_from; + + new_from = coerce_to_target_type(pstate, + assignExpr, exprType(assignExpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (new_from == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom assignment requires int type"), + errhint("You will need to rewrite or cast the expression."), + parser_errposition(pstate, exprLocation(assignExpr)))); + sbsref->refassgnexpr = (Expr *)new_from; + } + } + + sbsref->refupperindexpr = upperIndexpr; + + return sbsref; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + Custom *container= (Custom *) containerSource; + int index; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + return (Datum) container->first; + else + return (Datum) container->second; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + int index; + Custom *container = (Custom *) containerSource; + + if (sbstate->resnull) + return containerSource; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + container->first = DatumGetInt32(sbstate->replacevalue); + else + container->second = DatumGetInt32(sbstate->replacevalue); + + return (Datum) container; +} diff --git a/src/tutorial/subscripting.source b/src/tutorial/subscripting.source new file mode 100644 index 0000000000..837cf30612 --- /dev/null +++ b/src/tutorial/subscripting.source @@ -0,0 +1,71 @@ +--------------------------------------------------------------------------- +-- +-- subscripting.sql- +-- This file shows how to create a new subscripting procedure for +-- user-defined type. +-- +-- +-- Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group +-- Portions Copyright (c) 1994, Regents of the University of California +-- +-- src/tutorial/subscripting.source +-- +--------------------------------------------------------------------------- + +----------------------------- +-- Creating a new type: +-- We are going to create a new type called 'complex' which represents +-- complex numbers. +-- A user-defined type must have an input and an output function, and +-- optionally can have binary input and output functions. All of these +-- are usually user-defined C functions. +----------------------------- + +-- Assume the user defined functions are in /home/erthalion/programms/postgresql-master/src/tutorial/complex$DLSUFFIX +-- (we do not want to assume this is in the dynamic loader search path). +-- Look at $PWD/complex.c for the source. Note that we declare all of +-- them as STRICT, so we do not need to cope with NULL inputs in the +-- C code. We also mark them IMMUTABLE, since they always return the +-- same outputs given the same inputs. + +-- the input function 'complex_in' takes a null-terminated string (the +-- textual representation of the type) and turns it into the internal +-- (in memory) representation. You will get a message telling you 'complex' +-- does not exist yet but that's okay. + +CREATE FUNCTION custom_in(cstring) + RETURNS custom + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +-- the output function 'complex_out' takes the internal representation and +-- converts it into the textual representation. + +CREATE FUNCTION custom_out(custom) + RETURNS cstring + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 8, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler +); + +-- we can use it in a table + +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; -- 2.21.0 --u66dlpg6gaevsofr Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v33-0005-Filling-gaps-in-jsonb-arrays.patch" ^ permalink raw reply [nested|flat] 28+ messages in thread
* [PATCH v32 4/6] Subscripting documentation @ 2019-02-01 10:47 erthalion <[email protected]> 0 siblings, 0 replies; 28+ messages in thread From: erthalion @ 2019-02-01 10:47 UTC (permalink / raw) Supporting documentation for generalized subscripting. It includes the description of a new field in pg_type, the new section for jsonb documentation about subscripting feature on this data type, and also the tutorial about how to write subscripting operator for a custom data type. Reviewed-by: Tom Lane, Arthur Zakirov, Oleksandr Shulgin --- doc/src/sgml/catalogs.sgml | 8 ++ doc/src/sgml/extend.sgml | 6 + doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/json.sgml | 39 ++++++ doc/src/sgml/ref/create_type.sgml | 33 ++++- doc/src/sgml/xsubscripting.sgml | 111 +++++++++++++++++ src/tutorial/Makefile | 4 +- src/tutorial/subscripting.c | 201 ++++++++++++++++++++++++++++++ src/tutorial/subscripting.source | 71 +++++++++++ 9 files changed, 470 insertions(+), 4 deletions(-) create mode 100644 doc/src/sgml/xsubscripting.sgml create mode 100644 src/tutorial/subscripting.c create mode 100644 src/tutorial/subscripting.source diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 048ff284f7..b17e9ee382 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -8971,6 +8971,14 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l </para></entry> </row> + <row> + <entry><structfield>typsubshandler</structfield></entry> + <entry><type>regproc</type></entry> + <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry> + <entry>Custom subscripting function with type-specific logic for parsing + and validation, or 0 if this type doesn't support subscripting.</entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>typdefault</structfield> <type>text</type> diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml index 890ff97b7a..65cf5706b6 100644 --- a/doc/src/sgml/extend.sgml +++ b/doc/src/sgml/extend.sgml @@ -33,6 +33,11 @@ operators (starting in <xref linkend="xoper"/>) </para> </listitem> + <listitem> + <para> + subscripting procedure (starting in <xref linkend="xsubscripting"/>) + </para> + </listitem> <listitem> <para> operator classes for indexes (starting in <xref linkend="xindex"/>) @@ -481,6 +486,7 @@ RETURNS anycompatible AS ... &xaggr; &xtypes; &xoper; + &xsubscripting; &xindex; diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 64b5da0070..4c3fd37fc1 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -69,6 +69,7 @@ <!ENTITY xplang SYSTEM "xplang.sgml"> <!ENTITY xoper SYSTEM "xoper.sgml"> <!ENTITY xtypes SYSTEM "xtypes.sgml"> +<!ENTITY xsubscripting SYSTEM "xsubscripting.sgml"> <!ENTITY plperl SYSTEM "plperl.sgml"> <!ENTITY plpython SYSTEM "plpython.sgml"> <!ENTITY plsql SYSTEM "plpgsql.sgml"> diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml index c0a6554d4d..5c538dca05 100644 --- a/doc/src/sgml/json.sgml +++ b/doc/src/sgml/json.sgml @@ -602,6 +602,45 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu </para> </sect2> + <sect2 id="jsonb-subscripting"> + <title><type>jsonb</type> Subscripting</title> + <para> + <type>jsonb</type> data type supports array-style subscripting expressions + to extract or update particular elements. It's possible to use multiple + subscripting expressions to extract nested values. In this case, a chain of + subscripting expressions follows the same rules as the + <literal>path</literal> argument in <literal>jsonb_set</literal> function, + e.g. in case of arrays it is a 0-based operation or that negative integers + that appear in <literal>path</literal> count from the end of JSON arrays. + The result of subscripting expressions is always jsonb data type. An + example of subscripting syntax: +<programlisting> +-- Extract value by key +SELECT ('{"a": 1}'::jsonb)['a']; + +-- Extract nested value by key path +SELECT ('{"a": {"b": {"c": 1}}}'::jsonb)['a']['b']['c']; + +-- Extract element by index +SELECT ('[1, "2", null]'::jsonb)[1]; + +-- Update value by key +UPDATE table_name SET jsonb_field['key'] = 1; + +-- Select records using where clause with subscripting. Since the result of +-- subscripting is jsonb and we basically want to compare two jsonb objects, we +-- need to put the value in double quotes to be able to convert it to jsonb. +SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"'; +</programlisting> + + There is no special indexing support for such kind of expressions, but you + can always create a functional index that includes it +<programlisting> +CREATE INDEX idx ON table_name ((jsonb_field['key'])); +</programlisting> + </para> + </sect2> + <sect2> <title>Transforms</title> diff --git a/doc/src/sgml/ref/create_type.sgml b/doc/src/sgml/ref/create_type.sgml index 111f8e65d2..a34df4d247 100644 --- a/doc/src/sgml/ref/create_type.sgml +++ b/doc/src/sgml/ref/create_type.sgml @@ -54,6 +54,7 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> ( [ , ELEMENT = <replaceable class="parameter">element</replaceable> ] [ , DELIMITER = <replaceable class="parameter">delimiter</replaceable> ] [ , COLLATABLE = <replaceable class="parameter">collatable</replaceable> ] + [ , SUBSCRIPTING_HANDLER = <replaceable class="parameter">subscripting_handler_function</replaceable> ] ) CREATE TYPE <replaceable class="parameter">name</replaceable> @@ -196,8 +197,9 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> <replaceable class="parameter">receive_function</replaceable>, <replaceable class="parameter">send_function</replaceable>, <replaceable class="parameter">type_modifier_input_function</replaceable>, - <replaceable class="parameter">type_modifier_output_function</replaceable> and - <replaceable class="parameter">analyze_function</replaceable> + <replaceable class="parameter">type_modifier_output_function</replaceable>, + <replaceable class="parameter">analyze_function</replaceable>, + <replaceable class="parameter">subscripting_handler_function</replaceable> are optional. Generally these functions have to be coded in C or another low-level language. </para> @@ -454,6 +456,22 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> make use of the collation information; this does not happen automatically merely by marking the type collatable. </para> + + <para> + The optional + <replaceable class="parameter">subscripting_handler_function</replaceable> + contains type-specific logic for subscripting of the data type. + By default, there is no such function provided, which means that the data + type doesn't support subscripting. The subscripting function must be + declared to take a single argument of type <type>internal</type>, and return + a <type>internal</type> result. There are two examples of implementation for + subscripting functions in case of array + (<replaceable class="parameter">array_subscripting_handler</replaceable>) + and jsonb + (<replaceable class="parameter">jsonb_subscripting_handler</replaceable>) + types in <filename>src/backend/utils/adt/arrayfuncs.c</filename> and + <filename>src/backend/utils/adt/jsonfuncs.c</filename>, respectively. + </para> </refsect2> <refsect2> @@ -769,6 +787,17 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> </para> </listitem> </varlistentry> + + <varlistentry> + <term><replaceable class="parameter">subscripting_handler_function</replaceable></term> + <listitem> + <para> + The name of a function that returns list of type-specific callback functions to + support subscripting logic for the data type. + </para> + </listitem> + </varlistentry> + </variablelist> </refsect1> diff --git a/doc/src/sgml/xsubscripting.sgml b/doc/src/sgml/xsubscripting.sgml new file mode 100644 index 0000000000..7224e81fa2 --- /dev/null +++ b/doc/src/sgml/xsubscripting.sgml @@ -0,0 +1,111 @@ +<!-- doc/src/sgml/xsubscripting.sgml --> + + <sect1 id="xsubscripting"> + <title>User-defined subscripting procedure</title> + + <indexterm zone="xsubscripting"> + <primary>custom subscripting</primary> + </indexterm> + <para> + When you define a new base type, you can also specify a custom procedure to + handle subscripting expressions. It must contain logic for verification and + evaluation of this expression, i.e. fetching or updating some data in this + data type. For instance: +</para> +<programlisting><![CDATA[ +typedef struct Custom +{ + int first; + int second; +} Custom; + +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = someType; + sbsref->refassgntype = someType; + + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + // some validation and coercion logic + + return sbsref; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some assignment logic + + return newContainer; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some fetch logic based on sbstate +}]]> +</programlisting> + +<para> + Then you can define a subscripting procedure and a custom data type: +</para> +<programlisting> +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '<replaceable>filename</replaceable>' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 4, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler, +); +</programlisting> + +<para> + and use it as usual: +</para> +<programlisting> +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; +</programlisting> + + + <para> + The examples of custom subscripting implementation can be found in + <filename>subscripting.sql</filename> and <filename>subscripting.c</filename> + in the <filename>src/tutorial</filename> directory of the source distribution. + See the <filename>README</filename> file in that directory for instructions + about running the examples. + </para> + +</sect1> diff --git a/src/tutorial/Makefile b/src/tutorial/Makefile index 16dc390f71..0ead60c2d4 100644 --- a/src/tutorial/Makefile +++ b/src/tutorial/Makefile @@ -13,8 +13,8 @@ # #------------------------------------------------------------------------- -MODULES = complex funcs -DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql +MODULES = complex funcs subscripting +DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql subscripting.sql ifdef NO_PGXS subdir = src/tutorial diff --git a/src/tutorial/subscripting.c b/src/tutorial/subscripting.c new file mode 100644 index 0000000000..1eb8c45652 --- /dev/null +++ b/src/tutorial/subscripting.c @@ -0,0 +1,201 @@ +/* + * src/tutorial/subscripting.c + * + ****************************************************************************** + This file contains routines that can be bound to a Postgres backend and + called by the backend in the process of processing queries. The calling + format for these routines is dictated by Postgres architecture. +******************************************************************************/ + +#include "postgres.h" + +#include "catalog/pg_type.h" +#include "executor/executor.h" +#include "executor/execExpr.h" +#include "nodes/nodeFuncs.h" +#include "parser/parse_coerce.h" +#include "utils/builtins.h" +#include "utils/fmgrprotos.h" + +PG_MODULE_MAGIC; + +typedef struct Custom +{ + int first; + int second; +} Custom; + +SubscriptingRef * custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref); +SubscriptingRef * custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate); +Datum custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate); +Datum custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate); + +PG_FUNCTION_INFO_V1(custom_in); +PG_FUNCTION_INFO_V1(custom_out); +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +/***************************************************************************** + * Input/Output functions + *****************************************************************************/ + +Datum +custom_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + int firstValue, + secondValue; + Custom *result; + + if (sscanf(str, " ( %d , %d )", &firstValue, &secondValue) != 2) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for complex: \"%s\"", + str))); + + + result = (Custom *) palloc(sizeof(Custom)); + result->first = firstValue; + result->second = secondValue; + PG_RETURN_POINTER(result); +} + +Datum +custom_out(PG_FUNCTION_ARGS) +{ + Custom *custom = (Custom *) PG_GETARG_POINTER(0); + char *result; + + result = psprintf("(%d, %d)", custom->first, custom->second); + PG_RETURN_CSTRING(result); +} + +/***************************************************************************** + * Custom subscripting logic functions + *****************************************************************************/ + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = INT4OID; + sbsref->refassgntype = INT4OID; + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + List *upperIndexpr = NIL; + ListCell *l; + + if (sbsref->reflowerindexpr != NIL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *)lfirst(sbsref->reflowerindexpr->head)))))); + + foreach(l, sbsref->refupperindexpr) + { + Node *subexpr = (Node *) lfirst(l); + + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *) lfirst(sbsref->refupperindexpr->head)))))); + + subexpr = coerce_to_target_type(pstate, + subexpr, exprType(subexpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript must have integer type"), + parser_errposition(pstate, exprLocation(subexpr)))); + + upperIndexpr = lappend(upperIndexpr, subexpr); + + if (isAssignment) + { + Node *assignExpr = (Node *) sbsref->refassgnexpr; + Node *new_from; + + new_from = coerce_to_target_type(pstate, + assignExpr, exprType(assignExpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (new_from == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom assignment requires int type"), + errhint("You will need to rewrite or cast the expression."), + parser_errposition(pstate, exprLocation(assignExpr)))); + sbsref->refassgnexpr = (Expr *)new_from; + } + } + + sbsref->refupperindexpr = upperIndexpr; + + return sbsref; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + Custom *container= (Custom *) containerSource; + int index; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + return (Datum) container->first; + else + return (Datum) container->second; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + int index; + Custom *container = (Custom *) containerSource; + + if (sbstate->resnull) + return containerSource; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + container->first = DatumGetInt32(sbstate->replacevalue); + else + container->second = DatumGetInt32(sbstate->replacevalue); + + return (Datum) container; +} diff --git a/src/tutorial/subscripting.source b/src/tutorial/subscripting.source new file mode 100644 index 0000000000..837cf30612 --- /dev/null +++ b/src/tutorial/subscripting.source @@ -0,0 +1,71 @@ +--------------------------------------------------------------------------- +-- +-- subscripting.sql- +-- This file shows how to create a new subscripting procedure for +-- user-defined type. +-- +-- +-- Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group +-- Portions Copyright (c) 1994, Regents of the University of California +-- +-- src/tutorial/subscripting.source +-- +--------------------------------------------------------------------------- + +----------------------------- +-- Creating a new type: +-- We are going to create a new type called 'complex' which represents +-- complex numbers. +-- A user-defined type must have an input and an output function, and +-- optionally can have binary input and output functions. All of these +-- are usually user-defined C functions. +----------------------------- + +-- Assume the user defined functions are in /home/erthalion/programms/postgresql-master/src/tutorial/complex$DLSUFFIX +-- (we do not want to assume this is in the dynamic loader search path). +-- Look at $PWD/complex.c for the source. Note that we declare all of +-- them as STRICT, so we do not need to cope with NULL inputs in the +-- C code. We also mark them IMMUTABLE, since they always return the +-- same outputs given the same inputs. + +-- the input function 'complex_in' takes a null-terminated string (the +-- textual representation of the type) and turns it into the internal +-- (in memory) representation. You will get a message telling you 'complex' +-- does not exist yet but that's okay. + +CREATE FUNCTION custom_in(cstring) + RETURNS custom + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +-- the output function 'complex_out' takes the internal representation and +-- converts it into the textual representation. + +CREATE FUNCTION custom_out(custom) + RETURNS cstring + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 8, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler +); + +-- we can use it in a table + +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; -- 2.21.0 --ques2rolqe435p6o Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v32-0005-Polymorphic-subscripting.patch" ^ permalink raw reply [nested|flat] 28+ messages in thread
* [PATCH v32 4/6] Subscripting documentation @ 2019-02-01 10:47 erthalion <[email protected]> 0 siblings, 0 replies; 28+ messages in thread From: erthalion @ 2019-02-01 10:47 UTC (permalink / raw) Supporting documentation for generalized subscripting. It includes the description of a new field in pg_type, the new section for jsonb documentation about subscripting feature on this data type, and also the tutorial about how to write subscripting operator for a custom data type. Reviewed-by: Tom Lane, Arthur Zakirov, Oleksandr Shulgin --- doc/src/sgml/catalogs.sgml | 8 ++ doc/src/sgml/extend.sgml | 6 + doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/json.sgml | 39 ++++++ doc/src/sgml/ref/create_type.sgml | 33 ++++- doc/src/sgml/xsubscripting.sgml | 111 +++++++++++++++++ src/tutorial/Makefile | 4 +- src/tutorial/subscripting.c | 201 ++++++++++++++++++++++++++++++ src/tutorial/subscripting.source | 71 +++++++++++ 9 files changed, 470 insertions(+), 4 deletions(-) create mode 100644 doc/src/sgml/xsubscripting.sgml create mode 100644 src/tutorial/subscripting.c create mode 100644 src/tutorial/subscripting.source diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 048ff284f7..b17e9ee382 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -8971,6 +8971,14 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l </para></entry> </row> + <row> + <entry><structfield>typsubshandler</structfield></entry> + <entry><type>regproc</type></entry> + <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry> + <entry>Custom subscripting function with type-specific logic for parsing + and validation, or 0 if this type doesn't support subscripting.</entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>typdefault</structfield> <type>text</type> diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml index 890ff97b7a..65cf5706b6 100644 --- a/doc/src/sgml/extend.sgml +++ b/doc/src/sgml/extend.sgml @@ -33,6 +33,11 @@ operators (starting in <xref linkend="xoper"/>) </para> </listitem> + <listitem> + <para> + subscripting procedure (starting in <xref linkend="xsubscripting"/>) + </para> + </listitem> <listitem> <para> operator classes for indexes (starting in <xref linkend="xindex"/>) @@ -481,6 +486,7 @@ RETURNS anycompatible AS ... &xaggr; &xtypes; &xoper; + &xsubscripting; &xindex; diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 64b5da0070..4c3fd37fc1 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -69,6 +69,7 @@ <!ENTITY xplang SYSTEM "xplang.sgml"> <!ENTITY xoper SYSTEM "xoper.sgml"> <!ENTITY xtypes SYSTEM "xtypes.sgml"> +<!ENTITY xsubscripting SYSTEM "xsubscripting.sgml"> <!ENTITY plperl SYSTEM "plperl.sgml"> <!ENTITY plpython SYSTEM "plpython.sgml"> <!ENTITY plsql SYSTEM "plpgsql.sgml"> diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml index c0a6554d4d..5c538dca05 100644 --- a/doc/src/sgml/json.sgml +++ b/doc/src/sgml/json.sgml @@ -602,6 +602,45 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu </para> </sect2> + <sect2 id="jsonb-subscripting"> + <title><type>jsonb</type> Subscripting</title> + <para> + <type>jsonb</type> data type supports array-style subscripting expressions + to extract or update particular elements. It's possible to use multiple + subscripting expressions to extract nested values. In this case, a chain of + subscripting expressions follows the same rules as the + <literal>path</literal> argument in <literal>jsonb_set</literal> function, + e.g. in case of arrays it is a 0-based operation or that negative integers + that appear in <literal>path</literal> count from the end of JSON arrays. + The result of subscripting expressions is always jsonb data type. An + example of subscripting syntax: +<programlisting> +-- Extract value by key +SELECT ('{"a": 1}'::jsonb)['a']; + +-- Extract nested value by key path +SELECT ('{"a": {"b": {"c": 1}}}'::jsonb)['a']['b']['c']; + +-- Extract element by index +SELECT ('[1, "2", null]'::jsonb)[1]; + +-- Update value by key +UPDATE table_name SET jsonb_field['key'] = 1; + +-- Select records using where clause with subscripting. Since the result of +-- subscripting is jsonb and we basically want to compare two jsonb objects, we +-- need to put the value in double quotes to be able to convert it to jsonb. +SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"'; +</programlisting> + + There is no special indexing support for such kind of expressions, but you + can always create a functional index that includes it +<programlisting> +CREATE INDEX idx ON table_name ((jsonb_field['key'])); +</programlisting> + </para> + </sect2> + <sect2> <title>Transforms</title> diff --git a/doc/src/sgml/ref/create_type.sgml b/doc/src/sgml/ref/create_type.sgml index 111f8e65d2..a34df4d247 100644 --- a/doc/src/sgml/ref/create_type.sgml +++ b/doc/src/sgml/ref/create_type.sgml @@ -54,6 +54,7 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> ( [ , ELEMENT = <replaceable class="parameter">element</replaceable> ] [ , DELIMITER = <replaceable class="parameter">delimiter</replaceable> ] [ , COLLATABLE = <replaceable class="parameter">collatable</replaceable> ] + [ , SUBSCRIPTING_HANDLER = <replaceable class="parameter">subscripting_handler_function</replaceable> ] ) CREATE TYPE <replaceable class="parameter">name</replaceable> @@ -196,8 +197,9 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> <replaceable class="parameter">receive_function</replaceable>, <replaceable class="parameter">send_function</replaceable>, <replaceable class="parameter">type_modifier_input_function</replaceable>, - <replaceable class="parameter">type_modifier_output_function</replaceable> and - <replaceable class="parameter">analyze_function</replaceable> + <replaceable class="parameter">type_modifier_output_function</replaceable>, + <replaceable class="parameter">analyze_function</replaceable>, + <replaceable class="parameter">subscripting_handler_function</replaceable> are optional. Generally these functions have to be coded in C or another low-level language. </para> @@ -454,6 +456,22 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> make use of the collation information; this does not happen automatically merely by marking the type collatable. </para> + + <para> + The optional + <replaceable class="parameter">subscripting_handler_function</replaceable> + contains type-specific logic for subscripting of the data type. + By default, there is no such function provided, which means that the data + type doesn't support subscripting. The subscripting function must be + declared to take a single argument of type <type>internal</type>, and return + a <type>internal</type> result. There are two examples of implementation for + subscripting functions in case of array + (<replaceable class="parameter">array_subscripting_handler</replaceable>) + and jsonb + (<replaceable class="parameter">jsonb_subscripting_handler</replaceable>) + types in <filename>src/backend/utils/adt/arrayfuncs.c</filename> and + <filename>src/backend/utils/adt/jsonfuncs.c</filename>, respectively. + </para> </refsect2> <refsect2> @@ -769,6 +787,17 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> </para> </listitem> </varlistentry> + + <varlistentry> + <term><replaceable class="parameter">subscripting_handler_function</replaceable></term> + <listitem> + <para> + The name of a function that returns list of type-specific callback functions to + support subscripting logic for the data type. + </para> + </listitem> + </varlistentry> + </variablelist> </refsect1> diff --git a/doc/src/sgml/xsubscripting.sgml b/doc/src/sgml/xsubscripting.sgml new file mode 100644 index 0000000000..7224e81fa2 --- /dev/null +++ b/doc/src/sgml/xsubscripting.sgml @@ -0,0 +1,111 @@ +<!-- doc/src/sgml/xsubscripting.sgml --> + + <sect1 id="xsubscripting"> + <title>User-defined subscripting procedure</title> + + <indexterm zone="xsubscripting"> + <primary>custom subscripting</primary> + </indexterm> + <para> + When you define a new base type, you can also specify a custom procedure to + handle subscripting expressions. It must contain logic for verification and + evaluation of this expression, i.e. fetching or updating some data in this + data type. For instance: +</para> +<programlisting><![CDATA[ +typedef struct Custom +{ + int first; + int second; +} Custom; + +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = someType; + sbsref->refassgntype = someType; + + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + // some validation and coercion logic + + return sbsref; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some assignment logic + + return newContainer; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some fetch logic based on sbstate +}]]> +</programlisting> + +<para> + Then you can define a subscripting procedure and a custom data type: +</para> +<programlisting> +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '<replaceable>filename</replaceable>' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 4, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler, +); +</programlisting> + +<para> + and use it as usual: +</para> +<programlisting> +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; +</programlisting> + + + <para> + The examples of custom subscripting implementation can be found in + <filename>subscripting.sql</filename> and <filename>subscripting.c</filename> + in the <filename>src/tutorial</filename> directory of the source distribution. + See the <filename>README</filename> file in that directory for instructions + about running the examples. + </para> + +</sect1> diff --git a/src/tutorial/Makefile b/src/tutorial/Makefile index 16dc390f71..0ead60c2d4 100644 --- a/src/tutorial/Makefile +++ b/src/tutorial/Makefile @@ -13,8 +13,8 @@ # #------------------------------------------------------------------------- -MODULES = complex funcs -DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql +MODULES = complex funcs subscripting +DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql subscripting.sql ifdef NO_PGXS subdir = src/tutorial diff --git a/src/tutorial/subscripting.c b/src/tutorial/subscripting.c new file mode 100644 index 0000000000..1eb8c45652 --- /dev/null +++ b/src/tutorial/subscripting.c @@ -0,0 +1,201 @@ +/* + * src/tutorial/subscripting.c + * + ****************************************************************************** + This file contains routines that can be bound to a Postgres backend and + called by the backend in the process of processing queries. The calling + format for these routines is dictated by Postgres architecture. +******************************************************************************/ + +#include "postgres.h" + +#include "catalog/pg_type.h" +#include "executor/executor.h" +#include "executor/execExpr.h" +#include "nodes/nodeFuncs.h" +#include "parser/parse_coerce.h" +#include "utils/builtins.h" +#include "utils/fmgrprotos.h" + +PG_MODULE_MAGIC; + +typedef struct Custom +{ + int first; + int second; +} Custom; + +SubscriptingRef * custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref); +SubscriptingRef * custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate); +Datum custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate); +Datum custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate); + +PG_FUNCTION_INFO_V1(custom_in); +PG_FUNCTION_INFO_V1(custom_out); +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +/***************************************************************************** + * Input/Output functions + *****************************************************************************/ + +Datum +custom_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + int firstValue, + secondValue; + Custom *result; + + if (sscanf(str, " ( %d , %d )", &firstValue, &secondValue) != 2) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for complex: \"%s\"", + str))); + + + result = (Custom *) palloc(sizeof(Custom)); + result->first = firstValue; + result->second = secondValue; + PG_RETURN_POINTER(result); +} + +Datum +custom_out(PG_FUNCTION_ARGS) +{ + Custom *custom = (Custom *) PG_GETARG_POINTER(0); + char *result; + + result = psprintf("(%d, %d)", custom->first, custom->second); + PG_RETURN_CSTRING(result); +} + +/***************************************************************************** + * Custom subscripting logic functions + *****************************************************************************/ + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = INT4OID; + sbsref->refassgntype = INT4OID; + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + List *upperIndexpr = NIL; + ListCell *l; + + if (sbsref->reflowerindexpr != NIL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *)lfirst(sbsref->reflowerindexpr->head)))))); + + foreach(l, sbsref->refupperindexpr) + { + Node *subexpr = (Node *) lfirst(l); + + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *) lfirst(sbsref->refupperindexpr->head)))))); + + subexpr = coerce_to_target_type(pstate, + subexpr, exprType(subexpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript must have integer type"), + parser_errposition(pstate, exprLocation(subexpr)))); + + upperIndexpr = lappend(upperIndexpr, subexpr); + + if (isAssignment) + { + Node *assignExpr = (Node *) sbsref->refassgnexpr; + Node *new_from; + + new_from = coerce_to_target_type(pstate, + assignExpr, exprType(assignExpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (new_from == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom assignment requires int type"), + errhint("You will need to rewrite or cast the expression."), + parser_errposition(pstate, exprLocation(assignExpr)))); + sbsref->refassgnexpr = (Expr *)new_from; + } + } + + sbsref->refupperindexpr = upperIndexpr; + + return sbsref; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + Custom *container= (Custom *) containerSource; + int index; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + return (Datum) container->first; + else + return (Datum) container->second; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + int index; + Custom *container = (Custom *) containerSource; + + if (sbstate->resnull) + return containerSource; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + container->first = DatumGetInt32(sbstate->replacevalue); + else + container->second = DatumGetInt32(sbstate->replacevalue); + + return (Datum) container; +} diff --git a/src/tutorial/subscripting.source b/src/tutorial/subscripting.source new file mode 100644 index 0000000000..837cf30612 --- /dev/null +++ b/src/tutorial/subscripting.source @@ -0,0 +1,71 @@ +--------------------------------------------------------------------------- +-- +-- subscripting.sql- +-- This file shows how to create a new subscripting procedure for +-- user-defined type. +-- +-- +-- Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group +-- Portions Copyright (c) 1994, Regents of the University of California +-- +-- src/tutorial/subscripting.source +-- +--------------------------------------------------------------------------- + +----------------------------- +-- Creating a new type: +-- We are going to create a new type called 'complex' which represents +-- complex numbers. +-- A user-defined type must have an input and an output function, and +-- optionally can have binary input and output functions. All of these +-- are usually user-defined C functions. +----------------------------- + +-- Assume the user defined functions are in /home/erthalion/programms/postgresql-master/src/tutorial/complex$DLSUFFIX +-- (we do not want to assume this is in the dynamic loader search path). +-- Look at $PWD/complex.c for the source. Note that we declare all of +-- them as STRICT, so we do not need to cope with NULL inputs in the +-- C code. We also mark them IMMUTABLE, since they always return the +-- same outputs given the same inputs. + +-- the input function 'complex_in' takes a null-terminated string (the +-- textual representation of the type) and turns it into the internal +-- (in memory) representation. You will get a message telling you 'complex' +-- does not exist yet but that's okay. + +CREATE FUNCTION custom_in(cstring) + RETURNS custom + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +-- the output function 'complex_out' takes the internal representation and +-- converts it into the textual representation. + +CREATE FUNCTION custom_out(custom) + RETURNS cstring + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 8, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler +); + +-- we can use it in a table + +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; -- 2.21.0 --xuxpg5p32zizz4zy Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v32-0005-Polymorphic-subscripting.patch" ^ permalink raw reply [nested|flat] 28+ messages in thread
* [PATCH v29 4/6] Subscripting documentation @ 2019-02-01 10:47 erthalion <[email protected]> 0 siblings, 0 replies; 28+ messages in thread From: erthalion @ 2019-02-01 10:47 UTC (permalink / raw) Supporting documentation for generalized subscripting. It includes the description of a new field in pg_type, the new section for jsonb documentation about subscripting feature on this data type, and also the tutorial about how to write subscripting operator for a custom data type. Reviewed-by: Tom Lane, Arthur Zakirov, Oleksandr Shulgin --- doc/src/sgml/catalogs.sgml | 8 ++ doc/src/sgml/extend.sgml | 6 + doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/json.sgml | 39 ++++++ doc/src/sgml/ref/create_type.sgml | 33 ++++- doc/src/sgml/xsubscripting.sgml | 111 +++++++++++++++++ src/tutorial/Makefile | 4 +- src/tutorial/subscripting.c | 201 ++++++++++++++++++++++++++++++ src/tutorial/subscripting.source | 71 +++++++++++ 9 files changed, 470 insertions(+), 4 deletions(-) create mode 100644 doc/src/sgml/xsubscripting.sgml create mode 100644 src/tutorial/subscripting.c create mode 100644 src/tutorial/subscripting.source diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 55694c4368..b8fde6af12 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -8012,6 +8012,14 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l </para></entry> </row> + <row> + <entry><structfield>typsubshandler</structfield></entry> + <entry><type>regproc</type></entry> + <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry> + <entry>Custom subscripting function with type-specific logic for parsing + and validation, or 0 if this type doesn't support subscripting.</entry> + </row> + <row> <entry><structfield>typdefaultbin</structfield></entry> <entry><type>pg_node_tree</type></entry> diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml index a3046f22d0..9a4ce542ba 100644 --- a/doc/src/sgml/extend.sgml +++ b/doc/src/sgml/extend.sgml @@ -33,6 +33,11 @@ operators (starting in <xref linkend="xoper"/>) </para> </listitem> + <listitem> + <para> + subscripting procedure (starting in <xref linkend="xsubscripting"/>) + </para> + </listitem> <listitem> <para> operator classes for indexes (starting in <xref linkend="xindex"/>) @@ -314,6 +319,7 @@ &xaggr; &xtypes; &xoper; + &xsubscripting; &xindex; diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 3da2365ea9..650e21b7e1 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -69,6 +69,7 @@ <!ENTITY xplang SYSTEM "xplang.sgml"> <!ENTITY xoper SYSTEM "xoper.sgml"> <!ENTITY xtypes SYSTEM "xtypes.sgml"> +<!ENTITY xsubscripting SYSTEM "xsubscripting.sgml"> <!ENTITY plperl SYSTEM "plperl.sgml"> <!ENTITY plpython SYSTEM "plpython.sgml"> <!ENTITY plsql SYSTEM "plpgsql.sgml"> diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml index 6ff8751870..d6f2f1d55f 100644 --- a/doc/src/sgml/json.sgml +++ b/doc/src/sgml/json.sgml @@ -600,6 +600,45 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu </para> </sect2> + <sect2 id="jsonb-subscripting"> + <title><type>jsonb</type> Subscripting</title> + <para> + <type>jsonb</type> data type supports array-style subscripting expressions + to extract or update particular element. It's possible to use multiple + subscripting expressions to extract nested values. In this case a chain of + subscripting expressions follows the same rules as the + <literal>path</literal> argument in <literal>jsonb_set</literal> function, + e.g. in case of arrays it is a 0-based operation or that negative integers + that appear in <literal>path</literal> count from the end of JSON arrays. + The result of subscripting expressions is always jsonb data type. An + example of subscripting syntax: +<programlisting> +-- Extract value by key +SELECT ('{"a": 1}'::jsonb)['a']; + +-- Extract nested value by key path +SELECT ('{"a": {"b": {"c": 1}}}'::jsonb)['a']['b']['c']; + +-- Extract element by index +SELECT ('[1, "2", null]'::jsonb)[1]; + +-- Update value by key +UPDATE table_name SET jsonb_field['key'] = 1; + +-- Select records using where clause with subscripting. Since the result of +-- subscripting is jsonb and we basically want to compare two jsonb objects, we +-- need to put the value in double quotes to be able to convert it to jsonb. +SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"'; +</programlisting> + + There is no special indexing support for such kind of expressions, but you + always can create a functional index that includes it +<programlisting> +CREATE INDEX idx ON table_name ((jsonb_field['key'])); +</programlisting> + </para> + </sect2> + <sect2> <title>Transforms</title> diff --git a/doc/src/sgml/ref/create_type.sgml b/doc/src/sgml/ref/create_type.sgml index 175315f3d7..bd622f5ef3 100644 --- a/doc/src/sgml/ref/create_type.sgml +++ b/doc/src/sgml/ref/create_type.sgml @@ -54,6 +54,7 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> ( [ , ELEMENT = <replaceable class="parameter">element</replaceable> ] [ , DELIMITER = <replaceable class="parameter">delimiter</replaceable> ] [ , COLLATABLE = <replaceable class="parameter">collatable</replaceable> ] + [ , SUBSCRIPTING_HANDLER = <replaceable class="parameter">subscripting_handler_function</replaceable> ] ) CREATE TYPE <replaceable class="parameter">name</replaceable> @@ -196,8 +197,9 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> <replaceable class="parameter">receive_function</replaceable>, <replaceable class="parameter">send_function</replaceable>, <replaceable class="parameter">type_modifier_input_function</replaceable>, - <replaceable class="parameter">type_modifier_output_function</replaceable> and - <replaceable class="parameter">analyze_function</replaceable> + <replaceable class="parameter">type_modifier_output_function</replaceable>, + <replaceable class="parameter">analyze_function</replaceable>, + <replaceable class="parameter">subscripting_handler_function</replaceable> are optional. Generally these functions have to be coded in C or another low-level language. </para> @@ -454,6 +456,22 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> make use of the collation information; this does not happen automatically merely by marking the type collatable. </para> + + <para> + The optional + <replaceable class="parameter">subscripting_handler_function</replaceable> + contains type-specific logic for subscripting of the data type. + By default, there is no such function provided, which means that the data + type doesn't support subscripting. The subscripting function must be + declared to take a single argument of type <type>internal</type>, and return + a <type>internal</type> result. There are two examples of implementation for + subscripting functions in case of array + (<replaceable class="parameter">array_subscripting_handler</replaceable>) + and jsonb + (<replaceable class="parameter">jsonb_subscripting_handler</replaceable>) + types in <filename>src/backend/utils/adt/arrayfuncs.c</filename> and + <filename>src/backend/utils/adt/jsonfuncs.c</filename> corresponding. + </para> </refsect2> <refsect2> @@ -769,6 +787,17 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> </para> </listitem> </varlistentry> + + <varlistentry> + <term><replaceable class="parameter">subscripting_handler_function</replaceable></term> + <listitem> + <para> + The name of a function that returns list of type-specific callback functions to + support subscripting logic for the data type. + </para> + </listitem> + </varlistentry> + </variablelist> </refsect1> diff --git a/doc/src/sgml/xsubscripting.sgml b/doc/src/sgml/xsubscripting.sgml new file mode 100644 index 0000000000..d701631223 --- /dev/null +++ b/doc/src/sgml/xsubscripting.sgml @@ -0,0 +1,111 @@ +<!-- doc/src/sgml/xsubscripting.sgml --> + + <sect1 id="xsubscripting"> + <title>User-defined subscripting procedure</title> + + <indexterm zone="xsubscripting"> + <primary>custom subscripting</primary> + </indexterm> + <para> + When you define a new base type, you can also specify a custom procedures to + handle subscripting expressions. They must contain logic for verification and + evaluation of this expression, i.e. fetching or updating some data in this + data type. For instance: +</para> +<programlisting><![CDATA[ +typedef struct Custom +{ + int first; + int second; +} Custom; + +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = someType; + sbsref->refassgntype = someType; + + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + // some validation and coercion logic + + return sbsref; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some assignment logic + + return newContainer; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + // Some fetch logic based on sbsdata +}]]> +</programlisting> + +<para> + Then you can define a subscripting procedures and a custom data type: +</para> +<programlisting> +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '<replaceable>filename</replaceable>' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 4, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler, +); +</programlisting> + +<para> + and use it as usual: +</para> +<programlisting> +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; +</programlisting> + + + <para> + The examples of custom subscripting implementation can be found in + <filename>subscripting.sql</filename> and <filename>subscripting.c</filename> + in the <filename>src/tutorial</filename> directory of the source distribution. + See the <filename>README</filename> file in that directory for instructions + about running the examples. + </para> + +</sect1> diff --git a/src/tutorial/Makefile b/src/tutorial/Makefile index 16dc390f71..0ead60c2d4 100644 --- a/src/tutorial/Makefile +++ b/src/tutorial/Makefile @@ -13,8 +13,8 @@ # #------------------------------------------------------------------------- -MODULES = complex funcs -DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql +MODULES = complex funcs subscripting +DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql subscripting.sql ifdef NO_PGXS subdir = src/tutorial diff --git a/src/tutorial/subscripting.c b/src/tutorial/subscripting.c new file mode 100644 index 0000000000..1eb8c45652 --- /dev/null +++ b/src/tutorial/subscripting.c @@ -0,0 +1,201 @@ +/* + * src/tutorial/subscripting.c + * + ****************************************************************************** + This file contains routines that can be bound to a Postgres backend and + called by the backend in the process of processing queries. The calling + format for these routines is dictated by Postgres architecture. +******************************************************************************/ + +#include "postgres.h" + +#include "catalog/pg_type.h" +#include "executor/executor.h" +#include "executor/execExpr.h" +#include "nodes/nodeFuncs.h" +#include "parser/parse_coerce.h" +#include "utils/builtins.h" +#include "utils/fmgrprotos.h" + +PG_MODULE_MAGIC; + +typedef struct Custom +{ + int first; + int second; +} Custom; + +SubscriptingRef * custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref); +SubscriptingRef * custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate); +Datum custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate); +Datum custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate); + +PG_FUNCTION_INFO_V1(custom_in); +PG_FUNCTION_INFO_V1(custom_out); +PG_FUNCTION_INFO_V1(custom_subscripting_handler); + +/***************************************************************************** + * Input/Output functions + *****************************************************************************/ + +Datum +custom_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + int firstValue, + secondValue; + Custom *result; + + if (sscanf(str, " ( %d , %d )", &firstValue, &secondValue) != 2) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for complex: \"%s\"", + str))); + + + result = (Custom *) palloc(sizeof(Custom)); + result->first = firstValue; + result->second = secondValue; + PG_RETURN_POINTER(result); +} + +Datum +custom_out(PG_FUNCTION_ARGS) +{ + Custom *custom = (Custom *) PG_GETARG_POINTER(0); + char *result; + + result = psprintf("(%d, %d)", custom->first, custom->second); + PG_RETURN_CSTRING(result); +} + +/***************************************************************************** + * Custom subscripting logic functions + *****************************************************************************/ + +Datum +custom_subscripting_handler(PG_FUNCTION_ARGS) +{ + SubscriptRoutines *sbsroutines = (SubscriptRoutines *) + palloc(sizeof(SubscriptRoutines)); + + sbsroutines->prepare = custom_subscript_prepare; + sbsroutines->validate = custom_subscript_validate; + sbsroutines->fetch = custom_subscript_fetch; + sbsroutines->assign = custom_subscript_assign; + + PG_RETURN_POINTER(sbsroutines); +} + +SubscriptingRef * +custom_subscript_prepare(bool isAssignment, SubscriptingRef *sbsref) +{ + sbsref->refelemtype = INT4OID; + sbsref->refassgntype = INT4OID; + return sbsref; +} + +SubscriptingRef * +custom_subscript_validate(bool isAssignment, SubscriptingRef *sbsref, + ParseState *pstate) +{ + List *upperIndexpr = NIL; + ListCell *l; + + if (sbsref->reflowerindexpr != NIL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *)lfirst(sbsref->reflowerindexpr->head)))))); + + foreach(l, sbsref->refupperindexpr) + { + Node *subexpr = (Node *) lfirst(l); + + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript does not support slices"), + parser_errposition(pstate, exprLocation( + ((Node *) lfirst(sbsref->refupperindexpr->head)))))); + + subexpr = coerce_to_target_type(pstate, + subexpr, exprType(subexpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom subscript must have integer type"), + parser_errposition(pstate, exprLocation(subexpr)))); + + upperIndexpr = lappend(upperIndexpr, subexpr); + + if (isAssignment) + { + Node *assignExpr = (Node *) sbsref->refassgnexpr; + Node *new_from; + + new_from = coerce_to_target_type(pstate, + assignExpr, exprType(assignExpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (new_from == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("custom assignment requires int type"), + errhint("You will need to rewrite or cast the expression."), + parser_errposition(pstate, exprLocation(assignExpr)))); + sbsref->refassgnexpr = (Expr *)new_from; + } + } + + sbsref->refupperindexpr = upperIndexpr; + + return sbsref; +} + +Datum +custom_subscript_fetch(Datum containerSource, SubscriptingRefState *sbstate) +{ + Custom *container= (Custom *) containerSource; + int index; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + return (Datum) container->first; + else + return (Datum) container->second; +} + +Datum +custom_subscript_assign(Datum containerSource, SubscriptingRefState *sbstate) +{ + int index; + Custom *container = (Custom *) containerSource; + + if (sbstate->resnull) + return containerSource; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upperindex[0]); + + if (index == 1) + container->first = DatumGetInt32(sbstate->replacevalue); + else + container->second = DatumGetInt32(sbstate->replacevalue); + + return (Datum) container; +} diff --git a/src/tutorial/subscripting.source b/src/tutorial/subscripting.source new file mode 100644 index 0000000000..837cf30612 --- /dev/null +++ b/src/tutorial/subscripting.source @@ -0,0 +1,71 @@ +--------------------------------------------------------------------------- +-- +-- subscripting.sql- +-- This file shows how to create a new subscripting procedure for +-- user-defined type. +-- +-- +-- Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group +-- Portions Copyright (c) 1994, Regents of the University of California +-- +-- src/tutorial/subscripting.source +-- +--------------------------------------------------------------------------- + +----------------------------- +-- Creating a new type: +-- We are going to create a new type called 'complex' which represents +-- complex numbers. +-- A user-defined type must have an input and an output function, and +-- optionally can have binary input and output functions. All of these +-- are usually user-defined C functions. +----------------------------- + +-- Assume the user defined functions are in /home/erthalion/programms/postgresql-master/src/tutorial/complex$DLSUFFIX +-- (we do not want to assume this is in the dynamic loader search path). +-- Look at $PWD/complex.c for the source. Note that we declare all of +-- them as STRICT, so we do not need to cope with NULL inputs in the +-- C code. We also mark them IMMUTABLE, since they always return the +-- same outputs given the same inputs. + +-- the input function 'complex_in' takes a null-terminated string (the +-- textual representation of the type) and turns it into the internal +-- (in memory) representation. You will get a message telling you 'complex' +-- does not exist yet but that's okay. + +CREATE FUNCTION custom_in(cstring) + RETURNS custom + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +-- the output function 'complex_out' takes the internal representation and +-- converts it into the textual representation. + +CREATE FUNCTION custom_out(custom) + RETURNS cstring + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION custom_subscripting_handler(internal) + RETURNS internal + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 8, + input = custom_in, + output = custom_out, + subscripting_handler = custom_subscripting_handler +); + +-- we can use it in a table + +CREATE TABLE test_subscripting ( + data custom +); + +INSERT INTO test_subscripting VALUES ('(1, 2)'); + +SELECT data[0] from test_subscripting; + +UPDATE test_subscripting SET data[1] = 3; -- 2.21.0 --adquyx6kg5n26q6j Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v29-0005-Polymorphic-subscripting.patch" ^ permalink raw reply [nested|flat] 28+ messages in thread
* Missing LWLock protection in pgstat_reset_replslot() @ 2024-03-01 10:15 Bertrand Drouvot <[email protected]> 0 siblings, 0 replies; 28+ messages in thread From: Bertrand Drouvot @ 2024-03-01 10:15 UTC (permalink / raw) To: [email protected] Hi hackers, I think that pgstat_reset_replslot() is missing LWLock protection. Indeed, we don't have any guarantee that the slot is active (then preventing it to be dropped/recreated) when this function is executed. Attached a patch to add the missing protection. Regards, -- Bertrand Drouvot PostgreSQL Contributors Team RDS Open Source Databases Amazon Web Services: https://aws.amazon.com Attachments: [text/x-diff] v1-0001-Adding-LWLock-protection-in-pgstat_reset_replslot.patch (1.3K, ../../[email protected]/2-v1-0001-Adding-LWLock-protection-in-pgstat_reset_replslot.patch) download | inline diff: From 26007012272631a6d1024c143c8dd3eed9430c03 Mon Sep 17 00:00:00 2001 From: Bertrand Drouvot <[email protected]> Date: Fri, 1 Mar 2024 10:04:17 +0000 Subject: [PATCH v1] Adding LWLock protection in pgstat_reset_replslot() pgstat_reset_replslot() is missing a LWLock protection as we don't have any guarantee that the slot is active (then preventing it to be dropped/recreated) when this function is executed. --- src/backend/utils/activity/pgstat_replslot.c | 4 ++++ 1 file changed, 4 insertions(+) 100.0% src/backend/utils/activity/ diff --git a/src/backend/utils/activity/pgstat_replslot.c b/src/backend/utils/activity/pgstat_replslot.c index 70cabf2881..a113e1c486 100644 --- a/src/backend/utils/activity/pgstat_replslot.c +++ b/src/backend/utils/activity/pgstat_replslot.c @@ -46,6 +46,8 @@ pgstat_reset_replslot(const char *name) Assert(name != NULL); + LWLockAcquire(ReplicationSlotControlLock, LW_SHARED); + /* Check if the slot exits with the given name. */ slot = SearchNamedReplicationSlot(name, true); @@ -65,6 +67,8 @@ pgstat_reset_replslot(const char *name) /* reset this one entry */ pgstat_reset(PGSTAT_KIND_REPLSLOT, InvalidOid, ReplicationSlotIndex(slot)); + + LWLockRelease(ReplicationSlotControlLock); } /* -- 2.34.1 ^ permalink raw reply [nested|flat] 28+ messages in thread
end of thread, other threads:[~2024-03-01 10:15 UTC | newest] Thread overview: 28+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2019-02-01 10:47 [PATCH v33 4/5] Subscripting documentation erthalion <[email protected]> 2019-02-01 10:47 [PATCH v32 4/6] Subscripting documentation erthalion <[email protected]> 2019-02-01 10:47 [PATCH v33 4/5] Subscripting documentation erthalion <[email protected]> 2019-02-01 10:47 [PATCH v29 4/6] Subscripting documentation erthalion <[email protected]> 2019-02-01 10:47 [PATCH v33 4/5] Subscripting documentation erthalion <[email protected]> 2019-02-01 10:47 [PATCH v33 4/5] Subscripting documentation erthalion <[email protected]> 2019-02-01 10:47 [PATCH v33 4/5] Subscripting documentation erthalion <[email protected]> 2019-02-01 10:47 [PATCH v33 4/5] Subscripting documentation erthalion <[email protected]> 2019-02-01 10:47 [PATCH v33 4/5] Subscripting documentation erthalion <[email protected]> 2019-02-01 10:47 [PATCH v32 4/6] Subscripting documentation erthalion <[email protected]> 2019-02-01 10:47 [PATCH v33 4/5] Subscripting documentation erthalion <[email protected]> 2019-02-01 10:47 [PATCH v33 4/5] Subscripting documentation erthalion <[email protected]> 2019-02-01 10:47 [PATCH v33 4/5] Subscripting documentation erthalion <[email protected]> 2019-02-01 10:47 [PATCH v33 4/5] Subscripting documentation erthalion <[email protected]> 2019-02-01 10:47 [PATCH v35 4/5] Subscripting documentation erthalion <[email protected]> 2019-02-01 10:47 [PATCH v33 4/5] Subscripting documentation erthalion <[email protected]> 2019-02-01 10:47 [PATCH v33 4/5] Subscripting documentation erthalion <[email protected]> 2019-02-01 10:47 [PATCH v31 4/6] Subscripting documentation erthalion <[email protected]> 2019-02-01 10:47 [PATCH v33 4/5] Subscripting documentation erthalion <[email protected]> 2019-02-01 10:47 [PATCH v33 4/5] Subscripting documentation erthalion <[email protected]> 2019-02-01 10:47 [PATCH v33 4/5] Subscripting documentation erthalion <[email protected]> 2019-02-01 10:47 [PATCH v33 4/5] Subscripting documentation erthalion <[email protected]> 2019-02-01 10:47 [PATCH v33 4/5] Subscripting documentation erthalion <[email protected]> 2019-02-01 10:47 [PATCH v33 4/5] Subscripting documentation erthalion <[email protected]> 2019-02-01 10:47 [PATCH v33 4/5] Subscripting documentation erthalion <[email protected]> 2019-02-01 10:47 [PATCH v30 4/6] Subscripting documentation erthalion <[email protected]> 2019-02-01 10:47 [PATCH v34 4/5] Subscripting documentation erthalion <[email protected]> 2024-03-01 10:15 Missing LWLock protection in pgstat_reset_replslot() Bertrand Drouvot <[email protected]>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox