gpcontrib/gp_exttable_fdw/option.c (248 lines of code) (raw):
/*-------------------------------------------------------------------------
*
* option.c
* FDW option handling for gp_exttable_fdw
*
* Portions Copyright (c) 2007-2008, Greenplum inc
* Portions Copyright (c) 2012-Present VMware, Inc. or its affiliates.
*
* IDENTIFICATION
* gpcontrib/gp_exttable_fdw/option.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "cdb/cdbvars.h"
#include "utils/uri.h"
#include "utils/syscache.h"
#include "access/reloptions.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_foreign_server.h"
#include "catalog/pg_foreign_table.h"
#include "catalog/pg_user_mapping.h"
#include "commands/defrem.h"
#include "access/external.h"
#include "catalog/pg_extprotocol.h"
#include "mb/pg_wchar.h"
/*
* Describes the valid options for objects that this wrapper uses.
*/
typedef struct GpExttableFdwMustOption
{
const char *keyword;
Oid optcontext; /* OID of catalog in which option may appear */
} GpExttableFdwMustOption;
/*
* Valid options for gp_exttable_fdw.
*/
static const GpExttableFdwMustOption gp_exttable_fdw_must_options[] = {
{"location_uris", ForeignTableRelationId},
{"command", ForeignTableRelationId},
{"format_type", ForeignTableRelationId},
{NULL, InvalidOid}
};
/*
* Helper functions
*/
static bool is_must_option(const char *keyword, Oid context);
static void is_valid_locationuris(List *location_list, bool is_writable);
static void is_valid_rejectlimit(const char *reject_limit_type, const int32 reject_limit);
/*
* Validate the generic options given to a FOREIGN DATA WRAPPER, SERVER,
* USER MAPPING or FOREIGN TABLE that uses gp_exttable_fdw.
*
* Raise an ERROR if the option or its value is considered invalid.
*/
PG_FUNCTION_INFO_V1(gp_exttable_permission_check);
/* FDW validator for external tables */
Datum
gp_exttable_permission_check(PG_FUNCTION_ARGS)
{
List *options_list = untransformRelOptions(PG_GETARG_DATUM(0));
Oid catalog = PG_GETARG_OID(1);
ListCell *cell;
bool is_writable = false;
bool is_superuser = superuser();
List *location_list = NIL;
/* default reject_limit_type is r(ROW)*/
char *reject_limit_type = "r";
int32 reject_limit = -1;
bool formattype_found = false;
bool locationuris_found = false;
bool command_found = false;
bool rejectlimit_found = false;
/*
* Check that only options supported by gp_exttable_fdw, and allowed for the
* current object type, are given.
*/
foreach(cell, options_list)
{
DefElem *def = (DefElem *) lfirst(cell);
/*
* Validate option value, when we can do so without any context.
*/
if (pg_strcasecmp(def->defname, "is_writable") == 0)
{
/* these accept only boolean values */
is_writable = defGetBoolean(def);
}
else if(pg_strcasecmp(def->defname, "command") == 0)
{
command_found = true;
/* Never allow EXECUTE if not superuser. */
if(!is_superuser)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to create an EXECUTE external web table")));
}
else if(pg_strcasecmp(def->defname, "location_uris") == 0)
{
location_list = TokenizeLocationUris(defGetString(def));
locationuris_found = true;
}
else if(pg_strcasecmp(def->defname, "format_type") == 0)
{
char *format = (char *) defGetString(def);
if(pg_strcasecmp(format, "t") != 0 && pg_strcasecmp(format, "c") != 0
&& pg_strcasecmp(format, "b") != 0)
{
ereport(ERROR,
(errcode(ERRCODE_FDW_INVALID_ATTRIBUTE_VALUE),
errmsg("format_type must be [t | c | b], t(text), c(csv), b(custom)")));
}
formattype_found = true;
}
else if(pg_strcasecmp(def->defname, "reject_limit_type") == 0)
{
reject_limit_type = (char *) defGetString(def);
if (pg_strcasecmp(reject_limit_type, "r") != 0 && pg_strcasecmp( reject_limit_type, "p") != 0)
ereport(ERROR,
(errcode(ERRCODE_FDW_INVALID_ATTRIBUTE_VALUE),
errmsg("reject_limit_type must be [r | p], r(ROW) or p(PERCENT)")));
}
else if(pg_strcasecmp(def->defname, "reject_limit") == 0)
{
reject_limit = atoi((char *) defGetString(def));
rejectlimit_found = true;
}
else if(pg_strcasecmp(def->defname, "encoding") == 0)
{
char *encoding = (char *) defGetString(def);
if (!PG_VALID_ENCODING(atoi(encoding)))
ereport(ERROR,
(errcode(ERRCODE_FDW_INVALID_ATTRIBUTE_VALUE),
errmsg("%s is not a valid encoding code", encoding)));
}
}
if(!formattype_found && is_must_option("format_type", catalog))
{
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("must specify format_type option([t | c | b], t(text), c(csv), b(custom))")));
}
if(locationuris_found && command_found)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("location_uris and command options conflict with each other")));
if(is_must_option("location_uris", catalog) && !locationuris_found && !command_found)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("must specify one of location_uris and command option")));
if(!is_superuser && Gp_role == GP_ROLE_DISPATCH)
{
/*----------
* check permissions to create this external table.
*
* - Never allow 'file' exttables if not superuser.
* - Allow http, gpfdist or gpfdists tables if pg_auth has the right
* permissions for this role and for this type of table
*----------
*/
is_valid_locationuris(location_list, is_writable);
}
if(rejectlimit_found)
{
if(is_writable)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("single row error handling may not be used with a writable external table")));
is_valid_rejectlimit(reject_limit_type, reject_limit);
}
PG_RETURN_VOID();
}
/*
* Check whether the given option is a must for gp_exttable_fdw.
* context is the Oid of the catalog holding the object the option is for.
*/
static bool
is_must_option(const char *keyword, Oid context)
{
const GpExttableFdwMustOption *opt;
for (opt = gp_exttable_fdw_must_options; opt->keyword; opt++)
{
if (context == opt->optcontext && strcmp(opt->keyword, keyword) == 0)
return true;
}
return false;
}
static void
is_valid_locationuris(List *location_list, bool is_writable)
{
ListCell *first_uri = list_head(location_list);
Value *v = lfirst(first_uri);
char *uri_str = pstrdup(v->val.str);
Uri *uri = ParseExternalTableUri(uri_str);
if (uri->protocol == URI_FILE)
{
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to create an external table with a file protocol")));
}
else
{
/*
* Check if this role has the proper 'gpfdist', 'gpfdists' or
* 'http' permissions in pg_auth for creating this table.
*/
bool isnull;
HeapTuple tuple;
tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(GetUserId()));
if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("role \"%s\" does not exist (in DefineExternalRelation)",
GetUserNameFromId(GetUserId(), false))));
if ((uri->protocol == URI_GPFDIST || uri->protocol == URI_GPFDISTS) && is_writable)
{
Datum d_wextgpfd;
bool createwextgpfd;
d_wextgpfd = SysCacheGetAttr(AUTHOID, tuple,
Anum_pg_authid_rolcreatewextgpfd,
&isnull);
createwextgpfd = (isnull ? false : DatumGetBool(d_wextgpfd));
if (!createwextgpfd)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied: no privilege to create a writable gpfdist(s) external table")));
}
else if ((uri->protocol == URI_GPFDIST || uri->protocol == URI_GPFDISTS) && !is_writable)
{
Datum d_rextgpfd;
bool createrextgpfd;
d_rextgpfd = SysCacheGetAttr(AUTHOID, tuple,
Anum_pg_authid_rolcreaterextgpfd,
&isnull);
createrextgpfd = (isnull ? false : DatumGetBool(d_rextgpfd));
if (!createrextgpfd)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied: no privilege to create a readable gpfdist(s) external table")));
}
else if (uri->protocol == URI_HTTP && !is_writable)
{
Datum d_exthttp;
bool createrexthttp;
d_exthttp = SysCacheGetAttr(AUTHOID, tuple,
Anum_pg_authid_rolcreaterexthttp,
&isnull);
createrexthttp = (isnull ? false : DatumGetBool(d_exthttp));
if (!createrexthttp)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied: no privilege to create an http external table")));
}
else if (uri->protocol == URI_CUSTOM)
{
Oid ownerId = GetUserId();
char *protname = uri->customprotocol;
Oid ptcId = get_extprotocol_oid(protname, false);
AclResult aclresult;
/* Check we have the right permissions on this protocol */
if (!pg_extprotocol_ownercheck(ptcId, ownerId))
{
AclMode mode = (is_writable ? ACL_INSERT : ACL_SELECT);
aclresult = pg_extprotocol_aclcheck(ptcId, ownerId, mode);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, OBJECT_EXTPROTOCOL, protname);
}
}
else
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("internal error in DefineExternalRelation"),
errdetail("Protocol is %d, writable is %d.",
uri->protocol, is_writable)));
ReleaseSysCache(tuple);
}
FreeExternalTableUri(uri);
pfree(uri_str);
}
static void is_valid_rejectlimit(const char *reject_limit_type, const int32 reject_limit)
{
if (pg_strcasecmp(reject_limit_type, "r") == 0)
{
/* ROWS */
if (reject_limit < 2)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("segment reject limit in ROWS must be 2 or larger (got %d)",
reject_limit)));
}
else if(pg_strcasecmp(reject_limit_type, "p") == 0)
{
/* PERCENT */
if (reject_limit < 1 || reject_limit > 100)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("segment reject limit in PERCENT must be between 1 and 100 (got %d)",
reject_limit)));
}
else
{
/* invalid reject_limit_type */
ereport(ERROR,
(errcode(ERRCODE_FDW_INVALID_ATTRIBUTE_VALUE),
errmsg("reject_limit_type must be [r | p], r(ROW) or p(PERCENT)")));
}
}