Skip to content

Commit ea61e76

Browse files
committed
Add separate documentation with custom subscription examples
1 parent 1bfa84a commit ea61e76

File tree

5 files changed

+390
-2
lines changed

5 files changed

+390
-2
lines changed

doc/src/sgml/filelist.sgml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
<!ENTITY xplang SYSTEM "xplang.sgml">
7171
<!ENTITY xoper SYSTEM "xoper.sgml">
7272
<!ENTITY xtypes SYSTEM "xtypes.sgml">
73+
<!ENTITY xsubscription SYSTEM "xsubscription.sgml">
7374
<!ENTITY plperl SYSTEM "plperl.sgml">
7475
<!ENTITY plpython SYSTEM "plpython.sgml">
7576
<!ENTITY plsql SYSTEM "plpgsql.sgml">

doc/src/sgml/xsubscription.sgml

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<!-- doc/src/sgml/xsubscription.sgml -->
2+
3+
<sect1 id="xsubscription">
4+
<title>User-defined subscription procedure</title>
5+
6+
<indexterm zone="xsubscription">
7+
<primary>custom subscription</primary>
8+
</indexterm>
9+
When you define a new base type, you can also specify a custom procedure
10+
to handle subscription expressions. It should contains logic for verification
11+
and for extraction or update your data. For instance:
12+
13+
<programlisting><![CDATA[
14+
typedef struct Custom
15+
{
16+
int first;
17+
int second;
18+
} Custom;
19+
20+
Datum
21+
custom_subscription_evaluate(PG_FUNCTION_ARGS)
22+
{
23+
SubscriptionExecData *sbsdata = (SubscriptionExecData *) PG_GETARG_POINTER(1);
24+
Custom *result = (Custom *) sbsdata->containerSource;
25+
26+
// Some extraction or update logic based on sbsdata
27+
}
28+
29+
Datum
30+
custom_subscription_prepare(PG_FUNCTION_ARGS)
31+
{
32+
SubscriptionRef *sbsref = (SubscriptionRef *) PG_GETARG_POINTER(0);
33+
34+
// Some verifications or type coersion
35+
36+
PG_RETURN_POINTER(sbsref);
37+
}
38+
39+
PG_FUNCTION_INFO_V1(custom_subscription);
40+
41+
Datum
42+
custom_subscription(PG_FUNCTION_ARGS)
43+
{
44+
int op_type = PG_GETARG_INT32(0);
45+
FunctionCallInfoData target_fcinfo = get_slice_arguments(fcinfo, 1,
46+
fcinfo->nargs);
47+
48+
if (op_type & SBS_VALIDATION)
49+
return custom_subscription_prepare(&target_fcinfo);
50+
51+
if (op_type & SBS_EXEC)
52+
return custom_subscription_evaluate(&target_fcinfo);
53+
54+
elog(ERROR, "incorrect op_type for subscription function: %d", op_type);
55+
}]]>
56+
</programlisting>
57+
58+
Then you can define a subscription procedure and a custom data type:
59+
60+
<programlisting>
61+
CREATE FUNCTION custom_subscription(internal)
62+
RETURNS internal
63+
AS '<replaceable>filename</replaceable>'
64+
LANGUAGE C IMMUTABLE STRICT;
65+
66+
CREATE TYPE custom (
67+
internallength = 4,
68+
input = custom_in,
69+
output = custom_out,
70+
subscription = custom_subscription
71+
);
72+
</programlisting>
73+
74+
and use it as usual:
75+
76+
<programlisting>
77+
CREATE TABLE test_subscription (
78+
data custom,
79+
);
80+
81+
INSERT INTO test_subscription VALUES ('(1, 2)');
82+
83+
SELECT data[0] from test_subscription;
84+
85+
UPDATE test_subscription SET data[1] = 3;
86+
</programlisting>
87+
88+
</para>
89+
90+
<para>
91+
The examples of custom subscription implementation can be found in
92+
<filename>subscription.sql</filename> and <filename>subscription.c</filename>
93+
in the <filename>src/tutorial</> directory of the source distribution.
94+
See the <filename>README</> file in that directory for instructions
95+
about running the examples.
96+
</para>
97+
98+
</sect1>

src/tutorial/Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
#
1414
#-------------------------------------------------------------------------
1515

16-
MODULES = complex funcs
17-
DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql
16+
MODULES = complex funcs subscription
17+
DATA_built = advanced.sql basics.sql complex.sql funcs.sql syscat.sql subscription.sql
1818

1919
ifdef NO_PGXS
2020
subdir = src/tutorial

src/tutorial/subscription.c

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
/*
2+
* src/tutorial/subscription.c
3+
*
4+
******************************************************************************
5+
This file contains routines that can be bound to a Postgres backend and
6+
called by the backend in the process of processing queries. The calling
7+
format for these routines is dictated by Postgres architecture.
8+
******************************************************************************/
9+
10+
#include "postgres.h"
11+
12+
#include "catalog/pg_type.h"
13+
#include "executor/executor.h"
14+
#include "nodes/execnodes.h"
15+
#include "nodes/nodeFuncs.h"
16+
#include "parser/parse_coerce.h"
17+
#include "parser/parse_node.h"
18+
#include "utils/array.h"
19+
#include "fmgr.h"
20+
#include "funcapi.h"
21+
22+
PG_MODULE_MAGIC;
23+
24+
typedef struct Custom
25+
{
26+
int first;
27+
int second;
28+
} Custom;
29+
30+
31+
/*****************************************************************************
32+
* Input/Output functions
33+
*****************************************************************************/
34+
35+
PG_FUNCTION_INFO_V1(custom_in);
36+
37+
Datum
38+
custom_in(PG_FUNCTION_ARGS)
39+
{
40+
char *str = PG_GETARG_CSTRING(0);
41+
int firstValue,
42+
secondValue;
43+
Custom *result;
44+
45+
if (sscanf(str, " ( %d , %d )", &firstValue, &secondValue) != 2)
46+
ereport(ERROR,
47+
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
48+
errmsg("invalid input syntax for complex: \"%s\"",
49+
str)));
50+
51+
52+
result = (Custom *) palloc(sizeof(Custom));
53+
result->first = firstValue;
54+
result->second = secondValue;
55+
PG_RETURN_POINTER(result);
56+
}
57+
58+
PG_FUNCTION_INFO_V1(custom_out);
59+
60+
Datum
61+
custom_out(PG_FUNCTION_ARGS)
62+
{
63+
Custom *custom = (Custom *) PG_GETARG_POINTER(0);
64+
char *result;
65+
66+
result = psprintf("(%d, %d)", custom->first, custom->second);
67+
PG_RETURN_CSTRING(result);
68+
}
69+
70+
/*****************************************************************************
71+
* Custom subscription logic functions
72+
*****************************************************************************/
73+
74+
Datum
75+
custom_subscription_evaluate(PG_FUNCTION_ARGS)
76+
{
77+
SubscriptionRefExprState *sbstate = (SubscriptionRefExprState *) PG_GETARG_POINTER(0);
78+
SubscriptionExecData *sbsdata = (SubscriptionExecData *) PG_GETARG_POINTER(1);
79+
SubscriptionRef *custom_ref = (SubscriptionRef *) sbstate->xprstate.expr;
80+
Custom *result = (Custom *) sbsdata->containerSource;
81+
bool *is_null = sbsdata->isNull;
82+
bool is_assignment = (custom_ref->refassgnexpr != NULL);
83+
84+
int index;
85+
86+
if (sbsdata->indexprNumber != 1)
87+
ereport(ERROR, (errmsg("custom does not support nested subscription")));
88+
89+
index = DatumGetInt32(sbsdata->upper[0]);
90+
91+
if (is_assignment)
92+
{
93+
ExprContext *econtext = sbsdata->xprcontext;
94+
Datum sourceData;
95+
Datum save_datum;
96+
bool save_isNull;
97+
bool eisnull;
98+
99+
/*
100+
* We might have a nested-assignment situation, in which the
101+
* refassgnexpr is itself a FieldStore or SubscriptionRef that needs to
102+
* obtain and modify the previous value of the array element or slice
103+
* being replaced. If so, we have to extract that value from the
104+
* array and pass it down via the econtext's caseValue. It's safe to
105+
* reuse the CASE mechanism because there cannot be a CASE between
106+
* here and where the value would be needed, and an array assignment
107+
* can't be within a CASE either. (So saving and restoring the
108+
* caseValue is just paranoia, but let's do it anyway.)
109+
*
110+
* Since fetching the old element might be a nontrivial expense, do it
111+
* only if the argument appears to actually need it.
112+
*/
113+
save_datum = econtext->caseValue_datum;
114+
save_isNull = econtext->caseValue_isNull;
115+
116+
/*
117+
* Evaluate the value to be assigned into the container.
118+
*/
119+
sourceData = ExecEvalExpr(sbstate->refassgnexpr,
120+
econtext,
121+
&eisnull,
122+
NULL);
123+
124+
econtext->caseValue_datum = save_datum;
125+
econtext->caseValue_isNull = save_isNull;
126+
127+
/*
128+
* For an assignment to a fixed-length array type, both the original
129+
* array and the value to be assigned into it must be non-NULL, else
130+
* we punt and return the original array.
131+
*/
132+
if (sbstate->refattrlength > 0) /* fixed-length container? */
133+
if (eisnull || *is_null)
134+
return sbsdata->containerSource;
135+
136+
/*
137+
* For assignment to varlena container, we handle a NULL original array
138+
* by substituting an empty (zero-dimensional) array; insertion of the
139+
* new element will result in a singleton array value. It does not
140+
* matter whether the new element is NULL.
141+
*/
142+
if (*is_null)
143+
{
144+
sbsdata->containerSource =
145+
PointerGetDatum(construct_empty_array(custom_ref->refelemtype));
146+
*is_null = false;
147+
}
148+
149+
if (index == 1)
150+
result->first = DatumGetInt32(sourceData);
151+
else
152+
result->second = DatumGetInt32(sourceData);
153+
154+
PG_RETURN_POINTER(result);
155+
}
156+
else
157+
{
158+
if (index == 1)
159+
PG_RETURN_INT32(result->first);
160+
else
161+
PG_RETURN_INT32(result->second);
162+
}
163+
}
164+
165+
Datum
166+
custom_subscription_prepare(PG_FUNCTION_ARGS)
167+
{
168+
SubscriptionRef *sbsref = (SubscriptionRef *) PG_GETARG_POINTER(0);
169+
ParseState *pstate = (ParseState *) PG_GETARG_POINTER(1);
170+
List *upperIndexpr = NIL;
171+
ListCell *l;
172+
173+
if (sbsref->reflowerindexpr != NIL)
174+
ereport(ERROR,
175+
(errcode(ERRCODE_DATATYPE_MISMATCH),
176+
errmsg("custom subscript does not support slices"),
177+
parser_errposition(pstate, exprLocation(
178+
((Node *)lfirst(sbsref->reflowerindexpr->head))))));
179+
180+
foreach(l, sbsref->refupperindexpr)
181+
{
182+
Node *subexpr = (Node *) lfirst(l);
183+
184+
Assert(subexpr != NULL);
185+
186+
if (subexpr == NULL)
187+
ereport(ERROR,
188+
(errcode(ERRCODE_DATATYPE_MISMATCH),
189+
errmsg("custom subscript does not support slices"),
190+
parser_errposition(pstate, exprLocation(
191+
((Node *) lfirst(sbsref->refupperindexpr->head))))));
192+
193+
subexpr = coerce_to_target_type(pstate,
194+
subexpr, exprType(subexpr),
195+
INT4OID, -1,
196+
COERCION_ASSIGNMENT,
197+
COERCE_IMPLICIT_CAST,
198+
-1);
199+
if (subexpr == NULL)
200+
ereport(ERROR,
201+
(errcode(ERRCODE_DATATYPE_MISMATCH),
202+
errmsg("custom subscript must have int type"),
203+
parser_errposition(pstate, exprLocation(subexpr))));
204+
205+
upperIndexpr = lappend(upperIndexpr, subexpr);
206+
}
207+
208+
sbsref->refupperindexpr = upperIndexpr;
209+
sbsref->refelemtype = INT4OID;
210+
211+
PG_RETURN_POINTER(sbsref);
212+
}
213+
214+
PG_FUNCTION_INFO_V1(custom_subscription);
215+
216+
Datum
217+
custom_subscription(PG_FUNCTION_ARGS)
218+
{
219+
int op_type = PG_GETARG_INT32(0);
220+
FunctionCallInfoData target_fcinfo = get_slice_arguments(fcinfo, 1,
221+
fcinfo->nargs);
222+
223+
if (op_type & SBS_VALIDATION)
224+
return custom_subscription_prepare(&target_fcinfo);
225+
226+
if (op_type & SBS_EXEC)
227+
return custom_subscription_evaluate(&target_fcinfo);
228+
229+
elog(ERROR, "incorrect op_type for subscription function: %d", op_type);
230+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy