>From 1de5c88f364d4ef91c83eda0e27b8e8dc671870b Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Tue, 7 Apr 2015 15:42:07 -0300 Subject: [PATCH 3/3] ddl_deparse extension --- contrib/ddl_deparse/Makefile | 20 + contrib/ddl_deparse/ddl_deparse--1.0.sql | 9 + contrib/ddl_deparse/ddl_deparse.c | 7168 ++++++++++++++++++++++++++++++ contrib/ddl_deparse/ddl_deparse.control | 5 + contrib/ddl_deparse/ddl_deparse.h | 8 + contrib/ddl_deparse/ddl_json.c | 715 +++ contrib/ddl_deparse/hook.c | 32 + 7 files changed, 7957 insertions(+) create mode 100644 contrib/ddl_deparse/Makefile create mode 100644 contrib/ddl_deparse/ddl_deparse--1.0.sql create mode 100644 contrib/ddl_deparse/ddl_deparse.c create mode 100644 contrib/ddl_deparse/ddl_deparse.control create mode 100644 contrib/ddl_deparse/ddl_deparse.h create mode 100644 contrib/ddl_deparse/ddl_json.c create mode 100644 contrib/ddl_deparse/hook.c diff --git a/contrib/ddl_deparse/Makefile b/contrib/ddl_deparse/Makefile new file mode 100644 index 0000000..b408779 --- /dev/null +++ b/contrib/ddl_deparse/Makefile @@ -0,0 +1,20 @@ +# contrib/ddl_deparse/Makefile + +MODULE_big = ddl_deparse +OBJS = ddl_deparse.o ddl_json.o hook.o + +EXTENSION = ddl_deparse +DATA = ddl_deparse--1.0.sql + +PGFILEDESC = "ddl_deparse - deparsing support for DDL" + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/ddl_deparse +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/ddl_deparse/ddl_deparse--1.0.sql b/contrib/ddl_deparse/ddl_deparse--1.0.sql new file mode 100644 index 0000000..4c8bac9 --- /dev/null +++ b/contrib/ddl_deparse/ddl_deparse--1.0.sql @@ -0,0 +1,9 @@ +/* contrib/ddl_deparse/ddl_deparse--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION ddl_deparse" to load this file. \quit + +CREATE FUNCTION ddl_deparse_expand_command(json) +RETURNS text +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; diff --git a/contrib/ddl_deparse/ddl_deparse.c b/contrib/ddl_deparse/ddl_deparse.c new file mode 100644 index 0000000..ddbd1f3 --- /dev/null +++ b/contrib/ddl_deparse/ddl_deparse.c @@ -0,0 +1,7168 @@ +/*------------------------------------------------------------------------- + * + * ddl_deparse.c + * Functions to convert utility commands to machine-parseable + * representation + * + * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * NOTES + * + * This is intended to provide JSON blobs representing DDL commands, which can + * later be re-processed into plain strings by well-defined sprintf-like + * expansion. These JSON objects are intended to allow for machine-editing of + * the commands, by replacing certain nodes within the objects. + * + * Much of the information in the output blob actually comes from system + * catalogs, not from the command parse node, as it is impossible to reliably + * construct a fully-specified command (i.e. one not dependent on search_path + * etc) looking only at the parse node. + * + * IDENTIFICATION + * contrib/ddl_deparse/ddl_deparse.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "ddl_deparse.h" + +#include "access/htup_details.h" +#include "access/sysattr.h" +#include "catalog/heap.h" +#include "catalog/index.h" +#include "catalog/namespace.h" +#include "catalog/opfam_internal.h" +#include "catalog/pg_aggregate.h" +#include "catalog/pg_authid.h" +#include "catalog/pg_cast.h" +#include "catalog/pg_class.h" +#include "catalog/pg_collation.h" +#include "catalog/pg_constraint.h" +#include "catalog/pg_conversion.h" +#include "catalog/pg_depend.h" +#include "catalog/pg_extension.h" +#include "catalog/pg_foreign_data_wrapper.h" +#include "catalog/pg_foreign_server.h" +#include "catalog/pg_inherits.h" +#include "catalog/pg_language.h" +#include "catalog/pg_largeobject.h" +#include "catalog/pg_namespace.h" +#include "catalog/pg_opclass.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_opfamily.h" +#include "catalog/pg_policy.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_range.h" +#include "catalog/pg_rewrite.h" +#include "catalog/pg_trigger.h" +#include "catalog/pg_ts_config.h" +#include "catalog/pg_ts_config_map.h" +#include "catalog/pg_ts_dict.h" +#include "catalog/pg_ts_parser.h" +#include "catalog/pg_ts_template.h" +#include "catalog/pg_type.h" +#include "catalog/pg_user_mapping.h" +#include "commands/defrem.h" +#include "commands/sequence.h" +#include "foreign/foreign.h" +#include "funcapi.h" +#include "lib/ilist.h" +#include "lib/stringinfo.h" +#include "mb/pg_wchar.h" +#include "nodes/makefuncs.h" +#include "nodes/parsenodes.h" +#include "parser/analyze.h" +#include "parser/parse_collate.h" +#include "parser/parse_expr.h" +#include "parser/parse_relation.h" +#include "parser/parse_type.h" +#include "rewrite/rewriteHandler.h" +#include "tcop/deparse_utility.h" +#include "tcop/utility.h" +#include "utils/acl.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/jsonb.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/rel.h" +#include "utils/ruleutils.h" +#include "utils/syscache.h" + + +/* + * Before they are turned into JSONB representation, each command is + * represented as an object tree, using the structs below. + */ +typedef enum +{ + ObjTypeNull, + ObjTypeBool, + ObjTypeString, + ObjTypeArray, + ObjTypeInteger, + ObjTypeFloat, + ObjTypeObject +} ObjType; + +typedef struct ObjTree +{ + slist_head params; + int numParams; +} ObjTree; + +typedef struct ObjElem +{ + char *name; + ObjType objtype; + + union + { + bool boolean; + char *string; + int64 integer; + float8 flt; + ObjTree *object; + List *array; + } value; + slist_node node; +} ObjElem; + +static ObjElem *new_null_object(void); +static ObjElem *new_bool_object(bool value); +static ObjElem *new_string_object(char *value); +static ObjElem *new_object_object(ObjTree *value); +static ObjElem *new_array_object(List *array); +static ObjElem *new_integer_object(int64 value); +static ObjElem *new_float_object(float8 value); +static void append_null_object(ObjTree *tree, char *name); +static void append_bool_object(ObjTree *tree, char *name, bool value); +static void append_string_object(ObjTree *tree, char *name, char *value); +static void append_object_object(ObjTree *tree, char *name, ObjTree *value); +static void append_array_object(ObjTree *tree, char *name, List *array); +static void append_integer_object(ObjTree *tree, char *name, int64 value); +static void append_float_object(ObjTree *tree, char *name, float8 value); +static inline void append_premade_object(ObjTree *tree, ObjElem *elem); +static JsonbValue *objtree_to_jsonb_rec(ObjTree *tree, JsonbParseState *state); +static const char *stringify_objtype(ObjectType objtype); + +/* + * Allocate a new object tree to store parameter values. + */ +static ObjTree * +new_objtree(void) +{ + ObjTree *params; + + params = palloc(sizeof(ObjTree)); + params->numParams = 0; + slist_init(¶ms->params); + + return params; +} + +/* + * Allocate a new object tree to store parameter values -- varargs version. + * + * The "fmt" argument is used to append as a "fmt" element in the output blob. + * numobjs indicates the number of extra elements to append; for each one, a + * name (string), type (from the ObjType enum) and value must be supplied. The + * value must match the type given; for instance, ObjTypeInteger requires an + * int64, ObjTypeString requires a char *, ObjTypeArray requires a list (of + * ObjElem), ObjTypeObject requires an ObjTree, and so on. Each element type * + * must match the conversion specifier given in the format string, as described + * in ddl_deparse_expand_command, q.v. + * + * Note we don't have the luxury of sprintf-like compiler warnings for + * malformed argument lists. + */ +static ObjTree * +new_objtree_VA(char *fmt, int numobjs,...) +{ + ObjTree *tree; + va_list args; + int i; + + /* Set up the toplevel object and its "fmt" */ + tree = new_objtree(); + append_string_object(tree, "fmt", fmt); + + /* And process the given varargs */ + va_start(args, numobjs); + for (i = 0; i < numobjs; i++) + { + char *name; + ObjType type; + ObjElem *elem; + + name = va_arg(args, char *); + type = va_arg(args, ObjType); + + /* + * For all other param types there must be a value in the varargs. + * Fetch it and add the fully formed subobject into the main object. + */ + switch (type) + { + case ObjTypeBool: + elem = new_bool_object(va_arg(args, int)); + break; + case ObjTypeString: + elem = new_string_object(va_arg(args, char *)); + break; + case ObjTypeObject: + elem = new_object_object(va_arg(args, ObjTree *)); + break; + case ObjTypeArray: + elem = new_array_object(va_arg(args, List *)); + break; + case ObjTypeInteger: + elem = new_integer_object(va_arg(args, int64)); + break; + case ObjTypeFloat: + elem = new_float_object(va_arg(args, double)); + break; + case ObjTypeNull: + /* Null params don't have a value (obviously) */ + elem = new_null_object(); + break; + default: + elog(ERROR, "invalid ObjTree element type %d", type); + } + + elem->name = name; + append_premade_object(tree, elem); + } + + va_end(args); + return tree; +} + +/* Allocate a new parameter with a NULL value */ +static ObjElem * +new_null_object(void) +{ + ObjElem *param; + + param = palloc0(sizeof(ObjElem)); + + param->name = NULL; + param->objtype = ObjTypeNull; + + return param; +} + +/* Append a NULL object to a tree */ +static void +append_null_object(ObjTree *tree, char *name) +{ + ObjElem *param; + + param = new_null_object(); + param->name = name; + append_premade_object(tree, param); +} + +/* Allocate a new boolean parameter */ +static ObjElem * +new_bool_object(bool value) +{ + ObjElem *param; + + param = palloc0(sizeof(ObjElem)); + param->name = NULL; + param->objtype = ObjTypeBool; + param->value.boolean = value; + + return param; +} + +/* Append a boolean parameter to a tree */ +static void +append_bool_object(ObjTree *tree, char *name, bool value) +{ + ObjElem *param; + + param = new_bool_object(value); + param->name = name; + append_premade_object(tree, param); +} + +/* Allocate a new string object */ +static ObjElem * +new_string_object(char *value) +{ + ObjElem *param; + + Assert(value); + + param = palloc0(sizeof(ObjElem)); + param->name = NULL; + param->objtype = ObjTypeString; + param->value.string = value; + + return param; +} + +/* + * Append a string parameter to a tree. + */ +static void +append_string_object(ObjTree *tree, char *name, char *value) +{ + ObjElem *param; + + Assert(name); + param = new_string_object(value); + param->name = name; + append_premade_object(tree, param); +} + +static ObjElem * +new_integer_object(int64 value) +{ + ObjElem *param; + + param = palloc0(sizeof(ObjElem)); + param->name = NULL; + param->objtype = ObjTypeInteger; + param->value.integer = value; + + return param; +} + +static ObjElem * +new_float_object(float8 value) +{ + ObjElem *param; + + param = palloc0(sizeof(ObjElem)); + param->name = NULL; + param->objtype = ObjTypeFloat; + param->value.flt = value; + + return param; +} + +/* + * Append an int64 parameter to a tree. + */ +static void +append_integer_object(ObjTree *tree, char *name, int64 value) +{ + ObjElem *param; + + param = new_integer_object(value); + param->name = name; + append_premade_object(tree, param); +} + +/* + * Append a float8 parameter to a tree. + */ +static void +append_float_object(ObjTree *tree, char *name, float8 value) +{ + ObjElem *param; + + param = new_float_object(value); + param->name = name; + append_premade_object(tree, param); +} + +/* Allocate a new object parameter */ +static ObjElem * +new_object_object(ObjTree *value) +{ + ObjElem *param; + + param = palloc0(sizeof(ObjElem)); + param->name = NULL; + param->objtype = ObjTypeObject; + param->value.object = value; + + return param; +} + +/* Append an object parameter to a tree */ +static void +append_object_object(ObjTree *tree, char *name, ObjTree *value) +{ + ObjElem *param; + + Assert(name); + param = new_object_object(value); + param->name = name; + append_premade_object(tree, param); +} + +/* Allocate a new array parameter */ +static ObjElem * +new_array_object(List *array) +{ + ObjElem *param; + + param = palloc0(sizeof(ObjElem)); + param->name = NULL; + param->objtype = ObjTypeArray; + param->value.array = array; + + return param; +} + +/* Append an array parameter to a tree */ +static void +append_array_object(ObjTree *tree, char *name, List *array) +{ + ObjElem *param; + + param = new_array_object(array); + param->name = name; + append_premade_object(tree, param); +} + +/* Append a preallocated parameter to a tree */ +static inline void +append_premade_object(ObjTree *tree, ObjElem *elem) +{ + slist_push_head(&tree->params, &elem->node); + tree->numParams++; +} + +/* + * Helper for objtree_to_jsonb: process an individual element from an object or + * an array into the output parse state. + */ +static void +objtree_to_jsonb_element(JsonbParseState *state, ObjElem *object, + JsonbIteratorToken elem_token) +{ + ListCell *cell; + JsonbValue val; + + switch (object->objtype) + { + case ObjTypeNull: + val.type = jbvNull; + pushJsonbValue(&state, elem_token, &val); + break; + + case ObjTypeString: + val.type = jbvString; + val.val.string.len = strlen(object->value.string); + val.val.string.val = object->value.string; + pushJsonbValue(&state, elem_token, &val); + break; + + case ObjTypeInteger: + val.type = jbvNumeric; + val.val.numeric = (Numeric) + DatumGetNumeric(DirectFunctionCall1(int8_numeric, + object->value.integer)); + pushJsonbValue(&state, elem_token, &val); + break; + + case ObjTypeFloat: + val.type = jbvNumeric; + val.val.numeric = (Numeric) + DatumGetNumeric(DirectFunctionCall1(float8_numeric, + object->value.integer)); + pushJsonbValue(&state, elem_token, &val); + break; + + case ObjTypeBool: + val.type = jbvBool; + val.val.boolean = object->value.boolean; + pushJsonbValue(&state, elem_token, &val); + break; + + case ObjTypeObject: + /* recursively add the object into the existing parse state */ + objtree_to_jsonb_rec(object->value.object, state); + break; + + case ObjTypeArray: + pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL); + foreach(cell, object->value.array) + { + ObjElem *elem = lfirst(cell); + + objtree_to_jsonb_element(state, elem, WJB_ELEM); + } + pushJsonbValue(&state, WJB_END_ARRAY, NULL); + break; + + default: + elog(ERROR, "unrecognized object type %d", object->objtype); + break; + } +} + +/* + * Recursive helper for objtree_to_jsonb + */ +static JsonbValue * +objtree_to_jsonb_rec(ObjTree *tree, JsonbParseState *state) +{ + slist_iter iter; + + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + + slist_foreach(iter, &tree->params) + { + ObjElem *object = slist_container(ObjElem, node, iter.cur); + JsonbValue key; + + /* Push the key first */ + key.type = jbvString; + key.val.string.len = strlen(object->name); + key.val.string.val = object->name; + pushJsonbValue(&state, WJB_KEY, &key); + + /* Then process the value according to its type */ + objtree_to_jsonb_element(state, object, WJB_VALUE); + } + + return pushJsonbValue(&state, WJB_END_OBJECT, NULL); +} + +/* + * Create a JSONB representation from an ObjTree. + */ +static Jsonb * +objtree_to_jsonb(ObjTree *tree) +{ + JsonbValue *value; + + value = objtree_to_jsonb_rec(tree, NULL); + return JsonbValueToJsonb(value); +} + +/* + * A helper routine to setup %{}T elements. + */ +static ObjTree * +new_objtree_for_type(Oid typeId, int32 typmod) +{ + ObjTree *typeParam; + Oid typnspid; + char *typnsp; + char *typename; + char *typmodstr; + bool typarray; + + format_type_detailed(typeId, typmod, + &typnspid, &typename, &typmodstr, &typarray); + + if (!OidIsValid(typnspid)) + typnsp = pstrdup(""); + else if (isAnyTempNamespace(typnspid)) + typnsp = pstrdup("pg_temp"); + else + typnsp = get_namespace_name(typnspid); + + /* We don't use new_objtree_VA here because types don't have a "fmt" */ + typeParam = new_objtree(); + append_string_object(typeParam, "schemaname", typnsp); + append_string_object(typeParam, "typename", typename); + append_string_object(typeParam, "typmod", typmodstr); + append_bool_object(typeParam, "typarray", typarray); + + return typeParam; +} + +/* + * A helper routine to setup %{}D and %{}O elements + * + * Elements "schemaname" and "objname" are set. If the namespace OID + * corresponds to a temp schema, that's set to "pg_temp". + * + * The difference between those two element types is whether the objname will + * be quoted as an identifier or not, which is not something that this routine + * concerns itself with; that will be up to the expand function. + */ +static ObjTree * +new_objtree_for_qualname(Oid nspid, char *name) +{ + ObjTree *qualified; + char *namespace; + + /* + * We don't use new_objtree_VA here because these names don't have a "fmt" + */ + qualified = new_objtree(); + if (isAnyTempNamespace(nspid)) + namespace = pstrdup("pg_temp"); + else + namespace = get_namespace_name(nspid); + append_string_object(qualified, "schemaname", namespace); + append_string_object(qualified, "objname", pstrdup(name)); + + return qualified; +} + +/* + * A helper routine to setup %{}D and %{}O elements, with the object specified + * by classId/objId + * + * Elements "schemaname" and "objname" are set. If the object is a temporary + * object, the schema name is set to "pg_temp". + */ +static ObjTree * +new_objtree_for_qualname_id(Oid classId, Oid objectId) +{ + ObjTree *qualified; + Relation catalog; + HeapTuple catobj; + Datum objnsp; + Datum objname; + AttrNumber Anum_name; + AttrNumber Anum_namespace; + bool isnull; + + catalog = heap_open(classId, AccessShareLock); + + catobj = get_catalog_object_by_oid(catalog, objectId); + if (!catobj) + elog(ERROR, "cache lookup failed for object %u of catalog \"%s\"", + objectId, RelationGetRelationName(catalog)); + Anum_name = get_object_attnum_name(classId); + Anum_namespace = get_object_attnum_namespace(classId); + + objnsp = heap_getattr(catobj, Anum_namespace, RelationGetDescr(catalog), + &isnull); + if (isnull) + elog(ERROR, "unexpected NULL namespace"); + objname = heap_getattr(catobj, Anum_name, RelationGetDescr(catalog), + &isnull); + if (isnull) + elog(ERROR, "unexpected NULL name"); + + qualified = new_objtree_for_qualname(DatumGetObjectId(objnsp), + NameStr(*DatumGetName(objname))); + heap_close(catalog, AccessShareLock); + + return qualified; +} + +/* + * Helper routine for %{}R objects, with role specified by OID. (ACL_ID_PUBLIC + * means to use "public"). + */ +static ObjTree * +new_objtree_for_role_id(Oid roleoid) +{ + ObjTree *role; + + role = new_objtree(); + append_bool_object(role, "is_public", roleoid == ACL_ID_PUBLIC); + + if (roleoid != ACL_ID_PUBLIC) + { + HeapTuple roltup; + char *rolename; + + roltup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleoid)); + if (!HeapTupleIsValid(roltup)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("role with OID %u does not exist", roleoid))); + + rolename = NameStr(((Form_pg_authid) GETSTRUCT(roltup))->rolname); + append_string_object(role, "rolename", pstrdup(rolename)); + ReleaseSysCache(roltup); + } + + return role; +} + +/* + * Helper routine for %{}R objects, with role specified by name. + */ +static ObjTree * +new_objtree_for_role(char *rolename) +{ + ObjTree *role; + bool is_public; + + role = new_objtree(); + is_public = strcmp(rolename, "public") == 0; + append_bool_object(role, "is_public", is_public); + if (!is_public) + append_string_object(role, "rolename", rolename); + + return role; +} + +/* + * Helper routine for %{}R objects, with role specified by RoleSpec node. + * Special values such as ROLESPEC_CURRENT_USER are expanded to their final + * names. + */ +static ObjTree * +new_objtree_for_rolespec(RoleSpec *spec) +{ + ObjTree *role; + + role = new_objtree(); + append_bool_object(role, "is_public", + spec->roletype == ROLESPEC_PUBLIC); + if (spec->roletype != ROLESPEC_PUBLIC) + append_string_object(role, "rolename", get_rolespec_name((Node *) spec)); + + return role; +} + +/* + * Return the string representation of the given RELPERSISTENCE value + */ +static char * +get_persistence_str(char persistence) +{ + switch (persistence) + { + case RELPERSISTENCE_TEMP: + return "TEMPORARY"; + case RELPERSISTENCE_UNLOGGED: + return "UNLOGGED"; + case RELPERSISTENCE_PERMANENT: + return ""; + default: + elog(ERROR, "unexpected persistence marking %c", persistence); + return ""; /* make compiler happy */ + } +} + +/* + * Form an ObjElem to be used as a single argument in an aggregate argument + * list + */ +static ObjElem * +form_agg_argument(int idx, char *modes, char **names, Oid *types) +{ + ObjTree *arg; + + arg = new_objtree_VA("%{mode}s %{name}s %{type}T", 0); + + append_string_object(arg, "mode", + (modes && modes[idx] == 'v') ? "VARIADIC" : ""); + append_string_object(arg, "name", names ? names[idx] : ""); + append_object_object(arg, "type", + new_objtree_for_type(types[idx], -1)); + + return new_object_object(arg); +} + +static ObjTree * +deparse_DefineStmt_Aggregate(Oid objectId, DefineStmt *define) +{ + HeapTuple aggTup; + HeapTuple procTup; + ObjTree *stmt; + ObjTree *tmp; + List *list; + Datum initval; + bool isnull; + Form_pg_aggregate agg; + Form_pg_proc proc; + Form_pg_operator op; + HeapTuple tup; + + aggTup = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(aggTup)) + elog(ERROR, "cache lookup failed for aggregate with OID %u", objectId); + agg = (Form_pg_aggregate) GETSTRUCT(aggTup); + + procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(agg->aggfnoid)); + if (!HeapTupleIsValid(procTup)) + elog(ERROR, "cache lookup failed for procedure with OID %u", + agg->aggfnoid); + proc = (Form_pg_proc) GETSTRUCT(procTup); + + stmt = new_objtree_VA("CREATE AGGREGATE %{identity}D(%{types}s) " + "(%{elems:, }s)", 0); + + append_object_object(stmt, "identity", + new_objtree_for_qualname(proc->pronamespace, + NameStr(proc->proname))); + + /* + * Add the argument list. There are three cases to consider: + * + * 1. no arguments, in which case the signature is (*). + * + * 2. Not an ordered-set aggregate. In this case there's one or more + * arguments. + * + * 3. Ordered-set aggregates. These have zero or more direct arguments, and + * one or more ordered arguments. + * + * We don't need to consider default values or table parameters, and the + * only mode that we need to consider is VARIADIC. + */ + + if (proc->pronargs == 0) + { + append_string_object(stmt, "agg type", "noargs"); + append_string_object(stmt, "types", "*"); + } + else if (!AGGKIND_IS_ORDERED_SET(agg->aggkind)) + { + int i; + int nargs; + Oid *types; + char *modes; + char **names; + + nargs = get_func_arg_info(procTup, &types, &names, &modes); + + /* only direct arguments in this case */ + list = NIL; + for (i = 0; i < nargs; i++) + { + list = lappend(list, form_agg_argument(i, modes, names, types)); + } + + tmp = new_objtree_VA("%{direct:, }s", 1, + "direct", ObjTypeArray, list); + append_object_object(stmt, "types", tmp); + append_string_object(stmt, "agg type", "plain"); + } + else + { + int i; + int nargs; + Oid *types; + char *modes; + char **names; + + nargs = get_func_arg_info(procTup, &types, &names, &modes); + + tmp = new_objtree_VA("%{direct:, }s ORDER BY %{aggregated:, }s", 0); + + /* direct arguments ... */ + list = NIL; + for (i = 0; i < agg->aggnumdirectargs; i++) + { + list = lappend(list, form_agg_argument(i, modes, names, types)); + } + append_array_object(tmp, "direct", list); + + /* + * ... and aggregated arguments. If the last direct argument is + * VARIADIC, we need to repeat it here rather than searching for more + * arguments. (See aggr_args production in gram.y for an explanation.) + */ + if (modes && modes[agg->aggnumdirectargs - 1] == 'v') + { + list = list_make1(form_agg_argument(agg->aggnumdirectargs - 1, + modes, names, types)); + } + else + { + list = NIL; + for (i = agg->aggnumdirectargs; i < nargs; i++) + { + list = lappend(list, form_agg_argument(i, modes, names, types)); + } + } + append_array_object(tmp, "aggregated", list); + + append_object_object(stmt, "types", tmp); + append_string_object(stmt, "agg type", "ordered-set"); + } + + /* Add the definition clause */ + list = NIL; + + /* SFUNC */ + tmp = new_objtree_VA("SFUNC=%{procedure}D", 1, + "clause", ObjTypeString, "sfunc"); + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + agg->aggtransfn)); + list = lappend(list, new_object_object(tmp)); + + /* STYPE */ + tmp = new_objtree_VA("STYPE=%{type}T", 1, + "clause", ObjTypeString, "stype"); + append_object_object(tmp, "type", + new_objtree_for_type(agg->aggtranstype, -1)); + list = lappend(list, new_object_object(tmp)); + + /* SSPACE */ + tmp = new_objtree_VA("SSPACE=%{space}n", 1, + "clause", ObjTypeString, "sspace"); + if (agg->aggtransspace != 0) + append_integer_object(tmp, "space", agg->aggtransspace); + else + append_bool_object(tmp, "present", false); + list = lappend(list, new_object_object(tmp)); + + /* FINALFUNC */ + tmp = new_objtree_VA("FINALFUNC=%{procedure}D", 1, + "clause", ObjTypeString, "finalfunc"); + if (OidIsValid(agg->aggfinalfn)) append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + agg->aggfinalfn)); + else + append_bool_object(tmp, "present", false); + list = lappend(list, new_object_object(tmp)); + + /* FINALFUNC_EXTRA */ + tmp = new_objtree_VA("FINALFUNC_EXTRA=%{value}s", 1, + "clause", ObjTypeString, "finalfunc_extra"); + if (agg->aggfinalextra) + append_string_object(tmp, "value", "true"); + else + append_string_object(tmp, "value", "false"); + list = lappend(list, new_object_object(tmp)); + + /* INITCOND */ + initval = SysCacheGetAttr(AGGFNOID, aggTup, + Anum_pg_aggregate_agginitval, + &isnull); + tmp = new_objtree_VA("INITCOND=%{initval}L", 1, + "clause", ObjTypeString, "initcond"); + if (!isnull) + append_string_object(tmp, "initval", + TextDatumGetCString(initval)); + else + append_bool_object(tmp, "present", false); + list = lappend(list, new_object_object(tmp)); + + /* MSFUNC */ + tmp = new_objtree_VA("MSFUNC=%{procedure}D", 1, + "clause", ObjTypeString, "msfunc"); + if (OidIsValid(agg->aggmtransfn)) + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + agg->aggmtransfn)); + else + append_bool_object(tmp, "present", false); + list = lappend(list, new_object_object(tmp)); + + /* MSTYPE */ + tmp = new_objtree_VA("MSTYPE=%{type}T", 1, + "clause", ObjTypeString, "mstype"); + if (OidIsValid(agg->aggmtranstype)) + append_object_object(tmp, "type", + new_objtree_for_type(agg->aggmtranstype, -1)); + else + append_bool_object(tmp, "present", false); + list = lappend(list, new_object_object(tmp)); + + /* MSSPACE */ + tmp = new_objtree_VA("MSSPACE=%{space}n", 1, + "clause", ObjTypeString, "msspace"); + if (agg->aggmtransspace != 0) + append_integer_object(tmp, "space", agg->aggmtransspace); + else + append_bool_object(tmp, "present", false); + list = lappend(list, new_object_object(tmp)); + + /* MINVFUNC */ + tmp = new_objtree_VA("MINVFUNC=%{procedure}D", 1, + "clause", ObjTypeString, "minvfunc"); + if (OidIsValid(agg->aggminvtransfn)) + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + agg->aggminvtransfn)); + else + append_bool_object(tmp, "present", false); + list = lappend(list, new_object_object(tmp)); + + /* MFINALFUNC */ + tmp = new_objtree_VA("MFINALFUNC=%{procedure}D", 1, + "clause", ObjTypeString, "mfinalfunc"); + if (OidIsValid(agg->aggmfinalfn)) + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + agg->aggmfinalfn)); + else + append_bool_object(tmp, "present", false); + list = lappend(list, new_object_object(tmp)); + + /* MFINALFUNC_EXTRA */ + tmp = new_objtree_VA("MFINALFUNC_EXTRA=%{value}s", 1, + "clause", ObjTypeString, "mfinalfunc_extra"); + if (agg->aggmfinalextra) + append_string_object(tmp, "value", "true"); + else + append_bool_object(tmp, "present", false); + list = lappend(list, new_object_object(tmp)); + + /* MINITCOND */ + initval = SysCacheGetAttr(AGGFNOID, aggTup, + Anum_pg_aggregate_aggminitval, + &isnull); + tmp = new_objtree_VA("MINITCOND=%{initval}L", 1, + "clause", ObjTypeString, "minitcond"); + if (!isnull) + append_string_object(tmp, "initval", + TextDatumGetCString(initval)); + else + append_bool_object(tmp, "present", false); + list = lappend(list, new_object_object(tmp)); + + /* HYPOTHETICAL */ + tmp = new_objtree_VA("HYPOTHETICAL=%{value}s", 1, + "clause", ObjTypeString, "hypothetical"); + if (agg->aggkind == AGGKIND_HYPOTHETICAL) + append_string_object(tmp, "value", "true"); + else + append_bool_object(tmp, "present", false); + list = lappend(list, new_object_object(tmp)); + + /* SORTOP */ + tup = SearchSysCache1(OPEROID, ObjectIdGetDatum(agg->aggsortop)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for operator with OID %u", agg->aggsortop); + op = (Form_pg_operator) GETSTRUCT(tup); + + tmp = new_objtree_VA("SORTOP=%{operator}D", 1, + "clause", ObjTypeString, "sortop"); + if (OidIsValid(agg->aggsortop)) + append_object_object(tmp, "operator", + new_objtree_for_qualname(op->oprnamespace, + NameStr(op->oprname))); + else + append_bool_object(tmp, "present", false); + list = lappend(list, new_object_object(tmp)); + + ReleaseSysCache(tup); + + /* Done with the definition clause */ + append_array_object(stmt, "elems", list); + + ReleaseSysCache(procTup); + ReleaseSysCache(aggTup); + + return stmt; +} + +static ObjTree * +deparse_DefineStmt_Collation(Oid objectId, DefineStmt *define) +{ + HeapTuple colTup; + ObjTree *stmt; + Form_pg_collation colForm; + + colTup = SearchSysCache1(COLLOID, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(colTup)) + elog(ERROR, "cache lookup failed for collation with OID %u", objectId); + colForm = (Form_pg_collation) GETSTRUCT(colTup); + + stmt = new_objtree_VA( + "CREATE COLLATION %{identity}D (LC_COLLATE = %{collate}L, LC_CTYPE = %{ctype}L)", + 0); + + append_object_object(stmt, "identity", + new_objtree_for_qualname(colForm->collnamespace, + NameStr(colForm->collname))); + append_string_object(stmt, "collate", NameStr(colForm->collcollate)); + append_string_object(stmt, "ctype", NameStr(colForm->collctype)); + + ReleaseSysCache(colTup); + + return stmt; +} + +static ObjTree * +deparse_DefineStmt_Operator(Oid objectId, DefineStmt *define) +{ + HeapTuple oprTup; + ObjTree *stmt; + ObjTree *tmp; + List *list; + Form_pg_operator oprForm; + + oprTup = SearchSysCache1(OPEROID, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(oprTup)) + elog(ERROR, "cache lookup failed for operator with OID %u", objectId); + oprForm = (Form_pg_operator) GETSTRUCT(oprTup); + + stmt = new_objtree_VA("CREATE OPERATOR %{identity}O (%{elems:, }s)", 0); + + append_object_object(stmt, "identity", + new_objtree_for_qualname(oprForm->oprnamespace, + NameStr(oprForm->oprname))); + + /* Add the definition clause */ + list = NIL; + + /* PROCEDURE */ + tmp = new_objtree_VA("PROCEDURE=%{procedure}D", 1, + "clause", ObjTypeString, "procedure"); + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + oprForm->oprcode)); + list = lappend(list, new_object_object(tmp)); + + /* LEFTARG */ + tmp = new_objtree_VA("LEFTARG=%{type}T", 1, + "clause", ObjTypeString, "leftarg"); + if (OidIsValid(oprForm->oprleft)) + append_object_object(tmp, "type", + new_objtree_for_type(oprForm->oprleft, -1)); + else + append_bool_object(tmp, "present", false); + list = lappend(list, new_object_object(tmp)); + + /* RIGHTARG */ + tmp = new_objtree_VA("RIGHTARG=%{type}T", 1, + "clause", ObjTypeString, "rightarg"); + if (OidIsValid(oprForm->oprright)) + append_object_object(tmp, "type", + new_objtree_for_type(oprForm->oprright, -1)); + append_bool_object(tmp, "present", false); + list = lappend(list, new_object_object(tmp)); + + /* COMMUTATOR */ + tmp = new_objtree_VA("COMMUTATOR=%{oper}D", 1, + "clause", ObjTypeString, "commutator"); + if (OidIsValid(oprForm->oprcom)) + append_object_object(tmp, "oper", + new_objtree_for_qualname_id(OperatorRelationId, + oprForm->oprcom)); + else + append_bool_object(tmp, "present", false); + list = lappend(list, new_object_object(tmp)); + + /* NEGATOR */ + tmp = new_objtree_VA("NEGATOR=%{oper}D", 1, + "clause", ObjTypeString, "negator"); + if (OidIsValid(oprForm->oprnegate)) + append_object_object(tmp, "oper", + new_objtree_for_qualname_id(OperatorRelationId, + oprForm->oprnegate)); + else + append_bool_object(tmp, "present", false); + list = lappend(list, new_object_object(tmp)); + + /* RESTRICT */ + tmp = new_objtree_VA("RESTRICT=%{procedure}D", 1, + "clause", ObjTypeString, "restrict"); + if (OidIsValid(oprForm->oprrest)) + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + oprForm->oprrest)); + else + append_bool_object(tmp, "present", false); + list = lappend(list, new_object_object(tmp)); + + /* JOIN */ + tmp = new_objtree_VA("JOIN=%{procedure}D", 1, + "clause", ObjTypeString, "join"); + if (OidIsValid(oprForm->oprjoin)) + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + oprForm->oprjoin)); + else + append_bool_object(tmp, "present", false); + list = lappend(list, new_object_object(tmp)); + + /* MERGES */ + tmp = new_objtree_VA("MERGES", 1, + "clause", ObjTypeString, "merges"); + if (!oprForm->oprcanmerge) + append_bool_object(tmp, "present", false); + list = lappend(list, new_object_object(tmp)); + + /* HASHES */ + tmp = new_objtree_VA("HASHES", 1, + "clause", ObjTypeString, "hashes"); + if (!oprForm->oprcanhash) + append_bool_object(tmp, "present", false); + list = lappend(list, new_object_object(tmp)); + + append_array_object(stmt, "elems", list); + + ReleaseSysCache(oprTup); + + return stmt; +} + +static ObjTree * +deparse_DefineStmt_TSConfig(Oid objectId, DefineStmt *define, + ObjectAddress copied) +{ + HeapTuple tscTup; + HeapTuple tspTup; + ObjTree *stmt; + ObjTree *tmp; + Form_pg_ts_config tscForm; + Form_pg_ts_parser tspForm; + List *list; + + tscTup = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(tscTup)) + elog(ERROR, "cache lookup failed for text search configuration %u", + objectId); + tscForm = (Form_pg_ts_config) GETSTRUCT(tscTup); + + tspTup = SearchSysCache1(TSPARSEROID, ObjectIdGetDatum(tscForm->cfgparser)); + if (!HeapTupleIsValid(tspTup)) + elog(ERROR, "cache lookup failed for text search parser %u", + tscForm->cfgparser); + tspForm = (Form_pg_ts_parser) GETSTRUCT(tspTup); + + stmt = new_objtree_VA("CREATE TEXT SEARCH CONFIGURATION %{identity}D (%{elems:, }s)", + 0); + + append_object_object(stmt, "identity", + new_objtree_for_qualname(tscForm->cfgnamespace, + NameStr(tscForm->cfgname))); + + /* + * Add the definition clause. If we have COPY'ed an existing config, add + * a COPY clause; otherwise add a PARSER clause. + */ + list = NIL; + /* COPY */ + tmp = new_objtree_VA("COPY=%{tsconfig}D", 1, + "clause", ObjTypeString, "copy"); + if (copied.objectId != InvalidOid) + append_object_object(tmp, "tsconfig", + new_objtree_for_qualname_id(TSConfigRelationId, + copied.objectId)); + else + append_bool_object(tmp, "present", false); + list = lappend(list, new_object_object(tmp)); + + /* PARSER */ + tmp = new_objtree_VA("PARSER=%{parser}D", 1, + "clause", ObjTypeString, "parser"); + if (copied.objectId == InvalidOid) + append_object_object(tmp, "parser", + new_objtree_for_qualname(tspForm->prsnamespace, + NameStr(tspForm->prsname))); + else + append_bool_object(tmp, "present", false); + list = lappend(list, new_object_object(tmp)); + + append_array_object(stmt, "elems", list); + + ReleaseSysCache(tspTup); + ReleaseSysCache(tscTup); + + return stmt; +} + +static ObjTree * +deparse_DefineStmt_TSParser(Oid objectId, DefineStmt *define) +{ + HeapTuple tspTup; + ObjTree *stmt; + ObjTree *tmp; + List *list; + Form_pg_ts_parser tspForm; + + tspTup = SearchSysCache1(TSPARSEROID, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(tspTup)) + elog(ERROR, "cache lookup failed for text search parser with OID %u", + objectId); + tspForm = (Form_pg_ts_parser) GETSTRUCT(tspTup); + + stmt = new_objtree_VA("CREATE TEXT SEARCH PARSER %{identity}D (%{elems:, }s)", + 0); + + append_object_object(stmt, "identity", + new_objtree_for_qualname(tspForm->prsnamespace, + NameStr(tspForm->prsname))); + + /* Add the definition clause */ + list = NIL; + + /* START */ + tmp = new_objtree_VA("START=%{procedure}D", 1, + "clause", ObjTypeString, "start"); + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + tspForm->prsstart)); + list = lappend(list, new_object_object(tmp)); + + /* GETTOKEN */ + tmp = new_objtree_VA("GETTOKEN=%{procedure}D", 1, + "clause", ObjTypeString, "gettoken"); + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + tspForm->prstoken)); + list = lappend(list, new_object_object(tmp)); + + /* END */ + tmp = new_objtree_VA("END=%{procedure}D", 1, + "clause", ObjTypeString, "end"); + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + tspForm->prsend)); + list = lappend(list, new_object_object(tmp)); + + /* LEXTYPES */ + tmp = new_objtree_VA("LEXTYPES=%{procedure}D", 1, + "clause", ObjTypeString, "lextypes"); + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + tspForm->prslextype)); + list = lappend(list, new_object_object(tmp)); + + /* HEADLINE */ + tmp = new_objtree_VA("HEADLINE=%{procedure}D", 1, + "clause", ObjTypeString, "headline"); + if (OidIsValid(tspForm->prsheadline)) + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + tspForm->prsheadline)); + else + append_bool_object(tmp, "present", false); + list = lappend(list, new_object_object(tmp)); + + append_array_object(stmt, "elems", list); + + ReleaseSysCache(tspTup); + + return stmt; +} + +static ObjTree * +deparse_DefineStmt_TSDictionary(Oid objectId, DefineStmt *define) +{ + HeapTuple tsdTup; + ObjTree *stmt; + ObjTree *tmp; + List *list; + Datum options; + bool isnull; + Form_pg_ts_dict tsdForm; + + tsdTup = SearchSysCache1(TSDICTOID, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(tsdTup)) + elog(ERROR, "cache lookup failed for text search dictionary " + "with OID %u", objectId); + tsdForm = (Form_pg_ts_dict) GETSTRUCT(tsdTup); + + stmt = new_objtree_VA("CREATE TEXT SEARCH DICTIONARY %{identity}D " + "(%{elems:, }s)", 0); + + append_object_object(stmt, "identity", + new_objtree_for_qualname(tsdForm->dictnamespace, + NameStr(tsdForm->dictname))); + + /* Add the definition clause */ + list = NIL; + + /* TEMPLATE */ + tmp = new_objtree_VA("TEMPLATE=%{template}D", 1, + "clause", ObjTypeString, "template"); + append_object_object(tmp, "template", + new_objtree_for_qualname_id(TSTemplateRelationId, + tsdForm->dicttemplate)); + list = lappend(list, new_object_object(tmp)); + + /* + * options. XXX this is a pretty useless representation, but we can't do + * better without more ts_cache.c cooperation ... + */ + options = SysCacheGetAttr(TSDICTOID, tsdTup, + Anum_pg_ts_dict_dictinitoption, + &isnull); + tmp = new_objtree_VA("%{options}s", 0); + if (!isnull) + append_string_object(tmp, "options", TextDatumGetCString(options)); + else + append_bool_object(tmp, "present", false); + list = lappend(list, new_object_object(tmp)); + + append_array_object(stmt, "elems", list); + + ReleaseSysCache(tsdTup); + + return stmt; +} + +static ObjTree * +deparse_DefineStmt_TSTemplate(Oid objectId, DefineStmt *define) +{ + HeapTuple tstTup; + ObjTree *stmt; + ObjTree *tmp; + List *list; + Form_pg_ts_template tstForm; + + tstTup = SearchSysCache1(TSTEMPLATEOID, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(tstTup)) + elog(ERROR, "cache lookup failed for text search template with OID %u", + objectId); + tstForm = (Form_pg_ts_template) GETSTRUCT(tstTup); + + stmt = new_objtree_VA("CREATE TEXT SEARCH TEMPLATE %{identity}D " + "(%{elems:, }s)", 0); + + append_object_object(stmt, "identity", + new_objtree_for_qualname(tstForm->tmplnamespace, + NameStr(tstForm->tmplname))); + + /* Add the definition clause */ + list = NIL; + + /* INIT */ + tmp = new_objtree_VA("INIT=%{procedure}D", 1, + "clause", ObjTypeString, "init"); + if (OidIsValid(tstForm->tmplinit)) + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + tstForm->tmplinit)); + else + append_bool_object(tmp, "present", false); + list = lappend(list, new_object_object(tmp)); + + /* LEXIZE */ + tmp = new_objtree_VA("LEXIZE=%{procedure}D", 1, + "clause", ObjTypeString, "lexize"); + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + tstForm->tmpllexize)); + list = lappend(list, new_object_object(tmp)); + + append_array_object(stmt, "elems", list); + + ReleaseSysCache(tstTup); + + return stmt; +} + +static ObjTree * +deparse_DefineStmt_Type(Oid objectId, DefineStmt *define) +{ + HeapTuple typTup; + ObjTree *stmt; + ObjTree *tmp; + List *list; + char *str; + Datum dflt; + bool isnull; + Form_pg_type typForm; + + typTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(typTup)) + elog(ERROR, "cache lookup failed for type with OID %u", objectId); + typForm = (Form_pg_type) GETSTRUCT(typTup); + + /* Shortcut processing for shell types. */ + if (!typForm->typisdefined) + { + stmt = new_objtree_VA("CREATE TYPE %{identity}D", 1, + "is_shell", ObjTypeBool, true); + append_object_object(stmt, "identity", + new_objtree_for_qualname(typForm->typnamespace, + NameStr(typForm->typname))); + ReleaseSysCache(typTup); + return stmt; + } + + stmt = new_objtree_VA("CREATE TYPE %{identity}D (%{elems:, }s)", 1, + "is_shell", ObjTypeBool, false); + + append_object_object(stmt, "identity", + new_objtree_for_qualname(typForm->typnamespace, + NameStr(typForm->typname))); + + /* Add the definition clause */ + list = NIL; + + /* INPUT */ + tmp = new_objtree_VA("INPUT=%{procedure}D", 1, + "clause", ObjTypeString, "input"); + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + typForm->typinput)); + list = lappend(list, new_object_object(tmp)); + + /* OUTPUT */ + tmp = new_objtree_VA("OUTPUT=%{procedure}D", 1, + "clause", ObjTypeString, "output"); + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + typForm->typoutput)); + list = lappend(list, new_object_object(tmp)); + + /* RECEIVE */ + tmp = new_objtree_VA("RECEIVE=%{procedure}D", 1, + "clause", ObjTypeString, "receive"); + if (OidIsValid(typForm->typreceive)) + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + typForm->typreceive)); + else + append_bool_object(tmp, "present", false); + list = lappend(list, new_object_object(tmp)); + + /* SEND */ + tmp = new_objtree_VA("SEND=%{procedure}D", 1, + "clause", ObjTypeString, "send"); + if (OidIsValid(typForm->typsend)) + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + typForm->typsend)); + else + append_bool_object(tmp, "present", false); + list = lappend(list, new_object_object(tmp)); + + /* TYPMOD_IN */ + tmp = new_objtree_VA("TYPMOD_IN=%{procedure}D", 1, + "clause", ObjTypeString, "typmod_in"); + if (OidIsValid(typForm->typmodin)) + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + typForm->typmodin)); + else + append_bool_object(tmp, "present", false); + list = lappend(list, new_object_object(tmp)); + + /* TYPMOD_OUT */ + tmp = new_objtree_VA("TYPMOD_OUT=%{procedure}D", 1, + "clause", ObjTypeString, "typmod_out"); + if (OidIsValid(typForm->typmodout)) + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + typForm->typmodout)); + else + append_bool_object(tmp, "present", false); + list = lappend(list, new_object_object(tmp)); + + /* ANALYZE */ + tmp = new_objtree_VA("ANALYZE=%{procedure}D", 1, + "clause", ObjTypeString, "analyze"); + if (OidIsValid(typForm->typanalyze)) + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + typForm->typanalyze)); + else + append_bool_object(tmp, "present", false); + list = lappend(list, new_object_object(tmp)); + + /* INTERNALLENGTH */ + if (typForm->typlen == -1) + { + tmp = new_objtree_VA("INTERNALLENGTH=VARIABLE", 0); + } + else + { + tmp = new_objtree_VA("INTERNALLENGTH=%{typlen}n", 1, + "typlen", ObjTypeInteger, typForm->typlen); + } + append_string_object(tmp, "clause", "internallength"); + list = lappend(list, new_object_object(tmp)); + + /* PASSEDBYVALUE */ + tmp = new_objtree_VA("PASSEDBYVALUE", 1, + "clause", ObjTypeString, "passedbyvalue"); + if (!typForm->typbyval) + append_bool_object(tmp, "present", false); + list = lappend(list, new_object_object(tmp)); + + /* ALIGNMENT */ + tmp = new_objtree_VA("ALIGNMENT=%{align}s", 1, + "clause", ObjTypeString, "alignment"); + /* XXX it's odd to represent alignment with schema-qualified type names */ + switch (typForm->typalign) + { + case 'd': + str = "pg_catalog.float8"; + break; + case 'i': + str = "pg_catalog.int4"; + break; + case 's': + str = "pg_catalog.int2"; + break; + case 'c': + str = "pg_catalog.bpchar"; + break; + default: + elog(ERROR, "invalid alignment %c", typForm->typalign); + } + append_string_object(tmp, "align", str); + list = lappend(list, new_object_object(tmp)); + + tmp = new_objtree_VA("STORAGE=%{storage}s", 1, + "clause", ObjTypeString, "storage"); + switch (typForm->typstorage) + { + case 'p': + str = "plain"; + break; + case 'e': + str = "external"; + break; + case 'x': + str = "extended"; + break; + case 'm': + str = "main"; + break; + default: + elog(ERROR, "invalid storage specifier %c", typForm->typstorage); + } + append_string_object(tmp, "storage", str); + list = lappend(list, new_object_object(tmp)); + + /* CATEGORY */ + tmp = new_objtree_VA("CATEGORY=%{category}L", 1, + "clause", ObjTypeString, "category"); + append_string_object(tmp, "category", + psprintf("%c", typForm->typcategory)); + list = lappend(list, new_object_object(tmp)); + + /* PREFERRED */ + tmp = new_objtree_VA("PREFERRED=true", 1, + "clause", ObjTypeString, "preferred"); + if (!typForm->typispreferred) + append_bool_object(tmp, "present", false); + list = lappend(list, new_object_object(tmp)); + + /* DEFAULT */ + dflt = SysCacheGetAttr(TYPEOID, typTup, + Anum_pg_type_typdefault, + &isnull); + tmp = new_objtree_VA("DEFAULT=%{default}L", 1, + "clause", ObjTypeString, "default"); + if (!isnull) + append_string_object(tmp, "default", TextDatumGetCString(dflt)); + else + append_bool_object(tmp, "present", false); + list = lappend(list, new_object_object(tmp)); + + /* ELEMENT */ + tmp = new_objtree_VA("ELEMENT=%{elem}T", 1, + "clause", ObjTypeString, "element"); + if (OidIsValid(typForm->typelem)) + append_object_object(tmp, "elem", + new_objtree_for_type(typForm->typelem, -1)); + else + append_bool_object(tmp, "present", false); + list = lappend(list, new_object_object(tmp)); + + /* DELIMITER */ + tmp = new_objtree_VA("DELIMITER=%{delim}L", 1, + "clause", ObjTypeString, "delimiter"); + append_string_object(tmp, "delim", + psprintf("%c", typForm->typdelim)); + list = lappend(list, new_object_object(tmp)); + + /* COLLATABLE */ + tmp = new_objtree_VA("COLLATABLE=true", 1, + "clause", ObjTypeString, "collatable"); + if (!OidIsValid(typForm->typcollation)) + append_bool_object(tmp, "present", false); + list = lappend(list, new_object_object(tmp)); + + append_array_object(stmt, "elems", list); + + ReleaseSysCache(typTup); + + return stmt; +} + +static ObjTree * +deparse_DefineStmt(Oid objectId, Node *parsetree, ObjectAddress secondaryObj) +{ + DefineStmt *define = (DefineStmt *) parsetree; + ObjTree *defStmt; + + switch (define->kind) + { + case OBJECT_AGGREGATE: + defStmt = deparse_DefineStmt_Aggregate(objectId, define); + break; + + case OBJECT_COLLATION: + defStmt = deparse_DefineStmt_Collation(objectId, define); + break; + + case OBJECT_OPERATOR: + defStmt = deparse_DefineStmt_Operator(objectId, define); + break; + + case OBJECT_TSCONFIGURATION: + defStmt = deparse_DefineStmt_TSConfig(objectId, define, + secondaryObj); + break; + + case OBJECT_TSPARSER: + defStmt = deparse_DefineStmt_TSParser(objectId, define); + break; + + case OBJECT_TSDICTIONARY: + defStmt = deparse_DefineStmt_TSDictionary(objectId, define); + break; + + case OBJECT_TSTEMPLATE: + defStmt = deparse_DefineStmt_TSTemplate(objectId, define); + break; + + case OBJECT_TYPE: + defStmt = deparse_DefineStmt_Type(objectId, define); + break; + + default: + elog(ERROR, "unsupported object kind"); + return NULL; + } + + return defStmt; +} + +static ObjTree * +deparse_AlterTSConfigurationStmt(CollectedCommand *cmd) +{ + AlterTSConfigurationStmt *node = (AlterTSConfigurationStmt *) cmd->parsetree; + ObjTree *config; + ObjTree *tmp; + const char *const fmtcommon = "ALTER TEXT SEARCH CONFIGURATION %{identity}D"; + char *fmtrest; + char *type; + List *list; + ListCell *cell; + int i; + + /* determine the format string appropriate to each subcommand */ + switch (node->kind) + { + case ALTER_TSCONFIG_ADD_MAPPING: + fmtrest = "ADD MAPPING FOR %{tokentype:, }I WITH %{dictionaries:, }D"; + type = "add mapping for token"; + break; + + case ALTER_TSCONFIG_DROP_MAPPING: + fmtrest = "DROP MAPPING %{if_exists}s FOR %{tokentype}I"; + type = "drop mapping for token"; + break; + + case ALTER_TSCONFIG_ALTER_MAPPING_FOR_TOKEN: + fmtrest = "ALTER MAPPING FOR %{tokentype:, }I WITH %{dictionaries:, }D"; + type = "alter mapping for token"; + break; + + case ALTER_TSCONFIG_REPLACE_DICT: + fmtrest = "ALTER MAPPING REPLACE %{old_dictionary}D WITH %{new_dictionary}D"; + type = "alter mapping replace"; + break; + + case ALTER_TSCONFIG_REPLACE_DICT_FOR_TOKEN: + fmtrest = "ALTER MAPPING FOR %{tokentype:, }I REPLACE %{old_dictionary}D WITH %{new_dictionary}D"; + type = "alter mapping replace for token"; + break; + } + config = new_objtree_VA(psprintf("%s %s", fmtcommon, fmtrest), + 2, "identity", + ObjTypeObject, + new_objtree_for_qualname_id(cmd->d.atscfg.address.classId, + cmd->d.atscfg.address.objectId), + "type", ObjTypeString, type); + + /* Add the affected token list, for subcommands that have one */ + if (node->kind == ALTER_TSCONFIG_ADD_MAPPING || + node->kind == ALTER_TSCONFIG_ALTER_MAPPING_FOR_TOKEN || + node->kind == ALTER_TSCONFIG_REPLACE_DICT_FOR_TOKEN || + node->kind == ALTER_TSCONFIG_DROP_MAPPING) + { + list = NIL; + foreach(cell, node->tokentype) + list = lappend(list, new_string_object(strVal(lfirst(cell)))); + append_array_object(config, "tokentype", list); + } + + /* add further subcommand-specific elements */ + if (node->kind == ALTER_TSCONFIG_ADD_MAPPING || + node->kind == ALTER_TSCONFIG_ALTER_MAPPING_FOR_TOKEN) + { + /* ADD MAPPING and ALTER MAPPING FOR need a list of dictionaries */ + list = NIL; + for (i = 0; i < cmd->d.atscfg.ndicts; i++) + { + ObjTree *dictobj; + + dictobj = new_objtree_for_qualname_id(TSDictionaryRelationId, + cmd->d.atscfg.dictIds[i]); + list = lappend(list, + new_object_object(dictobj)); + } + append_array_object(config, "dictionaries", list); + } + else if (node->kind == ALTER_TSCONFIG_REPLACE_DICT || + node->kind == ALTER_TSCONFIG_REPLACE_DICT_FOR_TOKEN) + { + /* the REPLACE forms want old and new dictionaries */ + Assert(cmd->d.atscfg.ndicts == 2); + append_object_object(config, "old_dictionary", + new_objtree_for_qualname_id(TSDictionaryRelationId, + cmd->d.atscfg.dictIds[0])); + append_object_object(config, "new_dictionary", + new_objtree_for_qualname_id(TSDictionaryRelationId, + cmd->d.atscfg.dictIds[1])); + } + else + { + /* DROP wants the IF EXISTS clause */ + Assert(node->kind == ALTER_TSCONFIG_DROP_MAPPING); + tmp = new_objtree_VA("IF EXISTS", 0); + append_bool_object(tmp, "present", node->missing_ok); + append_object_object(config, "if_exists", tmp); + } + + return config; +} + +/* + * deparse_CreateExtensionStmt + * deparse a CreateExtensionStmt + * + * Given an extension OID and the parsetree that created it, return the JSON + * blob representing the creation command. + * + * XXX the current representation makes the output command dependant on the + * installed versions of the extension. Is this a problem? + */ +static ObjTree * +deparse_CreateExtensionStmt(Oid objectId, Node *parsetree) +{ + CreateExtensionStmt *node = (CreateExtensionStmt *) parsetree; + Relation pg_extension; + HeapTuple extTup; + Form_pg_extension extForm; + ObjTree *extStmt; + ObjTree *tmp; + List *list; + ListCell *cell; + + pg_extension = heap_open(ExtensionRelationId, AccessShareLock); + extTup = get_catalog_object_by_oid(pg_extension, objectId); + if (!HeapTupleIsValid(extTup)) + elog(ERROR, "cache lookup failed for extension with OID %u", + objectId); + extForm = (Form_pg_extension) GETSTRUCT(extTup); + + extStmt = new_objtree_VA("CREATE EXTENSION %{if_not_exists}s %{identity}I " + "%{options: }s", + 1, "identity", ObjTypeString, node->extname); + + /* IF NOT EXISTS */ + tmp = new_objtree_VA("IF NOT EXISTS", 0); + append_bool_object(tmp, "present", node->if_not_exists); + append_object_object(extStmt, "if_not_exists", tmp); + + /* List of options */ + list = NIL; + foreach(cell, node->options) + { + DefElem *opt = (DefElem *) lfirst(cell); + + if (strcmp(opt->defname, "schema") == 0) + { + /* skip this one; we add one unconditionally below */ + continue; + } + else if (strcmp(opt->defname, "new_version") == 0) + { + tmp = new_objtree_VA("VERSION %{version}L", 2, + "type", ObjTypeString, "version", + "version", ObjTypeString, defGetString(opt)); + list = lappend(list, new_object_object(tmp)); + } + else if (strcmp(opt->defname, "old_version") == 0) + { + tmp = new_objtree_VA("FROM %{version}L", 2, + "type", ObjTypeString, "from", + "version", ObjTypeString, defGetString(opt)); + list = lappend(list, new_object_object(tmp)); + } + else + elog(ERROR, "unsupported option %s", opt->defname); + } + + /* Add the SCHEMA option */ + tmp = new_objtree_VA("SCHEMA %{schema}I", + 2, "type", ObjTypeString, "schema", + "schema", ObjTypeString, + get_namespace_name(extForm->extnamespace)); + list = lappend(list, new_object_object(tmp)); + + /* done */ + append_array_object(extStmt, "options", list); + + heap_close(pg_extension, AccessShareLock); + + return extStmt; +} + +static ObjTree * +deparse_AlterExtensionStmt(Oid objectId, Node *parsetree) +{ + AlterExtensionStmt *node = (AlterExtensionStmt *) parsetree; + Relation pg_extension; + HeapTuple extTup; + Form_pg_extension extForm; + ObjTree *stmt; + ObjTree *tmp; + List *list = NIL; + ListCell *cell; + + pg_extension = heap_open(ExtensionRelationId, AccessShareLock); + extTup = get_catalog_object_by_oid(pg_extension, objectId); + if (!HeapTupleIsValid(extTup)) + elog(ERROR, "cache lookup failed for extension with OID %u", + objectId); + extForm = (Form_pg_extension) GETSTRUCT(extTup); + + stmt = new_objtree_VA("ALTER EXTENSION %{identity}I UPDATE %{options: }s", 1, + "identity", ObjTypeString, + NameStr(extForm->extname)); + + foreach(cell, node->options) + { + DefElem *opt = (DefElem *) lfirst(cell); + + if (strcmp(opt->defname, "new_version") == 0) + { + tmp = new_objtree_VA("TO %{version}L", 2, + "type", ObjTypeString, "version", + "version", ObjTypeString, defGetString(opt)); + list = lappend(list, new_object_object(tmp)); + } + else + elog(ERROR, "unsupported option %s", opt->defname); + } + + append_array_object(stmt, "options", list); + + heap_close(pg_extension, AccessShareLock); + + return stmt; +} + +/* + * ALTER EXTENSION ext ADD/DROP object + */ +static ObjTree * +deparse_AlterExtensionContentsStmt(Oid objectId, Node *parsetree, + ObjectAddress objectAddress) +{ + AlterExtensionContentsStmt *node = (AlterExtensionContentsStmt *) parsetree; + ObjTree *stmt; + char *fmt; + + Assert(node->action == +1 || node->action == -1); + + fmt = psprintf("ALTER EXTENSION %%{extidentity}I %s %s %%{objidentity}s", + node->action == +1 ? "ADD" : "DROP", + stringify_objtype(node->objtype)); + + stmt = new_objtree_VA(fmt, 2, "extidentity", ObjTypeString, node->extname, + "objidentity", ObjTypeString, + getObjectIdentity(&objectAddress)); + + return stmt; +} + +static ObjTree * +deparse_AlterDomainStmt(Oid objectId, Node *parsetree, + ObjectAddress constraintAddr) +{ + AlterDomainStmt *node = (AlterDomainStmt *) parsetree; + HeapTuple domTup; + Form_pg_type domForm; + ObjTree *alterDom; + char *fmt; + const char *type; + + /* ALTER DOMAIN DROP CONSTRAINT is handled by the DROP support code */ + if (node->subtype == 'X') + return NULL; + + domTup = SearchSysCache1(TYPEOID, objectId); + if (!HeapTupleIsValid(domTup)) + elog(ERROR, "cache lookup failed for domain with OID %u", + objectId); + domForm = (Form_pg_type) GETSTRUCT(domTup); + + switch (node->subtype) + { + case 'T': + /* SET DEFAULT / DROP DEFAULT */ + if (node->def == NULL) + { + fmt = "ALTER DOMAIN %{identity}D DROP DEFAULT"; + type = "drop default"; + } + else + { + fmt = "ALTER DOMAIN %{identity}D SET DEFAULT %{default}s"; + type = "set default"; + } + break; + case 'N': + /* DROP NOT NULL */ + fmt = "ALTER DOMAIN %{identity}D DROP NOT NULL"; + type = "drop not null"; + break; + case 'O': + /* SET NOT NULL */ + fmt = "ALTER DOMAIN %{identity}D SET NOT NULL"; + type = "set not null"; + break; + case 'C': + /* ADD CONSTRAINT. Only CHECK constraints are supported by domains */ + fmt = "ALTER DOMAIN %{identity}D ADD CONSTRAINT %{constraint_name}I %{definition}s"; + type = "add constraint"; + break; + case 'V': + /* VALIDATE CONSTRAINT */ + fmt = "ALTER DOMAIN %{identity}D VALIDATE CONSTRAINT %{constraint_name}I"; + type = "validate constraint"; + break; + default: + elog(ERROR, "invalid subtype %c", node->subtype); + } + + alterDom = new_objtree_VA(fmt, 1, "type", ObjTypeString, type); + append_object_object(alterDom, "identity", + new_objtree_for_qualname(domForm->typnamespace, + NameStr(domForm->typname))); + + /* + * Process subtype-specific options. Validating a constraint + * requires its name ... + */ + if (node->subtype == 'V') + append_string_object(alterDom, "constraint_name", node->name); + + /* ... a new constraint has a name and definition ... */ + if (node->subtype == 'C') + { + append_string_object(alterDom, "definition", + pg_get_constraintdef_string(constraintAddr.objectId, + false)); + /* can't rely on node->name here; might not be defined */ + append_string_object(alterDom, "constraint_name", + get_constraint_name(constraintAddr.objectId)); + } + + /* ... and setting a default has a definition only. */ + if (node->subtype == 'T' && node->def != NULL) + append_string_object(alterDom, "default", DomainGetDefault(domTup)); + + /* done */ + ReleaseSysCache(domTup); + + return alterDom; +} + +/* + * If a column name is specified, add an element for it; otherwise it's a + * table-level option. + */ +static ObjTree * +deparse_FdwOptions(List *options, char *colname) +{ + ObjTree *tmp; + + if (colname) + tmp = new_objtree_VA("ALTER COLUMN %{column}I OPTIONS (%{option:, }s)", + 1, "column", ObjTypeString, colname); + else + tmp = new_objtree_VA("OPTIONS (%{option:, }s)", 0); + + if (options != NIL) + { + List *optout = NIL; + ListCell *cell; + + foreach(cell, options) + { + DefElem *elem; + ObjTree *opt; + + elem = (DefElem *) lfirst(cell); + + switch (elem->defaction) + { + case DEFELEM_UNSPEC: + opt = new_objtree_VA("%{label}I %{value}L", 0); + break; + case DEFELEM_SET: + opt = new_objtree_VA("SET %{label}I %{value}L", 0); + break; + case DEFELEM_ADD: + opt = new_objtree_VA("ADD %{label}I %{value}L", 0); + break; + case DEFELEM_DROP: + opt = new_objtree_VA("DROP %{label}I", 0); + break; + default: + elog(ERROR, "invalid def action %d", elem->defaction); + opt = NULL; + } + + append_string_object(opt, "label", elem->defname); + append_string_object(opt, "value", + elem->arg ? defGetString(elem) : + defGetBoolean(elem) ? "TRUE" : "FALSE"); + + optout = lappend(optout, new_object_object(opt)); + } + + append_array_object(tmp, "option", optout); + } + else + append_bool_object(tmp, "present", false); + + return tmp; +} + +static ObjTree * +deparse_CreateFdwStmt(Oid objectId, Node *parsetree) +{ + CreateFdwStmt *node = (CreateFdwStmt *) parsetree; + HeapTuple fdwTup; + Form_pg_foreign_data_wrapper fdwForm; + Relation rel; + + ObjTree *createStmt; + ObjTree *tmp; + + rel = heap_open(ForeignDataWrapperRelationId, RowExclusiveLock); + + fdwTup = SearchSysCache1(FOREIGNDATAWRAPPEROID, + ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(fdwTup)) + elog(ERROR, "cache lookup failed for foreign-data wrapper %u", objectId); + + fdwForm = (Form_pg_foreign_data_wrapper) GETSTRUCT(fdwTup); + + createStmt = new_objtree_VA("CREATE FOREIGN DATA WRAPPER %{identity}I" + " %{handler}s %{validator}s %{generic_options}s", 1, + "identity", ObjTypeString, NameStr(fdwForm->fdwname)); + + /* add HANDLER clause */ + if (fdwForm->fdwhandler == InvalidOid) + tmp = new_objtree_VA("NO HANDLER", 0); + else + { + tmp = new_objtree_VA("HANDLER %{procedure}D", 0); + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + fdwForm->fdwhandler)); + } + append_object_object(createStmt, "handler", tmp); + + /* add VALIDATOR clause */ + if (fdwForm->fdwvalidator == InvalidOid) + tmp = new_objtree_VA("NO VALIDATOR", 0); + else + { + tmp = new_objtree_VA("VALIDATOR %{procedure}D", 0); + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + fdwForm->fdwvalidator)); + } + append_object_object(createStmt, "validator", tmp); + + /* add an OPTIONS clause, if any */ + append_object_object(createStmt, "generic_options", + deparse_FdwOptions(node->options, NULL)); + + ReleaseSysCache(fdwTup); + heap_close(rel, RowExclusiveLock); + + return createStmt; +} + +static ObjTree * +deparse_AlterFdwStmt(Oid objectId, Node *parsetree) +{ + AlterFdwStmt *node = (AlterFdwStmt *) parsetree; + HeapTuple fdwTup; + Form_pg_foreign_data_wrapper fdwForm; + Relation rel; + ObjTree *alterStmt; + List *fdw_options = NIL; + ListCell *cell; + + rel = heap_open(ForeignDataWrapperRelationId, RowExclusiveLock); + + fdwTup = SearchSysCache1(FOREIGNDATAWRAPPEROID, + ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(fdwTup)) + elog(ERROR, "cache lookup failed for foreign-data wrapper %u", objectId); + + fdwForm = (Form_pg_foreign_data_wrapper) GETSTRUCT(fdwTup); + + alterStmt = new_objtree_VA("ALTER FOREIGN DATA WRAPPER %{identity}I" + " %{fdw_options: }s %{generic_options}s", 1, + "identity", ObjTypeString, NameStr(fdwForm->fdwname)); + + /* + * Iterate through options, to see what changed, but use catalog as basis + * for new values. + */ + foreach(cell, node->func_options) + { + DefElem *elem; + ObjTree *tmp; + + elem = lfirst(cell); + + if (pg_strcasecmp(elem->defname, "handler") == 0) + { + /* add HANDLER clause */ + if (fdwForm->fdwhandler == InvalidOid) + tmp = new_objtree_VA("NO HANDLER", 0); + else + { + tmp = new_objtree_VA("HANDLER %{procedure}D", 0); + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + fdwForm->fdwhandler)); + } + fdw_options = lappend(fdw_options, new_object_object(tmp)); + } + else if (pg_strcasecmp(elem->defname, "validator") == 0) + { + /* add VALIDATOR clause */ + if (fdwForm->fdwvalidator == InvalidOid) + tmp = new_objtree_VA("NO VALIDATOR", 0); + else + { + tmp = new_objtree_VA("VALIDATOR %{procedure}D", 0); + append_object_object(tmp, "procedure", + new_objtree_for_qualname_id(ProcedureRelationId, + fdwForm->fdwvalidator)); + } + fdw_options = lappend(fdw_options, new_object_object(tmp)); + } + } + + /* Add HANDLER/VALIDATOR if specified */ + append_array_object(alterStmt, "fdw_options", fdw_options); + + + /* add an OPTIONS clause, if any */ + append_object_object(alterStmt, "generic_options", + deparse_FdwOptions(node->options, NULL)); + + ReleaseSysCache(fdwTup); + heap_close(rel, RowExclusiveLock); + + return alterStmt; +} + +static ObjTree * +deparse_CreateForeignServerStmt(Oid objectId, Node *parsetree) +{ + CreateForeignServerStmt *node = (CreateForeignServerStmt *) parsetree; + ObjTree *createServer; + ObjTree *tmp; + + createServer = new_objtree_VA("CREATE SERVER %{identity}I %{type}s %{version}s " + "FOREIGN DATA WRAPPER %{fdw}I %{generic_options}s", 2, + "identity", ObjTypeString, node->servername, + "fdw", ObjTypeString, node->fdwname); + + /* add a TYPE clause, if any */ + tmp = new_objtree_VA("TYPE %{type}L", 0); + if (node->servertype) + append_string_object(tmp, "type", node->servertype); + else + append_bool_object(tmp, "present", false); + append_object_object(createServer, "type", tmp); + + /* add a VERSION clause, if any */ + tmp = new_objtree_VA("VERSION %{version}L", 0); + if (node->version) + append_string_object(tmp, "version", node->version); + else + append_bool_object(tmp, "present", false); + append_object_object(createServer, "version", tmp); + + /* add an OPTIONS clause, if any */ + append_object_object(createServer, "generic_options", + deparse_FdwOptions(node->options, NULL)); + + return createServer; +} + +static ObjTree * +deparse_AlterForeignServerStmt(Oid objectId, Node *parsetree) +{ + AlterForeignServerStmt *node = (AlterForeignServerStmt *) parsetree; + ObjTree *alterServer; + ObjTree *tmp; + + alterServer = new_objtree_VA("ALTER SERVER %{identity}I %{version}s " + "%{generic_options}s", 1, + "identity", ObjTypeString, node->servername); + + /* add a VERSION clause, if any */ + tmp = new_objtree_VA("VERSION %{version}s", 0); + if (node->has_version && node->version) + append_string_object(tmp, "version", quote_literal_cstr(node->version)); + else if (node->has_version) + append_string_object(tmp, "version", "NULL"); + else + append_bool_object(tmp, "present", false); + append_object_object(alterServer, "version", tmp); + + /* add an OPTIONS clause, if any */ + append_object_object(alterServer, "generic_options", + deparse_FdwOptions(node->options, NULL)); + + return alterServer; +} + +static ObjTree * +deparse_CreateUserMappingStmt(Oid objectId, Node *parsetree) +{ + CreateUserMappingStmt *node = (CreateUserMappingStmt *) parsetree; + ObjTree *createStmt; + Relation rel; + HeapTuple tp; + Form_pg_user_mapping form; + ForeignServer *server; + + rel = heap_open(UserMappingRelationId, RowExclusiveLock); + + /* + * Lookup up object in the catalog, so we don't have to deal with + * current_user and such. + */ + tp = SearchSysCache1(USERMAPPINGOID, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for user mapping %u", objectId); + + form = (Form_pg_user_mapping) GETSTRUCT(tp); + + server = GetForeignServer(form->umserver); + + createStmt = new_objtree_VA("CREATE USER MAPPING FOR %{role}R SERVER %{server}I " + "%{generic_options}s", 1, + "server", ObjTypeString, server->servername); + + append_object_object(createStmt, "role", new_objtree_for_role_id(form->umuser)); + + /* add an OPTIONS clause, if any */ + append_object_object(createStmt, "generic_options", + deparse_FdwOptions(node->options, NULL)); + + ReleaseSysCache(tp); + heap_close(rel, RowExclusiveLock); + return createStmt; +} + +static ObjTree * +deparse_AlterUserMappingStmt(Oid objectId, Node *parsetree) +{ + AlterUserMappingStmt *node = (AlterUserMappingStmt *) parsetree; + ObjTree *alterStmt; + Relation rel; + HeapTuple tp; + Form_pg_user_mapping form; + ForeignServer *server; + + rel = heap_open(UserMappingRelationId, RowExclusiveLock); + + /* + * Lookup up object in the catalog, so we don't have to deal with + * current_user and such. + */ + + tp = SearchSysCache1(USERMAPPINGOID, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for user mapping %u", objectId); + + form = (Form_pg_user_mapping) GETSTRUCT(tp); + + server = GetForeignServer(form->umserver); + + alterStmt = new_objtree_VA("ALTER USER MAPPING FOR %{role}R SERVER %{server}I " + "%{generic_options}s", 1, + "server", ObjTypeString, server->servername); + + append_object_object(alterStmt, "role", new_objtree_for_role_id(form->umuser)); + + /* add an OPTIONS clause, if any */ + append_object_object(alterStmt, "generic_options", + deparse_FdwOptions(node->options, NULL)); + + ReleaseSysCache(tp); + heap_close(rel, RowExclusiveLock); + return alterStmt; +} + +/* + * deparse_ViewStmt + * deparse a ViewStmt + * + * Given a view OID and the parsetree that created it, return an ObjTree + * representing the creation command. + */ +static ObjTree * +deparse_ViewStmt(Oid objectId, Node *parsetree) +{ + ViewStmt *node = (ViewStmt *) parsetree; + ObjTree *viewStmt; + ObjTree *tmp; + Relation relation; + + relation = relation_open(objectId, AccessShareLock); + + viewStmt = new_objtree_VA("CREATE %{or_replace}s %{persistence}s VIEW %{identity}D AS %{query}s", + 2, + "or_replace", ObjTypeString, + node->replace ? "OR REPLACE" : "", + "persistence", ObjTypeString, + get_persistence_str(relation->rd_rel->relpersistence)); + + tmp = new_objtree_for_qualname(relation->rd_rel->relnamespace, + RelationGetRelationName(relation)); + append_object_object(viewStmt, "identity", tmp); + + append_string_object(viewStmt, "query", + pg_get_viewdef_internal(objectId)); + + relation_close(relation, AccessShareLock); + + return viewStmt; +} + +/* + * deparse_CreateTrigStmt + * Deparse a CreateTrigStmt (CREATE TRIGGER) + * + * Given a trigger OID and the parsetree that created it, return an ObjTree + * representing the creation command. + */ +static ObjTree * +deparse_CreateTrigStmt(Oid objectId, Node *parsetree) +{ + CreateTrigStmt *node = (CreateTrigStmt *) parsetree; + Relation pg_trigger; + HeapTuple trigTup; + Form_pg_trigger trigForm; + ObjTree *trigger; + ObjTree *tmp; + int tgnargs; + List *list; + List *events; + + pg_trigger = heap_open(TriggerRelationId, AccessShareLock); + + trigTup = get_catalog_object_by_oid(pg_trigger, objectId); + trigForm = (Form_pg_trigger) GETSTRUCT(trigTup); + + /* + * Some of the elements only make sense for CONSTRAINT TRIGGERs, but it + * seems simpler to use a single fmt string for both kinds of triggers. + */ + trigger = + new_objtree_VA("CREATE %{constraint}s TRIGGER %{name}I %{time}s %{events: OR }s " + "ON %{relation}D %{from_table}s %{constraint_attrs: }s " + "FOR EACH %{for_each}s %{when}s EXECUTE PROCEDURE %{function}s", + 2, + "name", ObjTypeString, node->trigname, + "constraint", ObjTypeString, + node->isconstraint ? "CONSTRAINT" : ""); + + if (node->timing == TRIGGER_TYPE_BEFORE) + append_string_object(trigger, "time", "BEFORE"); + else if (node->timing == TRIGGER_TYPE_AFTER) + append_string_object(trigger, "time", "AFTER"); + else if (node->timing == TRIGGER_TYPE_INSTEAD) + append_string_object(trigger, "time", "INSTEAD OF"); + else + elog(ERROR, "unrecognized trigger timing value %d", node->timing); + + /* + * Decode the events that the trigger fires for. The output is a list; + * in most cases it will just be a string with the even name, but when + * there's an UPDATE with a list of columns, we return a JSON object. + */ + events = NIL; + if (node->events & TRIGGER_TYPE_INSERT) + events = lappend(events, new_string_object("INSERT")); + if (node->events & TRIGGER_TYPE_DELETE) + events = lappend(events, new_string_object("DELETE")); + if (node->events & TRIGGER_TYPE_TRUNCATE) + events = lappend(events, new_string_object("TRUNCATE")); + if (node->events & TRIGGER_TYPE_UPDATE) + { + if (node->columns == NIL) + { + events = lappend(events, new_string_object("UPDATE")); + } + else + { + ObjTree *update; + ListCell *cell; + List *cols = NIL; + + /* + * Currently only UPDATE OF can be objects in the output JSON, but + * we add a "kind" element so that user code can distinguish + * possible future new event types. + */ + update = new_objtree_VA("UPDATE OF %{columns:, }I", + 1, "kind", ObjTypeString, "update_of"); + + foreach(cell, node->columns) + { + char *colname = strVal(lfirst(cell)); + + cols = lappend(cols, + new_string_object(colname)); + } + + append_array_object(update, "columns", cols); + + events = lappend(events, + new_object_object(update)); + } + } + append_array_object(trigger, "events", events); + + tmp = new_objtree_for_qualname_id(RelationRelationId, + trigForm->tgrelid); + append_object_object(trigger, "relation", tmp); + + tmp = new_objtree_VA("FROM %{relation}D", 0); + if (trigForm->tgconstrrelid) + { + ObjTree *rel; + + rel = new_objtree_for_qualname_id(RelationRelationId, + trigForm->tgconstrrelid); + append_object_object(tmp, "relation", rel); + } + else + append_bool_object(tmp, "present", false); + append_object_object(trigger, "from_table", tmp); + + list = NIL; + if (node->deferrable) + list = lappend(list, + new_string_object("DEFERRABLE")); + if (node->initdeferred) + list = lappend(list, + new_string_object("INITIALLY DEFERRED")); + append_array_object(trigger, "constraint_attrs", list); + + append_string_object(trigger, "for_each", + node->row ? "ROW" : "STATEMENT"); + + tmp = new_objtree_VA("WHEN (%{clause}s)", 0); + if (node->whenClause) + { + Node *whenClause; + Datum value; + bool isnull; + + value = fastgetattr(trigTup, Anum_pg_trigger_tgqual, + RelationGetDescr(pg_trigger), &isnull); + if (isnull) + elog(ERROR, "bogus NULL tgqual"); + + whenClause = stringToNode(TextDatumGetCString(value)); + append_string_object(tmp, "clause", + pg_get_trigger_whenclause(trigForm, + whenClause, + false)); + } + else + append_bool_object(tmp, "present", false); + append_object_object(trigger, "when", tmp); + + tmp = new_objtree_VA("%{funcname}D(%{args:, }L)", + 1, "funcname", ObjTypeObject, + new_objtree_for_qualname_id(ProcedureRelationId, + trigForm->tgfoid)); + list = NIL; + tgnargs = trigForm->tgnargs; + if (tgnargs > 0) + { + bytea *tgargs; + char *argstr; + bool isnull; + int findx; + int lentgargs; + char *p; + + tgargs = DatumGetByteaP(fastgetattr(trigTup, + Anum_pg_trigger_tgargs, + RelationGetDescr(pg_trigger), + &isnull)); + if (isnull) + elog(ERROR, "invalid NULL tgargs"); + argstr = (char *) VARDATA(tgargs); + lentgargs = VARSIZE_ANY_EXHDR(tgargs); + + p = argstr; + for (findx = 0; findx < tgnargs; findx++) + { + size_t tlen; + + /* verify that the argument encoding is correct */ + tlen = strlen(p); + if (p + tlen >= argstr + lentgargs) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid argument string (%s) for trigger \"%s\"", + argstr, NameStr(trigForm->tgname)))); + + list = lappend(list, new_string_object(p)); + + p += tlen + 1; + } + } + + append_array_object(tmp, "args", list); /* might be NIL */ + + append_object_object(trigger, "function", tmp); + + heap_close(pg_trigger, AccessShareLock); + + return trigger; +} + +static ObjTree * +deparse_RefreshMatViewStmt(Oid objectId, Node *parsetree) +{ + RefreshMatViewStmt *node = (RefreshMatViewStmt *) parsetree; + ObjTree *refreshStmt; + ObjTree *tmp; + + refreshStmt = new_objtree_VA("REFRESH MATERIALIZED VIEW %{concurrently}s %{identity}D " + "%{with_no_data}s", 0); + /* add a CONCURRENTLY clause */ + append_string_object(refreshStmt, "concurrently", + node->concurrent ? "CONCURRENTLY" : ""); + /* add the matview name */ + append_object_object(refreshStmt, "identity", + new_objtree_for_qualname_id(RelationRelationId, + objectId)); + /* add a WITH NO DATA clause */ + tmp = new_objtree_VA("WITH NO DATA", 1, + "present", ObjTypeBool, + node->skipData ? true : false); + append_object_object(refreshStmt, "with_no_data", tmp); + + return refreshStmt; +} + +/* + * deparse_ColumnDef + * Subroutine for CREATE TABLE deparsing + * + * Deparse a ColumnDef node within a regular (non typed) table creation. + * + * NOT NULL constraints in the column definition are emitted directly in the + * column definition by this routine; other constraints must be emitted + * elsewhere (the info in the parse node is incomplete anyway.) + */ +static ObjTree * +deparse_ColumnDef(Relation relation, List *dpcontext, bool composite, + ColumnDef *coldef, bool is_alter) +{ + ObjTree *column; + ObjTree *tmp; + Oid relid = RelationGetRelid(relation); + HeapTuple attrTup; + Form_pg_attribute attrForm; + Oid typid; + int32 typmod; + Oid typcollation; + bool saw_notnull; + ListCell *cell; + + /* + * Inherited columns without local definitions must not be emitted. XXX -- + * maybe it is useful to have them with "present = false" or some such? + */ + if (!coldef->is_local) + return NULL; + + attrTup = SearchSysCacheAttName(relid, coldef->colname); + if (!HeapTupleIsValid(attrTup)) + elog(ERROR, "could not find cache entry for column \"%s\" of relation %u", + coldef->colname, relid); + attrForm = (Form_pg_attribute) GETSTRUCT(attrTup); + + get_atttypetypmodcoll(relid, attrForm->attnum, + &typid, &typmod, &typcollation); + + /* Composite types use a slightly simpler format string */ + if (composite) + column = new_objtree_VA("%{name}I %{coltype}T %{collation}s", + 3, + "type", ObjTypeString, "column", + "name", ObjTypeString, coldef->colname, + "coltype", ObjTypeObject, + new_objtree_for_type(typid, typmod)); + else + column = new_objtree_VA("%{name}I %{coltype}T %{default}s %{not_null}s %{collation}s", + 3, + "type", ObjTypeString, "column", + "name", ObjTypeString, coldef->colname, + "coltype", ObjTypeObject, + new_objtree_for_type(typid, typmod)); + + tmp = new_objtree_VA("COLLATE %{name}D", 0); + if (OidIsValid(typcollation)) + { + ObjTree *collname; + + collname = new_objtree_for_qualname_id(CollationRelationId, + typcollation); + append_object_object(tmp, "name", collname); + } + else + append_bool_object(tmp, "present", false); + append_object_object(column, "collation", tmp); + + if (!composite) + { + /* + * Emit a NOT NULL declaration if necessary. Note that we cannot trust + * pg_attribute.attnotnull here, because that bit is also set when + * primary keys are specified; and we must not emit a NOT NULL + * constraint in that case, unless explicitely specified. Therefore, + * we scan the list of constraints attached to this column to determine + * whether we need to emit anything. + * (Fortunately, NOT NULL constraints cannot be table constraints.) + * + * In the ALTER TABLE cases, we also add a NOT NULL if the colDef is + * marked is_not_null. + */ + saw_notnull = false; + foreach(cell, coldef->constraints) + { + Constraint *constr = (Constraint *) lfirst(cell); + + if (constr->contype == CONSTR_NOTNULL) + saw_notnull = true; + } + if (is_alter && coldef->is_not_null) + saw_notnull = true; + + if (saw_notnull) + append_string_object(column, "not_null", "NOT NULL"); + else + append_string_object(column, "not_null", ""); + + tmp = new_objtree_VA("DEFAULT %{default}s", 0); + if (attrForm->atthasdef) + { + char *defstr; + + defstr = RelationGetColumnDefault(relation, attrForm->attnum, + dpcontext); + + append_string_object(tmp, "default", defstr); + } + else + append_bool_object(tmp, "present", false); + append_object_object(column, "default", tmp); + } + + ReleaseSysCache(attrTup); + + return column; +} + +/* + * deparse_ColumnDef_Typed + * Subroutine for CREATE TABLE OF deparsing + * + * Deparse a ColumnDef node within a typed table creation. This is simpler + * than the regular case, because we don't have to emit the type declaration, + * collation, or default. Here we only return something if the column is being + * declared NOT NULL. + * + * As in deparse_ColumnDef, any other constraint is processed elsewhere. + * + * FIXME --- actually, what about default values? + */ +static ObjTree * +deparse_ColumnDef_typed(Relation relation, List *dpcontext, ColumnDef *coldef) +{ + ObjTree *column = NULL; + Oid relid = RelationGetRelid(relation); + HeapTuple attrTup; + Form_pg_attribute attrForm; + Oid typid; + int32 typmod; + Oid typcollation; + bool saw_notnull; + ListCell *cell; + + attrTup = SearchSysCacheAttName(relid, coldef->colname); + if (!HeapTupleIsValid(attrTup)) + elog(ERROR, "could not find cache entry for column \"%s\" of relation %u", + coldef->colname, relid); + attrForm = (Form_pg_attribute) GETSTRUCT(attrTup); + + get_atttypetypmodcoll(relid, attrForm->attnum, + &typid, &typmod, &typcollation); + + /* + * Search for a NOT NULL declaration. As in deparse_ColumnDef, we rely on + * finding a constraint on the column rather than coldef->is_not_null. + * (This routine is never used for ALTER cases.) + */ + saw_notnull = false; + foreach(cell, coldef->constraints) + { + Constraint *constr = (Constraint *) lfirst(cell); + + if (constr->contype == CONSTR_NOTNULL) + { + saw_notnull = true; + break; + } + } + + if (saw_notnull) + column = new_objtree_VA("%{name}I WITH OPTIONS NOT NULL", 2, + "type", ObjTypeString, "column_notnull", + "name", ObjTypeString, coldef->colname); + + ReleaseSysCache(attrTup); + + return column; +} + +/* + * deparseTableElements + * Subroutine for CREATE TABLE deparsing + * + * Deal with all the table elements (columns and constraints). + * + * Note we ignore constraints in the parse node here; they are extracted from + * system catalogs instead. + */ +static List * +deparseTableElements(Relation relation, List *tableElements, List *dpcontext, + bool typed, bool composite) +{ + List *elements = NIL; + ListCell *lc; + + foreach(lc, tableElements) + { + Node *elt = (Node *) lfirst(lc); + + switch (nodeTag(elt)) + { + case T_ColumnDef: + { + ObjTree *tree; + + tree = typed ? + deparse_ColumnDef_typed(relation, dpcontext, + (ColumnDef *) elt) : + deparse_ColumnDef(relation, dpcontext, + composite, (ColumnDef *) elt, + false); + if (tree != NULL) + { + ObjElem *column; + + column = new_object_object(tree); + elements = lappend(elements, column); + } + } + break; + case T_Constraint: + break; + default: + elog(ERROR, "invalid node type %d", nodeTag(elt)); + } + } + + return elements; +} + +/* + * obtainConstraints + * Subroutine for CREATE TABLE/CREATE DOMAIN deparsing + * + * Given a table OID or domain OID, obtain its constraints and append them to + * the given elements list. The updated list is returned. + * + * This works for typed tables, regular tables, and domains. + * + * Note that CONSTRAINT_FOREIGN constraints are always ignored. + */ +static List * +obtainConstraints(List *elements, Oid relationId, Oid domainId) +{ + Relation conRel; + ScanKeyData key; + SysScanDesc scan; + HeapTuple tuple; + ObjTree *tmp; + + /* only one may be valid */ + Assert(OidIsValid(relationId) ^ OidIsValid(domainId)); + + /* + * scan pg_constraint to fetch all constraints linked to the given + * relation. + */ + conRel = heap_open(ConstraintRelationId, AccessShareLock); + if (OidIsValid(relationId)) + { + ScanKeyInit(&key, + Anum_pg_constraint_conrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(relationId)); + scan = systable_beginscan(conRel, ConstraintRelidIndexId, + true, NULL, 1, &key); + } + else + { + Assert(OidIsValid(domainId)); + ScanKeyInit(&key, + Anum_pg_constraint_contypid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(domainId)); + scan = systable_beginscan(conRel, ConstraintTypidIndexId, + true, NULL, 1, &key); + } + + /* + * For each constraint, add a node to the list of table elements. In + * these nodes we include not only the printable information ("fmt"), but + * also separate attributes to indicate the type of constraint, for + * automatic processing. + */ + while (HeapTupleIsValid(tuple = systable_getnext(scan))) + { + Form_pg_constraint constrForm; + char *contype; + + constrForm = (Form_pg_constraint) GETSTRUCT(tuple); + + switch (constrForm->contype) + { + case CONSTRAINT_CHECK: + contype = "check"; + break; + case CONSTRAINT_FOREIGN: + continue; /* not here */ + case CONSTRAINT_PRIMARY: + contype = "primary key"; + break; + case CONSTRAINT_UNIQUE: + contype = "unique"; + break; + case CONSTRAINT_TRIGGER: + contype = "trigger"; + break; + case CONSTRAINT_EXCLUSION: + contype = "exclusion"; + break; + default: + elog(ERROR, "unrecognized constraint type"); + } + + /* + * "type" and "contype" are not part of the printable output, but are + * useful to programmatically distinguish these from columns and among + * different constraint types. + * + * XXX it might be useful to also list the column names in a PK, etc. + */ + tmp = new_objtree_VA("CONSTRAINT %{name}I %{definition}s", + 4, + "type", ObjTypeString, "constraint", + "contype", ObjTypeString, contype, + "name", ObjTypeString, NameStr(constrForm->conname), + "definition", ObjTypeString, + pg_get_constraintdef_string(HeapTupleGetOid(tuple), + false)); + elements = lappend(elements, new_object_object(tmp)); + } + + systable_endscan(scan); + heap_close(conRel, AccessShareLock); + + return elements; +} + +/* + * deparse the ON COMMMIT ... clause for CREATE ... TEMPORARY ... + */ +static ObjTree * +deparse_OnCommitClause(OnCommitAction option) +{ + ObjTree *tmp; + + tmp = new_objtree_VA("ON COMMIT %{on_commit_value}s", 0); + switch (option) + { + case ONCOMMIT_DROP: + append_string_object(tmp, "on_commit_value", "DROP"); + break; + + case ONCOMMIT_DELETE_ROWS: + append_string_object(tmp, "on_commit_value", "DELETE ROWS"); + break; + + case ONCOMMIT_PRESERVE_ROWS: + append_string_object(tmp, "on_commit_value", "PRESERVE ROWS"); + break; + + case ONCOMMIT_NOOP: + append_null_object(tmp, "on_commit_value"); + append_bool_object(tmp, "present", false); + break; + } + + return tmp; +} + +/* + * Deparse DefElems, as used e.g. by ALTER COLUMN ... SET, into a list of SET + * (...) or RESET (...) contents. + */ +static ObjTree * +deparse_DefElem(DefElem *elem, bool is_reset) +{ + ObjTree *set; + ObjTree *optname; + + if (elem->defnamespace != NULL) + optname = new_objtree_VA("%{schema}I.%{label}I", 1, + "schema", ObjTypeString, elem->defnamespace); + else + optname = new_objtree_VA("%{label}I", 0); + + append_string_object(optname, "label", elem->defname); + + if (is_reset) + set = new_objtree_VA("%{label}s", 0); + else + set = new_objtree_VA("%{label}s = %{value}L", 1, + "value", ObjTypeString, + elem->arg ? defGetString(elem) : + defGetBoolean(elem) ? "TRUE" : "FALSE"); + + append_object_object(set, "label", optname); + return set; +} + +/* + * ... ALTER COLUMN ... SET/RESET (...) + */ +static ObjTree * +deparse_ColumnSetOptions(AlterTableCmd *subcmd) +{ + List *sets = NIL; + ListCell *cell; + ObjTree *tmp; + bool is_reset = subcmd->subtype == AT_ResetOptions; + + if (is_reset) + tmp = new_objtree_VA("ALTER COLUMN %{column}I RESET (%{options:, }s)", 0); + else + tmp = new_objtree_VA("ALTER COLUMN %{column}I SET (%{options:, }s)", 0); + + append_string_object(tmp, "column", subcmd->name); + + foreach(cell, (List *) subcmd->def) + { + DefElem *elem; + ObjTree *set; + + elem = (DefElem *) lfirst(cell); + set = deparse_DefElem(elem, is_reset); + sets = lappend(sets, new_object_object(set)); + } + + append_array_object(tmp, "options", sets); + + return tmp; +} + +/* + * ... ALTER COLUMN ... SET/RESET (...) + */ +static ObjTree * +deparse_RelSetOptions(AlterTableCmd *subcmd) +{ + List *sets = NIL; + ListCell *cell; + ObjTree *tmp; + bool is_reset = subcmd->subtype == AT_ResetRelOptions; + + if (is_reset) + tmp = new_objtree_VA("RESET (%{options:, }s)", 0); + else + tmp = new_objtree_VA("SET (%{options:, }s)", 0); + + foreach(cell, (List *) subcmd->def) + { + DefElem *elem; + ObjTree *set; + + elem = (DefElem *) lfirst(cell); + set = deparse_DefElem(elem, is_reset); + sets = lappend(sets, new_object_object(set)); + } + + append_array_object(tmp, "options", sets); + + return tmp; +} + +/* + * deparse_CreateStmt + * Deparse a CreateStmt (CREATE TABLE) + * + * Given a table OID and the parsetree that created it, return an ObjTree + * representing the creation command. + */ +static ObjTree * +deparse_CreateStmt(Oid objectId, Node *parsetree) +{ + CreateStmt *node = (CreateStmt *) parsetree; + Relation relation = relation_open(objectId, AccessShareLock); + List *dpcontext; + ObjTree *createStmt; + ObjTree *tmp; + List *list; + ListCell *cell; + char *fmtstr; + + /* + * Typed tables use a slightly different format string: we must not put + * table_elements with parents directly in the fmt string, because if + * there are no options the parens must not be emitted; and also, typed + * tables do not allow for inheritance. + */ + if (node->ofTypename) + fmtstr = "CREATE %{persistence}s TABLE %{if_not_exists}s %{identity}D " + "OF %{of_type}T %{table_elements}s " + "WITH (%{with:, }s) %{on_commit}s %{tablespace}s"; + else + fmtstr = "CREATE %{persistence}s TABLE %{if_not_exists}s %{identity}D " + "(%{table_elements:, }s) %{inherits}s " + "WITH (%{with:, }s) %{on_commit}s %{tablespace}s"; + + createStmt = + new_objtree_VA(fmtstr, 1, + "persistence", ObjTypeString, + get_persistence_str(relation->rd_rel->relpersistence)); + + tmp = new_objtree_for_qualname(relation->rd_rel->relnamespace, + RelationGetRelationName(relation)); + append_object_object(createStmt, "identity", tmp); + + append_string_object(createStmt, "if_not_exists", + node->if_not_exists ? "IF NOT EXISTS" : ""); + + dpcontext = deparse_context_for(RelationGetRelationName(relation), + objectId); + + if (node->ofTypename) + { + List *tableelts = NIL; + + /* + * We can't put table elements directly in the fmt string as an array + * surrounded by parens here, because an empty clause would cause a + * syntax error. Therefore, we use an indirection element and set + * present=false when there are no elements. + */ + append_string_object(createStmt, "table_kind", "typed"); + + tmp = new_objtree_for_type(relation->rd_rel->reloftype, -1); + append_object_object(createStmt, "of_type", tmp); + + tableelts = deparseTableElements(relation, node->tableElts, dpcontext, + true, /* typed table */ + false); /* not composite */ + tableelts = obtainConstraints(tableelts, objectId, InvalidOid); + if (tableelts == NIL) + tmp = new_objtree_VA("", 1, + "present", ObjTypeBool, false); + else + tmp = new_objtree_VA("(%{elements:, }s)", 1, + "elements", ObjTypeArray, tableelts); + append_object_object(createStmt, "table_elements", tmp); + } + else + { + List *tableelts = NIL; + + /* + * There is no need to process LIKE clauses separately; they have + * already been transformed into columns and constraints. + */ + append_string_object(createStmt, "table_kind", "plain"); + + /* + * Process table elements: column definitions and constraints. Only + * the column definitions are obtained from the parse node itself. To + * get constraints we rely on pg_constraint, because the parse node + * might be missing some things such as the name of the constraints. + */ + tableelts = deparseTableElements(relation, node->tableElts, dpcontext, + false, /* not typed table */ + false); /* not composite */ + tableelts = obtainConstraints(tableelts, objectId, InvalidOid); + + append_array_object(createStmt, "table_elements", tableelts); + + /* + * Add inheritance specification. We cannot simply scan the list of + * parents from the parser node, because that may lack the actual + * qualified names of the parent relations. Rather than trying to + * re-resolve them from the information in the parse node, it seems + * more accurate and convenient to grab it from pg_inherits. + */ + tmp = new_objtree_VA("INHERITS (%{parents:, }D)", 0); + if (list_length(node->inhRelations) > 0) + { + List *parents = NIL; + Relation inhRel; + SysScanDesc scan; + ScanKeyData key; + HeapTuple tuple; + + inhRel = heap_open(InheritsRelationId, RowExclusiveLock); + + ScanKeyInit(&key, + Anum_pg_inherits_inhrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(objectId)); + + scan = systable_beginscan(inhRel, InheritsRelidSeqnoIndexId, + true, NULL, 1, &key); + + while (HeapTupleIsValid(tuple = systable_getnext(scan))) + { + ObjTree *parent; + Form_pg_inherits formInh = (Form_pg_inherits) GETSTRUCT(tuple); + + parent = new_objtree_for_qualname_id(RelationRelationId, + formInh->inhparent); + parents = lappend(parents, new_object_object(parent)); + } + + systable_endscan(scan); + heap_close(inhRel, RowExclusiveLock); + + append_array_object(tmp, "parents", parents); + } + else + { + append_null_object(tmp, "parents"); + append_bool_object(tmp, "present", false); + } + append_object_object(createStmt, "inherits", tmp); + } + + tmp = new_objtree_VA("TABLESPACE %{tablespace}I", 0); + if (node->tablespacename) + append_string_object(tmp, "tablespace", node->tablespacename); + else + { + append_null_object(tmp, "tablespace"); + append_bool_object(tmp, "present", false); + } + append_object_object(createStmt, "tablespace", tmp); + + append_object_object(createStmt, "on_commit", + deparse_OnCommitClause(node->oncommit)); + + /* + * WITH clause. We always emit one, containing at least the OIDS option. + * That way we don't depend on the default value for default_with_oids. + * We can skip emitting other options if there don't appear in the parse + * node. + */ + tmp = new_objtree_VA("oids=%{value}s", 2, + "option", ObjTypeString, "oids", + "value", ObjTypeString, + relation->rd_rel->relhasoids ? "ON" : "OFF"); + list = list_make1(new_object_object(tmp)); + + foreach(cell, node->options) + { + DefElem *opt = (DefElem *) lfirst(cell); + + /* already handled above */ + if (strcmp(opt->defname, "oids") == 0) + continue; + + tmp = deparse_DefElem(opt, false); + list = lappend(list, new_object_object(tmp)); + } + append_array_object(createStmt, "with", list); + + relation_close(relation, AccessShareLock); + + return createStmt; +} + +static ObjTree * +deparse_CreateForeignTableStmt(Oid objectId, Node *parsetree) +{ + CreateForeignTableStmt *stmt = (CreateForeignTableStmt *) parsetree; + Relation relation = relation_open(objectId, AccessShareLock); + List *dpcontext; + ObjTree *createStmt; + ObjTree *tmp; + List *tableelts = NIL; + + createStmt = new_objtree_VA( + "CREATE FOREIGN TABLE %{if_not_exists}s %{identity}D " + "(%{table_elements:, }s) SERVER %{server}I " + "%{generic_options}s", 0); + + tmp = new_objtree_for_qualname(relation->rd_rel->relnamespace, + RelationGetRelationName(relation)); + append_object_object(createStmt, "identity", tmp); + + append_string_object(createStmt, "if_not_exists", + stmt->base.if_not_exists ? "IF NOT EXISTS" : ""); + + append_string_object(createStmt, "server", stmt->servername); + + dpcontext = deparse_context_for(RelationGetRelationName(relation), + objectId); + + tableelts = deparseTableElements(relation, stmt->base.tableElts, dpcontext, + false, /* not typed table */ + false); /* not composite */ + tableelts = obtainConstraints(tableelts, objectId, InvalidOid); + + append_array_object(createStmt, "table_elements", tableelts); + + /* add an OPTIONS clause, if any */ + append_object_object(createStmt, "generic_options", + deparse_FdwOptions(stmt->options, NULL)); + + relation_close(relation, AccessShareLock); + return createStmt; +} + + +/* + * deparse_CompositeTypeStmt + * Deparse a CompositeTypeStmt (CREATE TYPE AS) + * + * Given a type OID and the parsetree that created it, return an ObjTree + * representing the creation command. + */ +static ObjTree * +deparse_CompositeTypeStmt(Oid objectId, Node *parsetree) +{ + CompositeTypeStmt *node = (CompositeTypeStmt *) parsetree; + ObjTree *composite; + HeapTuple typtup; + Form_pg_type typform; + Relation typerel; + List *dpcontext; + List *tableelts = NIL; + + /* Find the pg_type entry and open the corresponding relation */ + typtup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(typtup)) + elog(ERROR, "cache lookup failed for type %u", objectId); + typform = (Form_pg_type) GETSTRUCT(typtup); + typerel = relation_open(typform->typrelid, AccessShareLock); + + dpcontext = deparse_context_for(RelationGetRelationName(typerel), + RelationGetRelid(typerel)); + + composite = new_objtree_VA("CREATE TYPE %{identity}D AS (%{columns:, }s)", + 0); + append_object_object(composite, "identity", + new_objtree_for_qualname_id(TypeRelationId, + objectId)); + + tableelts = deparseTableElements(typerel, node->coldeflist, dpcontext, + false, /* not typed */ + true); /* composite type */ + + append_array_object(composite, "columns", tableelts); + + heap_close(typerel, AccessShareLock); + ReleaseSysCache(typtup); + + return composite; +} + +/* + * deparse_CreateEnumStmt + * Deparse a CreateEnumStmt (CREATE TYPE AS ENUM) + * + * Given a type OID and the parsetree that created it, return an ObjTree + * representing the creation command. + */ +static ObjTree * +deparse_CreateEnumStmt(Oid objectId, Node *parsetree) +{ + CreateEnumStmt *node = (CreateEnumStmt *) parsetree; + ObjTree *enumtype; + List *values; + ListCell *cell; + + enumtype = new_objtree_VA("CREATE TYPE %{identity}D AS ENUM (%{values:, }L)", + 0); + append_object_object(enumtype, "identity", + new_objtree_for_qualname_id(TypeRelationId, + objectId)); + values = NIL; + foreach(cell, node->vals) + { + Value *val = (Value *) lfirst(cell); + + values = lappend(values, new_string_object(strVal(val))); + } + append_array_object(enumtype, "values", values); + + return enumtype; +} + +static ObjTree * +deparse_CreateRangeStmt(Oid objectId, Node *parsetree) +{ + ObjTree *range; + ObjTree *tmp; + List *definition = NIL; + Relation pg_range; + HeapTuple rangeTup; + Form_pg_range rangeForm; + ScanKeyData key[1]; + SysScanDesc scan; + + pg_range = heap_open(RangeRelationId, RowExclusiveLock); + + ScanKeyInit(&key[0], + Anum_pg_range_rngtypid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(objectId)); + + scan = systable_beginscan(pg_range, RangeTypidIndexId, true, + NULL, 1, key); + + rangeTup = systable_getnext(scan); + if (!HeapTupleIsValid(rangeTup)) + elog(ERROR, "cache lookup failed for range with type oid %u", + objectId); + + rangeForm = (Form_pg_range) GETSTRUCT(rangeTup); + + range = new_objtree_VA("CREATE TYPE %{identity}D AS RANGE (%{definition:, }s)", 0); + tmp = new_objtree_for_qualname_id(TypeRelationId, objectId); + append_object_object(range, "identity", tmp); + + /* SUBTYPE */ + tmp = new_objtree_for_qualname_id(TypeRelationId, + rangeForm->rngsubtype); + tmp = new_objtree_VA("SUBTYPE = %{type}D", + 2, + "clause", ObjTypeString, "subtype", + "type", ObjTypeObject, tmp); + definition = lappend(definition, new_object_object(tmp)); + + /* SUBTYPE_OPCLASS */ + if (OidIsValid(rangeForm->rngsubopc)) + { + tmp = new_objtree_for_qualname_id(OperatorClassRelationId, + rangeForm->rngsubopc); + tmp = new_objtree_VA("SUBTYPE_OPCLASS = %{opclass}D", + 2, + "clause", ObjTypeString, "opclass", + "opclass", ObjTypeObject, tmp); + definition = lappend(definition, new_object_object(tmp)); + } + + /* COLLATION */ + if (OidIsValid(rangeForm->rngcollation)) + { + tmp = new_objtree_for_qualname_id(CollationRelationId, + rangeForm->rngcollation); + tmp = new_objtree_VA("COLLATION = %{collation}D", + 2, + "clause", ObjTypeString, "collation", + "collation", ObjTypeObject, tmp); + definition = lappend(definition, new_object_object(tmp)); + } + + /* CANONICAL */ + if (OidIsValid(rangeForm->rngcanonical)) + { + tmp = new_objtree_for_qualname_id(ProcedureRelationId, + rangeForm->rngcanonical); + tmp = new_objtree_VA("CANONICAL = %{canonical}D", + 2, + "clause", ObjTypeString, "canonical", + "canonical", ObjTypeObject, tmp); + definition = lappend(definition, new_object_object(tmp)); + } + + /* SUBTYPE_DIFF */ + if (OidIsValid(rangeForm->rngsubdiff)) + { + tmp = new_objtree_for_qualname_id(ProcedureRelationId, + rangeForm->rngsubdiff); + tmp = new_objtree_VA("SUBTYPE_DIFF = %{subtype_diff}D", + 2, + "clause", ObjTypeString, "subtype_diff", + "subtype_diff", ObjTypeObject, tmp); + definition = lappend(definition, new_object_object(tmp)); + } + + append_array_object(range, "definition", definition); + + systable_endscan(scan); + heap_close(pg_range, RowExclusiveLock); + + return range; +} + +static ObjTree * +deparse_CreatePLangStmt(Oid objectId, Node *parsetree) +{ + CreatePLangStmt *node = (CreatePLangStmt *) parsetree; + ObjTree *createLang; + ObjTree *tmp; + HeapTuple langTup; + Form_pg_language langForm; + + + langTup = SearchSysCache1(LANGOID, + ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(langTup)) + elog(ERROR, "cache lookup failed for language %u", objectId); + langForm = (Form_pg_language) GETSTRUCT(langTup); + + if (node->plhandler == NIL) + createLang = + new_objtree_VA("CREATE %{or_replace}s %{trusted}s PROCEDURAL LANGUAGE %{identity}I", 0); + else + createLang = + new_objtree_VA("CREATE %{or_replace}s %{trusted}s PROCEDURAL LANGUAGE %{identity}I " + "HANDLER %{handler}D %{inline}s %{validator}s", 0); + + append_string_object(createLang, "or_replace", + node->replace ? "OR REPLACE" : ""); + append_string_object(createLang, "identity", node->plname); + append_string_object(createLang, "trusted", + langForm->lanpltrusted ? "TRUSTED" : ""); + + if (node->plhandler != NIL) + { + /* Add the HANDLER clause */ + append_object_object(createLang, "handler", + new_objtree_for_qualname_id(ProcedureRelationId, + langForm->lanplcallfoid)); + + /* Add the INLINE clause, if any */ + tmp = new_objtree_VA("INLINE %{handler_name}D", 0); + if (OidIsValid(langForm->laninline)) + { + append_object_object(tmp, "handler_name", + new_objtree_for_qualname_id(ProcedureRelationId, + langForm->laninline)); + } + else + append_bool_object(tmp, "present", false); + append_object_object(createLang, "inline", tmp); + + /* Add the VALIDATOR clause, if any */ + tmp = new_objtree_VA("VALIDATOR %{handler_name}D", 0); + if (OidIsValid(langForm->lanvalidator)) + { + append_object_object(tmp, "handler_name", + new_objtree_for_qualname_id(ProcedureRelationId, + langForm->lanvalidator)); + } + else + append_bool_object(tmp, "present", false); + append_object_object(createLang, "validator", tmp); + } + + ReleaseSysCache(langTup); + + return createLang; +} + +static ObjTree * +deparse_CreateDomain(Oid objectId, Node *parsetree) +{ + ObjTree *createDomain; + ObjTree *tmp; + HeapTuple typTup; + Form_pg_type typForm; + List *constraints; + + typTup = SearchSysCache1(TYPEOID, + objectId); + if (!HeapTupleIsValid(typTup)) + elog(ERROR, "cache lookup failed for domain with OID %u", objectId); + typForm = (Form_pg_type) GETSTRUCT(typTup); + + createDomain = new_objtree_VA("CREATE DOMAIN %{identity}D AS %{type}T %{not_null}s %{constraints}s %{collation}s", + 0); + + append_object_object(createDomain, + "identity", + new_objtree_for_qualname_id(TypeRelationId, + objectId)); + append_object_object(createDomain, + "type", + new_objtree_for_type(typForm->typbasetype, typForm->typtypmod)); + + if (typForm->typnotnull) + append_string_object(createDomain, "not_null", "NOT NULL"); + else + append_string_object(createDomain, "not_null", ""); + + constraints = obtainConstraints(NIL, InvalidOid, objectId); + tmp = new_objtree_VA("%{elements: }s", 0); + if (constraints == NIL) + append_bool_object(tmp, "present", false); + else + append_array_object(tmp, "elements", constraints); + append_object_object(createDomain, "constraints", tmp); + + tmp = new_objtree_VA("COLLATE %{collation}D", 0); + if (OidIsValid(typForm->typcollation)) + append_object_object(tmp, "collation", + new_objtree_for_qualname_id(CollationRelationId, + typForm->typcollation)); + else + append_bool_object(tmp, "present", false); + append_object_object(createDomain, "collation", tmp); + + ReleaseSysCache(typTup); + + return createDomain; +} + +static ObjTree * +deparse_FunctionSet(VariableSetKind kind, char *name, char *value) +{ + ObjTree *r; + + if (kind == VAR_RESET_ALL) + { + r = new_objtree_VA("RESET ALL", 0); + } + else if (value != NULL) + { + /* + * Some GUC variable names are 'LIST' type and hence must not be + * quoted. FIXME: shouldn't this and pg_get_functiondef() rather use + * guc.c to check for GUC_LIST? + */ + if (pg_strcasecmp(name, "DateStyle") == 0 + || pg_strcasecmp(name, "search_path") == 0) + r = new_objtree_VA("SET %{set_name}I TO %{set_value}s", 0); + else + r = new_objtree_VA("SET %{set_name}I TO %{set_value}L", 0); + + append_string_object(r, "set_name", name); + append_string_object(r, "set_value", value); + } + else + { + r = new_objtree_VA("RESET %{set_name}I", 0); + append_string_object(r, "set_name", name); + } + + return r; +} + +/* + * deparse_CreateFunctionStmt + * Deparse a CreateFunctionStmt (CREATE FUNCTION) + * + * Given a function OID and the parsetree that created it, return the JSON + * blob representing the creation command. + */ +static ObjTree * +deparse_CreateFunction(Oid objectId, Node *parsetree) +{ + CreateFunctionStmt *node = (CreateFunctionStmt *) parsetree; + ObjTree *createFunc; + ObjTree *sign; + ObjTree *tmp; + Datum tmpdatum; + char *fmt; + char *definition; + char *source; + char *probin; + List *params; + List *defaults; + List *sets = NIL; + ListCell *cell; + ListCell *curdef; + ListCell *table_params = NULL; + HeapTuple procTup; + Form_pg_proc procForm; + HeapTuple langTup; + Oid *typarray; + Form_pg_language langForm; + int i; + int typnum; + bool isnull; + + /* get the pg_proc tuple */ + procTup = SearchSysCache1(PROCOID, objectId); + if (!HeapTupleIsValid(procTup)) + elog(ERROR, "cache lookup failure for function with OID %u", + objectId); + procForm = (Form_pg_proc) GETSTRUCT(procTup); + + /* get the corresponding pg_language tuple */ + langTup = SearchSysCache1(LANGOID, procForm->prolang); + if (!HeapTupleIsValid(langTup)) + elog(ERROR, "cache lookup failure for language with OID %u", + procForm->prolang); + langForm = (Form_pg_language) GETSTRUCT(langTup); + + /* + * Determine useful values for prosrc and probin. We cope with probin + * being either NULL or "-", but prosrc must have a valid value. + */ + tmpdatum = SysCacheGetAttr(PROCOID, procTup, + Anum_pg_proc_prosrc, &isnull); + if (isnull) + elog(ERROR, "null prosrc in function with OID %u", objectId); + source = TextDatumGetCString(tmpdatum); + + /* Determine a useful value for probin */ + tmpdatum = SysCacheGetAttr(PROCOID, procTup, + Anum_pg_proc_probin, &isnull); + if (isnull) + probin = NULL; + else + { + probin = TextDatumGetCString(tmpdatum); + if (probin[0] == '\0' || strcmp(probin, "-") == 0) + probin = NULL; + } + + if (probin == NULL) + definition = "%{definition}L"; + else + definition = "%{objfile}L, %{symbol}L"; + + fmt = psprintf("CREATE %%{or_replace}s FUNCTION %%{signature}s " + "RETURNS %%{return_type}s LANGUAGE %%{language}I " + "%%{window}s %%{volatility}s %%{leakproof}s " + "%%{strict}s %%{security_definer}s %%{cost}s %%{rows}s " + "%%{set_options: }s " + "AS %s", definition); + + createFunc = new_objtree_VA(fmt, 1, + "or_replace", ObjTypeString, + node->replace ? "OR REPLACE" : ""); + + sign = new_objtree_VA("%{identity}D(%{arguments:, }s)", 0); + + /* + * To construct the arguments array, extract the type OIDs from the + * function's pg_proc entry. If pronargs equals the parameter list length, + * there are no OUT parameters and thus we can extract the type OID from + * proargtypes; otherwise we need to decode proallargtypes, which is + * a bit more involved. + */ + typarray = palloc(list_length(node->parameters) * sizeof(Oid)); + if (list_length(node->parameters) > procForm->pronargs) + { + bool isnull; + Datum alltypes; + Datum *values; + bool *nulls; + int nelems; + + alltypes = SysCacheGetAttr(PROCOID, procTup, + Anum_pg_proc_proallargtypes, &isnull); + if (isnull) + elog(ERROR, "NULL proallargtypes, but more parameters than args"); + deconstruct_array(DatumGetArrayTypeP(alltypes), + OIDOID, 4, 't', 'i', + &values, &nulls, &nelems); + if (nelems != list_length(node->parameters)) + elog(ERROR, "mismatched proallargatypes"); + for (i = 0; i < list_length(node->parameters); i++) + typarray[i] = values[i]; + } + else + { + for (i = 0; i < list_length(node->parameters); i++) + typarray[i] = procForm->proargtypes.values[i]; + } + + /* + * If there are any default expressions, we read the deparsed expression as + * a list so that we can attach them to each argument. + */ + tmpdatum = SysCacheGetAttr(PROCOID, procTup, + Anum_pg_proc_proargdefaults, &isnull); + if (!isnull) + { + defaults = FunctionGetDefaults(DatumGetTextP(tmpdatum)); + curdef = list_head(defaults); + } + else + { + defaults = NIL; + curdef = NULL; + } + + /* + * Now iterate over each parameter in the parsetree to create the + * parameters array. + */ + params = NIL; + typnum = 0; + foreach(cell, node->parameters) + { + FunctionParameter *param = (FunctionParameter *) lfirst(cell); + ObjTree *tmp2; + ObjTree *tmp3; + + /* + * A PARAM_TABLE parameter indicates end of input arguments; the + * following parameters are part of the return type. We ignore them + * here, but keep track of the current position in the list so that + * we can easily produce the return type below. + */ + if (param->mode == FUNC_PARAM_TABLE) + { + table_params = cell; + break; + } + + /* + * Note that %{name}s is a string here, not an identifier; the reason + * for this is that an absent parameter name must produce an empty + * string, not "", which is what would happen if we were to use + * %{name}I here. So we add another level of indirection to allow us + * to inject a "present" parameter. + */ + tmp2 = new_objtree_VA("%{mode}s %{name}s %{type}T %{default}s", 0); + append_string_object(tmp2, "mode", + param->mode == FUNC_PARAM_IN ? "IN" : + param->mode == FUNC_PARAM_OUT ? "OUT" : + param->mode == FUNC_PARAM_INOUT ? "INOUT" : + param->mode == FUNC_PARAM_VARIADIC ? "VARIADIC" : + "INVALID MODE"); + + /* optional wholesale suppression of "name" occurs here */ + append_object_object(tmp2, "name", + new_objtree_VA("%{name}I", 2, + "name", ObjTypeString, + param->name ? param->name : "NULL", + "present", ObjTypeBool, + param->name ? true : false)); + + tmp3 = new_objtree_VA("DEFAULT %{value}s", 0); + if (PointerIsValid(param->defexpr)) + { + char *expr; + + if (curdef == NULL) + elog(ERROR, "proargdefaults list too short"); + expr = lfirst(curdef); + + append_string_object(tmp3, "value", expr); + curdef = lnext(curdef); + } + else + append_bool_object(tmp3, "present", false); + append_object_object(tmp2, "default", tmp3); + + append_object_object(tmp2, "type", + new_objtree_for_type(typarray[typnum++], -1)); + + params = lappend(params, new_object_object(tmp2)); + } + append_array_object(sign, "arguments", params); + append_object_object(sign, "identity", + new_objtree_for_qualname_id(ProcedureRelationId, + objectId)); + append_object_object(createFunc, "signature", sign); + + /* + * A return type can adopt one of two forms: either a [SETOF] some_type, or + * a TABLE(list-of-types). We can tell the second form because we saw a + * table param above while scanning the argument list. + */ + if (table_params == NULL) + { + tmp = new_objtree_VA("%{setof}s %{rettype}T", 0); + append_string_object(tmp, "setof", + procForm->proretset ? "SETOF" : ""); + append_object_object(tmp, "rettype", + new_objtree_for_type(procForm->prorettype, -1)); + append_string_object(tmp, "return_form", "plain"); + } + else + { + List *rettypes = NIL; + ObjTree *tmp2; + + tmp = new_objtree_VA("TABLE (%{rettypes:, }s)", 0); + for (; table_params != NULL; table_params = lnext(table_params)) + { + FunctionParameter *param = lfirst(table_params); + + tmp2 = new_objtree_VA("%{name}I %{type}T", 0); + append_string_object(tmp2, "name", param->name); + append_object_object(tmp2, "type", + new_objtree_for_type(typarray[typnum++], -1)); + rettypes = lappend(rettypes, new_object_object(tmp2)); + } + + append_array_object(tmp, "rettypes", rettypes); + append_string_object(tmp, "return_form", "table"); + } + + append_object_object(createFunc, "return_type", tmp); + + append_string_object(createFunc, "language", + NameStr(langForm->lanname)); + + append_string_object(createFunc, "window", + procForm->proiswindow ? "WINDOW" : ""); + append_string_object(createFunc, "volatility", + procForm->provolatile == PROVOLATILE_VOLATILE ? + "VOLATILE" : + procForm->provolatile == PROVOLATILE_STABLE ? + "STABLE" : + procForm->provolatile == PROVOLATILE_IMMUTABLE ? + "IMMUTABLE" : "INVALID VOLATILITY"); + + append_string_object(createFunc, "leakproof", + procForm->proleakproof ? "LEAKPROOF" : ""); + append_string_object(createFunc, "strict", + procForm->proisstrict ? + "RETURNS NULL ON NULL INPUT" : + "CALLED ON NULL INPUT"); + + append_string_object(createFunc, "security_definer", + procForm->prosecdef ? + "SECURITY DEFINER" : "SECURITY INVOKER"); + + append_object_object(createFunc, "cost", + new_objtree_VA("COST %{cost}n", 1, + "cost", ObjTypeFloat, + procForm->procost)); + + tmp = new_objtree_VA("ROWS %{rows}n", 0); + if (procForm->prorows == 0) + append_bool_object(tmp, "present", false); + else + append_float_object(tmp, "rows", procForm->prorows); + append_object_object(createFunc, "rows", tmp); + + foreach(cell, node->options) + { + DefElem *defel = (DefElem *) lfirst(cell); + ObjTree *tmp = NULL; + + if (strcmp(defel->defname, "set") == 0) + { + VariableSetStmt *sstmt = (VariableSetStmt *) defel->arg; + char *value = ExtractSetVariableArgs(sstmt); + + tmp = deparse_FunctionSet(sstmt->kind, sstmt->name, value); + sets = lappend(sets, new_object_object(tmp)); + } + } + append_array_object(createFunc, "set_options", sets); + + if (probin == NULL) + { + append_string_object(createFunc, "definition", + source); + } + else + { + append_string_object(createFunc, "objfile", probin); + append_string_object(createFunc, "symbol", source); + } + + ReleaseSysCache(langTup); + ReleaseSysCache(procTup); + + return createFunc; +} + +/* + * deparse_AlterFunctionStmt + * Deparse a AlterFunctionStmt (ALTER FUNCTION) + * + * Given a function OID and the parsetree that created it, return the JSON + * blob representing the alter command. + */ +static ObjTree * +deparse_AlterFunction(Oid objectId, Node *parsetree) +{ + AlterFunctionStmt *node = (AlterFunctionStmt *) parsetree; + ObjTree *alterFunc; + ObjTree *sign; + HeapTuple procTup; + Form_pg_proc procForm; + List *params; + List *elems = NIL; + ListCell *cell; + int i; + + /* get the pg_proc tuple */ + procTup = SearchSysCache1(PROCOID, objectId); + if (!HeapTupleIsValid(procTup)) + elog(ERROR, "cache lookup failure for function with OID %u", + objectId); + procForm = (Form_pg_proc) GETSTRUCT(procTup); + + alterFunc = new_objtree_VA("ALTER FUNCTION %{signature}s %{definition: }s", 0); + + sign = new_objtree_VA("%{identity}D(%{arguments:, }s)", 0); + + params = NIL; + + /* + * ALTER FUNCTION does not change signature so we can use catalog + * to get input type Oids. + */ + for (i = 0; i < procForm->pronargs; i++) + { + ObjTree *tmp = new_objtree_VA("%{type}T", 0); + + append_object_object(tmp, "type", + new_objtree_for_type(procForm->proargtypes.values[i], -1)); + params = lappend(params, new_object_object(tmp)); + } + + append_array_object(sign, "arguments", params); + append_object_object(sign, "identity", + new_objtree_for_qualname_id(ProcedureRelationId, + objectId)); + append_object_object(alterFunc, "signature", sign); + + foreach(cell, node->actions) + { + DefElem *defel = (DefElem *) lfirst(cell); + ObjTree *tmp = NULL; + + if (strcmp(defel->defname, "volatility") == 0) + { + tmp = new_objtree_VA(strVal(defel->arg), 0); + } + else if (strcmp(defel->defname, "strict") == 0) + { + tmp = new_objtree_VA(intVal(defel->arg) ? + "RETURNS NULL ON NULL INPUT" : + "CALLED ON NULL INPUT", 0); + } + else if (strcmp(defel->defname, "security") == 0) + { + tmp = new_objtree_VA(intVal(defel->arg) ? + "SECURITY DEFINER" : "SECURITY INVOKER", 0); + } + else if (strcmp(defel->defname, "leakproof") == 0) + { + tmp = new_objtree_VA(intVal(defel->arg) ? + "LEAKPROOF" : "NOT LEAKPROOF", 0); + } + else if (strcmp(defel->defname, "cost") == 0) + { + tmp = new_objtree_VA("COST %{cost}n", 1, + "cost", ObjTypeFloat, + defGetNumeric(defel)); + } + else if (strcmp(defel->defname, "rows") == 0) + { + tmp = new_objtree_VA("ROWS %{rows}n", 0); + if (defGetNumeric(defel) == 0) + append_bool_object(tmp, "present", false); + else + append_float_object(tmp, "rows", + defGetNumeric(defel)); + } + else if (strcmp(defel->defname, "set") == 0) + { + VariableSetStmt *sstmt = (VariableSetStmt *) defel->arg; + char *value = ExtractSetVariableArgs(sstmt); + + tmp = deparse_FunctionSet(sstmt->kind, sstmt->name, value); + } + + elems = lappend(elems, new_object_object(tmp)); + } + + append_array_object(alterFunc, "definition", elems); + + ReleaseSysCache(procTup); + + return alterFunc; +} + +/* + * Return the given object type as a string. + */ +static const char * +stringify_objtype(ObjectType objtype) +{ + switch (objtype) + { + case OBJECT_AGGREGATE: + return "AGGREGATE"; + case OBJECT_CAST: + return "CAST"; + case OBJECT_COLUMN: + return "COLUMN"; + case OBJECT_COLLATION: + return "COLLATION"; + case OBJECT_CONVERSION: + return "CONVERSION"; + case OBJECT_DATABASE: + return "DATABASE"; + case OBJECT_DOMAIN: + return "DOMAIN"; + case OBJECT_EVENT_TRIGGER: + return "EVENT TRIGGER"; + case OBJECT_EXTENSION: + return "EXTENSION"; + case OBJECT_FDW: + return "FOREIGN DATA WRAPPER"; + case OBJECT_FOREIGN_SERVER: + return "SERVER"; + case OBJECT_FOREIGN_TABLE: + return "FOREIGN TABLE"; + case OBJECT_FUNCTION: + return "FUNCTION"; + case OBJECT_INDEX: + return "INDEX"; + case OBJECT_LANGUAGE: + return "LANGUAGE"; + case OBJECT_LARGEOBJECT: + return "LARGE OBJECT"; + case OBJECT_MATVIEW: + return "MATERIALIZED VIEW"; + case OBJECT_OPCLASS: + return "OPERATOR CLASS"; + case OBJECT_OPERATOR: + return "OPERATOR"; + case OBJECT_OPFAMILY: + return "OPERATOR FAMILY"; + case OBJECT_ROLE: + return "ROLE"; + case OBJECT_RULE: + return "RULE"; + case OBJECT_SCHEMA: + return "SCHEMA"; + case OBJECT_SEQUENCE: + return "SEQUENCE"; + case OBJECT_TABLE: + return "TABLE"; + case OBJECT_TABLESPACE: + return "TABLESPACE"; + case OBJECT_TRIGGER: + return "TRIGGER"; + case OBJECT_TSCONFIGURATION: + return "TEXT SEARCH CONFIGURATION"; + case OBJECT_TSDICTIONARY: + return "TEXT SEARCH DICTIONARY"; + case OBJECT_TSPARSER: + return "TEXT SEARCH PARSER"; + case OBJECT_TSTEMPLATE: + return "TEXT SEARCH TEMPLATE"; + case OBJECT_TYPE: + return "TYPE"; + case OBJECT_USER_MAPPING: + return "USER MAPPING"; + case OBJECT_VIEW: + return "VIEW"; + + default: + elog(ERROR, "unsupported object type %d", objtype); + } +} + +static ObjTree * +deparse_RenameStmt(ObjectAddress address, Node *parsetree) +{ + RenameStmt *node = (RenameStmt *) parsetree; + ObjTree *renameStmt; + char *fmtstr; + Relation relation; + Oid schemaId; + + /* + * FIXME --- this code is missing support for inheritance behavioral flags, + * i.e. the "*" and ONLY elements. + */ + + /* + * In a ALTER .. RENAME command, we don't have the original name of the + * object in system catalogs: since we inspect them after the command has + * executed, the old name is already gone. Therefore, we extract it from + * the parse node. Note we still extract the schema name from the catalog + * (it might not be present in the parse node); it cannot possibly have + * changed anyway. + * + * XXX what if there's another event trigger running concurrently that + * renames the schema or moves the object to another schema? Seems + * pretty far-fetched, but possible nonetheless. + */ + switch (node->renameType) + { + case OBJECT_TABLE: + case OBJECT_SEQUENCE: + case OBJECT_VIEW: + case OBJECT_MATVIEW: + case OBJECT_INDEX: + case OBJECT_FOREIGN_TABLE: + fmtstr = psprintf("ALTER %s %%{if_exists}s %%{identity}D RENAME TO %%{newname}I", + stringify_objtype(node->renameType)); + relation = relation_open(address.objectId, AccessShareLock); + schemaId = RelationGetNamespace(relation); + renameStmt = new_objtree_VA(fmtstr, 0); + append_object_object(renameStmt, "identity", + new_objtree_for_qualname(schemaId, + node->relation->relname)); + append_string_object(renameStmt, "if_exists", + node->missing_ok ? "IF EXISTS" : ""); + relation_close(relation, AccessShareLock); + break; + + case OBJECT_ATTRIBUTE: + case OBJECT_COLUMN: + relation = relation_open(address.objectId, AccessShareLock); + schemaId = RelationGetNamespace(relation); + + if (node->renameType == OBJECT_ATTRIBUTE) + { + fmtstr = "ALTER TYPE %{identity}D RENAME ATTRIBUTE %{colname}I TO %{newname}I %{cascade}s"; + renameStmt = new_objtree_VA(fmtstr, 0); + append_object_object(renameStmt, "cascade", + new_objtree_VA("CASCADE", 1, + "present", ObjTypeBool, + node->behavior == DROP_CASCADE)); + } + else + { + fmtstr = psprintf("ALTER %s %%{if_exists}s %%{identity}D RENAME COLUMN %%{colname}I TO %%{newname}I", + stringify_objtype(node->relationType)); + renameStmt = new_objtree_VA(fmtstr, 0); + } + + append_object_object(renameStmt, "identity", + new_objtree_for_qualname(schemaId, + node->relation->relname)); + append_string_object(renameStmt, "colname", node->subname); + + /* composite types do not support IF EXISTS */ + if (node->relationType != OBJECT_TYPE) + append_string_object(renameStmt, "if_exists", + node->missing_ok ? "IF EXISTS" : ""); + relation_close(relation, AccessShareLock); + + break; + + case OBJECT_SCHEMA: + { + renameStmt = + new_objtree_VA("ALTER SCHEMA %{identity}I RENAME TO %{newname}I", + 0); + append_string_object(renameStmt, "identity", node->subname); + } + break; + + case OBJECT_FDW: + case OBJECT_LANGUAGE: + case OBJECT_FOREIGN_SERVER: + { + fmtstr = psprintf("ALTER %s %%{identity}I RENAME TO %%{newname}I", + stringify_objtype(node->renameType)); + renameStmt = new_objtree_VA(fmtstr, 0); + append_string_object(renameStmt, "identity", + strVal(linitial(node->object))); + } + break; + + case OBJECT_COLLATION: + case OBJECT_CONVERSION: + case OBJECT_DOMAIN: + case OBJECT_TSDICTIONARY: + case OBJECT_TSPARSER: + case OBJECT_TSTEMPLATE: + case OBJECT_TSCONFIGURATION: + case OBJECT_TYPE: + { + HeapTuple objTup; + Relation catalog; + Datum objnsp; + bool isnull; + AttrNumber Anum_namespace; + ObjTree *ident; + + /* obtain object tuple */ + catalog = relation_open(address.classId, AccessShareLock); + objTup = get_catalog_object_by_oid(catalog, address.objectId); + + /* obtain namespace */ + Anum_namespace = get_object_attnum_namespace(address.classId); + objnsp = heap_getattr(objTup, Anum_namespace, + RelationGetDescr(catalog), &isnull); + if (isnull) + elog(ERROR, "invalid NULL namespace"); + + fmtstr = psprintf("ALTER %s %%{identity}D RENAME TO %%{newname}I", + stringify_objtype(node->renameType)); + renameStmt = new_objtree_VA(fmtstr, 0); + ident = new_objtree_for_qualname(DatumGetObjectId(objnsp), + strVal(llast(node->object))); + append_object_object(renameStmt, "identity", ident); + + relation_close(catalog, AccessShareLock); + } + break; + + case OBJECT_OPCLASS: + case OBJECT_OPFAMILY: + { + HeapTuple objTup; + HeapTuple amTup; + Relation catalog; + Datum objnsp; + bool isnull; + AttrNumber Anum_namespace; + Oid amoid; + ObjTree *ident; + + /* obtain object tuple */ + catalog = relation_open(address.classId, AccessShareLock); + objTup = get_catalog_object_by_oid(catalog, address.objectId); + + /* obtain namespace */ + Anum_namespace = get_object_attnum_namespace(address.classId); + objnsp = heap_getattr(objTup, Anum_namespace, + RelationGetDescr(catalog), &isnull); + if (isnull) + elog(ERROR, "invalid NULL namespace"); + + /* obtain AM tuple */ + if (node->renameType == OBJECT_OPCLASS) + amoid = ((Form_pg_opclass) GETSTRUCT(objTup))->opcmethod; + else + amoid = ((Form_pg_opfamily) GETSTRUCT(objTup))->opfmethod; + amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(amoid)); + if (!HeapTupleIsValid(amTup)) + elog(ERROR, "cache lookup failed for access method %u", amoid); + + + fmtstr = psprintf("ALTER %s %%{identity}D USING %%{amname}I RENAME TO %%{newname}I", + stringify_objtype(node->renameType)); + renameStmt = new_objtree_VA(fmtstr, 0); + + /* add the identity clauses */ + ident = new_objtree_for_qualname(DatumGetObjectId(objnsp), + strVal(llast(node->object))); + append_object_object(renameStmt, "identity", ident); + append_string_object(renameStmt, "amname", + pstrdup(NameStr(((Form_pg_am) GETSTRUCT(amTup))->amname))); + + ReleaseSysCache(amTup); + relation_close(catalog, AccessShareLock); + } + break; + + case OBJECT_AGGREGATE: + case OBJECT_FUNCTION: + { + char *ident; + HeapTuple proctup; + Form_pg_proc procform; + + proctup = SearchSysCache1(PROCOID, + ObjectIdGetDatum(address.objectId)); + if (!HeapTupleIsValid(proctup)) + elog(ERROR, "cache lookup failed for procedure %u", + address.objectId); + procform = (Form_pg_proc) GETSTRUCT(proctup); + + /* XXX does this work for ordered-set aggregates? */ + ident = psprintf("%s%s", + quote_qualified_identifier(get_namespace_name(procform->pronamespace), + strVal(llast(node->object))), + format_procedure_args(address.objectId, true)); + + fmtstr = psprintf("ALTER %s %%{identity}s RENAME TO %%{newname}I", + stringify_objtype(node->renameType)); + renameStmt = new_objtree_VA(fmtstr, 1, + "identity", ObjTypeString, ident); + + ReleaseSysCache(proctup); + } + break; + + case OBJECT_TABCONSTRAINT: + case OBJECT_DOMCONSTRAINT: + { + HeapTuple conTup; + Form_pg_constraint constrForm; + ObjTree *ident; + + conTup = SearchSysCache1(CONSTROID, address.objectId); + constrForm = (Form_pg_constraint) GETSTRUCT(conTup); + + if (node->renameType == OBJECT_TABCONSTRAINT) + { + fmtstr = "ALTER TABLE %{identity}D RENAME CONSTRAINT %{conname}I TO %{newname}I"; + ident = new_objtree_for_qualname_id(RelationRelationId, + constrForm->conrelid); + } + else + { + fmtstr = "ALTER DOMAIN %{identity}D RENAME CONSTRAINT %{conname}I TO %{newname}I"; + ident = new_objtree_for_qualname_id(TypeRelationId, + constrForm->contypid); + } + renameStmt = new_objtree_VA(fmtstr, 2, + "conname", ObjTypeString, node->subname, + "identity", ObjTypeObject, ident); + ReleaseSysCache(conTup); + } + break; + + case OBJECT_RULE: + { + HeapTuple rewrTup; + Form_pg_rewrite rewrForm; + Relation pg_rewrite; + + pg_rewrite = relation_open(RewriteRelationId, AccessShareLock); + rewrTup = get_catalog_object_by_oid(pg_rewrite, address.objectId); + rewrForm = (Form_pg_rewrite) GETSTRUCT(rewrTup); + + renameStmt = new_objtree_VA("ALTER RULE %{rulename}I ON %{identity}D RENAME TO %{newname}I", + 0); + append_string_object(renameStmt, "rulename", node->subname); + append_object_object(renameStmt, "identity", + new_objtree_for_qualname_id(RelationRelationId, + rewrForm->ev_class)); + relation_close(pg_rewrite, AccessShareLock); + } + break; + + case OBJECT_TRIGGER: + { + HeapTuple trigTup; + Form_pg_trigger trigForm; + Relation pg_trigger; + + pg_trigger = relation_open(TriggerRelationId, AccessShareLock); + trigTup = get_catalog_object_by_oid(pg_trigger, address.objectId); + trigForm = (Form_pg_trigger) GETSTRUCT(trigTup); + + renameStmt = new_objtree_VA("ALTER TRIGGER %{triggername}I ON %{identity}D RENAME TO %{newname}I", + 0); + append_string_object(renameStmt, "triggername", node->subname); + append_object_object(renameStmt, "identity", + new_objtree_for_qualname_id(RelationRelationId, + trigForm->tgrelid)); + relation_close(pg_trigger, AccessShareLock); + } + break; + + case OBJECT_POLICY: + { + HeapTuple polTup; + Form_pg_policy polForm; + Relation pg_policy; + ScanKeyData key; + SysScanDesc scan; + + pg_policy = relation_open(PolicyRelationId, AccessShareLock); + ScanKeyInit(&key, ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(address.objectId)); + scan = systable_beginscan(pg_policy, PolicyOidIndexId, true, + NULL, 1, &key); + polTup = systable_getnext(scan); + if (!HeapTupleIsValid(polTup)) + elog(ERROR, "cache lookup failed for policy %u", address.objectId); + polForm = (Form_pg_policy) GETSTRUCT(polTup); + + renameStmt = new_objtree_VA("ALTER POLICY %{if_exists}s %{policyname}I on %{identity}D RENAME TO %{newname}I", + 0); + append_string_object(renameStmt, "policyname", node->subname); + append_object_object(renameStmt, "identity", + new_objtree_for_qualname_id(RelationRelationId, + polForm->polrelid)); + append_string_object(renameStmt, "if_exists", + node->missing_ok ? "IF EXISTS" : ""); + systable_endscan(scan); + relation_close(pg_policy, AccessShareLock); + } + break; + + default: + elog(ERROR, "unsupported object type %d", node->renameType); + } + + append_string_object(renameStmt, "newname", node->newname); + + return renameStmt; +} + +/* + * deparse an ALTER ... SET SCHEMA command. + */ +static ObjTree * +deparse_AlterObjectSchemaStmt(ObjectAddress address, Node *parsetree, + ObjectAddress oldschema) +{ + AlterObjectSchemaStmt *node = (AlterObjectSchemaStmt *) parsetree; + ObjTree *alterStmt; + char *fmt; + char *identity; + char *newschema; + char *oldschname; + char *ident; + + newschema = node->newschema; + + fmt = psprintf("ALTER %s %%{identity}s SET SCHEMA %%{newschema}I", + stringify_objtype(node->objectType)); + alterStmt = new_objtree_VA(fmt, 0); + append_string_object(alterStmt, "newschema", newschema); + + /* + * Since the command has already taken place from the point of view of + * catalogs, getObjectIdentity returns the object name with the already + * changed schema. The output of our deparsing must return the original + * schema name however, so we chop the schema name off the identity string + * and then prepend the quoted schema name. + * + * XXX This is pretty clunky. Can we do better? + */ + identity = getObjectIdentity(&address); + oldschname = get_namespace_name(oldschema.objectId); + if (!oldschname) + elog(ERROR, "cache lookup failed for schema with OID %u", oldschema.objectId); + ident = psprintf("%s%s", + quote_identifier(oldschname), + identity + strlen(quote_identifier(newschema))); + append_string_object(alterStmt, "identity", ident); + + return alterStmt; +} + +static ObjTree * +deparse_AlterOwnerStmt(ObjectAddress address, Node *parsetree) +{ + AlterOwnerStmt *node = (AlterOwnerStmt *) parsetree; + ObjTree *ownerStmt; + char *fmt; + + fmt = psprintf("ALTER %s %%{identity}s OWNER TO %%{newowner}I", + stringify_objtype(node->objectType)); + ownerStmt = new_objtree_VA(fmt, 0); + append_string_object(ownerStmt, "newowner", + get_rolespec_name(node->newowner)); + + append_string_object(ownerStmt, "identity", + getObjectIdentity(&address)); + + return ownerStmt; +} + +static inline ObjElem * +deparse_Seq_Cache(ObjTree *parent, Form_pg_sequence seqdata) +{ + ObjTree *tmp; + char *tmpstr; + + tmpstr = psprintf(INT64_FORMAT, seqdata->cache_value); + tmp = new_objtree_VA("CACHE %{value}s", + 2, + "clause", ObjTypeString, "cache", + "value", ObjTypeString, tmpstr); + return new_object_object(tmp); +} + +static inline ObjElem * +deparse_Seq_Cycle(ObjTree *parent, Form_pg_sequence seqdata) +{ + ObjTree *tmp; + + tmp = new_objtree_VA("%{no}s CYCLE", + 2, + "clause", ObjTypeString, "cycle", + "no", ObjTypeString, + seqdata->is_cycled ? "" : "NO"); + return new_object_object(tmp); +} + +static inline ObjElem * +deparse_Seq_IncrementBy(ObjTree *parent, Form_pg_sequence seqdata) +{ + ObjTree *tmp; + char *tmpstr; + + tmpstr = psprintf(INT64_FORMAT, seqdata->increment_by); + tmp = new_objtree_VA("INCREMENT BY %{value}s", + 2, + "clause", ObjTypeString, "increment_by", + "value", ObjTypeString, tmpstr); + return new_object_object(tmp); +} + +static inline ObjElem * +deparse_Seq_Minvalue(ObjTree *parent, Form_pg_sequence seqdata) +{ + ObjTree *tmp; + char *tmpstr; + + tmpstr = psprintf(INT64_FORMAT, seqdata->min_value); + tmp = new_objtree_VA("MINVALUE %{value}s", + 2, + "clause", ObjTypeString, "minvalue", + "value", ObjTypeString, tmpstr); + return new_object_object(tmp); +} + +static inline ObjElem * +deparse_Seq_Maxvalue(ObjTree *parent, Form_pg_sequence seqdata) +{ + ObjTree *tmp; + char *tmpstr; + + tmpstr = psprintf(INT64_FORMAT, seqdata->max_value); + tmp = new_objtree_VA("MAXVALUE %{value}s", + 2, + "clause", ObjTypeString, "maxvalue", + "value", ObjTypeString, tmpstr); + return new_object_object(tmp); +} + +static inline ObjElem * +deparse_Seq_Startwith(ObjTree *parent, Form_pg_sequence seqdata) +{ + ObjTree *tmp; + char *tmpstr; + + tmpstr = psprintf(INT64_FORMAT, seqdata->start_value); + tmp = new_objtree_VA("START WITH %{value}s", + 2, + "clause", ObjTypeString, "start", + "value", ObjTypeString, tmpstr); + return new_object_object(tmp); +} + +static inline ObjElem * +deparse_Seq_Restart(ObjTree *parent, Form_pg_sequence seqdata) +{ + ObjTree *tmp; + char *tmpstr; + + tmpstr = psprintf(INT64_FORMAT, seqdata->last_value); + tmp = new_objtree_VA("RESTART %{value}s", + 2, + "clause", ObjTypeString, "restart", + "value", ObjTypeString, tmpstr); + return new_object_object(tmp); +} + +static ObjElem * +deparse_Seq_OwnedBy(ObjTree *parent, Oid sequenceId) +{ + ObjTree *ownedby = NULL; + Relation depRel; + SysScanDesc scan; + ScanKeyData keys[3]; + HeapTuple tuple; + + depRel = heap_open(DependRelationId, AccessShareLock); + ScanKeyInit(&keys[0], + Anum_pg_depend_classid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(RelationRelationId)); + ScanKeyInit(&keys[1], + Anum_pg_depend_objid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(sequenceId)); + ScanKeyInit(&keys[2], + Anum_pg_depend_objsubid, + BTEqualStrategyNumber, F_INT4EQ, + Int32GetDatum(0)); + + scan = systable_beginscan(depRel, DependDependerIndexId, true, + NULL, 3, keys); + while (HeapTupleIsValid(tuple = systable_getnext(scan))) + { + Oid ownerId; + Form_pg_depend depform; + ObjTree *tmp; + char *colname; + + depform = (Form_pg_depend) GETSTRUCT(tuple); + + /* only consider AUTO dependencies on pg_class */ + if (depform->deptype != DEPENDENCY_AUTO) + continue; + if (depform->refclassid != RelationRelationId) + continue; + if (depform->refobjsubid <= 0) + continue; + + ownerId = depform->refobjid; + colname = get_attname(ownerId, depform->refobjsubid); + if (colname == NULL) + continue; + + tmp = new_objtree_for_qualname_id(RelationRelationId, ownerId); + append_string_object(tmp, "attrname", colname); + ownedby = new_objtree_VA("OWNED BY %{owner}D", + 2, + "clause", ObjTypeString, "owned", + "owner", ObjTypeObject, tmp); + } + + systable_endscan(scan); + relation_close(depRel, AccessShareLock); + + /* + * If there's no owner column, emit an empty OWNED BY element, set up so + * that it won't print anything. + */ + if (!ownedby) + /* XXX this shouldn't happen ... */ + ownedby = new_objtree_VA("OWNED BY %{owner}D", + 3, + "clause", ObjTypeString, "owned", + "owner", ObjTypeNull, + "present", ObjTypeBool, false); + return new_object_object(ownedby); +} + +/* + * deparse_CreateSeqStmt + * deparse a CreateSeqStmt + * + * Given a sequence OID and the parsetree that created it, return an ObjTree + * representing the creation command. + */ +static ObjTree * +deparse_CreateSeqStmt(Oid objectId, Node *parsetree) +{ + ObjTree *createSeq; + ObjTree *tmp; + Relation relation = relation_open(objectId, AccessShareLock); + Form_pg_sequence seqdata; + List *elems = NIL; + + seqdata = get_sequence_values(objectId); + + createSeq = + new_objtree_VA("CREATE %{persistence}s SEQUENCE %{identity}D " + "%{definition: }s", + 1, + "persistence", ObjTypeString, + get_persistence_str(relation->rd_rel->relpersistence)); + + tmp = new_objtree_for_qualname(relation->rd_rel->relnamespace, + RelationGetRelationName(relation)); + append_object_object(createSeq, "identity", tmp); + + /* definition elements */ + elems = lappend(elems, deparse_Seq_Cache(createSeq, seqdata)); + elems = lappend(elems, deparse_Seq_Cycle(createSeq, seqdata)); + elems = lappend(elems, deparse_Seq_IncrementBy(createSeq, seqdata)); + elems = lappend(elems, deparse_Seq_Minvalue(createSeq, seqdata)); + elems = lappend(elems, deparse_Seq_Maxvalue(createSeq, seqdata)); + elems = lappend(elems, deparse_Seq_Startwith(createSeq, seqdata)); + elems = lappend(elems, deparse_Seq_Restart(createSeq, seqdata)); + /* we purposefully do not emit OWNED BY here */ + + append_array_object(createSeq, "definition", elems); + + relation_close(relation, AccessShareLock); + + return createSeq; +} + +/* + * deparse_AlterSeqStmt + * deparse an AlterSeqStmt + * + * Given a sequence OID and a parsetree that modified it, return an ObjTree + * representing the alter command. + */ +static ObjTree * +deparse_AlterSeqStmt(Oid objectId, Node *parsetree) +{ + ObjTree *alterSeq; + ObjTree *tmp; + Relation relation = relation_open(objectId, AccessShareLock); + Form_pg_sequence seqdata; + List *elems = NIL; + ListCell *cell; + + seqdata = get_sequence_values(objectId); + + alterSeq = + new_objtree_VA("ALTER SEQUENCE %{identity}D %{definition: }s", 0); + tmp = new_objtree_for_qualname(relation->rd_rel->relnamespace, + RelationGetRelationName(relation)); + append_object_object(alterSeq, "identity", tmp); + + foreach(cell, ((AlterSeqStmt *) parsetree)->options) + { + DefElem *elem = (DefElem *) lfirst(cell); + ObjElem *newelm; + + if (strcmp(elem->defname, "cache") == 0) + newelm = deparse_Seq_Cache(alterSeq, seqdata); + else if (strcmp(elem->defname, "cycle") == 0) + newelm = deparse_Seq_Cycle(alterSeq, seqdata); + else if (strcmp(elem->defname, "increment") == 0) + newelm = deparse_Seq_IncrementBy(alterSeq, seqdata); + else if (strcmp(elem->defname, "minvalue") == 0) + newelm = deparse_Seq_Minvalue(alterSeq, seqdata); + else if (strcmp(elem->defname, "maxvalue") == 0) + newelm = deparse_Seq_Maxvalue(alterSeq, seqdata); + else if (strcmp(elem->defname, "start") == 0) + newelm = deparse_Seq_Startwith(alterSeq, seqdata); + else if (strcmp(elem->defname, "restart") == 0) + newelm = deparse_Seq_Restart(alterSeq, seqdata); + else if (strcmp(elem->defname, "owned_by") == 0) + newelm = deparse_Seq_OwnedBy(alterSeq, objectId); + else + elog(ERROR, "invalid sequence option %s", elem->defname); + + elems = lappend(elems, newelm); + } + + append_array_object(alterSeq, "definition", elems); + + relation_close(relation, AccessShareLock); + + return alterSeq; +} + +/* + * deparse_IndexStmt + * deparse an IndexStmt + * + * Given an index OID and the parsetree that created it, return an ObjTree + * representing the creation command. + * + * If the index corresponds to a constraint, NULL is returned. + */ +static ObjTree * +deparse_IndexStmt(Oid objectId, Node *parsetree) +{ + IndexStmt *node = (IndexStmt *) parsetree; + ObjTree *indexStmt; + ObjTree *tmp; + Relation idxrel; + Relation heaprel; + char *index_am; + char *definition; + char *reloptions; + char *tablespace; + char *whereClause; + + if (node->primary || node->isconstraint) + { + /* + * indexes for PRIMARY KEY and other constraints are output + * separately; return empty here. + */ + return NULL; + } + + idxrel = relation_open(objectId, AccessShareLock); + heaprel = relation_open(idxrel->rd_index->indrelid, AccessShareLock); + + pg_get_indexdef_detailed(objectId, + &index_am, &definition, &reloptions, + &tablespace, &whereClause); + + indexStmt = + new_objtree_VA("CREATE %{unique}s INDEX %{concurrently}s %{if_not_exists}s %{name}I " + "ON %{table}D USING %{index_am}s (%{definition}s) " + "%{with}s %{tablespace}s %{where_clause}s", + 6, + "unique", ObjTypeString, node->unique ? "UNIQUE" : "", + "concurrently", ObjTypeString, + node->concurrent ? "CONCURRENTLY" : "", + "if_not_exists", ObjTypeString, node->if_not_exists ? "IF NOT EXISTS" : "", + "name", ObjTypeString, RelationGetRelationName(idxrel), + "definition", ObjTypeString, definition, + "index_am", ObjTypeString, index_am); + + tmp = new_objtree_for_qualname(heaprel->rd_rel->relnamespace, + RelationGetRelationName(heaprel)); + append_object_object(indexStmt, "table", tmp); + + /* reloptions */ + tmp = new_objtree_VA("WITH (%{opts}s)", 0); + if (reloptions) + append_string_object(tmp, "opts", reloptions); + else + append_bool_object(tmp, "present", false); + append_object_object(indexStmt, "with", tmp); + + /* tablespace */ + tmp = new_objtree_VA("TABLESPACE %{tablespace}s", 0); + if (tablespace) + append_string_object(tmp, "tablespace", tablespace); + else + append_bool_object(tmp, "present", false); + append_object_object(indexStmt, "tablespace", tmp); + + /* WHERE clause */ + tmp = new_objtree_VA("WHERE %{where}s", 0); + if (whereClause) + append_string_object(tmp, "where", whereClause); + else + append_bool_object(tmp, "present", false); + append_object_object(indexStmt, "where_clause", tmp); + + heap_close(idxrel, AccessShareLock); + heap_close(heaprel, AccessShareLock); + + return indexStmt; +} + +static ObjTree * +deparse_RuleStmt(Oid objectId, Node *parsetree) +{ + RuleStmt *node = (RuleStmt *) parsetree; + ObjTree *ruleStmt; + ObjTree *tmp; + Relation pg_rewrite; + Form_pg_rewrite rewrForm; + HeapTuple rewrTup; + SysScanDesc scan; + ScanKeyData key; + Datum ev_qual; + Datum ev_actions; + bool isnull; + char *qual; + List *actions; + List *list; + ListCell *cell; + + pg_rewrite = heap_open(RewriteRelationId, AccessShareLock); + ScanKeyInit(&key, + ObjectIdAttributeNumber, + BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(objectId)); + + scan = systable_beginscan(pg_rewrite, RewriteOidIndexId, true, + NULL, 1, &key); + rewrTup = systable_getnext(scan); + if (!HeapTupleIsValid(rewrTup)) + elog(ERROR, "cache lookup failed for rewrite rule with oid %u", + objectId); + + rewrForm = (Form_pg_rewrite) GETSTRUCT(rewrTup); + + ruleStmt = + new_objtree_VA("CREATE %{or_replace}s RULE %{identity}I " + "AS ON %{event}s TO %{table}D %{where_clause}s " + "DO %{instead}s (%{actions:; }s)", 2, + "identity", ObjTypeString, node->rulename, + "or_replace", ObjTypeString, + node->replace ? "OR REPLACE" : ""); + append_string_object(ruleStmt, "event", + node->event == CMD_SELECT ? "SELECT" : + node->event == CMD_UPDATE ? "UPDATE" : + node->event == CMD_DELETE ? "DELETE" : + node->event == CMD_INSERT ? "INSERT" : "XXX"); + append_object_object(ruleStmt, "table", + new_objtree_for_qualname_id(RelationRelationId, + rewrForm->ev_class)); + + append_string_object(ruleStmt, "instead", + node->instead ? "INSTEAD" : "ALSO"); + + ev_qual = heap_getattr(rewrTup, Anum_pg_rewrite_ev_qual, + RelationGetDescr(pg_rewrite), &isnull); + ev_actions = heap_getattr(rewrTup, Anum_pg_rewrite_ev_action, + RelationGetDescr(pg_rewrite), &isnull); + + pg_get_ruledef_details(ev_qual, ev_actions, &qual, &actions); + + tmp = new_objtree_VA("WHERE %{clause}s", 0); + + if (qual) + append_string_object(tmp, "clause", qual); + else + { + append_null_object(tmp, "clause"); + append_bool_object(tmp, "present", false); + } + + append_object_object(ruleStmt, "where_clause", tmp); + + list = NIL; + foreach(cell, actions) + { + char *action = lfirst(cell); + + list = lappend(list, new_string_object(action)); + } + append_array_object(ruleStmt, "actions", list); + + systable_endscan(scan); + heap_close(pg_rewrite, AccessShareLock); + + return ruleStmt; +} + +static ObjTree * +deparse_CreateTableAsStmt(Oid objectId, Node *parsetree) +{ + CreateTableAsStmt *node = (CreateTableAsStmt *) parsetree; + Relation relation = relation_open(objectId, AccessShareLock); + ObjTree *createStmt; + ObjTree *tmp; + ObjTree *tmp2; + char *fmt; + List *list; + ListCell *cell; + + /* + * Reject unsupported case right away. + */ + if (((Query *) (node->query))->commandType == CMD_UTILITY) + elog(ERROR, "unimplemented deparse of CREATE TABLE AS EXECUTE"); + + /* + * Note that INSERT INTO is deparsed as CREATE TABLE AS. They are + * functionally equivalent synonyms so there is no harm from this. + */ + if (node->relkind == OBJECT_MATVIEW) + fmt = "CREATE %{persistence}s MATERIALIZED VIEW %{if_not_exists}s " + "%{identity}D %{columns}s %{with_clause}s %{tablespace}s " + "AS %{query}s %{with_no_data}s"; + else + fmt = "CREATE %{persistence}s TABLE %{if_not_exists}s " + "%{identity}D %{columns}s %{with_clause}s %{on_commit}s %{tablespace}s " + "AS %{query}s %{with_no_data}s"; + + createStmt = + new_objtree_VA(fmt, 2, + "persistence", ObjTypeString, + get_persistence_str(node->into->rel->relpersistence), + "if_not_exists", ObjTypeString, + node->if_not_exists ? "IF NOT EXISTS" : ""); + append_object_object(createStmt, "identity", + new_objtree_for_qualname_id(RelationRelationId, + objectId)); + + /* Add an ON COMMIT clause. CREATE MATERIALIZED VIEW doesn't have one */ + if (node->relkind == OBJECT_TABLE) + { + append_object_object(createStmt, "on_commit", + deparse_OnCommitClause(node->into->onCommit)); + } + + /* add a TABLESPACE clause */ + tmp = new_objtree_VA("TABLESPACE %{tablespace}I", 0); + if (node->into->tableSpaceName) + append_string_object(tmp, "tablespace", node->into->tableSpaceName); + else + { + append_null_object(tmp, "tablespace"); + append_bool_object(tmp, "present", false); + } + append_object_object(createStmt, "tablespace", tmp); + + /* add a WITH NO DATA clause */ + tmp = new_objtree_VA("WITH NO DATA", 1, + "present", ObjTypeBool, + node->into->skipData ? true : false); + append_object_object(createStmt, "with_no_data", tmp); + + /* add the column list, if any */ + if (node->into->colNames == NIL) + tmp = new_objtree_VA("", 1, + "present", ObjTypeBool, false); + else + { + ListCell *cell; + + list = NIL; + foreach (cell, node->into->colNames) + list = lappend(list, new_string_object(strVal(lfirst(cell)))); + + tmp = new_objtree_VA("(%{columns:, }I)", 1, + "columns", ObjTypeArray, list); + } + append_object_object(createStmt, "columns", tmp); + + /* add the query */ + Assert(IsA(node->query, Query)); + append_string_object(createStmt, "query", + pg_get_createtableas_def((Query *) node->query)); + + + /* + * WITH clause. In CREATE TABLE AS, like for CREATE TABLE, we always emit + * one, containing at least the OIDS option; CREATE MATERIALIZED VIEW + * doesn't support the oids option, so we have to make the clause reduce to + * empty if no other options are specified. + */ + tmp = new_objtree_VA("WITH (%{with:, }s)", 0); + if (node->relkind == OBJECT_MATVIEW && node->into->options == NIL) + append_bool_object(tmp, "present", false); + else + { + if (node->relkind == OBJECT_TABLE) + { + tmp2 = new_objtree_VA("oids=%{value}s", 2, + "option", ObjTypeString, "oids", + "value", ObjTypeString, + relation->rd_rel->relhasoids ? "ON" : "OFF"); + list = list_make1(new_object_object(tmp2)); + } + else + list = NIL; + + foreach (cell, node->into->options) + { + DefElem *opt = (DefElem *) lfirst(cell); + + if (strcmp(opt->defname, "oids") == 0) + continue; + + tmp2 = deparse_DefElem(opt, false); + list = lappend(list, new_object_object(tmp2)); + } + append_array_object(tmp, "with", list); + } + append_object_object(createStmt, "with_clause", tmp); + + relation_close(relation, AccessShareLock); + + return createStmt; +} + +/* + * deparse_CreateSchemaStmt + * deparse a CreateSchemaStmt + * + * Given a schema OID and the parsetree that created it, return an ObjTree + * representing the creation command. + * + * Note we don't output the schema elements given in the creation command. + * They must be output separately. (In the current implementation, + * CreateSchemaCommand passes them back to ProcessUtility, which will lead to + * this file if appropriate.) + */ +static ObjTree * +deparse_CreateSchemaStmt(Oid objectId, Node *parsetree) +{ + CreateSchemaStmt *node = (CreateSchemaStmt *) parsetree; + ObjTree *createSchema; + ObjTree *auth; + + createSchema = + new_objtree_VA("CREATE SCHEMA %{if_not_exists}s %{name}I %{authorization}s", + 2, + "name", ObjTypeString, node->schemaname, + "if_not_exists", ObjTypeString, + node->if_not_exists ? "IF NOT EXISTS" : ""); + + auth = new_objtree_VA("AUTHORIZATION %{authorization_role}I", 0); + if (node->authrole) + append_string_object(auth, "authorization_role", + get_rolespec_name(node->authrole)); + else + { + append_null_object(auth, "authorization_role"); + append_bool_object(auth, "present", false); + } + append_object_object(createSchema, "authorization", auth); + + return createSchema; +} + +static ObjTree * +deparse_AlterEnumStmt(Oid objectId, Node *parsetree) +{ + AlterEnumStmt *node = (AlterEnumStmt *) parsetree; + ObjTree *alterEnum; + ObjTree *tmp; + + alterEnum = + new_objtree_VA("ALTER TYPE %{identity}D ADD VALUE %{if_not_exists}s %{value}L %{position}s", + 0); + + append_string_object(alterEnum, "if_not_exists", + node->skipIfExists ? "IF NOT EXISTS" : ""); + append_object_object(alterEnum, "identity", + new_objtree_for_qualname_id(TypeRelationId, + objectId)); + append_string_object(alterEnum, "value", node->newVal); + tmp = new_objtree_VA("%{after_or_before}s %{neighbour}L", 0); + if (node->newValNeighbor) + { + append_string_object(tmp, "after_or_before", + node->newValIsAfter ? "AFTER" : "BEFORE"); + append_string_object(tmp, "neighbour", node->newValNeighbor); + } + else + { + append_bool_object(tmp, "present", false); + } + append_object_object(alterEnum, "position", tmp); + + return alterEnum; +} + +/* + * Append a NULL-or-quoted-literal clause. Useful for COMMENT and SECURITY + * LABEL. + */ +static void +append_literal_or_null(ObjTree *mainobj, char *elemname, char *value) +{ + ObjTree *top; + ObjTree *part; + + top = new_objtree_VA("%{null}s%{literal}s", 0); + part = new_objtree_VA("NULL", 1, + "present", ObjTypeBool, + !value); + append_object_object(top, "null", part); + part = new_objtree_VA("%{value}L", 1, + "present", ObjTypeBool, + !!value); + if (value) + append_string_object(part, "value", value); + append_object_object(top, "literal", part); + + append_object_object(mainobj, elemname, top); +} + +/* + * Deparse a CommentStmt when it pertains to a constraint. + */ +static ObjTree * +deparse_CommentOnConstraintSmt(Oid objectId, Node *parsetree) +{ + CommentStmt *node = (CommentStmt *) parsetree; + ObjTree *comment; + HeapTuple constrTup; + Form_pg_constraint constrForm; + char *fmt; + ObjectAddress addr; + + Assert(node->objtype == OBJECT_TABCONSTRAINT || node->objtype == OBJECT_DOMCONSTRAINT); + + constrTup = SearchSysCache1(CONSTROID, objectId); + if (!HeapTupleIsValid(constrTup)) + elog(ERROR, "cache lookup failed for constraint %u", objectId); + constrForm = (Form_pg_constraint) GETSTRUCT(constrTup); + + if (OidIsValid(constrForm->conrelid)) + ObjectAddressSet(addr, RelationRelationId, constrForm->conrelid); + else + ObjectAddressSet(addr, TypeRelationId, constrForm->contypid); + + fmt = psprintf("COMMENT ON CONSTRAINT %%{identity}s ON %s%%{parentobj}s IS %%{comment}s", + node->objtype == OBJECT_TABCONSTRAINT ? "" : "DOMAIN "); + comment = new_objtree_VA(fmt, 0); + + /* Add the comment clause */ + append_literal_or_null(comment, "comment", node->comment); + + append_string_object(comment, "identity", pstrdup(NameStr(constrForm->conname))); + + append_string_object(comment, "parentobj", + getObjectIdentity(&addr)); + + ReleaseSysCache(constrTup); + + return comment; +} + +static ObjTree * +deparse_CommentStmt(ObjectAddress address, Node *parsetree) +{ + CommentStmt *node = (CommentStmt *) parsetree; + ObjTree *comment; + char *fmt; + char *identity; + + /* + * Constraints are sufficiently different that it is easier to handle them + * separately. + */ + if (node->objtype == OBJECT_DOMCONSTRAINT || + node->objtype == OBJECT_TABCONSTRAINT) + { + Assert(address.classId == ConstraintRelationId); + return deparse_CommentOnConstraintSmt(address.objectId, parsetree); + } + + fmt = psprintf("COMMENT ON %s %%{identity}s IS %%{comment}s", + stringify_objtype(node->objtype)); + comment = new_objtree_VA(fmt, 0); + + /* Add the comment clause; can be either NULL or a quoted literal. */ + append_literal_or_null(comment, "comment", node->comment); + + /* + * Add the object identity clause. For zero argument aggregates we need to + * add the (*) bit; in all other cases we can just use getObjectIdentity. + * + * XXX shouldn't we instead fix the object identities for zero-argument + * aggregates? + */ + if (node->objtype == OBJECT_AGGREGATE) + { + HeapTuple procTup; + Form_pg_proc procForm; + + procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(address.objectId)); + if (!HeapTupleIsValid(procTup)) + elog(ERROR, "cache lookup failed for procedure %u", address.objectId); + procForm = (Form_pg_proc) GETSTRUCT(procTup); + if (procForm->pronargs == 0) + identity = psprintf("%s(*)", + quote_qualified_identifier(get_namespace_name(procForm->pronamespace), + NameStr(procForm->proname))); + else + identity = getObjectIdentity(&address); + ReleaseSysCache(procTup); + } + else + identity = getObjectIdentity(&address); + + append_string_object(comment, "identity", identity); + + return comment; +} + +/* + * Add common clauses to CreatePolicy or AlterPolicy deparse objects + */ +static void +add_policy_clauses(ObjTree *policyStmt, Oid policyOid, List *roles, + bool do_qual, bool do_with_check) +{ + Relation polRel = heap_open(PolicyRelationId, AccessShareLock); + HeapTuple polTup = get_catalog_object_by_oid(polRel, policyOid); + ObjTree *tmp; + Form_pg_policy polForm; + + if (!HeapTupleIsValid(polTup)) + elog(ERROR, "cache lookup failed for policy %u", policyOid); + + polForm = (Form_pg_policy) GETSTRUCT(polTup); + + /* add the "ON table" clause */ + append_object_object(policyStmt, "table", + new_objtree_for_qualname_id(RelationRelationId, + polForm->polrelid)); + + /* + * Add the "TO role" clause, if any. In the CREATE case, it always + * contains at least PUBLIC, but in the ALTER case it might be empty. + */ + tmp = new_objtree_VA("TO %{role:, }R", 0); + if (roles) + { + List *list = NIL; + ListCell *cell; + + foreach (cell, roles) + { + RoleSpec *spec = (RoleSpec *) lfirst(cell); + + list = lappend(list, + new_object_object(new_objtree_for_rolespec(spec))); + } + append_array_object(tmp, "role", list); + } + else + { + append_bool_object(tmp, "present", false); + } + append_object_object(policyStmt, "to_role", tmp); + + /* add the USING clause, if any */ + tmp = new_objtree_VA("USING (%{expression}s)", 0); + if (do_qual) + { + Datum deparsed; + Datum storedexpr; + bool isnull; + + storedexpr = heap_getattr(polTup, Anum_pg_policy_polqual, + RelationGetDescr(polRel), &isnull); + if (isnull) + elog(ERROR, "invalid NULL polqual expression in policy %u", policyOid); + deparsed = DirectFunctionCall2(pg_get_expr, storedexpr, polForm->polrelid); + append_string_object(tmp, "expression", + TextDatumGetCString(deparsed)); + } + else + append_bool_object(tmp, "present", false); + append_object_object(policyStmt, "using", tmp); + + /* add the WITH CHECK clause, if any */ + tmp = new_objtree_VA("WITH CHECK (%{expression}s)", 0); + if (do_with_check) + { + Datum deparsed; + Datum storedexpr; + bool isnull; + + storedexpr = heap_getattr(polTup, Anum_pg_policy_polwithcheck, + RelationGetDescr(polRel), &isnull); + if (isnull) + elog(ERROR, "invalid NULL polwithcheck expression in policy %u", policyOid); + deparsed = DirectFunctionCall2(pg_get_expr, storedexpr, polForm->polrelid); + append_string_object(tmp, "expression", + TextDatumGetCString(deparsed)); + } + else + append_bool_object(tmp, "present", false); + append_object_object(policyStmt, "with_check", tmp); + + relation_close(polRel, AccessShareLock); +} + +static ObjTree * +deparse_CreatePolicyStmt(Oid objectId, Node *parsetree) +{ + CreatePolicyStmt *node = (CreatePolicyStmt *) parsetree; + ObjTree *policy; + + policy = new_objtree_VA("CREATE POLICY %{identity}I ON %{table}D %{for_command}s " + "%{to_role}s %{using}s %{with_check}s", 2, + "identity", ObjTypeString, node->policy_name, + "for_command", ObjTypeObject, + new_objtree_VA("FOR %{command}s", 1, + "command", ObjTypeString, node->cmd)); + + /* add the rest of the stuff */ + add_policy_clauses(policy, objectId, node->roles, !!node->qual, + !!node->with_check); + + return policy; +} + +static ObjTree * +deparse_AlterPolicyStmt(Oid objectId, Node *parsetree) +{ + AlterPolicyStmt *node = (AlterPolicyStmt *) parsetree; + ObjTree *policy; + + policy = new_objtree_VA("ALTER POLICY %{identity}I ON %{table}D " + "%{to_role}s %{using}s %{with_check}s", 1, + "identity", ObjTypeString, node->policy_name); + + /* add the rest of the stuff */ + add_policy_clauses(policy, objectId, node->roles, !!node->qual, + !!node->with_check); + + return policy; +} + +static ObjTree * +deparse_SecLabelStmt(ObjectAddress address, Node *parsetree) +{ + SecLabelStmt *node = (SecLabelStmt *) parsetree; + ObjTree *label; + char *fmt; + + Assert(node->provider); + + fmt = psprintf("SECURITY LABEL FOR %%{provider}s ON %s %%{identity}s IS %%{label}s", + stringify_objtype(node->objtype)); + label = new_objtree_VA(fmt, 0); + + /* Add the label clause; can be either NULL or a quoted literal. */ + append_literal_or_null(label, "label", node->label); + + /* Add the security provider clause */ + append_string_object(label, "provider", node->provider); + + /* Add the object identity clause */ + append_string_object(label, "identity", + getObjectIdentity(&address)); + + return label; +} + +static ObjTree * +deparse_CreateConversion(Oid objectId, Node *parsetree) +{ + HeapTuple conTup; + Relation convrel; + Form_pg_conversion conForm; + ObjTree *ccStmt; + + convrel = heap_open(ConversionRelationId, AccessShareLock); + conTup = get_catalog_object_by_oid(convrel, objectId); + if (!HeapTupleIsValid(conTup)) + elog(ERROR, "cache lookup failed for conversion with OID %u", objectId); + conForm = (Form_pg_conversion) GETSTRUCT(conTup); + + ccStmt = new_objtree_VA("CREATE %{default}s CONVERSION %{identity}D FOR " + "%{source}L TO %{dest}L FROM %{function}D", 0); + + append_string_object(ccStmt, "default", + conForm->condefault ? "DEFAULT" : ""); + append_object_object(ccStmt, "identity", + new_objtree_for_qualname(conForm->connamespace, + NameStr(conForm->conname))); + append_string_object(ccStmt, "source", (char *) + pg_encoding_to_char(conForm->conforencoding)); + append_string_object(ccStmt, "dest", (char *) + pg_encoding_to_char(conForm->contoencoding)); + append_object_object(ccStmt, "function", + new_objtree_for_qualname_id(ProcedureRelationId, + conForm->conproc)); + + heap_close(convrel, AccessShareLock); + + return ccStmt; +} + +static ObjTree * +deparse_CreateCastStmt(Oid objectId, Node *parsetree) +{ + CreateCastStmt *node = (CreateCastStmt *) parsetree; + Relation castrel; + HeapTuple castTup; + Form_pg_cast castForm; + ObjTree *createCast; + char *context; + + castrel = heap_open(CastRelationId, AccessShareLock); + castTup = get_catalog_object_by_oid(castrel, objectId); + if (!HeapTupleIsValid(castTup)) + elog(ERROR, "cache lookup failed for cast with OID %u", objectId); + castForm = (Form_pg_cast) GETSTRUCT(castTup); + + createCast = new_objtree_VA("CREATE CAST (%{sourcetype}T AS %{targettype}T) %{mechanism}s %{context}s", + 2, "sourcetype", ObjTypeObject, + new_objtree_for_type(castForm->castsource, -1), + "targettype", ObjTypeObject, + new_objtree_for_type(castForm->casttarget, -1)); + + if (node->inout) + append_string_object(createCast, "mechanism", "WITH INOUT"); + else if (node->func == NULL) + append_string_object(createCast, "mechanism", "WITHOUT FUNCTION"); + else + { + ObjTree *tmp; + StringInfoData func; + HeapTuple funcTup; + Form_pg_proc funcForm; + int i; + + funcTup = SearchSysCache1(PROCOID, castForm->castfunc); + funcForm = (Form_pg_proc) GETSTRUCT(funcTup); + + initStringInfo(&func); + appendStringInfo(&func, "%s(", + quote_qualified_identifier(get_namespace_name(funcForm->pronamespace), + NameStr(funcForm->proname))); + for (i = 0; i < funcForm->pronargs; i++) + appendStringInfoString(&func, + format_type_be_qualified(funcForm->proargtypes.values[i])); + appendStringInfoChar(&func, ')'); + + tmp = new_objtree_VA("WITH FUNCTION %{castfunction}s", 1, + "castfunction", ObjTypeString, func.data); + append_object_object(createCast, "mechanism", tmp); + + ReleaseSysCache(funcTup); + } + + switch (node->context) + { + case COERCION_IMPLICIT: + context = "AS IMPLICIT"; + break; + case COERCION_ASSIGNMENT: + context = "AS ASSIGNMENT"; + break; + case COERCION_EXPLICIT: + context = ""; + break; + default: + elog(ERROR, "invalid coercion code %c", node->context); + return NULL; /* keep compiler quiet */ + } + append_string_object(createCast, "context", context); + + heap_close(castrel, AccessShareLock); + + return createCast; +} + +static ObjTree * +deparse_CreateOpClassStmt(CollectedCommand *cmd) +{ + Oid opcoid = cmd->d.createopc.address.objectId; + HeapTuple opcTup; + HeapTuple opfTup; + Form_pg_opfamily opfForm; + Form_pg_opclass opcForm; + ObjTree *stmt; + ObjTree *tmp; + List *list; + ListCell *cell; + + opcTup = SearchSysCache1(CLAOID, ObjectIdGetDatum(opcoid)); + if (!HeapTupleIsValid(opcTup)) + elog(ERROR, "cache lookup failed for opclass with OID %u", opcoid); + opcForm = (Form_pg_opclass) GETSTRUCT(opcTup); + + opfTup = SearchSysCache1(OPFAMILYOID, opcForm->opcfamily); + if (!HeapTupleIsValid(opfTup)) + elog(ERROR, "cache lookup failed for operator family with OID %u", opcForm->opcfamily); + opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup); + + stmt = new_objtree_VA("CREATE OPERATOR CLASS %{identity}D %{default}s " + "FOR TYPE %{type}T USING %{amname}I %{opfamily}s " + "AS %{items:, }s", 0); + + append_object_object(stmt, "identity", + new_objtree_for_qualname(opcForm->opcnamespace, + NameStr(opcForm->opcname))); + + /* + * Add the FAMILY clause; but if it has the same name and namespace as the + * opclass, then have it expand to empty, because it would cause a failure + * if the opfamily was created internally. + */ + tmp = new_objtree_VA("FAMILY %{opfamily}D", 1, + "opfamily", ObjTypeObject, + new_objtree_for_qualname(opfForm->opfnamespace, + NameStr(opfForm->opfname))); + if (strcmp(NameStr(opfForm->opfname), NameStr(opcForm->opcname)) == 0 && + opfForm->opfnamespace == opcForm->opcnamespace) + append_bool_object(tmp, "present", false); + append_object_object(stmt, "opfamily", tmp); + + /* Add the DEFAULT clause */ + append_string_object(stmt, "default", + opcForm->opcdefault ? "DEFAULT" : ""); + + /* Add the FOR TYPE clause */ + append_object_object(stmt, "type", + new_objtree_for_type(opcForm->opcintype, -1)); + + /* Add the USING clause */ + append_string_object(stmt, "amname", get_am_name(opcForm->opcmethod)); + + /* + * Add the initial item list. Note we always add the STORAGE clause, even + * when it is implicit in the original command. + */ + tmp = new_objtree_VA("STORAGE %{type}T", 0); + append_object_object(tmp, "type", + new_objtree_for_type(opcForm->opckeytype != InvalidOid ? + opcForm->opckeytype : opcForm->opcintype, + -1)); + list = list_make1(new_object_object(tmp)); + + /* Add the declared operators */ + /* XXX this duplicates code in deparse_AlterOpFamily */ + foreach(cell, cmd->d.createopc.operators) + { + OpFamilyMember *oper = lfirst(cell); + ObjTree *tmp; + + tmp = new_objtree_VA("OPERATOR %{num}n %{operator}O(%{ltype}T, %{rtype}T) %{purpose}s", + 0); + append_integer_object(tmp, "num", oper->number); + append_object_object(tmp, "operator", + new_objtree_for_qualname_id(OperatorRelationId, + oper->object)); + /* add the types */ + append_object_object(tmp, "ltype", + new_objtree_for_type(oper->lefttype, -1)); + append_object_object(tmp, "rtype", + new_objtree_for_type(oper->righttype, -1)); + /* Add the FOR SEARCH / FOR ORDER BY clause */ + if (oper->sortfamily == InvalidOid) + append_string_object(tmp, "purpose", "FOR SEARCH"); + else + { + ObjTree *tmp2; + + tmp2 = new_objtree_VA("FOR ORDER BY %{opfamily}D", 0); + append_object_object(tmp2, "opfamily", + new_objtree_for_qualname_id(OperatorFamilyRelationId, + oper->sortfamily)); + append_object_object(tmp, "purpose", tmp2); + } + + list = lappend(list, new_object_object(tmp)); + } + + /* Add the declared support functions */ + foreach(cell, cmd->d.createopc.procedures) + { + OpFamilyMember *proc = lfirst(cell); + ObjTree *tmp; + HeapTuple procTup; + Form_pg_proc procForm; + Oid *proargtypes; + List *arglist; + int i; + + tmp = new_objtree_VA("FUNCTION %{num}n (%{ltype}T, %{rtype}T) %{function}D(%{argtypes:, }T)", 0); + append_integer_object(tmp, "num", proc->number); + procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(proc->object)); + if (!HeapTupleIsValid(procTup)) + elog(ERROR, "cache lookup failed for procedure %u", proc->object); + procForm = (Form_pg_proc) GETSTRUCT(procTup); + + append_object_object(tmp, "function", + new_objtree_for_qualname(procForm->pronamespace, + NameStr(procForm->proname))); + proargtypes = procForm->proargtypes.values; + arglist = NIL; + for (i = 0; i < procForm->pronargs; i++) + { + ObjTree *arg; + + arg = new_objtree_for_type(proargtypes[i], -1); + arglist = lappend(arglist, new_object_object(arg)); + } + append_array_object(tmp, "argtypes", arglist); + + ReleaseSysCache(procTup); + /* Add the types */ + append_object_object(tmp, "ltype", + new_objtree_for_type(proc->lefttype, -1)); + append_object_object(tmp, "rtype", + new_objtree_for_type(proc->righttype, -1)); + + list = lappend(list, new_object_object(tmp)); + } + + append_array_object(stmt, "items", list); + + ReleaseSysCache(opfTup); + ReleaseSysCache(opcTup); + + return stmt; +} + +static ObjTree * +deparse_CreateOpFamily(Oid objectId, Node *parsetree) +{ + HeapTuple opfTup; + HeapTuple amTup; + Form_pg_opfamily opfForm; + Form_pg_am amForm; + ObjTree *copfStmt; + ObjTree *tmp; + + opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(opfTup)) + elog(ERROR, "cache lookup failed for operator family with OID %u", objectId); + opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup); + + amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod)); + if (!HeapTupleIsValid(amTup)) + elog(ERROR, "cache lookup failed for access method %u", + opfForm->opfmethod); + amForm = (Form_pg_am) GETSTRUCT(amTup); + + copfStmt = new_objtree_VA("CREATE OPERATOR FAMILY %{identity}D USING %{amname}I", + 0); + + tmp = new_objtree_for_qualname(opfForm->opfnamespace, + NameStr(opfForm->opfname)); + append_object_object(copfStmt, "identity", tmp); + append_string_object(copfStmt, "amname", NameStr(amForm->amname)); + + ReleaseSysCache(amTup); + ReleaseSysCache(opfTup); + + return copfStmt; +} + +static ObjTree * +deparse_GrantStmt(CollectedCommand *cmd) +{ + InternalGrant *istmt; + ObjTree *grantStmt; + char *fmt; + char *objtype; + List *list; + ListCell *cell; + Oid classId; + ObjTree *tmp; + + istmt = cmd->d.grant.istmt; + + switch (istmt->objtype) + { + case ACL_OBJECT_COLUMN: + case ACL_OBJECT_RELATION: + objtype = "TABLE"; + classId = RelationRelationId; + break; + case ACL_OBJECT_SEQUENCE: + objtype = "SEQUENCE"; + classId = RelationRelationId; + break; + case ACL_OBJECT_DOMAIN: + objtype = "DOMAIN"; + classId = TypeRelationId; + break; + case ACL_OBJECT_FDW: + objtype = "FOREIGN DATA WRAPPER"; + classId = ForeignDataWrapperRelationId; + break; + case ACL_OBJECT_FOREIGN_SERVER: + objtype = "FOREIGN SERVER"; + classId = ForeignServerRelationId; + break; + case ACL_OBJECT_FUNCTION: + objtype = "FUNCTION"; + classId = ProcedureRelationId; + break; + case ACL_OBJECT_LANGUAGE: + objtype = "LANGUAGE"; + classId = LanguageRelationId; + break; + case ACL_OBJECT_LARGEOBJECT: + objtype = "LARGE OBJECT"; + classId = LargeObjectRelationId; + break; + case ACL_OBJECT_NAMESPACE: + objtype = "SCHEMA"; + classId = NamespaceRelationId; + break; + case ACL_OBJECT_TYPE: + objtype = "TYPE"; + classId = TypeRelationId; + break; + case ACL_OBJECT_DATABASE: + case ACL_OBJECT_TABLESPACE: + objtype = ""; + classId = InvalidOid; + elog(ERROR, "global objects not supported"); + default: + elog(ERROR, "invalid ACL_OBJECT value %d", istmt->objtype); + } + + /* GRANT TO or REVOKE FROM */ + if (istmt->is_grant) + fmt = psprintf("GRANT %%{privileges:, }s ON %s %%{privtarget:, }s " + "TO %%{grantees:, }R %%{grant_option}s", + objtype); + else + fmt = psprintf("REVOKE %%{grant_option}s %%{privileges:, }s ON %s %%{privtarget:, }s " + "FROM %%{grantees:, }R %%{cascade}s", + objtype); + + grantStmt = new_objtree_VA(fmt, 0); + + /* build list of privileges to grant/revoke */ + if (istmt->all_privs) + { + tmp = new_objtree_VA("ALL PRIVILEGES", 0); + list = list_make1(new_object_object(tmp)); + } + else + { + list = NIL; + + if (istmt->privileges & ACL_INSERT) + list = lappend(list, new_string_object("INSERT")); + if (istmt->privileges & ACL_SELECT) + list = lappend(list, new_string_object("SELECT")); + if (istmt->privileges & ACL_UPDATE) + list = lappend(list, new_string_object("UPDATE")); + if (istmt->privileges & ACL_DELETE) + list = lappend(list, new_string_object("DELETE")); + if (istmt->privileges & ACL_TRUNCATE) + list = lappend(list, new_string_object("TRUNCATE")); + if (istmt->privileges & ACL_REFERENCES) + list = lappend(list, new_string_object("REFERENCES")); + if (istmt->privileges & ACL_TRIGGER) + list = lappend(list, new_string_object("TRIGGER")); + if (istmt->privileges & ACL_EXECUTE) + list = lappend(list, new_string_object("EXECUTE")); + if (istmt->privileges & ACL_USAGE) + list = lappend(list, new_string_object("USAGE")); + if (istmt->privileges & ACL_CREATE) + list = lappend(list, new_string_object("CREATE")); + if (istmt->privileges & ACL_CREATE_TEMP) + list = lappend(list, new_string_object("TEMPORARY")); + if (istmt->privileges & ACL_CONNECT) + list = lappend(list, new_string_object("CONNECT")); + + if (istmt->col_privs != NIL) + { + ListCell *ocell; + + foreach(ocell, istmt->col_privs) + { + AccessPriv *priv = lfirst(ocell); + List *cols = NIL; + + tmp = new_objtree_VA("%{priv}s (%{cols:, }I)", 0); + foreach(cell, priv->cols) + { + Value *colname = lfirst(cell); + + cols = lappend(cols, + new_string_object(strVal(colname))); + } + append_array_object(tmp, "cols", cols); + if (priv->priv_name == NULL) + append_string_object(tmp, "priv", "ALL PRIVILEGES"); + else + append_string_object(tmp, "priv", priv->priv_name); + + list = lappend(list, new_object_object(tmp)); + } + } + } + append_array_object(grantStmt, "privileges", list); + + /* target objects. We use object identities here */ + list = NIL; + foreach(cell, istmt->objects) + { + Oid objid = lfirst_oid(cell); + ObjectAddress addr; + + addr.classId = classId; + addr.objectId = objid; + addr.objectSubId = 0; + + tmp = new_objtree_VA("%{identity}s", 0); + append_string_object(tmp, "identity", + getObjectIdentity(&addr)); + list = lappend(list, new_object_object(tmp)); + } + append_array_object(grantStmt, "privtarget", list); + + /* list of grantees */ + list = NIL; + foreach(cell, istmt->grantees) + { + Oid grantee = lfirst_oid(cell); + + tmp = new_objtree_for_role_id(grantee); + list = lappend(list, new_object_object(tmp)); + } + append_array_object(grantStmt, "grantees", list); + + /* the wording of the grant option is variable ... */ + if (istmt->is_grant) + append_string_object(grantStmt, "grant_option", + istmt->grant_option ? "WITH GRANT OPTION" : ""); + else + append_string_object(grantStmt, "grant_option", + istmt->grant_option ? "GRANT OPTION FOR" : ""); + + if (!istmt->is_grant) + { + if (istmt->behavior == DROP_CASCADE) + append_string_object(grantStmt, "cascade", "CASCADE"); + else + append_string_object(grantStmt, "cascade", ""); + } + + return grantStmt; +} + +/* + * Deparse an ALTER OPERATOR FAMILY ADD/DROP command. + */ +static ObjTree * +deparse_AlterOpFamily(CollectedCommand *cmd) +{ + ObjTree *alterOpFam; + AlterOpFamilyStmt *stmt = (AlterOpFamilyStmt *) cmd->parsetree; + HeapTuple ftp; + Form_pg_opfamily opfForm; + List *list; + ListCell *cell; + + ftp = SearchSysCache1(OPFAMILYOID, + ObjectIdGetDatum(cmd->d.opfam.address.objectId)); + if (!HeapTupleIsValid(ftp)) + elog(ERROR, "cache lookup failed for operator family %u", + cmd->d.opfam.address.objectId); + opfForm = (Form_pg_opfamily) GETSTRUCT(ftp); + + if (!stmt->isDrop) + alterOpFam = new_objtree_VA("ALTER OPERATOR FAMILY %{identity}D " + "USING %{amname}I ADD %{items:, }s", 0); + else + alterOpFam = new_objtree_VA("ALTER OPERATOR FAMILY %{identity}D " + "USING %{amname}I DROP %{items:, }s", 0); + append_object_object(alterOpFam, "identity", + new_objtree_for_qualname(opfForm->opfnamespace, + NameStr(opfForm->opfname))); + append_string_object(alterOpFam, "amname", stmt->amname); + + list = NIL; + foreach(cell, cmd->d.opfam.operators) + { + OpFamilyMember *oper = lfirst(cell); + ObjTree *tmp; + + if (!stmt->isDrop) + tmp = new_objtree_VA("OPERATOR %{num}n %{operator}O(%{ltype}T, %{rtype}T) %{purpose}s", + 0); + else + tmp = new_objtree_VA("OPERATOR %{num}n (%{ltype}T, %{rtype}T)", + 0); + append_integer_object(tmp, "num", oper->number); + + /* Add the operator name; the DROP case doesn't have this */ + if (!stmt->isDrop) + { + append_object_object(tmp, "operator", + new_objtree_for_qualname_id(OperatorRelationId, + oper->object)); + } + + /* Add the types */ + append_object_object(tmp, "ltype", + new_objtree_for_type(oper->lefttype, -1)); + append_object_object(tmp, "rtype", + new_objtree_for_type(oper->righttype, -1)); + + /* Add the FOR SEARCH / FOR ORDER BY clause; not in the DROP case */ + if (!stmt->isDrop) + { + if (oper->sortfamily == InvalidOid) + append_string_object(tmp, "purpose", "FOR SEARCH"); + else + { + ObjTree *tmp2; + + tmp2 = new_objtree_VA("FOR ORDER BY %{opfamily}D", 0); + append_object_object(tmp2, "opfamily", + new_objtree_for_qualname_id(OperatorFamilyRelationId, + oper->sortfamily)); + append_object_object(tmp, "purpose", tmp2); + } + } + + list = lappend(list, new_object_object(tmp)); + } + + foreach(cell, cmd->d.opfam.procedures) + { + OpFamilyMember *proc = lfirst(cell); + ObjTree *tmp; + + if (!stmt->isDrop) + tmp = new_objtree_VA("FUNCTION %{num}n (%{ltype}T, %{rtype}T) %{function}D(%{argtypes:, }T)", 0); + else + tmp = new_objtree_VA("FUNCTION %{num}n (%{ltype}T, %{rtype}T)", 0); + append_integer_object(tmp, "num", proc->number); + + /* Add the function name and arg types; the DROP case doesn't have this */ + if (!stmt->isDrop) + { + HeapTuple procTup; + Form_pg_proc procForm; + Oid *proargtypes; + List *arglist; + int i; + + procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(proc->object)); + if (!HeapTupleIsValid(procTup)) + elog(ERROR, "cache lookup failed for procedure %u", proc->object); + procForm = (Form_pg_proc) GETSTRUCT(procTup); + + append_object_object(tmp, "function", + new_objtree_for_qualname(procForm->pronamespace, + NameStr(procForm->proname))); + proargtypes = procForm->proargtypes.values; + arglist = NIL; + for (i = 0; i < procForm->pronargs; i++) + { + ObjTree *arg; + + arg = new_objtree_for_type(proargtypes[i], -1); + arglist = lappend(arglist, new_object_object(arg)); + } + append_array_object(tmp, "argtypes", arglist); + + ReleaseSysCache(procTup); + } + + /* Add the types */ + append_object_object(tmp, "ltype", + new_objtree_for_type(proc->lefttype, -1)); + append_object_object(tmp, "rtype", + new_objtree_for_type(proc->righttype, -1)); + + list = lappend(list, new_object_object(tmp)); + } + + append_array_object(alterOpFam, "items", list); + + ReleaseSysCache(ftp); + + return alterOpFam; +} + +static ObjTree * +deparse_AlterDefaultPrivilegesStmt(CollectedCommand *cmd) +{ + ObjTree *alterStmt; + AlterDefaultPrivilegesStmt *stmt = (AlterDefaultPrivilegesStmt *) cmd->parsetree; + List *roles = NIL; + List *schemas = NIL; + List *grantees; + List *privs; + ListCell *cell; + ObjTree *tmp; + ObjTree *grant; + + alterStmt = new_objtree_VA("ALTER DEFAULT PRIVILEGES %{in_schema}s " + "%{for_roles}s %{grant}s", 0); + + /* Scan the parse node to dig out the FOR ROLE and IN SCHEMA clauses */ + foreach(cell, stmt->options) + { + DefElem *opt = (DefElem *) lfirst(cell); + ListCell *cell2; + + Assert(IsA(opt, DefElem)); + Assert(IsA(opt->arg, List)); + if (strcmp(opt->defname, "roles") == 0) + { + foreach(cell2, (List *) opt->arg) + { + Value *val = lfirst(cell2); + ObjTree *obj = new_objtree_for_role(strVal(val)); + + roles = lappend(roles, new_object_object(obj)); + } + } + else if (strcmp(opt->defname, "schemas") == 0) + { + foreach(cell2, (List *) opt->arg) + { + Value *val = lfirst(cell2); + + schemas = lappend(schemas, + new_string_object(strVal(val))); + } + } + } + + /* Add the FOR ROLE clause, if any */ + tmp = new_objtree_VA("FOR ROLE %{roles:, }R", 0); + append_array_object(tmp, "roles", roles); + if (roles == NIL) + append_bool_object(tmp, "present", false); + append_object_object(alterStmt, "for_roles", tmp); + + /* Add the IN SCHEMA clause, if any */ + tmp = new_objtree_VA("IN SCHEMA %{schemas:, }I", 0); + append_array_object(tmp, "schemas", schemas); + if (schemas == NIL) + append_bool_object(tmp, "present", false); + append_object_object(alterStmt, "in_schema", tmp); + + /* Add the GRANT subcommand */ + if (stmt->action->is_grant) + grant = new_objtree_VA("GRANT %{privileges:, }s ON %{target}s " + "TO %{grantees:, }R %{grant_option}s", 0); + else + grant = new_objtree_VA("REVOKE %{grant_option}s %{privileges:, }s " + "ON %{target}s FROM %{grantees:, }R", 0); + + /* add the GRANT OPTION clause */ + tmp = new_objtree_VA(stmt->action->is_grant ? + "WITH GRANT OPTION" : "GRANT OPTION FOR", + 1, "present", ObjTypeBool, + stmt->action->grant_option); + append_object_object(grant, "grant_option", tmp); + + /* add the target object type */ + append_string_object(grant, "target", cmd->d.defprivs.objtype); + + /* add the grantee list */ + grantees = NIL; + foreach(cell, stmt->action->grantees) + { + RoleSpec *spec = (RoleSpec *) lfirst(cell); + ObjTree *obj = new_objtree_for_rolespec(spec); + + grantees = lappend(grantees, new_object_object(obj)); + } + append_array_object(grant, "grantees", grantees); + + /* + * Add the privileges list. This uses the parser struct, as opposed to the + * InternalGrant format used by GRANT. There are enough other differences + * that this doesn't seem worth improving. + */ + if (stmt->action->privileges == NIL) + privs = list_make1(new_string_object("ALL PRIVILEGES")); + else + { + privs = NIL; + + foreach(cell, stmt->action->privileges) + { + AccessPriv *priv = lfirst(cell); + + Assert(priv->cols == NIL); + privs = lappend(privs, + new_string_object(priv->priv_name)); + } + } + + append_array_object(grant, "privileges", privs); + + append_object_object(alterStmt, "grant", grant); + + return alterStmt; +} + +static ObjTree * +deparse_AlterTableStmt(CollectedCommand *cmd) +{ + ObjTree *alterTableStmt; + ObjTree *tmp; + ObjTree *tmp2; + List *dpcontext; + Relation rel; + List *subcmds = NIL; + ListCell *cell; + char *fmtstr; + const char *reltype; + bool istype = false; + + Assert(cmd->type == SCT_AlterTable); + + rel = relation_open(cmd->d.alterTable.objectId, AccessShareLock); + dpcontext = deparse_context_for(RelationGetRelationName(rel), + cmd->d.alterTable.objectId); + + switch (rel->rd_rel->relkind) + { + case RELKIND_RELATION: + reltype = "TABLE"; + break; + case RELKIND_INDEX: + reltype = "INDEX"; + break; + case RELKIND_VIEW: + reltype = "VIEW"; + break; + case RELKIND_COMPOSITE_TYPE: + reltype = "TYPE"; + istype = true; + break; + case RELKIND_FOREIGN_TABLE: + reltype = "FOREIGN TABLE"; + break; + + default: + elog(ERROR, "unexpected relkind %d", rel->rd_rel->relkind); + reltype = NULL;; + } + + fmtstr = psprintf("ALTER %s %%{identity}D %%{subcmds:, }s", reltype); + alterTableStmt = new_objtree_VA(fmtstr, 0); + + tmp = new_objtree_for_qualname(rel->rd_rel->relnamespace, + RelationGetRelationName(rel)); + append_object_object(alterTableStmt, "identity", tmp); + + foreach(cell, cmd->d.alterTable.subcmds) + { + CollectedATSubcmd *sub = (CollectedATSubcmd *) lfirst(cell); + AlterTableCmd *subcmd = (AlterTableCmd *) sub->parsetree; + ObjTree *tree; + + Assert(IsA(subcmd, AlterTableCmd)); + + switch (subcmd->subtype) + { + case AT_AddColumn: + case AT_AddColumnRecurse: + /* XXX need to set the "recurse" bit somewhere? */ + Assert(IsA(subcmd->def, ColumnDef)); + tree = deparse_ColumnDef(rel, dpcontext, false, + (ColumnDef *) subcmd->def, true); + fmtstr = psprintf("ADD %s %%{definition}s", + istype ? "ATTRIBUTE" : "COLUMN"); + tmp = new_objtree_VA(fmtstr, 2, + "type", ObjTypeString, "add column", + "definition", ObjTypeObject, tree); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_AddIndexConstraint: + case AT_ReAddIndex: + case AT_ReAddConstraint: + case AT_ProcessedConstraint: + case AT_ReplaceRelOptions: + /* Subtypes used for internal operations; nothing to do here */ + break; + + case AT_AddColumnToView: + /* CREATE OR REPLACE VIEW -- nothing to do here */ + break; + + case AT_ColumnDefault: + if (subcmd->def == NULL) + { + tmp = new_objtree_VA("ALTER COLUMN %{column}I DROP DEFAULT", + 1, "type", ObjTypeString, "drop default"); + } + else + { + List *dpcontext; + HeapTuple attrtup; + AttrNumber attno; + + tmp = new_objtree_VA("ALTER COLUMN %{column}I SET DEFAULT %{definition}s", + 1, "type", ObjTypeString, "set default"); + + dpcontext = deparse_context_for(RelationGetRelationName(rel), + RelationGetRelid(rel)); + attrtup = SearchSysCacheAttName(RelationGetRelid(rel), subcmd->name); + attno = ((Form_pg_attribute) GETSTRUCT(attrtup))->attnum; + append_string_object(tmp, "definition", + RelationGetColumnDefault(rel, attno, dpcontext)); + ReleaseSysCache(attrtup); + } + append_string_object(tmp, "column", subcmd->name); + + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_DropNotNull: + tmp = new_objtree_VA("ALTER COLUMN %{column}I DROP NOT NULL", + 1, "type", ObjTypeString, "drop not null"); + append_string_object(tmp, "column", subcmd->name); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_SetNotNull: + tmp = new_objtree_VA("ALTER COLUMN %{column}I SET NOT NULL", + 1, "type", ObjTypeString, "set not null"); + append_string_object(tmp, "column", subcmd->name); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_SetStatistics: + { + Assert(IsA(subcmd->def, Integer)); + tmp = new_objtree_VA("ALTER COLUMN %{column}I SET STATISTICS %{statistics}n", + 3, "type", ObjTypeString, "set statistics", + "column", ObjTypeString, subcmd->name, + "statistics", ObjTypeInteger, + intVal((Value *) subcmd->def)); + subcmds = lappend(subcmds, new_object_object(tmp)); + } + break; + + case AT_SetOptions: + case AT_ResetOptions: + subcmds = lappend(subcmds, new_object_object( + deparse_ColumnSetOptions(subcmd))); + break; + + case AT_SetStorage: + Assert(IsA(subcmd->def, String)); + tmp = new_objtree_VA("ALTER COLUMN %{column}I SET STORAGE %{storage}s", + 3, "type", ObjTypeString, "set storage", + "column", ObjTypeString, subcmd->name, + "storage", ObjTypeString, + strVal((Value *) subcmd->def)); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_DropColumnRecurse: + case AT_DropColumn: + fmtstr = psprintf("DROP %s %%{column}I %%{cascade}s", + istype ? "ATTRIBUTE" : "COLUMN"); + tmp = new_objtree_VA(fmtstr, 2, + "type", ObjTypeString, "drop column", + "column", ObjTypeString, subcmd->name); + tmp2 = new_objtree_VA("CASCADE", 1, + "present", ObjTypeBool, subcmd->behavior); + append_object_object(tmp, "cascade", tmp2); + + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_AddIndex: + { + Oid idxOid = sub->address.objectId; + IndexStmt *istmt; + Relation idx; + const char *idxname; + Oid constrOid; + + Assert(IsA(subcmd->def, IndexStmt)); + istmt = (IndexStmt *) subcmd->def; + + if (!istmt->isconstraint) + break; + + idx = relation_open(idxOid, AccessShareLock); + idxname = RelationGetRelationName(idx); + + constrOid = get_relation_constraint_oid( + cmd->d.alterTable.objectId, idxname, false); + + tmp = new_objtree_VA("ADD CONSTRAINT %{name}I %{definition}s", + 3, "type", ObjTypeString, "add constraint", + "name", ObjTypeString, idxname, + "definition", ObjTypeString, + pg_get_constraintdef_string(constrOid, false)); + subcmds = lappend(subcmds, new_object_object(tmp)); + + relation_close(idx, AccessShareLock); + } + break; + + case AT_AddConstraint: + case AT_AddConstraintRecurse: + { + /* XXX need to set the "recurse" bit somewhere? */ + Oid constrOid = sub->address.objectId; + + tmp = new_objtree_VA("ADD CONSTRAINT %{name}I %{definition}s", + 3, "type", ObjTypeString, "add constraint", + "name", ObjTypeString, get_constraint_name(constrOid), + "definition", ObjTypeString, + pg_get_constraintdef_string(constrOid, false)); + subcmds = lappend(subcmds, new_object_object(tmp)); + } + break; + + case AT_AlterConstraint: + { + Oid constrOid = sub->address.objectId; + Constraint *c = (Constraint *) subcmd->def; + + /* if no constraint was altered, silently skip it */ + if (!OidIsValid(constrOid)) + break; + + Assert(IsA(c, Constraint)); + tmp = new_objtree_VA("ALTER CONSTRAINT %{name}I %{deferrable}s %{init_deferred}s", + 2, "type", ObjTypeString, "alter constraint", + "name", ObjTypeString, get_constraint_name(constrOid)); + append_string_object(tmp, "deferrable", c->deferrable ? + "DEFERRABLE" : "NOT DEFERRABLE"); + append_string_object(tmp, "init_deferred", c->initdeferred ? + "INITIALLY DEFERRED" : "INITIALLY IMMEDIATE"); + subcmds = lappend(subcmds, new_object_object(tmp)); + } + break; + + case AT_ValidateConstraintRecurse: + case AT_ValidateConstraint: + tmp = new_objtree_VA("VALIDATE CONSTRAINT %{constraint}I", 2, + "type", ObjTypeString, "validate constraint", + "constraint", ObjTypeString, subcmd->name); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_DropConstraintRecurse: + case AT_DropConstraint: + tmp = new_objtree_VA("DROP CONSTRAINT %{constraint}I", 2, + "type", ObjTypeString, "drop constraint", + "constraint", ObjTypeString, subcmd->name); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_AlterColumnType: + { + TupleDesc tupdesc = RelationGetDescr(rel); + Form_pg_attribute att; + ColumnDef *def; + + att = tupdesc->attrs[sub->address.objectSubId - 1]; + def = (ColumnDef *) subcmd->def; + Assert(IsA(def, ColumnDef)); + + fmtstr = psprintf("ALTER %s %%{column}I SET DATA TYPE %%{datatype}T %%{collation}s %s", + istype ? "ATTRIBUTE" : "COLUMN", + istype ? "%{cascade}s" : "%{using}s"); + + tmp = new_objtree_VA(fmtstr, 2, + "type", ObjTypeString, "alter column type", + "column", ObjTypeString, subcmd->name); + /* add the TYPE clause */ + append_object_object(tmp, "datatype", + new_objtree_for_type(att->atttypid, + att->atttypmod)); + + /* add a COLLATE clause, if needed */ + tmp2 = new_objtree_VA("COLLATE %{name}D", 0); + if (OidIsValid(att->attcollation)) + { + ObjTree *collname; + + collname = new_objtree_for_qualname_id(CollationRelationId, + att->attcollation); + append_object_object(tmp2, "name", collname); + } + else + append_bool_object(tmp2, "present", false); + append_object_object(tmp, "collation", tmp2); + + /* if not a composite type, add the USING clause */ + if (!istype) + { + /* + * If there's a USING clause, transformAlterTableStmt + * ran it through transformExpr and stored the + * resulting node in cooked_default, which we can use + * here. + */ + tmp2 = new_objtree_VA("USING %{expression}s", 0); + if (def->raw_default) + { + Datum deparsed; + char *defexpr; + + defexpr = nodeToString(def->cooked_default); + deparsed = DirectFunctionCall2(pg_get_expr, + CStringGetTextDatum(defexpr), + RelationGetRelid(rel)); + append_string_object(tmp2, "expression", + TextDatumGetCString(deparsed)); + } + else + append_bool_object(tmp2, "present", false); + append_object_object(tmp, "using", tmp2); + } + + /* if it's a composite type, add the CASCADE clause */ + if (istype) + { + tmp2 = new_objtree_VA("CASCADE", 0); + if (subcmd->behavior != DROP_CASCADE) + append_bool_object(tmp2, "present", false); + append_object_object(tmp, "cascade", tmp2); + } + + subcmds = lappend(subcmds, new_object_object(tmp)); + } + break; + + case AT_AlterColumnGenericOptions: + tmp = deparse_FdwOptions((List *) subcmd->def, + subcmd->name); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_ChangeOwner: + tmp = new_objtree_VA("OWNER TO %{owner}I", + 2, "type", ObjTypeString, "change owner", + "owner", ObjTypeString, + get_rolespec_name(subcmd->newowner)); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_ClusterOn: + tmp = new_objtree_VA("CLUSTER ON %{index}I", 2, + "type", ObjTypeString, "cluster on", + "index", ObjTypeString, subcmd->name); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_DropCluster: + tmp = new_objtree_VA("SET WITHOUT CLUSTER", 1, + "type", ObjTypeString, "set without cluster"); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_SetLogged: + tmp = new_objtree_VA("SET LOGGED", 1, + "type", ObjTypeString, "set logged"); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_SetUnLogged: + tmp = new_objtree_VA("SET UNLOGGED", 1, + "type", ObjTypeString, "set unlogged"); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_AddOidsRecurse: + case AT_AddOids: + tmp = new_objtree_VA("SET WITH OIDS", 1, + "type", ObjTypeString, "set with oids"); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_DropOids: + tmp = new_objtree_VA("SET WITHOUT OIDS", 1, + "type", ObjTypeString, "set without oids"); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_SetTableSpace: + tmp = new_objtree_VA("SET TABLESPACE %{tablespace}I", 2, + "type", ObjTypeString, "set tablespace", + "tablespace", ObjTypeString, subcmd->name); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_SetRelOptions: + case AT_ResetRelOptions: + subcmds = lappend(subcmds, new_object_object( + deparse_RelSetOptions(subcmd))); + break; + + case AT_EnableTrig: + tmp = new_objtree_VA("ENABLE TRIGGER %{trigger}I", 2, + "type", ObjTypeString, "enable trigger", + "trigger", ObjTypeString, subcmd->name); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_EnableAlwaysTrig: + tmp = new_objtree_VA("ENABLE ALWAYS TRIGGER %{trigger}I", 2, + "type", ObjTypeString, "enable always trigger", + "trigger", ObjTypeString, subcmd->name); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_EnableReplicaTrig: + tmp = new_objtree_VA("ENABLE REPLICA TRIGGER %{trigger}I", 2, + "type", ObjTypeString, "enable replica trigger", + "trigger", ObjTypeString, subcmd->name); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_DisableTrig: + tmp = new_objtree_VA("DISABLE TRIGGER %{trigger}I", 2, + "type", ObjTypeString, "disable trigger", + "trigger", ObjTypeString, subcmd->name); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_EnableTrigAll: + tmp = new_objtree_VA("ENABLE TRIGGER ALL", 1, + "type", ObjTypeString, "enable trigger all"); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_DisableTrigAll: + tmp = new_objtree_VA("DISABLE TRIGGER ALL", 1, + "type", ObjTypeString, "disable trigger all"); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_EnableTrigUser: + tmp = new_objtree_VA("ENABLE TRIGGER USER", 1, + "type", ObjTypeString, "enable trigger user"); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_DisableTrigUser: + tmp = new_objtree_VA("DISABLE TRIGGER USER", 1, + "type", ObjTypeString, "disable trigger user"); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_EnableRule: + tmp = new_objtree_VA("ENABLE RULE %{rule}I", 2, + "type", ObjTypeString, "enable rule", + "rule", ObjTypeString, subcmd->name); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_EnableAlwaysRule: + tmp = new_objtree_VA("ENABLE ALWAYS RULE %{rule}I", 2, + "type", ObjTypeString, "enable always rule", + "rule", ObjTypeString, subcmd->name); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_EnableReplicaRule: + tmp = new_objtree_VA("ENABLE REPLICA RULE %{rule}I", 2, + "type", ObjTypeString, "enable replica rule", + "rule", ObjTypeString, subcmd->name); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_DisableRule: + tmp = new_objtree_VA("DISABLE RULE %{rule}I", 2, + "type", ObjTypeString, "disable rule", + "rule", ObjTypeString, subcmd->name); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_AddInherit: + tmp = new_objtree_VA("INHERIT %{parent}D", + 2, "type", ObjTypeString, "inherit", + "parent", ObjTypeObject, + new_objtree_for_qualname_id(RelationRelationId, + sub->address.objectId)); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_DropInherit: + tmp = new_objtree_VA("NO INHERIT %{parent}D", + 2, "type", ObjTypeString, "drop inherit", + "parent", ObjTypeObject, + new_objtree_for_qualname_id(RelationRelationId, + sub->address.objectId)); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_AddOf: + tmp = new_objtree_VA("OF %{type_of}T", + 2, "type", ObjTypeString, "add of", + "type_of", ObjTypeObject, + new_objtree_for_type(sub->address.objectId, -1)); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_DropOf: + tmp = new_objtree_VA("NOT OF", + 1, "type", ObjTypeString, "not of"); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_ReplicaIdentity: + tmp = new_objtree_VA("REPLICA IDENTITY %{ident}s", 1, + "type", ObjTypeString, "replica identity"); + switch (((ReplicaIdentityStmt *) subcmd->def)->identity_type) + { + case REPLICA_IDENTITY_DEFAULT: + append_string_object(tmp, "ident", "DEFAULT"); + break; + case REPLICA_IDENTITY_FULL: + append_string_object(tmp, "ident", "FULL"); + break; + case REPLICA_IDENTITY_NOTHING: + append_string_object(tmp, "ident", "NOTHING"); + break; + case REPLICA_IDENTITY_INDEX: + tmp2 = new_objtree_VA("USING INDEX %{index}I", 1, + "index", ObjTypeString, + ((ReplicaIdentityStmt *) subcmd->def)->name); + append_object_object(tmp, "ident", tmp2); + break; + } + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_EnableRowSecurity: + tmp = new_objtree_VA("ENABLE ROW LEVEL SECURITY", 1, + "type", ObjTypeString, "enable row security"); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_DisableRowSecurity: + tmp = new_objtree_VA("DISABLE ROW LEVEL SECURITY", 1, + "type", ObjTypeString, "disable row security"); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_GenericOptions: + tmp = deparse_FdwOptions((List *) subcmd->def, NULL); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + default: + elog(WARNING, "unsupported alter table subtype %d", + subcmd->subtype); + break; + } + } + + heap_close(rel, AccessShareLock); + + if (list_length(subcmds) == 0) + return NULL; + + append_array_object(alterTableStmt, "subcmds", subcmds); + return alterTableStmt; +} + +/* + * Handle deparsing of simple commands. + * + * This function contains a large switch that mirrors that in + * ProcessUtilitySlow. All cases covered there should also be covered here. + */ +static ObjTree * +deparse_simple_command(CollectedCommand *cmd) +{ + Oid objectId; + Node *parsetree; + ObjTree *command; + + Assert(cmd->type == SCT_Simple); + + parsetree = cmd->parsetree; + objectId = cmd->d.simple.address.objectId; + + /* This switch needs to handle everything that ProcessUtilitySlow does */ + switch (nodeTag(parsetree)) + { + case T_CreateSchemaStmt: + command = deparse_CreateSchemaStmt(objectId, parsetree); + break; + + case T_CreateStmt: + command = deparse_CreateStmt(objectId, parsetree); + break; + + case T_CreateForeignTableStmt: + command = deparse_CreateForeignTableStmt(objectId, parsetree); + break; + + case T_AlterTableStmt: + case T_AlterTableMoveAllStmt: + /* handled elsewhere */ + elog(ERROR, "unexpected command type %s", CreateCommandTag(parsetree)); + break; + + case T_AlterDomainStmt: + command = deparse_AlterDomainStmt(objectId, parsetree, + cmd->d.simple.secondaryObject); + break; + + /* other local objects */ + case T_DefineStmt: + command = deparse_DefineStmt(objectId, parsetree, + cmd->d.simple.secondaryObject); + break; + + case T_IndexStmt: + command = deparse_IndexStmt(objectId, parsetree); + break; + + case T_CreateExtensionStmt: + command = deparse_CreateExtensionStmt(objectId, parsetree); + break; + + case T_AlterExtensionStmt: + command = deparse_AlterExtensionStmt(objectId, parsetree); + break; + + case T_AlterExtensionContentsStmt: + command = deparse_AlterExtensionContentsStmt(objectId, parsetree, + cmd->d.simple.secondaryObject); + break; + + case T_CreateFdwStmt: + command = deparse_CreateFdwStmt(objectId, parsetree); + break; + + case T_AlterFdwStmt: + command = deparse_AlterFdwStmt(objectId, parsetree); + break; + + case T_CreateForeignServerStmt: + command = deparse_CreateForeignServerStmt(objectId, parsetree); + break; + + case T_AlterForeignServerStmt: + command = deparse_AlterForeignServerStmt(objectId, parsetree); + break; + + case T_CreateUserMappingStmt: + command = deparse_CreateUserMappingStmt(objectId, parsetree); + break; + + case T_AlterUserMappingStmt: + command = deparse_AlterUserMappingStmt(objectId, parsetree); + break; + + case T_DropUserMappingStmt: + /* goes through performDeletion; no action needed here */ + command = NULL; + break; + + case T_ImportForeignSchemaStmt: + /* generated commands are stashed individually */ + elog(ERROR, "unexpected command %s", CreateCommandTag(parsetree)); + break; + + case T_CompositeTypeStmt: /* CREATE TYPE (composite) */ + command = deparse_CompositeTypeStmt(objectId, parsetree); + break; + + case T_CreateEnumStmt: /* CREATE TYPE AS ENUM */ + command = deparse_CreateEnumStmt(objectId, parsetree); + break; + + case T_CreateRangeStmt: /* CREATE TYPE AS RANGE */ + command = deparse_CreateRangeStmt(objectId, parsetree); + break; + + case T_AlterEnumStmt: + command = deparse_AlterEnumStmt(objectId, parsetree); + break; + + case T_ViewStmt: /* CREATE VIEW */ + command = deparse_ViewStmt(objectId, parsetree); + break; + + case T_CreateFunctionStmt: + command = deparse_CreateFunction(objectId, parsetree); + break; + + case T_AlterFunctionStmt: + command = deparse_AlterFunction(objectId, parsetree); + break; + + case T_RuleStmt: + command = deparse_RuleStmt(objectId, parsetree); + break; + + case T_CreateSeqStmt: + command = deparse_CreateSeqStmt(objectId, parsetree); + break; + + case T_AlterSeqStmt: + command = deparse_AlterSeqStmt(objectId, parsetree); + break; + + case T_CreateTableAsStmt: + command = deparse_CreateTableAsStmt(objectId, parsetree); + break; + + case T_RefreshMatViewStmt: + command = deparse_RefreshMatViewStmt(objectId, parsetree); + break; + + case T_CreateTrigStmt: + command = deparse_CreateTrigStmt(objectId, parsetree); + break; + + case T_CreatePLangStmt: + command = deparse_CreatePLangStmt(objectId, parsetree); + break; + + case T_CreateDomainStmt: + command = deparse_CreateDomain(objectId, parsetree); + break; + + case T_CreateConversionStmt: + command = deparse_CreateConversion(objectId, parsetree); + break; + + case T_CreateCastStmt: + command = deparse_CreateCastStmt(objectId, parsetree); + break; + + case T_CreateOpClassStmt: + /* handled elsewhere */ + elog(ERROR, "unexpected command type %s", CreateCommandTag(parsetree)); + break; + + case T_CreateOpFamilyStmt: + command = deparse_CreateOpFamily(objectId, parsetree); + break; + + case T_AlterOpFamilyStmt: + /* handled elsewhere */ + elog(ERROR, "unexpected command type %s", CreateCommandTag(parsetree)); + break; + + case T_AlterTSDictionaryStmt: + elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree)); + break; + + case T_AlterTSConfigurationStmt: + /* handled elsewhere */ + elog(ERROR, "unexpected command type %s", CreateCommandTag(parsetree)); + break; + + case T_DropStmt: + /* goes through performDeletion; no action needed here */ + command = NULL; + break; + + case T_RenameStmt: + command = deparse_RenameStmt(cmd->d.simple.address, parsetree); + break; + + case T_AlterObjectSchemaStmt: + command = deparse_AlterObjectSchemaStmt(cmd->d.simple.address, + parsetree, + cmd->d.simple.secondaryObject); + break; + + case T_AlterOwnerStmt: + command = deparse_AlterOwnerStmt(cmd->d.simple.address, parsetree); + break; + + case T_CommentStmt: + command = deparse_CommentStmt(cmd->d.simple.address, parsetree); + break; + + case T_GrantStmt: + /* handled elsewhere */ + elog(ERROR, "unexpected command type %s", CreateCommandTag(parsetree)); + break; + + case T_DropOwnedStmt: + /* goes through performDeletion; no action needed here */ + command = NULL; + break; + + case T_AlterDefaultPrivilegesStmt: + /* handled elsewhere */ + elog(ERROR, "unexpected command type %s", CreateCommandTag(parsetree)); + break; + + case T_CreatePolicyStmt: /* CREATE POLICY */ + command = deparse_CreatePolicyStmt(objectId, parsetree); + break; + + case T_AlterPolicyStmt: /* ALTER POLICY */ + command = deparse_AlterPolicyStmt(objectId, parsetree); + break; + + case T_SecLabelStmt: + command = deparse_SecLabelStmt(cmd->d.simple.address, parsetree); + break; + + default: + command = NULL; + elog(LOG, "unrecognized node type: %d", + (int) nodeTag(parsetree)); + } + + return command; +} + +/* + * Given a CollectedCommand, return a JSON representation of it. + * + * The command is expanded fully, so that there are no ambiguities even in the + * face of search_path changes. + */ +char * +deparse_utility_command(CollectedCommand *cmd) +{ + OverrideSearchPath *overridePath; + MemoryContext oldcxt; + MemoryContext tmpcxt; + ObjTree *tree; + char *command; + StringInfoData str; + + /* + * Allocate everything done by the deparsing routines into a temp context, + * to avoid having to sprinkle them with memory handling code; but allocate + * the output StringInfo before switching. + */ + initStringInfo(&str); + tmpcxt = AllocSetContextCreate(CurrentMemoryContext, + "deparse ctx", + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); + oldcxt = MemoryContextSwitchTo(tmpcxt); + + /* + * Many routines underlying this one will invoke ruleutils.c functionality + * in order to obtain deparsed versions of expressions. In such results, + * we want all object names to be qualified, so that results are "portable" + * to environments with different search_path settings. Rather than inject + * what would be repetitive calls to override search path all over the + * place, we do it centrally here. + */ + overridePath = GetOverrideSearchPath(CurrentMemoryContext); + overridePath->schemas = NIL; + overridePath->addCatalog = false; + overridePath->addTemp = true; + PushOverrideSearchPath(overridePath); + + switch (cmd->type) + { + case SCT_Simple: + tree = deparse_simple_command(cmd); + break; + case SCT_AlterTable: + tree = deparse_AlterTableStmt(cmd); + break; + case SCT_Grant: + tree = deparse_GrantStmt(cmd); + break; + case SCT_AlterOpFamily: + tree = deparse_AlterOpFamily(cmd); + break; + case SCT_CreateOpClass: + tree = deparse_CreateOpClassStmt(cmd); + break; + case SCT_AlterDefaultPrivileges: + tree = deparse_AlterDefaultPrivilegesStmt(cmd); + break; + case SCT_AlterTSConfig: + tree = deparse_AlterTSConfigurationStmt(cmd); + break; + default: + elog(ERROR, "unexpected deparse node type %d", cmd->type); + } + + PopOverrideSearchPath(); + + if (tree) + { + Jsonb *jsonb; + + jsonb = objtree_to_jsonb(tree); + command = JsonbToCString(&str, &jsonb->root, 128); + } + else + command = NULL; + + /* + * Clean up. Note that since we created the StringInfo in the caller's + * context, the output string is not deleted here. + */ + MemoryContextSwitchTo(oldcxt); + MemoryContextDelete(tmpcxt); + + return command; +} diff --git a/contrib/ddl_deparse/ddl_deparse.control b/contrib/ddl_deparse/ddl_deparse.control new file mode 100644 index 0000000..bfecb93 --- /dev/null +++ b/contrib/ddl_deparse/ddl_deparse.control @@ -0,0 +1,5 @@ +# ddl_deparse extension +comment = 'DDL deparse support' +default_version = '1.0' +module_pathname = '$libdir/ddl_deparse' +relocatable = true diff --git a/contrib/ddl_deparse/ddl_deparse.h b/contrib/ddl_deparse/ddl_deparse.h new file mode 100644 index 0000000..d48ccac --- /dev/null +++ b/contrib/ddl_deparse/ddl_deparse.h @@ -0,0 +1,8 @@ +#ifndef DDL_DEPARSE_H +#define DDL_DEPARSE_H + +#include "tcop/deparse_utility.h" + +extern char *deparse_utility_command(CollectedCommand *cmd); + +#endif /* DDL_DEPARSE_H */ diff --git a/contrib/ddl_deparse/ddl_json.c b/contrib/ddl_deparse/ddl_json.c new file mode 100644 index 0000000..b67a32a --- /dev/null +++ b/contrib/ddl_deparse/ddl_json.c @@ -0,0 +1,715 @@ +/*------------------------------------------------------------------------- + * + * ddl_json.c + * JSON code related to DDL command deparsing + * + * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * contrib/ddl_deparse/ddl_json.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "lib/stringinfo.h" +#include "utils/builtins.h" +#include "utils/jsonb.h" + +PG_FUNCTION_INFO_V1(ddl_deparse_expand_command); + +typedef enum +{ + SpecTypename, + SpecOperatorname, + SpecDottedName, + SpecString, + SpecNumber, + SpecStringLiteral, + SpecIdentifier, + SpecRole +} convSpecifier; + +typedef enum +{ + tv_absent, + tv_true, + tv_false +} trivalue; + +static void expand_one_jsonb_element(StringInfo out, char *param, + JsonbValue *jsonval, convSpecifier specifier, + const char *fmt); +static void expand_jsonb_array(StringInfo out, char *param, + JsonbValue *jsonarr, char *arraysep, + convSpecifier specifier, const char *fmt); +static void fmtstr_error_callback(void *arg); + +static trivalue +find_bool_in_jsonbcontainer(JsonbContainer *container, char *keyname) +{ + JsonbValue key; + JsonbValue *value; + bool result; + + key.type = jbvString; + key.val.string.val = keyname; + key.val.string.len = strlen(keyname); + value = findJsonbValueFromContainer(container, + JB_FOBJECT, &key); + if (value == NULL) + return tv_absent; + if (value->type != jbvBool) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("element \"%s\" is not of type boolean", + keyname))); + result = value->val.boolean ? tv_true : tv_false; + pfree(value); + + return result; +} + +/* + * Given a JsonbContainer, find the JsonbValue with the given key name in it. + * If it's of a type other than jbvString, an error is raised. If it doesn't + * exist, an error is raised if missing_ok; otherwise return NULL. + * + * If it exists and is a string, a freshly palloc'ed copy is returned. + * + * If *length is not NULL, it is set to the length of the string. + */ +static char * +find_string_in_jsonbcontainer(JsonbContainer *container, char *keyname, + bool missing_ok, int *length) +{ + JsonbValue key; + JsonbValue *value; + char *str; + + /* XXX verify that this is an object, not an array */ + + key.type = jbvString; + key.val.string.val = keyname; + key.val.string.len = strlen(keyname); + value = findJsonbValueFromContainer(container, + JB_FOBJECT, &key); + if (value == NULL) + { + if (missing_ok) + return NULL; + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("missing element \"%s\" in json object", keyname))); + } + + str = pnstrdup(value->val.string.val, value->val.string.len); + if (length) + *length = value->val.string.len; + pfree(value); + return str; +} + +#define ADVANCE_PARSE_POINTER(ptr,end_ptr) \ + do { \ + if (++(ptr) >= (end_ptr)) \ + ereport(ERROR, \ + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), \ + errmsg("unterminated format specifier"))); \ + } while (0) + +/* + * Recursive helper for pg_event_trigger_expand_command + * + * Find the "fmt" element in the given container, and expand it into the + * provided StringInfo. + */ +static void +expand_fmt_recursive(JsonbContainer *container, StringInfo out) +{ + JsonbValue key; + JsonbValue *value; + const char *cp; + const char *start_ptr; + const char *end_ptr; + int len; + + start_ptr = find_string_in_jsonbcontainer(container, "fmt", false, &len); + end_ptr = start_ptr + len; + + for (cp = start_ptr; cp < end_ptr; cp++) + { + convSpecifier specifier; + bool is_array; + char *param = NULL; + char *arraysep = NULL; + + if (*cp != '%') + { + appendStringInfoCharMacro(out, *cp); + continue; + } + + is_array = false; + + ADVANCE_PARSE_POINTER(cp, end_ptr); + + /* Easy case: %% outputs a single % */ + if (*cp == '%') + { + appendStringInfoCharMacro(out, *cp); + continue; + } + + /* + * Scan the mandatory element name. Allow for an array separator + * (which may be the empty string) to be specified after colon. + */ + if (*cp == '{') + { + StringInfoData parbuf; + StringInfoData arraysepbuf; + StringInfo appendTo; + + initStringInfo(&parbuf); + appendTo = &parbuf; + + ADVANCE_PARSE_POINTER(cp, end_ptr); + for (; cp < end_ptr;) + { + if (*cp == ':') + { + /* + * found array separator delimiter; element name is now + * complete, start filling the separator. + */ + initStringInfo(&arraysepbuf); + appendTo = &arraysepbuf; + is_array = true; + ADVANCE_PARSE_POINTER(cp, end_ptr); + continue; + } + + if (*cp == '}') + { + ADVANCE_PARSE_POINTER(cp, end_ptr); + break; + } + appendStringInfoCharMacro(appendTo, *cp); + ADVANCE_PARSE_POINTER(cp, end_ptr); + } + param = parbuf.data; + if (is_array) + arraysep = arraysepbuf.data; + } + if (param == NULL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("missing conversion name in conversion specifier"))); + + switch (*cp) + { + case 'I': + specifier = SpecIdentifier; + break; + case 'D': + specifier = SpecDottedName; + break; + case 's': + specifier = SpecString; + break; + case 'L': + specifier = SpecStringLiteral; + break; + case 'T': + specifier = SpecTypename; + break; + case 'O': + specifier = SpecOperatorname; + break; + case 'n': + specifier = SpecNumber; + break; + case 'R': + specifier = SpecRole; + break; + default: + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid conversion specifier \"%c\"", *cp))); + } + + /* + * Obtain the element to be expanded. + */ + key.type = jbvString; + key.val.string.val = param; + key.val.string.len = strlen(param); + + value = findJsonbValueFromContainer(container, JB_FOBJECT, &key); + + /* Validate that we got an array if the format string specified one. */ + + /* And finally print out the data */ + if (is_array) + expand_jsonb_array(out, param, value, arraysep, specifier, start_ptr); + else + expand_one_jsonb_element(out, param, value, specifier, start_ptr); + } +} + +/* + * Expand a json value as an identifier. The value must be of type string. + */ +static void +expand_jsonval_identifier(StringInfo buf, JsonbValue *jsonval) +{ + char *str; + + Assert(jsonval->type == jbvString); + + str = pnstrdup(jsonval->val.string.val, + jsonval->val.string.len); + appendStringInfoString(buf, quote_identifier(str)); + pfree(str); +} + +/* + * Expand a json value as a dot-separated-name. The value must be of type + * object and must contain elements "schemaname" (optional), "objname" + * (mandatory), "attrname" (optional). Double quotes are added to each element + * as necessary, and dot separators where needed. + * + * One day we might need a "catalog" element as well, but no current use case + * needs that. + */ +static void +expand_jsonval_dottedname(StringInfo buf, JsonbValue *jsonval) +{ + char *str; + + str = find_string_in_jsonbcontainer(jsonval->val.binary.data, + "schemaname", true, NULL); + if (str) + { + appendStringInfo(buf, "%s.", quote_identifier(str)); + pfree(str); + } + + str = find_string_in_jsonbcontainer(jsonval->val.binary.data, + "objname", false, NULL); + appendStringInfo(buf, "%s", quote_identifier(str)); + pfree(str); + + str = find_string_in_jsonbcontainer(jsonval->val.binary.data, + "attrname", true, NULL); + if (str) + { + appendStringInfo(buf, ".%s", quote_identifier(str)); + pfree(str); + } +} + +/* + * expand a json value as a type name. + */ +static void +expand_jsonval_typename(StringInfo buf, JsonbValue *jsonval) +{ + char *schema = NULL; + char *typename; + char *typmodstr; + trivalue is_array; + char *array_decor; + + /* + * We omit schema-qualifying the output name if the schema element is + * either the empty string or NULL; the difference between those two cases + * is that in the latter we quote the type name, in the former we don't. + * This allows for types with special typmod needs, such as interval and + * timestamp (see format_type_detailed), while at the same time allowing + * for the schema name to be omitted from type names that require quotes + * but are to be obtained from a user schema. + */ + + schema = find_string_in_jsonbcontainer(jsonval->val.binary.data, + "schemaname", true, NULL); + typename = find_string_in_jsonbcontainer(jsonval->val.binary.data, + "typename", false, NULL); + typmodstr = find_string_in_jsonbcontainer(jsonval->val.binary.data, + "typmod", true, NULL); + is_array = find_bool_in_jsonbcontainer(jsonval->val.binary.data, + "typarray"); + switch (is_array) + { + default: + case tv_absent: + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("missing typarray element"))); + break; + case tv_true: + array_decor = "[]"; + break; + case tv_false: + array_decor = ""; + break; + } + + if (schema == NULL) + appendStringInfo(buf, "%s%s%s", + quote_identifier(typename), + typmodstr ? typmodstr : "", + array_decor); + else if (schema[0] == '\0') + appendStringInfo(buf, "%s%s%s", + typename, + typmodstr ? typmodstr : "", + array_decor); + else + appendStringInfo(buf, "%s.%s%s%s", + quote_identifier(schema), + quote_identifier(typename), + typmodstr ? typmodstr : "", + array_decor); +} + +/* + * Expand a json value as an operator name + */ +static void +expand_jsonval_operator(StringInfo buf, JsonbValue *jsonval) +{ + char *str; + + str = find_string_in_jsonbcontainer(jsonval->val.binary.data, + "schemaname", true, NULL); + /* schema might be NULL or empty */ + if (str != NULL && str[0] != '\0') + appendStringInfo(buf, "%s.", quote_identifier(str)); + + str = find_string_in_jsonbcontainer(jsonval->val.binary.data, + "objname", false, NULL); + appendStringInfoString(buf, str); +} + +/* + * Expand a json value as a string. The value must be of type string or of + * type object. In the latter case it must contain a "fmt" element which will + * be recursively expanded; also, if the object contains an element "present" + * and it is set to false, the expansion is the empty string. + */ +static void +expand_jsonval_string(StringInfo buf, JsonbValue *jsonval) +{ + if (jsonval->type == jbvString) + { + appendBinaryStringInfo(buf, jsonval->val.string.val, + jsonval->val.string.len); + } + else if (jsonval->type == jbvBinary) + { + trivalue present; + + present = find_bool_in_jsonbcontainer(jsonval->val.binary.data, + "present"); + /* + * If "present" is set to false, this element expands to empty; + * otherwise (either true or absent), fall through to expand "fmt". + */ + if (present == tv_false) + return; + + expand_fmt_recursive(jsonval->val.binary.data, buf); + } +} + +/* + * Expand a json value as a string literal + */ +static void +expand_jsonval_strlit(StringInfo buf, JsonbValue *jsonval) +{ + char *str; + StringInfoData dqdelim; + static const char dqsuffixes[] = "_XYZZYX_"; + int dqnextchar = 0; + + str = pnstrdup(jsonval->val.string.val, jsonval->val.string.len); + + /* easy case: if there are no ' and no \, just use a single quote */ + if (strchr(str, '\'') == NULL && + strchr(str, '\\') == NULL) + { + appendStringInfo(buf, "'%s'", str); + pfree(str); + return; + } + + /* Otherwise need to find a useful dollar-quote delimiter */ + initStringInfo(&dqdelim); + appendStringInfoString(&dqdelim, "$"); + while (strstr(str, dqdelim.data) != NULL) + { + appendStringInfoChar(&dqdelim, dqsuffixes[dqnextchar++]); + dqnextchar %= sizeof(dqsuffixes) - 1; + } + /* add trailing $ */ + appendStringInfoChar(&dqdelim, '$'); + + /* And finally produce the quoted literal into the output StringInfo */ + appendStringInfo(buf, "%s%s%s", dqdelim.data, str, dqdelim.data); + pfree(dqdelim.data); + pfree(str); +} + +/* + * Expand a json value as an integer quantity + */ +static void +expand_jsonval_number(StringInfo buf, JsonbValue *jsonval) +{ + char *strdatum; + + strdatum = DatumGetCString(DirectFunctionCall1(numeric_out, + NumericGetDatum(jsonval->val.numeric))); + appendStringInfoString(buf, strdatum); +} + +/* + * Expand a json value as a role name. If the is_public element is set to + * true, PUBLIC is expanded (no quotes); otherwise, expand the given role name, + * quoting as an identifier. + */ +static void +expand_jsonval_role(StringInfo buf, JsonbValue *jsonval) +{ + trivalue is_public; + + is_public = find_bool_in_jsonbcontainer(jsonval->val.binary.data, + "is_public"); + if (is_public == tv_true) + appendStringInfoString(buf, "PUBLIC"); + else + { + char *rolename; + + rolename = find_string_in_jsonbcontainer(jsonval->val.binary.data, + "rolename", false, NULL); + appendStringInfoString(buf, quote_identifier(rolename)); + } +} + +/* + * Expand one json element into the output StringInfo according to the + * conversion specifier. The element type is validated, and an error is raised + * if it doesn't match what we expect for the conversion specifier. + */ +static void +expand_one_jsonb_element(StringInfo out, char *param, JsonbValue *jsonval, + convSpecifier specifier, const char *fmt) +{ + ErrorContextCallback sqlerrcontext; + + /* If we were given a format string, setup an ereport() context callback */ + if (fmt) + { + sqlerrcontext.callback = fmtstr_error_callback; + sqlerrcontext.arg = (void *) fmt; + sqlerrcontext.previous = error_context_stack; + error_context_stack = &sqlerrcontext; + } + + if (!jsonval) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("element \"%s\" not found", param))); + + switch (specifier) + { + case SpecIdentifier: + if (jsonval->type != jbvString) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("expected JSON string for %%I element \"%s\", got %d", + param, jsonval->type))); + expand_jsonval_identifier(out, jsonval); + break; + + case SpecDottedName: + if (jsonval->type != jbvBinary) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("expected JSON object for %%D element \"%s\", got %d", + param, jsonval->type))); + expand_jsonval_dottedname(out, jsonval); + break; + + case SpecString: + if (jsonval->type != jbvString && + jsonval->type != jbvBinary) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("expected JSON string or object for %%s element \"%s\", got %d", + param, jsonval->type))); + expand_jsonval_string(out, jsonval); + break; + + case SpecStringLiteral: + if (jsonval->type != jbvString) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("expected JSON string for %%L element \"%s\", got %d", + param, jsonval->type))); + expand_jsonval_strlit(out, jsonval); + break; + + case SpecTypename: + if (jsonval->type != jbvBinary) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("expected JSON object for %%T element \"%s\", got %d", + param, jsonval->type))); + expand_jsonval_typename(out, jsonval); + break; + + case SpecOperatorname: + if (jsonval->type != jbvBinary) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("expected JSON object for %%O element \"%s\", got %d", + param, jsonval->type))); + expand_jsonval_operator(out, jsonval); + break; + + case SpecNumber: + if (jsonval->type != jbvNumeric) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("expected JSON numeric for %%n element \"%s\", got %d", + param, jsonval->type))); + expand_jsonval_number(out, jsonval); + break; + + case SpecRole: + if (jsonval->type != jbvBinary) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("expected JSON object for %%R element \"%s\", got %d", + param, jsonval->type))); + expand_jsonval_role(out, jsonval); + break; + } + + if (fmt) + error_context_stack = sqlerrcontext.previous; +} + +/* + * Iterate on the elements of a JSON array, expanding each one into the output + * StringInfo per the given conversion specifier, separated by the given + * separator. + */ +static void +expand_jsonb_array(StringInfo out, char *param, + JsonbValue *jsonarr, char *arraysep, convSpecifier specifier, + const char *fmt) +{ + ErrorContextCallback sqlerrcontext; + JsonbContainer *container; + JsonbIterator *it; + JsonbValue v; + int type; + bool first = true; + + /* If we were given a format string, setup an ereport() context callback */ + if (fmt) + { + sqlerrcontext.callback = fmtstr_error_callback; + sqlerrcontext.arg = (void *) fmt; + sqlerrcontext.previous = error_context_stack; + error_context_stack = &sqlerrcontext; + } + + if (jsonarr->type != jbvBinary) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("element \"%s\" is not a JSON array", param))); + + container = jsonarr->val.binary.data; + if ((container->header & JB_FARRAY) == 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("element \"%s\" is not a JSON array", param))); + + it = JsonbIteratorInit(container); + while ((type = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) + { + switch (type) + { + case WJB_ELEM: + if (!first) + appendStringInfoString(out, arraysep); + first = false; + expand_one_jsonb_element(out, param, &v, specifier, NULL); + break; + } + } + + if (fmt) + error_context_stack = sqlerrcontext.previous; +} + +/*------ + * Returns a formatted string from a JSON object. + * + * The starting point is the element named "fmt" (which must be a string). + * This format string may contain zero or more %-escapes, which consist of an + * element name enclosed in { }, possibly followed by a conversion modifier, + * followed by a conversion specifier. Possible conversion specifiers are: + * + * % expand to a literal %. + * I expand as a single, non-qualified identifier + * D expand as a possibly-qualified identifier + * T expand as a type name + * O expand as an operator name + * L expand as a string literal (quote using single quotes) + * s expand as a simple string (no quoting) + * n expand as a simple number (no quoting) + * R expand as a role name (possibly quoted name, or PUBLIC) + * + * The element name may have an optional separator specification preceded + * by a colon. Its presence indicates that the element is expected to be + * an array; the specified separator is used to join the array elements. + *------ + */ +Datum +ddl_deparse_expand_command(PG_FUNCTION_ARGS) +{ + text *json = PG_GETARG_TEXT_P(0); + Datum d; + Jsonb *jsonb; + StringInfoData out; + + initStringInfo(&out); + d = DirectFunctionCall1(jsonb_in, + PointerGetDatum(TextDatumGetCString(json))); + jsonb = (Jsonb *) DatumGetPointer(d); + + expand_fmt_recursive(&jsonb->root, &out); + + PG_RETURN_TEXT_P(CStringGetTextDatum(out.data)); +} + +/* + * Error context callback for JSON format string expansion. + * + * Possible improvement: indicate which element we're expanding, if applicable + */ +static void +fmtstr_error_callback(void *arg) +{ + errcontext("while expanding format string \"%s\"", (char *) arg); + +} diff --git a/contrib/ddl_deparse/hook.c b/contrib/ddl_deparse/hook.c new file mode 100644 index 0000000..766c230 --- /dev/null +++ b/contrib/ddl_deparse/hook.c @@ -0,0 +1,32 @@ +/*-------------------------------------------------------------------------- + * contrib/ddl_deparse/hook.c + * + * Copyright (c) 2015, PostgreSQL Global Development Group + * ------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "ddl_deparse.h" +#include "commands/event_trigger.h" +#include "miscadmin.h" + +PG_MODULE_MAGIC; + +void _PG_init(void); + +void +_PG_init(void) +{ + if (IsUnderPostmaster) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("module must be loaded via shared_preload_libraries"))); + + /* CommandDeparse hook. There can only be one */ + if (CommandDeparse_hook != NULL) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("only one module can provide a CommandDeparse hook"))); + + CommandDeparse_hook = deparse_utility_command; +} -- 2.1.4