mirror of https://github.com/citusdata/citus.git
627 lines
16 KiB
C
627 lines
16 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* deparse_domain_stmts.c
|
|
* Functions to turn all Statement structures related to domains back
|
|
* into sql.
|
|
*
|
|
* Copyright (c) Citus Data, Inc.
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
#include "access/htup_details.h"
|
|
#include "catalog/heap.h"
|
|
#include "catalog/namespace.h"
|
|
#include "catalog/pg_type.h"
|
|
#include "nodes/makefuncs.h"
|
|
#include "nodes/nodeFuncs.h"
|
|
#include "nodes/parsenodes.h"
|
|
#include "parser/parse_coerce.h"
|
|
#include "parser/parse_collate.h"
|
|
#include "parser/parse_expr.h"
|
|
#include "parser/parse_node.h"
|
|
#include "parser/parse_type.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/lsyscache.h"
|
|
#include "utils/ruleutils.h"
|
|
#include "utils/syscache.h"
|
|
|
|
#include "distributed/citus_ruleutils.h"
|
|
#include "distributed/deparser.h"
|
|
#include "distributed/listutils.h"
|
|
#include "distributed/namespace_utils.h"
|
|
|
|
static void AppendConstraint(StringInfo buf, Constraint *constraint, List *domainName,
|
|
TypeName *typeName);
|
|
static Node * replace_domain_constraint_value(ParseState *pstate, ColumnRef *cref);
|
|
static Node * TransformDefaultExpr(Node *expr, List *domainName, TypeName *typeName);
|
|
static Node * TransformConstraintExpr(Node *expr, TypeName *typeName);
|
|
static CoerceToDomainValue * GetCoerceDomainValue(TypeName *typeName);
|
|
static char * TypeNameAsIdentifier(TypeName *typeName);
|
|
|
|
static Oid DomainGetBaseTypeOid(List *names, int32 *baseTypeMod);
|
|
|
|
static void AppendAlterDomainStmtSetDefault(StringInfo buf, AlterDomainStmt *stmt);
|
|
static void AppendAlterDomainStmtAddConstraint(StringInfo buf, AlterDomainStmt *stmt);
|
|
static void AppendAlterDomainStmtDropConstraint(StringInfo buf, AlterDomainStmt *stmt);
|
|
|
|
|
|
/*
|
|
* DeparseCreateDomainStmt returns the sql representation for the CREATE DOMAIN statement.
|
|
*/
|
|
char *
|
|
DeparseCreateDomainStmt(Node *node)
|
|
{
|
|
CreateDomainStmt *stmt = castNode(CreateDomainStmt, node);
|
|
StringInfoData buf = { 0 };
|
|
initStringInfo(&buf);
|
|
|
|
const char *domainIdentifier = NameListToQuotedString(stmt->domainname);
|
|
const char *typeIdentifier = TypeNameAsIdentifier(stmt->typeName);
|
|
appendStringInfo(&buf, "CREATE DOMAIN %s AS %s", domainIdentifier, typeIdentifier);
|
|
|
|
if (stmt->collClause)
|
|
{
|
|
const char *collateIdentifier =
|
|
NameListToQuotedString(stmt->collClause->collname);
|
|
appendStringInfo(&buf, " COLLATE %s", collateIdentifier);
|
|
}
|
|
|
|
Constraint *constraint = NULL;
|
|
foreach_ptr(constraint, stmt->constraints)
|
|
{
|
|
AppendConstraint(&buf, constraint, stmt->domainname, stmt->typeName);
|
|
}
|
|
|
|
appendStringInfoString(&buf, ";");
|
|
|
|
return buf.data;
|
|
}
|
|
|
|
|
|
/*
|
|
* TypeNameAsIdentifier returns the sql identifier of a TypeName. This is more complex
|
|
* than concatenating the schema name and typename since certain types contain modifiers
|
|
* that need to be correctly represented.
|
|
*/
|
|
static char *
|
|
TypeNameAsIdentifier(TypeName *typeName)
|
|
{
|
|
int32 typmod = 0;
|
|
Oid typeOid = InvalidOid;
|
|
bits16 formatFlags = FORMAT_TYPE_TYPEMOD_GIVEN | FORMAT_TYPE_FORCE_QUALIFY;
|
|
|
|
typenameTypeIdAndMod(NULL, typeName, &typeOid, &typmod);
|
|
|
|
return format_type_extended(typeOid, typmod, formatFlags);
|
|
}
|
|
|
|
|
|
/*
|
|
* DeparseDropDomainStmt returns the sql for teh DROP DOMAIN statement.
|
|
*/
|
|
char *
|
|
DeparseDropDomainStmt(Node *node)
|
|
{
|
|
DropStmt *stmt = castNode(DropStmt, node);
|
|
StringInfoData buf = { 0 };
|
|
initStringInfo(&buf);
|
|
|
|
appendStringInfoString(&buf, "DROP DOMAIN ");
|
|
if (stmt->missing_ok)
|
|
{
|
|
appendStringInfoString(&buf, "IF EXISTS ");
|
|
}
|
|
|
|
TypeName *domainName = NULL;
|
|
bool first = true;
|
|
foreach_ptr(domainName, stmt->objects)
|
|
{
|
|
if (!first)
|
|
{
|
|
appendStringInfoString(&buf, ", ");
|
|
}
|
|
first = false;
|
|
|
|
const char *identifier = NameListToQuotedString(domainName->names);
|
|
appendStringInfoString(&buf, identifier);
|
|
}
|
|
|
|
if (stmt->behavior == DROP_CASCADE)
|
|
{
|
|
appendStringInfoString(&buf, " CASCADE");
|
|
}
|
|
|
|
appendStringInfoString(&buf, ";");
|
|
|
|
return buf.data;
|
|
}
|
|
|
|
|
|
/*
|
|
* DeparseAlterDomainStmt returns the sql representation of the DOMAIN specific ALTER
|
|
* statements.
|
|
*/
|
|
char *
|
|
DeparseAlterDomainStmt(Node *node)
|
|
{
|
|
AlterDomainStmt *stmt = castNode(AlterDomainStmt, node);
|
|
|
|
StringInfoData buf = { 0 };
|
|
initStringInfo(&buf);
|
|
|
|
appendStringInfo(&buf, "ALTER DOMAIN %s ", NameListToQuotedString(stmt->typeName));
|
|
switch (stmt->subtype)
|
|
{
|
|
case 'T': /* SET DEFAULT */
|
|
{
|
|
AppendAlterDomainStmtSetDefault(&buf, stmt);
|
|
break;
|
|
}
|
|
|
|
case 'N': /* DROP NOT NULL */
|
|
{
|
|
appendStringInfoString(&buf, "DROP NOT NULL");
|
|
break;
|
|
}
|
|
|
|
case 'O': /* SET NOT NULL */
|
|
{
|
|
appendStringInfoString(&buf, "SET NOT NULL");
|
|
break;
|
|
}
|
|
|
|
case 'C': /* ADD [CONSTRAINT name] */
|
|
{
|
|
AppendAlterDomainStmtAddConstraint(&buf, stmt);
|
|
break;
|
|
}
|
|
|
|
case 'X': /* DROP CONSTRAINT */
|
|
{
|
|
AppendAlterDomainStmtDropConstraint(&buf, stmt);
|
|
break;
|
|
}
|
|
|
|
case 'V': /* VALIDATE CONSTRAINT */
|
|
{
|
|
appendStringInfo(&buf, "VALIDATE CONSTRAINT %s",
|
|
quote_identifier(stmt->name));
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
elog(ERROR, "unsupported alter domain statement for distribution");
|
|
}
|
|
}
|
|
|
|
appendStringInfoChar(&buf, ';');
|
|
|
|
return buf.data;
|
|
}
|
|
|
|
|
|
/*
|
|
* DeparseDomainRenameConstraintStmt returns the sql representation of the domain
|
|
* constraint renaming.
|
|
*/
|
|
char *
|
|
DeparseDomainRenameConstraintStmt(Node *node)
|
|
{
|
|
RenameStmt *stmt = castNode(RenameStmt, node);
|
|
|
|
StringInfoData buf = { 0 };
|
|
initStringInfo(&buf);
|
|
|
|
char *domainIdentifier = NameListToQuotedString(castNode(List, stmt->object));
|
|
appendStringInfo(&buf, "ALTER DOMAIN %s RENAME CONSTRAINT %s TO %s;",
|
|
domainIdentifier,
|
|
quote_identifier(stmt->subname),
|
|
quote_identifier(stmt->newname));
|
|
|
|
return buf.data;
|
|
}
|
|
|
|
|
|
/*
|
|
* DeparseAlterDomainOwnerStmt returns the sql representation of the ALTER DOMAIN OWNER
|
|
* statement.
|
|
*/
|
|
char *
|
|
DeparseAlterDomainOwnerStmt(Node *node)
|
|
{
|
|
AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node);
|
|
|
|
StringInfoData buf = { 0 };
|
|
initStringInfo(&buf);
|
|
|
|
List *domainName = castNode(List, stmt->object);
|
|
char *domainIdentifier = NameListToQuotedString(domainName);
|
|
appendStringInfo(&buf, "ALTER DOMAIN %s OWNER TO %s;",
|
|
domainIdentifier,
|
|
RoleSpecString(stmt->newowner, true));
|
|
|
|
return buf.data;
|
|
}
|
|
|
|
|
|
/*
|
|
* DeparseRenameDomainStmt returns the sql representation of the ALTER DOMAIN RENAME
|
|
* statement.
|
|
*/
|
|
char *
|
|
DeparseRenameDomainStmt(Node *node)
|
|
{
|
|
RenameStmt *stmt = castNode(RenameStmt, node);
|
|
|
|
StringInfoData buf = { 0 };
|
|
initStringInfo(&buf);
|
|
|
|
List *domainName = castNode(List, stmt->object);
|
|
char *domainIdentifier = NameListToQuotedString(domainName);
|
|
appendStringInfo(&buf, "ALTER DOMAIN %s RENAME TO %s;",
|
|
domainIdentifier,
|
|
quote_identifier(stmt->newname));
|
|
|
|
return buf.data;
|
|
}
|
|
|
|
|
|
/*
|
|
* DeparseAlterDomainSchemaStmt returns the sql representation of the ALTER DOMAIN SET
|
|
* SCHEMA statement.
|
|
*/
|
|
char *
|
|
DeparseAlterDomainSchemaStmt(Node *node)
|
|
{
|
|
AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
|
|
|
|
StringInfoData buf = { 0 };
|
|
initStringInfo(&buf);
|
|
|
|
List *domainName = castNode(List, stmt->object);
|
|
char *domainIdentifier = NameListToQuotedString(domainName);
|
|
appendStringInfo(&buf, "ALTER DOMAIN %s SET SCHEMA %s;",
|
|
domainIdentifier,
|
|
quote_identifier(stmt->newschema));
|
|
|
|
return buf.data;
|
|
}
|
|
|
|
|
|
/*
|
|
* DomainGetBaseTypeOid returns the type Oid and the type modifiers of the type underlying
|
|
* a domain addresses by the namelist provided as the names argument. The type modifier is
|
|
* only provided if the baseTypeMod pointer is a valid pointer on where to write the
|
|
* modifier (not a NULL pointer).
|
|
*
|
|
* If the type cannot be found this function will raise a non-userfacing error. Care needs
|
|
* to be taken by the caller that the domain is actually existing.
|
|
*/
|
|
static Oid
|
|
DomainGetBaseTypeOid(List *names, int32 *baseTypeMod)
|
|
{
|
|
TypeName *domainName = makeTypeNameFromNameList(names);
|
|
Oid domainoid = typenameTypeId(NULL, domainName);
|
|
HeapTuple tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(domainoid));
|
|
if (!HeapTupleIsValid(tup))
|
|
{
|
|
elog(ERROR, "cache lookup failed for type %u", domainoid);
|
|
}
|
|
Form_pg_type typTup = (Form_pg_type) GETSTRUCT(tup);
|
|
Oid baseTypeOid = typTup->typbasetype;
|
|
if (baseTypeMod)
|
|
{
|
|
*baseTypeMod = typTup->typtypmod;
|
|
}
|
|
ReleaseSysCache(tup);
|
|
return baseTypeOid;
|
|
}
|
|
|
|
|
|
/*
|
|
* AppendAlterDomainStmtSetDefault is a helper function that appends the default value
|
|
* portion of an ALTER DOMAIN statement that is changing the default value of the domain.
|
|
*/
|
|
static void
|
|
AppendAlterDomainStmtSetDefault(StringInfo buf, AlterDomainStmt *stmt)
|
|
{
|
|
if (stmt->def == NULL)
|
|
{
|
|
/* no default expression is a DROP DEFAULT statment */
|
|
appendStringInfoString(buf, "DROP DEFAULT");
|
|
return;
|
|
}
|
|
|
|
int32 baseTypMod = 0;
|
|
Oid baseOid = DomainGetBaseTypeOid(stmt->typeName, &baseTypMod);
|
|
TypeName *baseTypeName = makeTypeNameFromOid(baseOid, baseTypMod);
|
|
|
|
/* cook the default expression, without cooking we can't deparse */
|
|
Node *expr = stmt->def;
|
|
expr = TransformDefaultExpr(expr, stmt->typeName, baseTypeName);
|
|
|
|
/* deparse while the searchpath is cleared to force qualification of identifiers */
|
|
PushOverrideEmptySearchPath(CurrentMemoryContext);
|
|
char *exprSql = deparse_expression(expr, NIL, true, true);
|
|
PopOverrideSearchPath();
|
|
|
|
appendStringInfo(buf, "SET DEFAULT %s", exprSql);
|
|
}
|
|
|
|
|
|
/*
|
|
* AppendAlterDomainStmtAddConstraint is a helper function that appends the constraint
|
|
* specification for an ALTER DOMAIN statement that adds a constraint to the domain.
|
|
*/
|
|
static void
|
|
AppendAlterDomainStmtAddConstraint(StringInfo buf, AlterDomainStmt *stmt)
|
|
{
|
|
if (stmt->def == NULL || !IsA(stmt->def, Constraint))
|
|
{
|
|
ereport(ERROR, (errmsg("unable to deparse ALTER DOMAIN statement due to "
|
|
"unexpected contents")));
|
|
}
|
|
|
|
Constraint *constraint = castNode(Constraint, stmt->def);
|
|
appendStringInfoString(buf, "ADD");
|
|
|
|
int32 baseTypMod = 0;
|
|
Oid baseOid = DomainGetBaseTypeOid(stmt->typeName, &baseTypMod);
|
|
TypeName *baseTypeName = makeTypeNameFromOid(baseOid, baseTypMod);
|
|
|
|
AppendConstraint(buf, constraint, stmt->typeName, baseTypeName);
|
|
|
|
if (!constraint->initially_valid)
|
|
{
|
|
appendStringInfoString(buf, " NOT VALID");
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* AppendAlterDomainStmtDropConstraint is a helper function that appends the DROP
|
|
* CONSTRAINT part of an ALTER DOMAIN statement for an alter statement that drops a
|
|
* constraint.
|
|
*/
|
|
static void
|
|
AppendAlterDomainStmtDropConstraint(StringInfo buf, AlterDomainStmt *stmt)
|
|
{
|
|
appendStringInfoString(buf, "DROP CONSTRAINT ");
|
|
|
|
if (stmt->missing_ok)
|
|
{
|
|
appendStringInfoString(buf, "IF EXISTS ");
|
|
}
|
|
|
|
appendStringInfoString(buf, quote_identifier(stmt->name));
|
|
|
|
if (stmt->behavior == DROP_CASCADE)
|
|
{
|
|
appendStringInfoString(buf, " CASCADE");
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* AppendConstraint is a helper function that appends a constraint specification to a sql
|
|
* string that is adding a constraint.
|
|
*
|
|
* There are multiple places where a constraint specification is appended to sql strings.
|
|
*
|
|
* Given the complexities of serializing a constraint they all use this routine.
|
|
*/
|
|
static void
|
|
AppendConstraint(StringInfo buf, Constraint *constraint, List *domainName,
|
|
TypeName *typeName)
|
|
{
|
|
if (constraint->conname)
|
|
{
|
|
appendStringInfo(buf, " CONSTRAINT %s", quote_identifier(constraint->conname));
|
|
}
|
|
|
|
switch (constraint->contype)
|
|
{
|
|
case CONSTR_CHECK:
|
|
{
|
|
Node *expr = NULL;
|
|
if (constraint->raw_expr)
|
|
{
|
|
/* the expression was parsed from sql, still needs to transform */
|
|
expr = TransformConstraintExpr(constraint->raw_expr, typeName);
|
|
}
|
|
else if (constraint->cooked_expr)
|
|
{
|
|
/* expression was read from the catalog, no cooking required just parse */
|
|
expr = stringToNode(constraint->cooked_expr);
|
|
}
|
|
else
|
|
{
|
|
elog(ERROR, "missing expression for domain constraint");
|
|
}
|
|
|
|
PushOverrideEmptySearchPath(CurrentMemoryContext);
|
|
char *exprSql = deparse_expression(expr, NIL, true, true);
|
|
PopOverrideSearchPath();
|
|
|
|
appendStringInfo(buf, " CHECK (%s)", exprSql);
|
|
return;
|
|
}
|
|
|
|
case CONSTR_DEFAULT:
|
|
{
|
|
Node *expr = NULL;
|
|
if (constraint->raw_expr)
|
|
{
|
|
/* the expression was parsed from sql, still needs to transform */
|
|
expr = TransformDefaultExpr(constraint->raw_expr, domainName, typeName);
|
|
}
|
|
else if (constraint->cooked_expr)
|
|
{
|
|
/* expression was read from the catalog, no cooking required just parse */
|
|
expr = stringToNode(constraint->cooked_expr);
|
|
}
|
|
else
|
|
{
|
|
elog(ERROR, "missing expression for domain default");
|
|
}
|
|
|
|
PushOverrideEmptySearchPath(CurrentMemoryContext);
|
|
char *exprSql = deparse_expression(expr, NIL, true, true);
|
|
PopOverrideSearchPath();
|
|
|
|
appendStringInfo(buf, " DEFAULT %s", exprSql);
|
|
return;
|
|
}
|
|
|
|
case CONSTR_NOTNULL:
|
|
{
|
|
appendStringInfoString(buf, " NOT NULL");
|
|
return;
|
|
}
|
|
|
|
case CONSTR_NULL:
|
|
{
|
|
appendStringInfoString(buf, " NULL");
|
|
return;
|
|
}
|
|
|
|
default:
|
|
{
|
|
ereport(ERROR, (errmsg("unsupported constraint for distributed domain")));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* TransformDefaultExpr transforms a default expression from the expression passed on the
|
|
* AST to a cooked version that postgres uses internally.
|
|
*
|
|
* Only the cooked version can be easily turned back into a sql string, hence its use in
|
|
* the deparser. This is only called for default expressions that don't have a cooked
|
|
* variant stored.
|
|
*/
|
|
static Node *
|
|
TransformDefaultExpr(Node *expr, List *domainName, TypeName *typeName)
|
|
{
|
|
const char *domainNameStr = NameListToQuotedString(domainName);
|
|
int32 basetypeMod = 0; /* capture typeMod during lookup */
|
|
Type tup = typenameType(NULL, typeName, &basetypeMod);
|
|
Oid basetypeoid = typeTypeId(tup);
|
|
|
|
ReleaseSysCache(tup);
|
|
|
|
ParseState *pstate = make_parsestate(NULL);
|
|
Node *defaultExpr = cookDefault(pstate, expr,
|
|
basetypeoid,
|
|
basetypeMod,
|
|
domainNameStr,
|
|
0);
|
|
|
|
return defaultExpr;
|
|
}
|
|
|
|
|
|
/*
|
|
* TransformConstraintExpr transforms a constraint expression from the expression passed
|
|
* on the AST to a cooked version that postgres uses internally.
|
|
*
|
|
* Only the cooked version can be easily turned back into a sql string, hence its use in
|
|
* the deparser. This is only called for default expressions that don't have a cooked
|
|
* variant stored.
|
|
*/
|
|
static Node *
|
|
TransformConstraintExpr(Node *expr, TypeName *typeName)
|
|
{
|
|
/*
|
|
* Convert the A_EXPR in raw_expr into an EXPR
|
|
*/
|
|
ParseState *pstate = make_parsestate(NULL);
|
|
|
|
/*
|
|
* Set up a CoerceToDomainValue to represent the occurrence of VALUE in
|
|
* the expression. Note that it will appear to have the type of the base
|
|
* type, not the domain. This seems correct since within the check
|
|
* expression, we should not assume the input value can be considered a
|
|
* member of the domain.
|
|
*/
|
|
|
|
CoerceToDomainValue *domVal = GetCoerceDomainValue(typeName);
|
|
|
|
pstate->p_pre_columnref_hook = replace_domain_constraint_value;
|
|
pstate->p_ref_hook_state = (void *) domVal;
|
|
|
|
expr = transformExpr(pstate, expr, EXPR_KIND_DOMAIN_CHECK);
|
|
|
|
/*
|
|
* Make sure it yields a boolean result.
|
|
*/
|
|
expr = coerce_to_boolean(pstate, expr, "CHECK");
|
|
|
|
/*
|
|
* Fix up collation information.
|
|
*/
|
|
assign_expr_collations(pstate, expr);
|
|
|
|
return expr;
|
|
}
|
|
|
|
|
|
/*
|
|
* GetCoerceDomainValue creates a stub CoerceToDomainValue struct representing the type
|
|
* referenced by the typeName.
|
|
*/
|
|
static CoerceToDomainValue *
|
|
GetCoerceDomainValue(TypeName *typeName)
|
|
{
|
|
int32 typMod = 0; /* capture typeMod during lookup */
|
|
Type tup = LookupTypeName(NULL, typeName, &typMod, false);
|
|
if (tup == NULL)
|
|
{
|
|
elog(ERROR, "unable to lookup type information for %s",
|
|
NameListToQuotedString(typeName->names));
|
|
}
|
|
|
|
CoerceToDomainValue *domVal = makeNode(CoerceToDomainValue);
|
|
domVal->typeId = typeTypeId(tup);
|
|
domVal->typeMod = typMod;
|
|
domVal->collation = typeTypeCollation(tup);
|
|
domVal->location = -1;
|
|
|
|
ReleaseSysCache(tup);
|
|
return domVal;
|
|
}
|
|
|
|
|
|
/* Parser pre_columnref_hook for domain CHECK constraint parsing */
|
|
static Node *
|
|
replace_domain_constraint_value(ParseState *pstate, ColumnRef *cref)
|
|
{
|
|
/*
|
|
* Check for a reference to "value", and if that's what it is, replace
|
|
* with a CoerceToDomainValue as prepared for us by domainAddConstraint.
|
|
* (We handle VALUE as a name, not a keyword, to avoid breaking a lot of
|
|
* applications that have used VALUE as a column name in the past.)
|
|
*/
|
|
if (list_length(cref->fields) == 1)
|
|
{
|
|
Node *field1 = (Node *) linitial(cref->fields);
|
|
Assert(IsA(field1, String));
|
|
char *colname = strVal(field1);
|
|
|
|
if (strcmp(colname, "value") == 0)
|
|
{
|
|
CoerceToDomainValue *domVal = copyObject(pstate->p_ref_hook_state);
|
|
|
|
/* Propagate location knowledge, if any */
|
|
domVal->location = cref->location;
|
|
return (Node *) domVal;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|