1 /*
2  * This file and its contents are licensed under the Timescale License.
3  * Please see the included NOTICE for copyright information and
4  * LICENSE-TIMESCALE for a copy of the license.
5  */
6 #include <postgres.h>
7 #include <utils/rel.h>
8 #include <lib/stringinfo.h>
9 #include <utils/acl.h>
10 #include <utils/builtins.h>
11 #include <utils/lsyscache.h>
12 #include <utils/relcache.h>
13 #include <utils/ruleutils.h>
14 #include <utils/syscache.h>
15 #include <utils/fmgroids.h>
16 #include <utils/rel.h>
17 #include <commands/tablespace.h>
18 #include <commands/defrem.h>
19 #include <access/relscan.h>
20 #include <catalog/pg_class.h>
21 #include <catalog/indexing.h>
22 #include <catalog/pg_constraint.h>
23 #include <catalog/pg_index.h>
24 #include <catalog/pg_authid.h>
25 #include <catalog/pg_proc.h>
26 #include <catalog/namespace.h>
27 #include <nodes/pg_list.h>
28 #include <funcapi.h>
29 #include <fmgr.h>
30 
31 #include <constraint.h>
32 #include <extension.h>
33 #include <utils.h>
34 #include <export.h>
35 #include <compat/compat.h>
36 #include <trigger.h>
37 
38 #include "deparse.h"
39 #include "guc.h"
40 #include "utils.h"
41 
42 /*
43  * Deparse a table into a set of SQL commands that can be used to recreate it.
44  * Together with column definition it deparses constraints, indexes, triggers
45  * and rules as well.  There are some table types that are not supported:
46  * temporary, partitioned, foreign, inherited and a table that uses
47  * options. Row security is also not supported.
48  */
49 typedef const char *(*GetCmdFunc)(Oid oid);
50 
51 static const char *
get_index_cmd(Oid oid)52 get_index_cmd(Oid oid)
53 {
54 	return pg_get_indexdef_string(oid);
55 }
56 
57 static const char *
get_constraint_cmd(Oid oid)58 get_constraint_cmd(Oid oid)
59 {
60 	return pg_get_constraintdef_command(oid);
61 }
62 
63 static FunctionCallInfo
build_fcinfo_data(Oid oid)64 build_fcinfo_data(Oid oid)
65 {
66 	FunctionCallInfo fcinfo = palloc(SizeForFunctionCallInfo(1));
67 
68 	InitFunctionCallInfoData(*fcinfo, NULL, 1, InvalidOid, NULL, NULL);
69 	FC_ARG(fcinfo, 0) = ObjectIdGetDatum(oid);
70 	FC_NULL(fcinfo, 0) = false;
71 
72 	return fcinfo;
73 }
74 
75 static const char *
get_trigger_cmd(Oid oid)76 get_trigger_cmd(Oid oid)
77 {
78 	return TextDatumGetCString(pg_get_triggerdef(build_fcinfo_data(oid)));
79 }
80 
81 static const char *
get_function_cmd(Oid oid)82 get_function_cmd(Oid oid)
83 {
84 	return TextDatumGetCString(pg_get_functiondef(build_fcinfo_data(oid)));
85 }
86 
87 static const char *
get_rule_cmd(Oid oid)88 get_rule_cmd(Oid oid)
89 {
90 	return TextDatumGetCString(pg_get_ruledef(build_fcinfo_data(oid)));
91 }
92 
93 static List *
get_cmds(List * oids,GetCmdFunc get_cmd)94 get_cmds(List *oids, GetCmdFunc get_cmd)
95 {
96 	List *cmds = NIL;
97 	ListCell *cell;
98 
99 	foreach (cell, oids)
100 	{
101 		StringInfo cmd = makeStringInfo();
102 
103 		appendStringInfo(cmd, "%s;", get_cmd(lfirst_oid(cell)));
104 		cmds = lappend(cmds, cmd->data);
105 	}
106 	return cmds;
107 }
108 
109 static List *
get_constraint_cmds(List * constraint_oids)110 get_constraint_cmds(List *constraint_oids)
111 {
112 	return get_cmds(constraint_oids, get_constraint_cmd);
113 }
114 
115 static List *
get_index_cmds(List * index_oids)116 get_index_cmds(List *index_oids)
117 {
118 	return get_cmds(index_oids, get_index_cmd);
119 }
120 
121 static List *
get_trigger_cmds(List * trigger_oids)122 get_trigger_cmds(List *trigger_oids)
123 {
124 	return get_cmds(trigger_oids, get_trigger_cmd);
125 }
126 
127 static List *
get_function_cmds(List * function_oids)128 get_function_cmds(List *function_oids)
129 {
130 	return get_cmds(function_oids, get_function_cmd);
131 }
132 
133 static List *
get_rule_cmds(List * rule_oids)134 get_rule_cmds(List *rule_oids)
135 {
136 	return get_cmds(rule_oids, get_rule_cmd);
137 }
138 
139 static bool
column_is_serial(Relation rel,Name column)140 column_is_serial(Relation rel, Name column)
141 {
142 	const char *relation_name;
143 	LOCAL_FCINFO(fcinfo, 2);
144 
145 	/* Prepare call to pg_get_serial_sequence() function.
146 	 *
147 	 * We have to manually prepare the function call context here instead
148 	 * of using the DirectFunctionCall2() because we expect to get
149 	 * NULL return value. */
150 	relation_name = quote_qualified_identifier(get_namespace_name(rel->rd_rel->relnamespace),
151 											   NameStr(rel->rd_rel->relname));
152 	InitFunctionCallInfoData(*fcinfo, NULL, 2, InvalidOid, NULL, NULL);
153 	FC_ARG(fcinfo, 0) = CStringGetTextDatum(relation_name);
154 	FC_ARG(fcinfo, 1) = CStringGetTextDatum(column->data);
155 	FC_NULL(fcinfo, 0) = false;
156 	FC_NULL(fcinfo, 1) = false;
157 	pg_get_serial_sequence(fcinfo);
158 
159 	return !fcinfo->isnull;
160 }
161 
162 static void
deparse_columns(StringInfo stmt,Relation rel)163 deparse_columns(StringInfo stmt, Relation rel)
164 {
165 	int att_idx;
166 	TupleDesc rel_desc = RelationGetDescr(rel);
167 	TupleConstr *constraints = rel_desc->constr;
168 
169 	for (att_idx = 0; att_idx < rel_desc->natts; att_idx++)
170 	{
171 		int dim_idx;
172 		Form_pg_attribute attr = TupleDescAttr(rel_desc, att_idx);
173 		bits16 flags = FORMAT_TYPE_TYPEMOD_GIVEN;
174 
175 		if (attr->attisdropped)
176 			continue;
177 
178 		/*
179 		 * if it's not a builtin type then schema qualify the same. There's a function
180 		 * deparse_type_name in fdw, but we don't want cross linking unnecessarily
181 		 */
182 		if (attr->atttypid >= FirstBootstrapObjectId)
183 			flags |= FORMAT_TYPE_FORCE_QUALIFY;
184 
185 		appendStringInfo(stmt,
186 						 "\"%s\" %s",
187 						 NameStr(attr->attname),
188 						 format_type_extended(attr->atttypid, attr->atttypmod, flags));
189 
190 		if (attr->attnotnull)
191 			appendStringInfoString(stmt, " NOT NULL");
192 
193 		if (OidIsValid(attr->attcollation))
194 			appendStringInfo(stmt, " COLLATE \"%s\"", get_collation_name(attr->attcollation));
195 
196 		if (attr->atthasdef)
197 		{
198 			int co_idx;
199 
200 			for (co_idx = 0; co_idx < constraints->num_defval; co_idx++)
201 			{
202 				AttrDefault attr_def = constraints->defval[co_idx];
203 
204 				if (attr->attnum == attr_def.adnum)
205 				{
206 					char *attr_default;
207 
208 					/* Skip default expression in case if column is serial
209 					 * (has dependant sequence object) */
210 					if (column_is_serial(rel, &attr->attname))
211 						break;
212 
213 					attr_default =
214 						TextDatumGetCString(DirectFunctionCall2(pg_get_expr,
215 																CStringGetTextDatum(attr_def.adbin),
216 																ObjectIdGetDatum(rel->rd_id)));
217 
218 					if (attr->attgenerated == ATTRIBUTE_GENERATED_STORED)
219 						appendStringInfo(stmt, " GENERATED ALWAYS AS %s STORED", attr_default);
220 					else
221 					{
222 						appendStringInfo(stmt, " DEFAULT %s", attr_default);
223 					}
224 					break;
225 				}
226 			}
227 		}
228 
229 		for (dim_idx = 1; dim_idx < attr->attndims; dim_idx++)
230 			appendStringInfoString(stmt, "[]");
231 
232 		if (att_idx != (rel_desc->natts - 1))
233 			appendStringInfoString(stmt, ", ");
234 	}
235 }
236 
237 typedef struct ConstraintContext
238 {
239 	List *constraints;
240 	List **constraint_indexes;
241 } ConstraintContext;
242 
243 static ConstraintProcessStatus
add_constraint(HeapTuple constraint_tuple,void * ctx)244 add_constraint(HeapTuple constraint_tuple, void *ctx)
245 {
246 	ConstraintContext *cc = ctx;
247 	Form_pg_constraint constraint = (Form_pg_constraint) GETSTRUCT(constraint_tuple);
248 	Oid constroid;
249 
250 	if (OidIsValid(constraint->conindid))
251 		*cc->constraint_indexes = lappend_oid(*cc->constraint_indexes, constraint->conindid);
252 	constroid = constraint->oid;
253 	cc->constraints = lappend_oid(cc->constraints, constroid);
254 	return CONSTR_PROCESSED;
255 }
256 
257 static List *
get_constraint_oids(Oid relid,List ** constraint_indexes)258 get_constraint_oids(Oid relid, List **constraint_indexes)
259 {
260 	ConstraintContext cc = {
261 		.constraints = NIL,
262 		.constraint_indexes = constraint_indexes,
263 	};
264 
265 	ts_constraint_process(relid, add_constraint, &cc);
266 
267 	return cc.constraints;
268 }
269 
270 static List *
get_index_oids(Relation rel,List * exclude_indexes)271 get_index_oids(Relation rel, List *exclude_indexes)
272 {
273 	List *indexes = NIL;
274 	ListCell *cell;
275 
276 	foreach (cell, RelationGetIndexList(rel))
277 	{
278 		Oid indexid = lfirst_oid(cell);
279 
280 		if (!list_member_oid(exclude_indexes, indexid))
281 			indexes = lappend_oid(indexes, indexid);
282 	}
283 	return indexes;
284 }
285 
286 /*
287  *  Specifically exclude the hypertable insert blocker from this list.  A table which was recreated
288  * with that trigger present would not be able to made into a hypertable.
289  */
290 static List *
get_trigger_oids(Relation rel)291 get_trigger_oids(Relation rel)
292 {
293 	List *triggers = NIL;
294 
295 	if (rel->trigdesc != NULL)
296 	{
297 		int i;
298 
299 		for (i = 0; i < rel->trigdesc->numtriggers; i++)
300 		{
301 			const Trigger trigger = rel->trigdesc->triggers[i];
302 
303 			if (!trigger.tgisinternal && strcmp(trigger.tgname, INSERT_BLOCKER_NAME) != 0)
304 				triggers = lappend_oid(triggers, trigger.tgoid);
305 		}
306 	}
307 	return triggers;
308 }
309 
310 static List *
get_trigger_function_oids(Relation rel)311 get_trigger_function_oids(Relation rel)
312 {
313 	List *functions = NIL;
314 
315 	if (rel->trigdesc != NULL)
316 	{
317 		int i;
318 
319 		for (i = 0; i < rel->trigdesc->numtriggers; i++)
320 		{
321 			const Trigger trigger = rel->trigdesc->triggers[i];
322 
323 			if (!trigger.tgisinternal && strcmp(trigger.tgname, INSERT_BLOCKER_NAME) != 0)
324 				functions = lappend_oid(functions, trigger.tgfoid);
325 		}
326 	}
327 
328 	return functions;
329 }
330 
331 static List *
get_rule_oids(Relation rel)332 get_rule_oids(Relation rel)
333 {
334 	List *rules = NIL;
335 
336 	if (rel->rd_rules != NULL)
337 	{
338 		int i;
339 
340 		for (i = 0; i < rel->rd_rules->numLocks; i++)
341 		{
342 			const RewriteRule *rule = rel->rd_rules->rules[i];
343 
344 			rules = lappend_oid(rules, rule->ruleId);
345 		}
346 	}
347 	return rules;
348 }
349 
350 static void
validate_relation(Relation rel)351 validate_relation(Relation rel)
352 {
353 	if (rel->rd_rel->relkind != RELKIND_RELATION)
354 		ereport(ERROR,
355 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
356 				 errmsg("given relation is not an ordinary table")));
357 
358 	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
359 		ereport(ERROR,
360 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
361 				 errmsg("temporary table is not supported")));
362 
363 	if (rel->rd_rel->relrowsecurity)
364 		ereport(ERROR,
365 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("row security is not supported")));
366 }
367 
368 TableInfo *
deparse_create_table_info(Oid relid)369 deparse_create_table_info(Oid relid)
370 {
371 	List *exclude_indexes = NIL;
372 	TableInfo *table_info = palloc0(sizeof(TableInfo));
373 	Relation rel = table_open(relid, AccessShareLock);
374 
375 	if (rel == NULL)
376 		ereport(ERROR, (errmsg("relation with id %u not found", relid)));
377 
378 	validate_relation(rel);
379 
380 	table_info->relid = relid;
381 	table_info->constraints = get_constraint_oids(relid, &exclude_indexes);
382 	table_info->indexes = get_index_oids(rel, exclude_indexes);
383 	table_info->triggers = get_trigger_oids(rel);
384 	table_info->functions = get_trigger_function_oids(rel);
385 	table_info->rules = get_rule_oids(rel);
386 	table_close(rel, AccessShareLock);
387 	return table_info;
388 }
389 
390 static void
deparse_get_tabledef_with(const TableInfo * table_info,StringInfo create_table)391 deparse_get_tabledef_with(const TableInfo *table_info, StringInfo create_table)
392 {
393 	ListCell *cell;
394 	List *opts = ts_get_reloptions(table_info->relid);
395 
396 	if (list_length(opts) == 0)
397 		return;
398 
399 	appendStringInfoString(create_table, " WITH (");
400 
401 	foreach (cell, opts)
402 	{
403 		DefElem *def = (DefElem *) lfirst(cell);
404 
405 		appendStringInfo(create_table,
406 						 "%s%s=%s",
407 						 cell != list_head(opts) ? "," : "",
408 						 def->defname,
409 						 defGetString(def));
410 	}
411 
412 	appendStringInfoChar(create_table, ')');
413 }
414 
415 TableDef *
deparse_get_tabledef(TableInfo * table_info)416 deparse_get_tabledef(TableInfo *table_info)
417 {
418 	StringInfo create_table = makeStringInfo();
419 	StringInfo set_schema = makeStringInfo();
420 	TableDef *table_def = palloc0(sizeof(TableDef));
421 	Relation rel = table_open(table_info->relid, AccessShareLock);
422 
423 	appendStringInfo(set_schema,
424 					 "SET SCHEMA %s;",
425 					 quote_literal_cstr(get_namespace_name(rel->rd_rel->relnamespace)));
426 	table_def->schema_cmd = set_schema->data;
427 
428 	appendStringInfoString(create_table, "CREATE");
429 	if (rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED)
430 		appendStringInfoString(create_table, " UNLOGGED");
431 	appendStringInfoString(create_table, " TABLE");
432 
433 	appendStringInfo(create_table,
434 					 " \"%s\".\"%s\" (",
435 					 get_namespace_name(rel->rd_rel->relnamespace),
436 					 NameStr(rel->rd_rel->relname));
437 
438 	deparse_columns(create_table, rel);
439 
440 	appendStringInfoChar(create_table, ')');
441 	appendStringInfo(create_table, " USING \"%s\" ", get_am_name(rel->rd_rel->relam));
442 	deparse_get_tabledef_with(table_info, create_table);
443 
444 	appendStringInfoChar(create_table, ';');
445 	table_def->create_cmd = create_table->data;
446 
447 	table_def->constraint_cmds = get_constraint_cmds(table_info->constraints);
448 	table_def->index_cmds = get_index_cmds(table_info->indexes);
449 	table_def->trigger_cmds = get_trigger_cmds(table_info->triggers);
450 	table_def->function_cmds = get_function_cmds(table_info->functions);
451 	table_def->rule_cmds = get_rule_cmds(table_info->rules);
452 
453 	table_close(rel, AccessShareLock);
454 	return table_def;
455 }
456 
457 /*
458  * Append a privilege name to a string if the privilege is set.
459  *
460  * Parameters:
461  *    buf: Buffer to append to.
462  *    pfirst: Pointer to variable to remember if elements are already added.
463  *    privs: Bitmap of privilege flags.
464  *    mask: Mask for privilege to check.
465  *    priv_name: String with name of privilege to add.
466  */
467 static void
append_priv_if_set(StringInfo buf,bool * priv_added,uint32 privs,uint32 mask,const char * priv_name)468 append_priv_if_set(StringInfo buf, bool *priv_added, uint32 privs, uint32 mask,
469 				   const char *priv_name)
470 {
471 	if (privs & mask)
472 	{
473 		if (*priv_added)
474 			appendStringInfoString(buf, ", ");
475 		else
476 			*priv_added = true;
477 		appendStringInfoString(buf, priv_name);
478 	}
479 }
480 
481 static void
append_privs_as_text(StringInfo buf,uint32 privs)482 append_privs_as_text(StringInfo buf, uint32 privs)
483 {
484 	bool priv_added = false;
485 	append_priv_if_set(buf, &priv_added, privs, ACL_INSERT, "INSERT");
486 	append_priv_if_set(buf, &priv_added, privs, ACL_SELECT, "SELECT");
487 	append_priv_if_set(buf, &priv_added, privs, ACL_UPDATE, "UPDATE");
488 	append_priv_if_set(buf, &priv_added, privs, ACL_DELETE, "DELETE");
489 	append_priv_if_set(buf, &priv_added, privs, ACL_TRUNCATE, "TRUNCATE");
490 	append_priv_if_set(buf, &priv_added, privs, ACL_REFERENCES, "REFERENCES");
491 	append_priv_if_set(buf, &priv_added, privs, ACL_TRIGGER, "TRIGGER");
492 }
493 
494 /*
495  * Create grant statements for a relation.
496  *
497  * This will create a list of grant statements, one for each role.
498  */
499 static List *
deparse_grant_commands_for_relid(Oid relid)500 deparse_grant_commands_for_relid(Oid relid)
501 {
502 	HeapTuple reltup;
503 	Form_pg_class pg_class_tuple;
504 	List *cmds = NIL;
505 	Datum acl_datum;
506 	bool is_null;
507 	Oid owner_id;
508 	Acl *acl;
509 	int i;
510 	const AclItem *acldat;
511 
512 	reltup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
513 	if (!HeapTupleIsValid(reltup))
514 		elog(ERROR, "cache lookup failed for relation %u", relid);
515 	pg_class_tuple = (Form_pg_class) GETSTRUCT(reltup);
516 
517 	if (pg_class_tuple->relkind != RELKIND_RELATION)
518 		ereport(ERROR,
519 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
520 				 errmsg("\"%s\" is not an ordinary table", NameStr(pg_class_tuple->relname))));
521 
522 	owner_id = pg_class_tuple->relowner;
523 	acl_datum = SysCacheGetAttr(RELOID, reltup, Anum_pg_class_relacl, &is_null);
524 
525 	if (is_null)
526 		acl = acldefault(OBJECT_TABLE, owner_id);
527 	else
528 		acl = DatumGetAclP(acl_datum);
529 
530 	acldat = ACL_DAT(acl);
531 	for (i = 0; i < ACL_NUM(acl); i++)
532 	{
533 		const AclItem *aclitem = &acldat[i];
534 		Oid role_id = aclitem->ai_grantee;
535 		StringInfo grant_cmd;
536 		HeapTuple utup;
537 
538 		/* We skip the owner of the table since she automatically have all
539 		 * privileges on the table. */
540 		if (role_id == owner_id)
541 			continue;
542 
543 		grant_cmd = makeStringInfo();
544 		utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(role_id));
545 
546 		if (!HeapTupleIsValid(utup))
547 			continue;
548 
549 		appendStringInfoString(grant_cmd, "GRANT ");
550 		append_privs_as_text(grant_cmd, aclitem->ai_privs);
551 		appendStringInfo(grant_cmd,
552 						 " ON TABLE %s.%s TO %s",
553 						 quote_identifier(get_namespace_name(pg_class_tuple->relnamespace)),
554 						 quote_identifier(NameStr(pg_class_tuple->relname)),
555 						 quote_identifier(NameStr(((Form_pg_authid) GETSTRUCT(utup))->rolname)));
556 
557 		ReleaseSysCache(utup);
558 		cmds = lappend(cmds, grant_cmd->data);
559 	}
560 
561 	ReleaseSysCache(reltup);
562 
563 	return cmds;
564 }
565 
566 List *
deparse_get_tabledef_commands(Oid relid)567 deparse_get_tabledef_commands(Oid relid)
568 {
569 	TableInfo *table_info = deparse_create_table_info(relid);
570 	TableDef *table_def = deparse_get_tabledef(table_info);
571 
572 	return deparse_get_tabledef_commands_from_tabledef(table_def);
573 }
574 
575 List *
deparse_get_tabledef_commands_from_tabledef(TableDef * table_def)576 deparse_get_tabledef_commands_from_tabledef(TableDef *table_def)
577 {
578 	List *cmds = NIL;
579 
580 	cmds = lappend(cmds, (char *) table_def->schema_cmd);
581 	cmds = lappend(cmds, (char *) table_def->create_cmd);
582 	cmds = list_concat(cmds, table_def->constraint_cmds);
583 	cmds = list_concat(cmds, table_def->index_cmds);
584 	cmds = list_concat(cmds, table_def->function_cmds);
585 	cmds = list_concat(cmds, table_def->trigger_cmds);
586 	cmds = list_concat(cmds, table_def->rule_cmds);
587 	return cmds;
588 }
589 
590 const char *
deparse_get_tabledef_commands_concat(Oid relid)591 deparse_get_tabledef_commands_concat(Oid relid)
592 {
593 	StringInfo tabledef = makeStringInfo();
594 	ListCell *cell;
595 
596 	foreach (cell, deparse_get_tabledef_commands(relid))
597 		appendStringInfoString(tabledef, lfirst(cell));
598 
599 	return tabledef->data;
600 }
601 
602 static const char *
deparse_get_add_dimension_command(Hypertable * ht,Dimension * dimension)603 deparse_get_add_dimension_command(Hypertable *ht, Dimension *dimension)
604 {
605 	StringInfo dim_cmd = makeStringInfo();
606 
607 	appendStringInfo(dim_cmd,
608 					 "SELECT * FROM %s.add_dimension(%s, %s, ",
609 					 quote_identifier(ts_extension_schema_name()),
610 					 quote_literal_cstr(
611 						 quote_qualified_identifier(get_namespace_name(
612 														get_rel_namespace(ht->main_table_relid)),
613 													get_rel_name(ht->main_table_relid))),
614 					 quote_literal_cstr(NameStr(dimension->fd.column_name)));
615 
616 	if (dimension->type == DIMENSION_TYPE_CLOSED)
617 		appendStringInfo(dim_cmd,
618 						 "number_partitions => %d, partitioning_func => %s);",
619 						 dimension->fd.num_slices,
620 						 quote_literal_cstr(
621 							 quote_qualified_identifier(NameStr(
622 															dimension->fd.partitioning_func_schema),
623 														NameStr(dimension->fd.partitioning_func))));
624 	else
625 		appendStringInfo(dim_cmd,
626 						 "chunk_time_interval => " INT64_FORMAT ");",
627 						 dimension->fd.interval_length);
628 
629 	return dim_cmd->data;
630 }
631 
632 DeparsedHypertableCommands *
deparse_get_distributed_hypertable_create_command(Hypertable * ht)633 deparse_get_distributed_hypertable_create_command(Hypertable *ht)
634 {
635 	Hyperspace *space = ht->space;
636 	Dimension *time_dim = &space->dimensions[0];
637 	StringInfo hypertable_cmd = makeStringInfo();
638 	DeparsedHypertableCommands *result = palloc(sizeof(DeparsedHypertableCommands));
639 
640 	appendStringInfo(hypertable_cmd,
641 					 "SELECT * FROM %s.create_hypertable(%s",
642 					 quote_identifier(ts_extension_schema_name()),
643 					 quote_literal_cstr(
644 						 quote_qualified_identifier(get_namespace_name(
645 														get_rel_namespace(ht->main_table_relid)),
646 													get_rel_name(ht->main_table_relid))));
647 
648 	appendStringInfo(hypertable_cmd,
649 					 ", time_column_name => %s",
650 					 quote_literal_cstr(NameStr(time_dim->fd.column_name)));
651 
652 	if (time_dim->fd.partitioning_func.data[0] != '\0')
653 		appendStringInfo(hypertable_cmd,
654 						 ", time_partitioning_func => %s",
655 						 quote_literal_cstr(
656 							 quote_qualified_identifier(NameStr(
657 															time_dim->fd.partitioning_func_schema),
658 														NameStr(time_dim->fd.partitioning_func))));
659 
660 	appendStringInfo(hypertable_cmd,
661 					 ", associated_schema_name => %s",
662 					 quote_literal_cstr(NameStr(ht->fd.associated_schema_name)));
663 	appendStringInfo(hypertable_cmd,
664 					 ", associated_table_prefix => %s",
665 					 quote_literal_cstr(NameStr(ht->fd.associated_table_prefix)));
666 
667 	appendStringInfo(hypertable_cmd,
668 					 ", chunk_time_interval => " INT64_FORMAT "",
669 					 time_dim->fd.interval_length);
670 
671 	if (OidIsValid(ht->chunk_sizing_func))
672 	{
673 		appendStringInfo(hypertable_cmd,
674 						 ", chunk_sizing_func => %s",
675 						 quote_literal_cstr(
676 							 quote_qualified_identifier(NameStr(ht->fd.chunk_sizing_func_schema),
677 														NameStr(ht->fd.chunk_sizing_func_name))));
678 		appendStringInfo(hypertable_cmd,
679 						 ", chunk_target_size => '" INT64_FORMAT "'",
680 						 ht->fd.chunk_target_size);
681 	}
682 
683 	/*
684 	 * Data node is assumed to not have any preexisting conflicting table or hypertable.
685 	 * Any default indices will have already been created by the access node.
686 	 */
687 	appendStringInfoString(hypertable_cmd, ", if_not_exists => FALSE");
688 	appendStringInfoString(hypertable_cmd, ", migrate_data => FALSE");
689 	appendStringInfoString(hypertable_cmd, ", create_default_indexes => FALSE");
690 	appendStringInfo(hypertable_cmd, ", replication_factor => %d", HYPERTABLE_DISTRIBUTED_MEMBER);
691 
692 	appendStringInfoString(hypertable_cmd, ");");
693 
694 	result->table_create_command = hypertable_cmd->data;
695 	result->dimension_add_commands = NIL;
696 
697 	if (space->num_dimensions > 1)
698 	{
699 		int i;
700 
701 		for (i = 1; i < space->num_dimensions; i++)
702 			result->dimension_add_commands =
703 				lappend(result->dimension_add_commands,
704 						(char *) deparse_get_add_dimension_command(ht, &space->dimensions[i]));
705 	}
706 
707 	result->grant_commands = deparse_grant_commands_for_relid(ht->main_table_relid);
708 
709 	return result;
710 }
711 
712 #define DEFAULT_SCALAR_RESULT_NAME "*"
713 
714 static void
deparse_result_type(StringInfo sql,FunctionCallInfo fcinfo)715 deparse_result_type(StringInfo sql, FunctionCallInfo fcinfo)
716 {
717 	TupleDesc tupdesc;
718 	char *scalarname;
719 	Oid resulttypeid;
720 	int i;
721 
722 	switch (get_call_result_type(fcinfo, &resulttypeid, &tupdesc))
723 	{
724 		case TYPEFUNC_SCALAR:
725 			/* scalar result type */
726 			Assert(NULL == tupdesc);
727 			Assert(OidIsValid(resulttypeid));
728 
729 			/* Check if the function has a named OUT parameter */
730 			scalarname = get_func_result_name(fcinfo->flinfo->fn_oid);
731 
732 			/* If there is no named OUT parameter, use the default name */
733 			if (NULL != scalarname)
734 			{
735 				appendStringInfoString(sql, scalarname);
736 				pfree(scalarname);
737 			}
738 			else
739 				appendStringInfoString(sql, DEFAULT_SCALAR_RESULT_NAME);
740 			break;
741 		case TYPEFUNC_COMPOSITE:
742 			/* determinable rowtype result */
743 			Assert(NULL != tupdesc);
744 
745 			for (i = 0; i < tupdesc->natts; i++)
746 			{
747 				if (!tupdesc->attrs[i].attisdropped)
748 				{
749 					appendStringInfoString(sql, NameStr(tupdesc->attrs[i].attname));
750 
751 					if (i < (tupdesc->natts - 1))
752 						appendStringInfoChar(sql, ',');
753 				}
754 			}
755 			break;
756 		case TYPEFUNC_RECORD:
757 			/* indeterminate rowtype result */
758 		case TYPEFUNC_COMPOSITE_DOMAIN:
759 			/* domain over determinable rowtype result */
760 		case TYPEFUNC_OTHER:
761 			elog(ERROR, "unsupported result type for deparsing");
762 			break;
763 	}
764 }
765 
766 /*
767  * Deparse a function call.
768  *
769  * Turn a function call back into a string. In theory, we could just call
770  * deparse_expression() (ruleutils.c) on the original function expression (as
771  * given by fcinfo->flinfo->fn_expr), but we'd like to support deparsing also
772  * when the expression is not available (e.g., when invoking by OID from C
773  * code). Further, deparse_expression() doesn't explicitly give the parameter
774  * names, which is important in order to maintain forward-compatibility with
775  * the remote version of the function in case it has reordered the parameters.
776  */
777 const char *
deparse_func_call(FunctionCallInfo fcinfo)778 deparse_func_call(FunctionCallInfo fcinfo)
779 {
780 	HeapTuple ftup;
781 	Form_pg_proc procform;
782 	StringInfoData sql;
783 	const char *funcnamespace;
784 	OverrideSearchPath search_path = {
785 		.schemas = NIL,
786 		.addCatalog = false,
787 		.addTemp = false,
788 	};
789 	Oid funcid = fcinfo->flinfo->fn_oid;
790 	Oid *argtypes;
791 	char **argnames;
792 	char *argmodes;
793 	int i;
794 
795 	initStringInfo(&sql);
796 	appendStringInfoString(&sql, "SELECT ");
797 	deparse_result_type(&sql, fcinfo);
798 
799 	/* First fetch the function's pg_proc row to inspect its rettype */
800 	ftup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
801 
802 	if (!HeapTupleIsValid(ftup))
803 		elog(ERROR, "cache lookup failed for function %u", funcid);
804 
805 	procform = (Form_pg_proc) GETSTRUCT(ftup);
806 	funcnamespace = get_namespace_name(procform->pronamespace);
807 
808 	/* If the function has named OUT-only parameters, then number of arguments
809 	 * returned by this get_func_arg_info can be greater than
810 	 * fcinfo->nargs. But we don't care about OUT-only arguements here. */
811 	get_func_arg_info(ftup, &argtypes, &argnames, &argmodes);
812 
813 	appendStringInfo(&sql,
814 					 " FROM %s(",
815 					 quote_qualified_identifier(funcnamespace, NameStr(procform->proname)));
816 	ReleaseSysCache(ftup);
817 
818 	/* Temporarily set a NULL search path. This makes identifier types (e.g.,
819 	 * regclass / tables) be fully qualified, which is needed since the search
820 	 * path on a remote node is not guaranteed to be the same. */
821 	PushOverrideSearchPath(&search_path);
822 
823 	for (i = 0; i < fcinfo->nargs; i++)
824 	{
825 		const char *argvalstr = "NULL";
826 		bool add_type_cast = false;
827 
828 		switch (argtypes[i])
829 		{
830 			case ANYOID:
831 			case ANYELEMENTOID:
832 				/* For pseudo types, try to resolve the "real" argument type
833 				 * from the function expression, if present */
834 				if (NULL != fcinfo->flinfo && NULL != fcinfo->flinfo->fn_expr)
835 				{
836 					Oid expr_argtype = get_fn_expr_argtype(fcinfo->flinfo, i);
837 
838 					/* Function parameters that aren't typed need type casts,
839 					 * but only add a cast if the expr contained a "real" type
840 					 * and not an unknown or pseudo type. */
841 					if (OidIsValid(expr_argtype) && expr_argtype != UNKNOWNOID &&
842 						expr_argtype != argtypes[i])
843 						add_type_cast = true;
844 
845 					argtypes[i] = expr_argtype;
846 				}
847 				break;
848 			default:
849 				break;
850 		}
851 
852 		if (!FC_NULL(fcinfo, i))
853 		{
854 			bool isvarlena;
855 			Oid outfuncid;
856 
857 			if (!OidIsValid(argtypes[i]))
858 				elog(ERROR, "invalid type for argument %d", i);
859 
860 			getTypeOutputInfo(argtypes[i], &outfuncid, &isvarlena);
861 			Assert(OidIsValid(outfuncid));
862 			argvalstr = quote_literal_cstr(OidOutputFunctionCall(outfuncid, FC_ARG(fcinfo, i)));
863 		}
864 
865 		appendStringInfo(&sql, "%s => %s", argnames[i], argvalstr);
866 
867 		if (add_type_cast)
868 			appendStringInfo(&sql, "::%s", format_type_be(argtypes[i]));
869 
870 		if (i < (fcinfo->nargs - 1))
871 			appendStringInfoChar(&sql, ',');
872 	}
873 
874 	PopOverrideSearchPath();
875 
876 	if (NULL != argtypes)
877 		pfree(argtypes);
878 
879 	if (NULL != argnames)
880 		pfree(argnames);
881 
882 	if (NULL != argmodes)
883 		pfree(argmodes);
884 
885 	appendStringInfoChar(&sql, ')');
886 
887 	return sql.data;
888 }
889 
890 /*
891  * Deparse a function by OID.
892  *
893  * The function arguments should be given as datums in the vararg list and
894  * need to be specified in the order given by the (OID) function's signature.
895  */
896 const char *
deparse_oid_function_call_coll(Oid funcid,Oid collation,unsigned int num_args,...)897 deparse_oid_function_call_coll(Oid funcid, Oid collation, unsigned int num_args, ...)
898 {
899 	FunctionCallInfo fcinfo = palloc(SizeForFunctionCallInfo(num_args));
900 	FmgrInfo flinfo;
901 	const char *result;
902 	va_list args;
903 	unsigned int i;
904 
905 	fmgr_info(funcid, &flinfo);
906 	InitFunctionCallInfoData(*fcinfo, &flinfo, num_args, collation, NULL, NULL);
907 	va_start(args, num_args);
908 
909 	for (i = 0; i < num_args; i++)
910 	{
911 		FC_ARG(fcinfo, i) = va_arg(args, Datum);
912 		FC_NULL(fcinfo, i) = false;
913 	}
914 
915 	va_end(args);
916 
917 	result = deparse_func_call(fcinfo);
918 
919 	/* Check for null result, since caller is clearly not expecting one */
920 	if (fcinfo->isnull)
921 		elog(ERROR, "function %u returned NULL", flinfo.fn_oid);
922 
923 	return result;
924 }
925 
926 const char *
deparse_grant_revoke_on_database(Node * node,const char * dbname)927 deparse_grant_revoke_on_database(Node *node, const char *dbname)
928 {
929 	GrantStmt *stmt = castNode(GrantStmt, node);
930 	ListCell *lc;
931 
932 	/*
933 	   GRANT { { CREATE | CONNECT | TEMPORARY | TEMP } [, ...] | ALL [ PRIVILEGES ] }
934 	   ON DATABASE database_name [, ...]
935 	   TO role_specification [, ...] [ WITH GRANT OPTION ]
936 	   [ GRANTED BY role_specification ]
937 
938 	   REVOKE [ GRANT OPTION FOR ]
939 	   { { CREATE | CONNECT | TEMPORARY | TEMP } [, ...] | ALL [ PRIVILEGES ] }
940 	   ON DATABASE database_name [, ...]
941 	   FROM role_specification [, ...]
942 	   [ GRANTED BY role_specification ]
943 	   [ CASCADE | RESTRICT ]
944 	*/
945 	StringInfo command = makeStringInfo();
946 
947 	/* GRANT/REVOKE */
948 	if (stmt->is_grant)
949 		appendStringInfoString(command, "GRANT ");
950 	else
951 		appendStringInfoString(command, "REVOKE ");
952 
953 	/* privileges [, ...] | ALL */
954 	if (stmt->privileges == NULL)
955 	{
956 		/* ALL */
957 		appendStringInfoString(command, "ALL ");
958 	}
959 	else
960 	{
961 		foreach (lc, stmt->privileges)
962 		{
963 			AccessPriv *priv = lfirst(lc);
964 
965 			appendStringInfo(command,
966 							 "%s%s ",
967 							 priv->priv_name,
968 							 lnext_compat(stmt->privileges, lc) != NULL ? "," : "");
969 		}
970 	}
971 
972 	/* Set database name of the data node */
973 	appendStringInfo(command, "ON DATABASE %s ", quote_identifier(dbname));
974 
975 	/* TO/FROM role_spec [, ...] */
976 	if (stmt->is_grant)
977 		appendStringInfoString(command, "TO ");
978 	else
979 		appendStringInfoString(command, "FROM ");
980 
981 	foreach (lc, stmt->grantees)
982 	{
983 		RoleSpec *role_spec = lfirst(lc);
984 
985 		appendStringInfo(command,
986 						 "%s%s ",
987 						 role_spec->rolename,
988 						 lnext_compat(stmt->grantees, lc) != NULL ? "," : "");
989 	}
990 
991 	if (stmt->grant_option)
992 		appendStringInfoString(command, "WITH GRANT OPTION ");
993 
994 #if PG14
995 	/* [ GRANTED BY role_specification ] */
996 	if (stmt->grantor)
997 		appendStringInfo(command, "GRANTED BY %s ", quote_identifier(stmt->grantor->rolename));
998 #endif
999 
1000 	/* CASCADE | RESTRICT */
1001 	if (!stmt->is_grant && stmt->behavior == DROP_CASCADE)
1002 		appendStringInfoString(command, "CASCADE");
1003 
1004 	return command->data;
1005 }
1006