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