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