1 /*-------------------------------------------------------------------------
2 *
3 * deparse_extension_stmts.c
4 * All routines to deparse extension statements.
5 * This file contains deparse functions for extension statement deparsing
6 * as well as related helper functions.
7 *
8 * Copyright (c), Citus Data, Inc.
9 *
10 *-------------------------------------------------------------------------
11 */
12
13 #include "postgres.h"
14
15 #include "catalog/namespace.h"
16 #include "commands/defrem.h"
17 #include "distributed/deparser.h"
18 #include "distributed/listutils.h"
19 #include "lib/stringinfo.h"
20 #include "nodes/parsenodes.h"
21 #include "nodes/pg_list.h"
22 #include "utils/builtins.h"
23
24 /* Local functions forward declarations for helper functions */
25 static void AppendCreateExtensionStmt(StringInfo buf, CreateExtensionStmt *stmt);
26 static void AppendCreateExtensionStmtOptions(StringInfo buf, List *options);
27 static void AppendDropExtensionStmt(StringInfo buf, DropStmt *stmt);
28 static void AppendExtensionNameList(StringInfo buf, List *objects);
29 static void AppendAlterExtensionSchemaStmt(StringInfo buf,
30 AlterObjectSchemaStmt *alterExtensionSchemaStmt);
31 static void AppendAlterExtensionStmt(StringInfo buf,
32 AlterExtensionStmt *alterExtensionStmt);
33
34
35 /*
36 * GetExtensionOption returns DefElem * node with "defname" from "options" list
37 */
38 DefElem *
GetExtensionOption(List * extensionOptions,const char * defname)39 GetExtensionOption(List *extensionOptions, const char *defname)
40 {
41 DefElem *defElement = NULL;
42 foreach_ptr(defElement, extensionOptions)
43 {
44 if (IsA(defElement, DefElem) &&
45 strncmp(defElement->defname, defname, NAMEDATALEN) == 0)
46 {
47 return defElement;
48 }
49 }
50
51 return NULL;
52 }
53
54
55 /*
56 * DeparseCreateExtensionStmt builds and returns a string representing the
57 * CreateExtensionStmt to be sent to worker nodes.
58 */
59 char *
DeparseCreateExtensionStmt(Node * node)60 DeparseCreateExtensionStmt(Node *node)
61 {
62 CreateExtensionStmt *stmt = castNode(CreateExtensionStmt, node);
63 StringInfoData sql = { 0 };
64 initStringInfo(&sql);
65
66 AppendCreateExtensionStmt(&sql, stmt);
67
68 return sql.data;
69 }
70
71
72 /*
73 * AppendCreateExtensionStmt appends a string representing the CreateExtensionStmt to a buffer
74 */
75 static void
AppendCreateExtensionStmt(StringInfo buf,CreateExtensionStmt * createExtensionStmt)76 AppendCreateExtensionStmt(StringInfo buf, CreateExtensionStmt *createExtensionStmt)
77 {
78 appendStringInfoString(buf, "CREATE EXTENSION ");
79
80 if (createExtensionStmt->if_not_exists)
81 {
82 appendStringInfoString(buf, "IF NOT EXISTS ");
83 }
84
85 /*
86 * Up until here we have been ending the statement in a space, which makes it possible
87 * to just append the quoted extname. From here onwards we will not have the string
88 * ending in a space so appends should begin with a whitespace.
89 */
90 appendStringInfoString(buf, quote_identifier(createExtensionStmt->extname));
91 AppendCreateExtensionStmtOptions(buf, createExtensionStmt->options);
92 appendStringInfoString(buf, ";");
93 }
94
95
96 /*
97 * AppendCreateExtensionStmtOptions takes the option list of a CreateExtensionStmt and
98 * loops over the options to add them to the statement we are building.
99 *
100 * An error will be thrown if we run into an unsupported option, comparable to how
101 * postgres gives an error when parsing this list.
102 */
103 static void
AppendCreateExtensionStmtOptions(StringInfo buf,List * options)104 AppendCreateExtensionStmtOptions(StringInfo buf, List *options)
105 {
106 if (list_length(options) > 0)
107 {
108 /* only append WITH if we actual will add options to the statement */
109 appendStringInfoString(buf, " WITH");
110 }
111
112 /* Add the options to the statement */
113 DefElem *defElem = NULL;
114 foreach_ptr(defElem, options)
115 {
116 if (strcmp(defElem->defname, "schema") == 0)
117 {
118 const char *schemaName = defGetString(defElem);
119 appendStringInfo(buf, " SCHEMA %s", quote_identifier(schemaName));
120 }
121 else if (strcmp(defElem->defname, "new_version") == 0)
122 {
123 const char *newVersion = defGetString(defElem);
124 appendStringInfo(buf, " VERSION %s", quote_identifier(newVersion));
125 }
126 else if (strcmp(defElem->defname, "old_version") == 0)
127 {
128 const char *oldVersion = defGetString(defElem);
129 appendStringInfo(buf, " FROM %s", quote_identifier(oldVersion));
130 }
131 else if (strcmp(defElem->defname, "cascade") == 0)
132 {
133 bool cascade = defGetBoolean(defElem);
134 if (cascade)
135 {
136 appendStringInfoString(buf, " CASCADE");
137 }
138 }
139 else
140 {
141 elog(ERROR, "unrecognized option: %s", defElem->defname);
142 }
143 }
144 }
145
146
147 /*
148 * DeparseAlterExtensionStmt builds and returns a string representing the
149 * AlterExtensionStmt to be sent to worker nodes.
150 */
151 char *
DeparseAlterExtensionStmt(Node * node)152 DeparseAlterExtensionStmt(Node *node)
153 {
154 AlterExtensionStmt *stmt = castNode(AlterExtensionStmt, node);
155 StringInfoData sql = { 0 };
156 initStringInfo(&sql);
157
158 AppendAlterExtensionStmt(&sql, stmt);
159
160 return sql.data;
161 }
162
163
164 /*
165 * AppendAlterExtensionStmt appends a string representing the AlterExtensionStmt to a buffer
166 */
167 static void
AppendAlterExtensionStmt(StringInfo buf,AlterExtensionStmt * alterExtensionStmt)168 AppendAlterExtensionStmt(StringInfo buf, AlterExtensionStmt *alterExtensionStmt)
169 {
170 List *optionsList = alterExtensionStmt->options;
171
172 const char *extensionName = alterExtensionStmt->extname;
173 extensionName = quote_identifier(extensionName);
174
175 appendStringInfo(buf, "ALTER EXTENSION %s UPDATE", extensionName);
176
177 /*
178 * Append the options for ALTER EXTENSION ... UPDATE
179 * Currently there is only 1 option, but this structure follows how postgres parses
180 * the options.
181 */
182 DefElem *option = NULL;
183 foreach_ptr(option, optionsList)
184 {
185 if (strcmp(option->defname, "new_version") == 0)
186 {
187 const char *newVersion = defGetString(option);
188 appendStringInfo(buf, " TO %s", quote_identifier(newVersion));
189 }
190 else
191 {
192 elog(ERROR, "unrecognized option: %s", option->defname);
193 }
194 }
195
196 appendStringInfoString(buf, ";");
197 }
198
199
200 /*
201 * DeparseDropExtensionStmt builds and returns a string representing the DropStmt
202 */
203 char *
DeparseDropExtensionStmt(Node * node)204 DeparseDropExtensionStmt(Node *node)
205 {
206 DropStmt *stmt = castNode(DropStmt, node);
207 StringInfoData str = { 0 };
208 initStringInfo(&str);
209
210 AppendDropExtensionStmt(&str, stmt);
211
212 return str.data;
213 }
214
215
216 /*
217 * AppendDropExtensionStmt appends a string representing the DropStmt for
218 * an extension to a buffer.
219 */
220 static void
AppendDropExtensionStmt(StringInfo str,DropStmt * dropStmt)221 AppendDropExtensionStmt(StringInfo str, DropStmt *dropStmt)
222 {
223 /* we append "IF NOT EXISTS" clause regardless of the content of the statement. */
224 appendStringInfoString(str, "DROP EXTENSION IF EXISTS ");
225
226 /*
227 * Pick the distributed ones from the "objects" list that is storing
228 * the object names to be deleted.
229 */
230 AppendExtensionNameList(str, dropStmt->objects);
231
232 /* depending on behaviour field of DropStmt, we should append CASCADE or RESTRICT */
233 if (dropStmt->behavior == DROP_CASCADE)
234 {
235 appendStringInfoString(str, " CASCADE;");
236 }
237 else
238 {
239 appendStringInfoString(str, " RESTRICT;");
240 }
241 }
242
243
244 /*
245 * AppendExtensionNameList appends a string representing the list of
246 * extension names to a buffer.
247 */
248 static void
AppendExtensionNameList(StringInfo str,List * objects)249 AppendExtensionNameList(StringInfo str, List *objects)
250 {
251 ListCell *objectCell = NULL;
252
253 foreach(objectCell, objects)
254 {
255 const char *extensionName = strVal(lfirst(objectCell));
256 extensionName = quote_identifier(extensionName);
257
258 if (objectCell != list_head(objects))
259 {
260 appendStringInfo(str, ", ");
261 }
262
263 appendStringInfoString(str, extensionName);
264 }
265 }
266
267
268 /*
269 * DeparseAlterExtensionSchemaStmt builds and returns a string representing the
270 * AlterObjectSchemaStmt (ALTER EXTENSION SET SCHEMA).
271 */
272 char *
DeparseAlterExtensionSchemaStmt(Node * node)273 DeparseAlterExtensionSchemaStmt(Node *node)
274 {
275 AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
276 StringInfoData str = { 0 };
277 initStringInfo(&str);
278
279 Assert(stmt->objectType == OBJECT_EXTENSION);
280
281 AppendAlterExtensionSchemaStmt(&str, stmt);
282
283 return str.data;
284 }
285
286
287 /*
288 * AppendAlterExtensionSchemaStmt appends a string representing the AlterObjectSchemaStmt
289 * for an extension to a buffer.
290 */
291 static void
AppendAlterExtensionSchemaStmt(StringInfo buf,AlterObjectSchemaStmt * alterExtensionSchemaStmt)292 AppendAlterExtensionSchemaStmt(StringInfo buf,
293 AlterObjectSchemaStmt *alterExtensionSchemaStmt)
294 {
295 Assert(alterExtensionSchemaStmt->objectType == OBJECT_EXTENSION);
296
297 const char *extensionName = strVal(alterExtensionSchemaStmt->object);
298 const char *newSchemaName = alterExtensionSchemaStmt->newschema;
299
300 extensionName = quote_identifier(extensionName);
301 newSchemaName = quote_identifier(newSchemaName);
302
303 appendStringInfo(buf, "ALTER EXTENSION %s SET SCHEMA %s;", extensionName,
304 newSchemaName);
305 }
306