From 59ec0017594957f42a632d7fe407a8314a00da65 Mon Sep 17 00:00:00 2001 From: erthalion <9erthalion6@gmail.com> Date: Sun, 29 Oct 2017 22:29:32 +0100 Subject: [PATCH 4/4] Subscripting documentation --- doc/src/sgml/catalogs.sgml | 7 ++ doc/src/sgml/extend.sgml | 6 ++ doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/json.sgml | 25 ++++++ doc/src/sgml/ref/create_type.sgml | 30 ++++++- doc/src/sgml/xsubscripting.sgml | 102 +++++++++++++++++++++++ src/tutorial/Makefile | 4 +- src/tutorial/subscripting.c | 165 ++++++++++++++++++++++++++++++++++++++ src/tutorial/subscripting.source | 83 +++++++++++++++++++ 9 files changed, 420 insertions(+), 3 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 ef60a58631..787db64be5 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -7838,6 +7838,13 @@ SCRAM-SHA-256$<iteration count>:&l + typsubsparse + regproc + pg_proc.oid + Custom subscripting function with type-specific logic, or 0 if this type doesn't support subscripting. + + + typdefaultbin pg_node_tree diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml index e819010875..3c2c11bf8b 100644 --- a/doc/src/sgml/extend.sgml +++ b/doc/src/sgml/extend.sgml @@ -35,6 +35,11 @@ + subscripting procedure (starting in ) + + + + operator classes for indexes (starting in ) @@ -314,6 +319,7 @@ &xaggr; &xtypes; &xoper; + &xsubscripting; &xindex; diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index a72c50eadb..d05f447810 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -69,6 +69,7 @@ + diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml index 05ecef2ffc..1154f07ef8 100644 --- a/doc/src/sgml/json.sgml +++ b/doc/src/sgml/json.sgml @@ -569,4 +569,29 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu compared using the default database collation. + + + <type>jsonb</> Subscripting + + jsonb data type supports array-style subscripting expressions to extract or update particular element. An example of subscripting syntax: + +-- 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 +SELECT * from table_name where jsonb_field['key'] = '"value"'; + + + + + diff --git a/doc/src/sgml/ref/create_type.sgml b/doc/src/sgml/ref/create_type.sgml index 1b409ad22f..ce22a9a9dd 100644 --- a/doc/src/sgml/ref/create_type.sgml +++ b/doc/src/sgml/ref/create_type.sgml @@ -54,6 +54,7 @@ CREATE TYPE name ( [ , ELEMENT = element ] [ , DELIMITER = delimiter ] [ , COLLATABLE = collatable ] + [ , SUBSCRIPTING = subscripting_function ] ) CREATE TYPE name @@ -194,7 +195,8 @@ CREATE TYPE name send_function, type_modifier_input_function, type_modifier_output_function and - analyze_function + analyze_function, + subscripting_function are optional. Generally these functions have to be coded in C or another low-level language. @@ -451,6 +453,22 @@ CREATE TYPE name make use of the collation information; this does not happen automatically merely by marking the type collatable. + + + The optional + subscripting_function + contains type-specific logic for subscripting of the data type. + By default, there is no such function, which means that the data + type doesn't support subscripting. The subscripting function must be + declared to take a single argument of type internal, and return + a internal result. There are two examples of implementation for + subscripting function in case of array + (array_subscripting) + and jsonb + (jsonb_subscripting) + types in src/backend/utils/adt/arrayfuncs.c and + src/backend/utils/adt/jsonfuncs.c corresponding. + @@ -766,6 +784,16 @@ CREATE TYPE name + + + subscripting_function + + + The name of a function that contains type-specific subscripting logic for + the data type. + + + diff --git a/doc/src/sgml/xsubscripting.sgml b/doc/src/sgml/xsubscripting.sgml new file mode 100644 index 0000000000..b5e909b106 --- /dev/null +++ b/doc/src/sgml/xsubscripting.sgml @@ -0,0 +1,102 @@ + + + + User-defined subscripting procedure + + + custom subscripting + + + When you define a new base type, you can also specify a custom procedure + to handle subscripting expressions. It should contain logic for verification + and decide which function must be used for evaluation of this expression. + For instance: + +containerSource; + ExprEvalStep *step = (ExprEvalStep *) PG_GETARG_POINTER(1); + + // Some assign logic based on sbsdata +} + +Datum +custom_subscripting_assign(PG_FUNCTION_ARGS) +{ + Custom *containerSource = (Custom *) PG_GETARG_DATUM(0); + ExprEvalStep *step = (ExprEvalStep *) PG_GETARG_POINTER(1); + + // Some extraction logic based on sbsdata +} + +PG_FUNCTION_INFO_V1(custom_subscripting); + +Datum +custom_subscript_parse(PG_FUNCTION_ARGS) +{ + bool isAssignment = PG_GETARG_BOOL(0); + SubscriptingRef *sbsref = (SubscriptingRef *) PG_GETARG_POINTER(0); + Datum assign_proc = CStringGetTextDatum("custom_subscripting_assign"); + Datum extract_proc = CStringGetTextDatum("custom_subscripting_extract"); + + // Some verifications or type coersion + + if (isAssignment) + sbsref->refevalfunc = DirectFunctionCall1(to_regproc, assign_proc); + else + sbsref->refevalfunc = DirectFunctionCall1(to_regproc, extract_proc); + + PG_RETURN_POINTER(sbsref); +}]]> + + + + Then you can define a subscripting procedure and a custom data type: + + +CREATE FUNCTION custom_subscript_parse(internal) + RETURNS internal + AS 'filename' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 4, + input = custom_in, + output = custom_out, + subscripting = custom_subscript_parse +); + + + + and use it as usual: + + +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; + + + + + The examples of custom subscripting implementation can be found in + subscripting.sql and subscripting.c + in the src/tutorial directory of the source distribution. + See the README file in that directory for instructions + about running the examples. + + + 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..4e4e434f25 --- /dev/null +++ b/src/tutorial/subscripting.c @@ -0,0 +1,165 @@ +/* + * 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; + +PG_FUNCTION_INFO_V1(custom_in); +PG_FUNCTION_INFO_V1(custom_subscripting_parse); +PG_FUNCTION_INFO_V1(custom_subscripting_assign); +PG_FUNCTION_INFO_V1(custom_subscripting_fetch); + +/***************************************************************************** + * 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); +} + +PG_FUNCTION_INFO_V1(custom_out); + +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_assign(PG_FUNCTION_ARGS) +{ + Custom *containerSource = (Custom *) PG_GETARG_DATUM(0); + ExprEvalStep *step = (ExprEvalStep *) PG_GETARG_POINTER(1); + + SubscriptingRefState *sbstate = step->d.sbsref.state; + int index; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upper[0]); + + if (index == 1) + containerSource->first = DatumGetInt32(sbstate->replacevalue); + else + containerSource->second = DatumGetInt32(sbstate->replacevalue); + + PG_RETURN_POINTER(containerSource); +} + + +Datum +custom_subscripting_fetch(PG_FUNCTION_ARGS) +{ + Custom *containerSource = (Custom *) PG_GETARG_DATUM(0); + ExprEvalStep *step = (ExprEvalStep *) PG_GETARG_POINTER(1); + SubscriptingRefState *sbstate = step->d.sbsref.state; + + int index; + + if (sbstate->numupper != 1) + ereport(ERROR, (errmsg("custom does not support nested subscripting"))); + + index = DatumGetInt32(sbstate->upper[0]); + + if (index == 1) + PG_RETURN_INT32(containerSource->first); + else + PG_RETURN_INT32(containerSource->second); +} + +Datum +custom_subscripting_parse(PG_FUNCTION_ARGS) +{ + bool isAssignment = PG_GETARG_BOOL(0); + SubscriptingRef *sbsref = (SubscriptingRef *) PG_GETARG_POINTER(1); + ParseState *pstate = (ParseState *) PG_GETARG_POINTER(2); + 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); + + Assert(subexpr != NULL); + + 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 int type"), + parser_errposition(pstate, exprLocation(subexpr)))); + + upperIndexpr = lappend(upperIndexpr, subexpr); + } + + sbsref->refupperindexpr = upperIndexpr; + sbsref->refelemtype = INT4OID; + + PG_RETURN_POINTER(sbsref); +} diff --git a/src/tutorial/subscripting.source b/src/tutorial/subscripting.source new file mode 100644 index 0000000000..3417f1ce3d --- /dev/null +++ b/src/tutorial/subscripting.source @@ -0,0 +1,83 @@ +--------------------------------------------------------------------------- +-- +-- 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_parse(internal) + RETURNS internal + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION custom_subscripting_fetch(internal) + RETURNS internal + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION custom_subscripting_assign(internal) + RETURNS internal + AS '_OBJWD_/subscripting' + LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE custom ( + internallength = 8, + input = custom_in, + output = custom_out, + subscripting_parse = custom_subscripting_parse, + subscripting_fetch = custom_subscripting_fetch, + subscripting_assign = custom_subscripting_assign +); + +-- 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.13.0