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)