Skip to content

Commit 3ef6732

Browse files
committed
Add WITH-clause parser
For a number of DDL statements we want to pass in our own arguments as part of the WITH statement. This commit adds a parser for such statements, and tests to ensure that this parser behaves correctly. Currently, all our WITH options will be namespaced under "timescaledb". In other words, if we wish to add an option to CREATE INDEX called "foo", it will be used CREATE INDEX ... WITH (timescaledb.foo='bar') This should ensure that all our options are consistent with each other, and that it is obvious which options are ours, and which are not.
1 parent 037fbf6 commit 3ef6732

7 files changed

+931
-0
lines changed

src/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ set(SOURCES
4949
trigger.c
5050
utils.c
5151
version.c
52+
with_clause_parser.c
5253
)
5354

5455
# Add test source code in Debug builds

src/with_clause_parser.c

+169
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
/*
2+
* This file and its contents are licensed under the Apache License 2.0.
3+
* Please see the included NOTICE for copyright information and
4+
* LICENSE-APACHE for a copy of the license.
5+
*/
6+
#include <postgres.h>
7+
#include <c.h>
8+
#include <fmgr.h>
9+
10+
#include <access/htup_details.h>
11+
#include <catalog/pg_type.h>
12+
#include <commands/defrem.h>
13+
#include <nodes/parsenodes.h>
14+
#include <utils/builtins.h>
15+
#include <utils/lsyscache.h>
16+
#include <utils/syscache.h>
17+
18+
#include "with_clause_parser.h"
19+
20+
#define TIMESCALEDB_NAMESPACE "timescaledb"
21+
22+
/*
23+
* Filter a list of DefElem based on a namespace.
24+
* This function will iterate through DefElem and output up to two lists:
25+
* within_namespace: every element within the namespace
26+
* not_within_namespace: all the other elements
27+
*
28+
* That is, given a with clause like:
29+
* WITH (foo.foo_para, bar.bar_param, baz.baz_param)
30+
*
31+
* ts_with_clause_filter(elems, "foo", in, not_in) will have
32+
* in = foo.foo_para
33+
* not_in = bar.bar_param, baz.baz_param
34+
*/
35+
void
36+
ts_with_clause_filter(const List *def_elems, List **within_namespace, List **not_within_namespace)
37+
{
38+
ListCell *cell;
39+
40+
foreach (cell, def_elems)
41+
{
42+
DefElem *def = (DefElem *) lfirst(cell);
43+
44+
if (def->defnamespace != NULL &&
45+
pg_strcasecmp(def->defnamespace, TIMESCALEDB_NAMESPACE) == 0)
46+
{
47+
if (within_namespace != NULL)
48+
*within_namespace = lappend(*within_namespace, def);
49+
}
50+
else if (not_within_namespace != NULL)
51+
{
52+
*not_within_namespace = lappend(*not_within_namespace, def);
53+
}
54+
}
55+
}
56+
57+
static Datum parse_arg(WithClauseDefinition arg, DefElem *def);
58+
59+
/*
60+
* Deserialize and apply the values in a WITH clause based on the on_arg table.
61+
*
62+
* This function will go through every element in def_elems and search for a
63+
* corresponding argument in args, if one is found it will attemt to deserialize
64+
* the argument, using that table elements deserialize function, then apply it
65+
* to state.
66+
*
67+
* This is used to turn the list into a form more useful for our internal
68+
* functions
69+
*/
70+
WithClauseResult *
71+
ts_with_clauses_parse(const List *def_elems, const WithClauseDefinition *args, Size nargs)
72+
{
73+
ListCell *cell;
74+
WithClauseResult *results = palloc0(sizeof(*results) * nargs);
75+
Size i;
76+
77+
for (i = 0; i < nargs; i++)
78+
{
79+
results[i].parsed = args[i].default_val;
80+
results[i].is_default = true;
81+
}
82+
83+
foreach (cell, def_elems)
84+
{
85+
DefElem *def = (DefElem *) lfirst(cell);
86+
bool argument_recognized = false;
87+
88+
for (i = 0; i < nargs; i++)
89+
{
90+
if (pg_strcasecmp(def->defname, args[i].arg_name) == 0)
91+
{
92+
argument_recognized = true;
93+
94+
if (!results[i].is_default)
95+
ereport(ERROR,
96+
(errcode(ERRCODE_AMBIGUOUS_PARAMETER),
97+
errmsg("duplicate parameter \"%s.%s\"",
98+
def->defnamespace,
99+
def->defname)));
100+
101+
results[i].parsed = parse_arg(args[i], def);
102+
results[i].is_default = false;
103+
break;
104+
}
105+
}
106+
107+
if (!argument_recognized)
108+
ereport(ERROR,
109+
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
110+
errmsg("unrecognized parameter \"%s.%s\"", def->defnamespace, def->defname)));
111+
}
112+
113+
return results;
114+
}
115+
116+
static Datum
117+
parse_arg(WithClauseDefinition arg, DefElem *def)
118+
{
119+
char *value;
120+
Datum val;
121+
Oid in_fn;
122+
Oid typIOParam;
123+
124+
if (!OidIsValid(arg.type_id))
125+
elog(ERROR, "argument \"%s.%s\" not implemented", def->defnamespace, def->defname);
126+
127+
if (def->arg != NULL)
128+
value = defGetString(def);
129+
else if (arg.type_id == BOOLOID)
130+
/* for booleans, postgres defines the option timescale.foo to be the same as
131+
* timescaledb.foo='true' so if no value is found set it to "true" here */
132+
value = "true";
133+
else
134+
ereport(ERROR,
135+
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
136+
errmsg("parameter \"%s.%s\" must have a value", def->defnamespace, def->defname)));
137+
138+
getTypeInputInfo(arg.type_id, &in_fn, &typIOParam);
139+
140+
Assert(OidIsValid(in_fn));
141+
142+
PG_TRY();
143+
{
144+
val = OidInputFunctionCall(in_fn, value, typIOParam, 0);
145+
}
146+
PG_CATCH();
147+
{
148+
Form_pg_type typetup;
149+
HeapTuple tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(arg.type_id));
150+
if (!HeapTupleIsValid(tup))
151+
elog(ERROR,
152+
"cache lookup failed for type of %s.%s '%u'",
153+
def->defnamespace,
154+
def->defname,
155+
arg.type_id);
156+
157+
typetup = (Form_pg_type) GETSTRUCT(tup);
158+
159+
ereport(ERROR,
160+
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
161+
errmsg("invalid value for %s.%s '%s'", def->defnamespace, def->defname, value),
162+
errhint("%s.%s must be a valid %s",
163+
def->defnamespace,
164+
def->defname,
165+
NameStr(typetup->typname))));
166+
}
167+
PG_END_TRY();
168+
return val;
169+
}

src/with_clause_parser.h

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* This file and its contents are licensed under the Apache License 2.0.
3+
* Please see the included NOTICE for copyright information and
4+
* LICENSE-APACHE for a copy of the license.
5+
*/
6+
#include <postgres.h>
7+
#include <c.h>
8+
9+
#include <nodes/parsenodes.h>
10+
11+
/* find the length of a statically sized array */
12+
#define TS_ARRAY_LEN(array) (sizeof(array) / sizeof(*array))
13+
14+
typedef struct WithClauseDefinition
15+
{
16+
const char *arg_name;
17+
Oid type_id;
18+
Datum default_val;
19+
} WithClauseDefinition;
20+
21+
typedef struct WithClauseResult
22+
{
23+
bool is_default;
24+
Datum parsed;
25+
} WithClauseResult;
26+
27+
extern void ts_with_clause_filter(const List *def_elems, List **within_namespace,
28+
List **not_within_namespace);
29+
30+
extern WithClauseResult *ts_with_clauses_parse(const List *def_elems,
31+
const WithClauseDefinition *args, Size nargs);

0 commit comments

Comments
 (0)