public inbox for [email protected]
help / color / mirror / Atom feed[PATCH v30 4/6] Subscripting documentation
29+ messages / 3 participants
[nested] [flat]
* [PATCH v33 4/5] Subscripting documentation
@ 2019-02-01 10:47 erthalion <[email protected]>
0 siblings, 0 replies; 29+ 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] 29+ messages in thread
* [PATCH v33 4/5] Subscripting documentation
@ 2019-02-01 10:47 erthalion <[email protected]>
0 siblings, 0 replies; 29+ 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] 29+ messages in thread
* [PATCH v33 4/5] Subscripting documentation
@ 2019-02-01 10:47 erthalion <[email protected]>
0 siblings, 0 replies; 29+ 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] 29+ messages in thread
* [PATCH v33 4/5] Subscripting documentation
@ 2019-02-01 10:47 erthalion <[email protected]>
0 siblings, 0 replies; 29+ 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] 29+ messages in thread
* [PATCH v33 4/5] Subscripting documentation
@ 2019-02-01 10:47 erthalion <[email protected]>
0 siblings, 0 replies; 29+ 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] 29+ messages in thread
* [PATCH v33 4/5] Subscripting documentation
@ 2019-02-01 10:47 erthalion <[email protected]>
0 siblings, 0 replies; 29+ 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] 29+ messages in thread
* [PATCH v33 4/5] Subscripting documentation
@ 2019-02-01 10:47 erthalion <[email protected]>
0 siblings, 0 replies; 29+ 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] 29+ messages in thread
* [PATCH v33 4/5] Subscripting documentation
@ 2019-02-01 10:47 erthalion <[email protected]>
0 siblings, 0 replies; 29+ 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] 29+ messages in thread
* [PATCH v33 4/5] Subscripting documentation
@ 2019-02-01 10:47 erthalion <[email protected]>
0 siblings, 0 replies; 29+ 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] 29+ messages in thread
* [PATCH v33 4/5] Subscripting documentation
@ 2019-02-01 10:47 erthalion <[email protected]>
0 siblings, 0 replies; 29+ 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] 29+ messages in thread
* [PATCH v30 4/6] Subscripting documentation
@ 2019-02-01 10:47 erthalion <[email protected]>
0 siblings, 0 replies; 29+ 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] 29+ messages in thread
* [PATCH v33 4/5] Subscripting documentation
@ 2019-02-01 10:47 erthalion <[email protected]>
0 siblings, 0 replies; 29+ 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] 29+ messages in thread
* [PATCH v33 4/5] Subscripting documentation
@ 2019-02-01 10:47 erthalion <[email protected]>
0 siblings, 0 replies; 29+ 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] 29+ messages in thread
* [PATCH v33 4/5] Subscripting documentation
@ 2019-02-01 10:47 erthalion <[email protected]>
0 siblings, 0 replies; 29+ 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] 29+ messages in thread
* [PATCH v31 4/6] Subscripting documentation
@ 2019-02-01 10:47 erthalion <[email protected]>
0 siblings, 0 replies; 29+ 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] 29+ messages in thread
* [PATCH v33 4/5] Subscripting documentation
@ 2019-02-01 10:47 erthalion <[email protected]>
0 siblings, 0 replies; 29+ 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] 29+ messages in thread
* [PATCH v32 4/6] Subscripting documentation
@ 2019-02-01 10:47 erthalion <[email protected]>
0 siblings, 0 replies; 29+ 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] 29+ messages in thread
* [PATCH v33 4/5] Subscripting documentation
@ 2019-02-01 10:47 erthalion <[email protected]>
0 siblings, 0 replies; 29+ 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] 29+ messages in thread
* [PATCH v33 4/5] Subscripting documentation
@ 2019-02-01 10:47 erthalion <[email protected]>
0 siblings, 0 replies; 29+ 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] 29+ messages in thread
* [PATCH v33 4/5] Subscripting documentation
@ 2019-02-01 10:47 erthalion <[email protected]>
0 siblings, 0 replies; 29+ 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] 29+ messages in thread
* [PATCH v33 4/5] Subscripting documentation
@ 2019-02-01 10:47 erthalion <[email protected]>
0 siblings, 0 replies; 29+ 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] 29+ messages in thread
* [PATCH v32 4/6] Subscripting documentation
@ 2019-02-01 10:47 erthalion <[email protected]>
0 siblings, 0 replies; 29+ 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] 29+ messages in thread
* [PATCH v29 4/6] Subscripting documentation
@ 2019-02-01 10:47 erthalion <[email protected]>
0 siblings, 0 replies; 29+ 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] 29+ messages in thread
* [PATCH v33 4/5] Subscripting documentation
@ 2019-02-01 10:47 erthalion <[email protected]>
0 siblings, 0 replies; 29+ 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] 29+ messages in thread
* [PATCH v33 4/5] Subscripting documentation
@ 2019-02-01 10:47 erthalion <[email protected]>
0 siblings, 0 replies; 29+ 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] 29+ messages in thread
* [PATCH v35 4/5] Subscripting documentation
@ 2019-02-01 10:47 erthalion <[email protected]>
0 siblings, 0 replies; 29+ 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] 29+ messages in thread
* [PATCH v34 4/5] Subscripting documentation
@ 2019-02-01 10:47 erthalion <[email protected]>
0 siblings, 0 replies; 29+ 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] 29+ messages in thread
* Re: Trigger violates foreign key constraint
@ 2023-10-30 22:03 David G. Johnston <[email protected]>
2023-10-31 10:40 ` Re: Trigger violates foreign key constraint Laurenz Albe <[email protected]>
0 siblings, 1 reply; 29+ messages in thread
From: David G. Johnston @ 2023-10-30 22:03 UTC (permalink / raw)
To: Laurenz Albe <[email protected]>; +Cc: Tom Lane <[email protected]>; pgsql-hackers
On Mon, Oct 30, 2023 at 2:50 PM David G. Johnston <
[email protected]> wrote:
> On Tue, Oct 3, 2023 at 12:52 AM Laurenz Albe <[email protected]>
> wrote:
>
>> On Mon, 2023-10-02 at 09:49 -0400, Tom Lane wrote:
>> > This is by design: triggers operate at a lower level than
>> > foreign keys, so an ill-conceived trigger can break an FK constraint.
>> > That's documented somewhere, though maybe not visibly enough.
>>
>> Not having found any documentation, I propose the attached caution.
>>
>>
> I dislike scaring the user like this without providing any context on what
> conditions or actions are problematic.
>
> The ON DELETE and ON UPDATE clauses of foreign keys are implemented as
> system triggers on the referenced table that invoke additional delete or
> update commands on the referencing table. The final outcome of these
> additional commands are not checked - it is the responsibility of the DBA
> to ensure that the user triggers on the referencing table actually remove
> the rows they are requested to remove, or update to NULL any referencing
> foreign key columns. In particular, before row triggers that return NULL
> will prevent the delete/update from occurring and thus result in a violated
> foreign key constraint.
>
> Add sgml as needed, note the original patch missed adding "<productname>"
> to PostgreSQL.
>
>
Additionally, the existing place this is covered is here:
"""
Trigger functions invoked by per-statement triggers should always return
NULL. Trigger functions invoked by per-row triggers can return a table row
(a value of type HeapTuple) to the calling executor, if they choose. A
row-level trigger fired before an operation has the following choices:
It can return NULL to skip the operation for the current row. This
instructs the executor to not perform the row-level operation that invoked
the trigger (the insertion, modification, or deletion of a particular table
row).
For row-level INSERT and UPDATE triggers only, the returned row becomes the
row that will be inserted or will replace the row being updated. This
allows the trigger function to modify the row being inserted or updated.
A row-level BEFORE trigger that does not intend to cause either of these
behaviors must be careful to return as its result the same row that was
passed in (that is, the NEW row for INSERT and UPDATE triggers, the OLD row
for DELETE triggers).
"""
We should probably add a note pointing back to the DDL chapter and that
more concisely says.
"Note: If this table also contains any foreign key constraints with on
update or on delete clauses, then a failure to return the same row that was
passed in for update and delete triggers is going to result in broken
referential integrity for the affected row."
I do like "broken referential integrity" from the original patch over
"violated foreign key constraint" - so that needs to be substituted in for
the final part of my earlier proposal if we go with its more detailed
wording. My issue with "violated" is that it sounds like the system is
going to catch it at the end - broken doesn't have the same implication.
David J.
^ permalink raw reply [nested|flat] 29+ messages in thread
* Re: Trigger violates foreign key constraint
2023-10-30 22:03 Re: Trigger violates foreign key constraint David G. Johnston <[email protected]>
@ 2023-10-31 10:40 ` Laurenz Albe <[email protected]>
0 siblings, 0 replies; 29+ messages in thread
From: Laurenz Albe @ 2023-10-31 10:40 UTC (permalink / raw)
To: David G. Johnston <[email protected]>; +Cc: Tom Lane <[email protected]>; pgsql-hackers
Thanks for having a look at my patch!
On Mon, 2023-10-30 at 15:03 -0700, David G. Johnston wrote:
> On Mon, Oct 30, 2023 at 2:50 PM David G. Johnston <[email protected]> wrote:
> > On Tue, Oct 3, 2023 at 12:52 AM Laurenz Albe <[email protected]> wrote:
> > > On Mon, 2023-10-02 at 09:49 -0400, Tom Lane wrote:
> > > > This is by design: triggers operate at a lower level than
> > > > foreign keys, so an ill-conceived trigger can break an FK constraint.
> > > > That's documented somewhere, though maybe not visibly enough.
> > >
> > > Not having found any documentation, I propose the attached caution.
> >
> > I dislike scaring the user like this without providing any context on what
> > conditions or actions are problematic.
I specifically *want* to scare^H^H^H^H^Halert the user, and I thought I
provided sufficient context and a link to a more detailed description of
how triggers behave.
What is unclear or lacking in the proposed wording?
In particular, other triggers
defined on the referencing table can cancel or modify the effects of
cascading delete or update, thereby breaking referential integrity.
> > The ON DELETE and ON UPDATE clauses of foreign keys are implemented as system triggers
> > on the referenced table that invoke additional delete or update commands on the
> > referencing table. The final outcome of these additional commands are not checked -
> > it is the responsibility of the DBA to ensure that the user triggers on the
> > referencing table actually remove the rows they are requested to remove, or
> > update to NULL any referencing foreign key columns. In particular, before row
> > triggers that return NULL will prevent the delete/update from occurring and thus
> > result in a violated foreign key constraint.
I didn't plan to write a novel on the topic... and I don't think your wording is
clearer than mine. I went over my text again with the intent to add clarity, but
apart from a few minor modifications ("other triggers" -> "user-defined triggers")
I couldn't make it clearer. I'd have to write an example to make it clearer,
and that would certainly be out of scope.
> > Add sgml as needed, note the original patch missed adding "<productname>" to PostgreSQL.
Ah, thanks for noticing! Fixed.
>
> Additionally, the existing place this is covered is here:
>
> [https://www.postgresql.org/docs/current/trigger-definition.html]
>
> We should probably add a note pointing back to the DDL chapter and that more concisely says.
>
> "Note: If this table also contains any foreign key constraints with on update
> or on delete clauses, then a failure to return the same row that was passed in
> for update and delete triggers is going to result in broken referential integrity
> for the affected row."
My patch already contains a link to this very section.
I tried to understand your sentence and had to read it several times. I don't
think that it adds clarity to my patch.
Attached is a slightly modified version of the patch.
Yours,
Laurenz Albe
Attachments:
[text/x-patch] 0001-Document-foreign-key-internals.V2.patch (1.5K, ../../[email protected]/2-0001-Document-foreign-key-internals.V2.patch)
download | inline diff:
From c1474f943b8fdb0f496c30cd87339e18d6e13a20 Mon Sep 17 00:00:00 2001
From: Laurenz Albe <[email protected]>
Date: Tue, 31 Oct 2023 11:38:59 +0100
Subject: [PATCH] Document foreign key internals
Warn the user that foreign keys are implemented as triggers, and
that user-defined triggers can interact with them and break
referential integrity.
Author: Laurenz Albe
Reviewed-by: David G. Johnston
Discussion: https://postgr.es/m/b81fe38fcc25a81be6e2e5b3fc1ff624130762fa.camel%40cybertec.at
---
doc/src/sgml/ddl.sgml | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 075ff32991..2c1c06702e 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -1224,6 +1224,19 @@ CREATE TABLE posts (
syntax in the reference documentation for
<xref linkend="sql-createtable"/>.
</para>
+
+ <note>
+ <para>
+ Foreign key constraints are implemented as system triggers in
+ <productname>PostgreSQL</productname>. As such, they are subject to the
+ trigger firing rules described in <xref linkend="trigger-definition"/>.
+ In particular, user-defined triggers on the referencing table can cancel
+ or modify the effects of cascading deletes or updates, thereby breaking
+ referential integrity. This is not considered a bug, and it is the
+ responsibility of the user to write triggers so that such problems are
+ avoided.
+ </para>
+ </note>
</sect2>
<sect2 id="ddl-constraints-exclusion">
--
2.41.0
^ permalink raw reply [nested|flat] 29+ messages in thread
end of thread, other threads:[~2023-10-31 10:40 UTC | newest]
Thread overview: 29+ 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 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 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 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 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 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 v32 4/6] 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 v35 4/5] Subscripting documentation erthalion <[email protected]>
2019-02-01 10:47 [PATCH v34 4/5] Subscripting documentation erthalion <[email protected]>
2023-10-30 22:03 Re: Trigger violates foreign key constraint David G. Johnston <[email protected]>
2023-10-31 10:40 ` Re: Trigger violates foreign key constraint Laurenz Albe <[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