* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
* -------------------------------------------------------------------------
*
* connector.cpp
* The entrance function of extension connector.
*
* There are two connect functions:
*
* * exec_on_extension(text, text) -- by DN with privileges control
* * exec_hadoop_sql(text, text, text) -- by CN
*
* IDENTIFICATION
* Code/src/gausskernel/cbb/extension/connector/connector.cpp
*
* -------------------------------------------------------------------------
*/
#include "postgres.h"
#include "knl/knl_variable.h"
#include "utils/builtins.h"
#include "datasource/datasource.h"
#include "miscadmin.h"
#include "parser/scansup.h"
#include "utils/acl.h"
#include "connector.h"
#include "gaussdb_version.h"
#include "odbc_connector.h"
PG_MODULE_MAGIC;
#define REMOTE_DEFAULT_ENCODING "UTF8"
#define DEFAULT_DRIVERTYPE "odbc"
* Key set in Extension Connector
*
* Note: length of the keyname must be less than NAMEDATALEN
*/
#define NAME_DSN "dsn"
#define NAME_SQL "sql"
#define NAME_USERNAME "username"
#define NAME_PASSWORD "password"
#define NAME_ENCODING "encoding"
static void init_key_value(void);
static void set_default_if_not_given(void);
static void read_one_key_value(const char* key, const char* value);
static void get_conn_info(FuncCallContext* funcctx, FunctionCallInfo fcinfo);
static void init_ec(FuncCallContext* funcctx, FunctionCallInfo fcinfo);
static void check_args3(FuncCallContext* funcctx, FunctionCallInfo fcinfo);
static void copy_ec_info(char* field, const char* info, int fieldMaxLen);
* Structures for Extension Connector Controler: a base class
*/
class ECControlBasic {
public:
virtual void connect() = 0;
virtual void execute() = 0;
virtual Datum fetch(bool& isEnd) = 0;
virtual void end() = 0;
virtual ~ECControlBasic(){};
};
* A Specific Extension Connector Controler based on ODBC
*/
class ECControlODBC : public ECControlBasic {
public:
ECControlODBC(FuncCallContext* fctx, FunctionCallInfo finfo);
ECControlODBC() : funcctx(NULL), fcinfo(NULL)
{}
virtual ~ECControlODBC() override {};
void connect() override;
void execute() override;
Datum fetch(bool& isEnd);
void end() override;
private:
FuncCallContext* funcctx;
FunctionCallInfo fcinfo;
};
ECControlODBC::ECControlODBC(FuncCallContext* fctx, FunctionCallInfo finfo) : funcctx(fctx), fcinfo(finfo)
{}
void ECControlODBC::connect()
{
connect_odbc((const char*)t_thrd.conn_cxt.value_dsn,
(const char*)t_thrd.conn_cxt.value_username,
(const char*)t_thrd.conn_cxt.value_password,
funcctx,
fcinfo);
}
void ECControlODBC::execute()
{
exec_odbc((const char*)t_thrd.conn_cxt.value_dsn,
(const char*)t_thrd.conn_cxt.value_sql,
(const char*)t_thrd.conn_cxt.value_encoding);
}
Datum ECControlODBC::fetch(bool& isEnd)
{
return fetch_odbc(
(const char*)t_thrd.conn_cxt.value_dsn, funcctx, isEnd, (const char*)t_thrd.conn_cxt.value_encoding);
}
void ECControlODBC::end()
{
if (t_thrd.conn_cxt.value_username != NULL) {
errno_t ret;
ret = memset_s(t_thrd.conn_cxt.value_username,
strlen(t_thrd.conn_cxt.value_username),
0,
strlen(t_thrd.conn_cxt.value_username));
securec_check(ret, "\0", "\0");
}
if (t_thrd.conn_cxt.value_password != NULL) {
errno_t ret;
ret = memset_s(t_thrd.conn_cxt.value_password,
strlen(t_thrd.conn_cxt.value_password),
0,
strlen(t_thrd.conn_cxt.value_password));
securec_check(ret, "\0", "\0");
}
end_odbc((const char*)t_thrd.conn_cxt.value_dsn);
}
* Get value of a key by a given string and its len
*/
#define GetKeyValueByString(name, value, p, len) \
do { \
errno_t ret; \
if ((value) != NULL) \
ereport(ERROR, \
(errmodule(MOD_EC), \
errcode(ERRCODE_INVALID_PARAMETER_VALUE), \
errmsg("key '%s' is given more than once!", name))); \
value = (char*)palloc0((len) + 1); \
ret = memcpy_s(value, (len) + 1, p, (len)); \
securec_check(ret, "\0", "\0"); \
} while (0)
* init_key_value
* initialize key value
*
* Note: This function must be called before reading any key-value
*/
static void init_key_value(void)
{
t_thrd.conn_cxt.value_drivertype = NULL;
t_thrd.conn_cxt.value_dsn = NULL;
t_thrd.conn_cxt.value_username = NULL;
t_thrd.conn_cxt.value_password = NULL;
t_thrd.conn_cxt.value_sql = NULL;
t_thrd.conn_cxt.value_encoding = NULL;
}
* set_default_if_not_given
* Set default value of keys if not given
*
* Note: This function should be called after read key-value
*/
static void set_default_if_not_given(void)
{
errno_t ret;
int len;
if (t_thrd.conn_cxt.value_encoding == NULL) {
len = strlen(REMOTE_DEFAULT_ENCODING);
t_thrd.conn_cxt.value_encoding = (char*)palloc0(len + 1);
ret = memcpy_s(t_thrd.conn_cxt.value_encoding, len + 1, REMOTE_DEFAULT_ENCODING, len);
securec_check(ret, "\0", "\0");
}
if (t_thrd.conn_cxt.value_drivertype == NULL) {
len = strlen(DEFAULT_DRIVERTYPE);
t_thrd.conn_cxt.value_drivertype = (char*)palloc0(len + 1);
ret = memcpy_s(t_thrd.conn_cxt.value_drivertype, len + 1, DEFAULT_DRIVERTYPE, len);
securec_check(ret, "\0", "\0");
}
}
* read_one_key_value:
* read one key-value from a given key and value
*
* @IN key: key name
* @IN value: value of the key
* @RETURN void
*
* Note: We don't report error if the key doesn't match any keys in EC system,
* because those keys may be used in some objects elsewhere.
*/
static void read_one_key_value(const char* key, const char* value)
{
size_t len;
if (key == NULL || value == NULL)
return;
len = strlen(value);
if (0 == strcasecmp(key, NAME_DSN)) {
GetKeyValueByString(NAME_DSN, t_thrd.conn_cxt.value_dsn, value, len);
} else if (0 == strcasecmp(key, NAME_ENCODING)) {
GetKeyValueByString(NAME_ENCODING, t_thrd.conn_cxt.value_encoding, value, len);
} else if (0 == strcasecmp(key, NAME_USERNAME)) {
errno_t errCode;
char *plainuid = NULL;
if (len == 0)
return;
decryptECString(value, &plainuid, SOURCE_MODE);
GetKeyValueByString(NAME_USERNAME, t_thrd.conn_cxt.value_username, plainuid, len);
errCode = memset_s(plainuid, strlen(plainuid), 0, strlen(plainuid));
securec_check(errCode, "\0", "\0");
pfree(plainuid);
} else if (0 == strcasecmp(key, NAME_PASSWORD)) {
errno_t errCode;
char *plainpwd = NULL;
if (len == 0)
return;
decryptECString(value, &plainpwd, SOURCE_MODE);
GetKeyValueByString(NAME_PASSWORD, t_thrd.conn_cxt.value_password, plainpwd, len);
errCode = memset_s(plainpwd, strlen(plainpwd), 0, strlen(plainpwd));
securec_check(errCode, "\0", "\0");
pfree(plainpwd);
} else
return;
}
* get_conn_info:
* get connection information, which is defined by the format of KV
*
* @IN funcctx: function call context
* @IN fcinfo: function call info
* @RETURN void
*/
static void get_conn_info(FuncCallContext* funcctx, FunctionCallInfo fcinfo)
{
DataSource* data_source = NULL;
MemoryContext oldcontext;
ListCell* cell = NULL;
AclResult aclresult;
Oid sourceid;
Oid userid;
char* srcname = NULL;
char* str = NULL;
int len;
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
init_key_value();
* Parameter-1: Data Source Object
*/
if (PG_ARGISNULL(0)) {
ereport(ERROR,
(errmodule(MOD_EC),
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("missing data source to be connected for first parameter.")));
}
str = text_to_cstring(PG_GETARG_TEXT_PP(0));
srcname = pstrdup(str);
truncate_identifier(srcname, strlen(srcname), true);
data_source = GetDataSourceByName(srcname, false);
if (data_source == NULL)
ereport(ERROR,
(errmodule(MOD_EC),
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("data source \"%s\" does not exist", srcname)));
userid = GetUserId();
sourceid = data_source->sourceid;
aclresult = pg_extension_data_source_aclcheck(sourceid, userid, ACL_USAGE);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, ACL_KIND_DATA_SOURCE, data_source->srcname);
foreach (cell, data_source->options) {
DefElem* def = (DefElem*)lfirst(cell);
read_one_key_value(def->defname, strVal(def->arg));
}
* Parameter-2: Statement to be executed
*/
if (PG_ARGISNULL(1)) {
ereport(ERROR,
(errmodule(MOD_EC),
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("missing statement to be executed for second parameter.")));
}
str = text_to_cstring(PG_GETARG_TEXT_PP(1));
len = strlen(str);
GetKeyValueByString(NAME_SQL, t_thrd.conn_cxt.value_sql, str, len);
set_default_if_not_given();
MemoryContextSwitchTo(oldcontext);
}
void clean_ec_conn(void)
{
if (t_thrd.conn_cxt.ecCtrl)
t_thrd.conn_cxt.ecCtrl->end();
}
void delete_ec_ctrl(void)
{
if (t_thrd.conn_cxt.ecCtrl) {
delete t_thrd.conn_cxt.ecCtrl;
t_thrd.conn_cxt.ecCtrl = NULL;
}
}
* init_ec:
* Check keys and create a ECControler
*
* @IN funcctx: Function Call Context
* @IN fcinfo: FunctionCallInfo
* @RETURN: void
*/
static void init_ec(FuncCallContext* funcctx, FunctionCallInfo fcinfo)
{
MemoryContext oldcontext;
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
AssertEreport((t_thrd.conn_cxt.value_drivertype != NULL), MOD_EC, "driver is NULL");
if (0 == strcasecmp(t_thrd.conn_cxt.value_drivertype, "odbc")) {
* Job based on ODBC.
*
* We connect to other server by ODBC-driver,
* execute sql or other statements in the remote server,
* and get what we want.
*/
if (t_thrd.conn_cxt.value_dsn == NULL) {
ereport(ERROR,
(errmodule(MOD_EC),
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("for ODBC driver, we need a DSN!")));
}
AssertEreport((t_thrd.conn_cxt.value_sql != NULL), MOD_EC, "input sql is NULL");
delete_ec_ctrl();
t_thrd.conn_cxt.ecCtrl = new ECControlODBC(funcctx, fcinfo);
if (unlikely(t_thrd.conn_cxt.ecCtrl == NULL)) {
ereport(ERROR, (errmodule(MOD_EC), errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
errmsg("Initialize ODBC Connector failed due to insufficient memory.")));
}
}
MemoryContextSwitchTo(oldcontext);
}
* check_args3
* check the arguments (total 3 parameters) for old function exec_hadoop_sql
*
* @IN funcctx: FuncCallContext*, for Set Returning Functions
* @IN fcinfo: FunctionCallInfo
* @return void
*/
static void check_args3(FuncCallContext* funcctx, FunctionCallInfo fcinfo)
{
char* str = NULL;
Size len;
MemoryContext oldcontext;
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
init_key_value();
* argu_1: DSN
*/
if (PG_ARGISNULL(0)) {
ereport(
ERROR, (errmodule(MOD_EC), errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("DSN should not be NULL!")));
} else {
str = text_to_cstring(PG_GETARG_TEXT_PP(0));
len = strlen(str);
if (unlikely(len == 0)) {
ereport(ERROR, (errmodule(MOD_EC), errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("Invalid string length")));
}
GetKeyValueByString(NAME_DSN, t_thrd.conn_cxt.value_dsn, str, len);
}
* argu_2: SQL
*/
if (PG_ARGISNULL(1)) {
ereport(ERROR, (errmodule(MOD_EC), errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("SQL can not be null!")));
} else {
str = text_to_cstring(PG_GETARG_TEXT_PP(1));
len = strlen(str);
if (unlikely(len == 0)) {
ereport(ERROR, (errmodule(MOD_EC), errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("Invalid string length")));
}
GetKeyValueByString(NAME_SQL, t_thrd.conn_cxt.value_sql, str, len);
}
* argu_3: Encoding name
*/
if (PG_ARGISNULL(2)) {
errno_t ret;
len = strlen(REMOTE_DEFAULT_ENCODING);
t_thrd.conn_cxt.value_encoding = (char*)palloc0(len + 1);
ret = memcpy_s(t_thrd.conn_cxt.value_encoding, len + 1, REMOTE_DEFAULT_ENCODING, len);
securec_check(ret, "\0", "\0");
} else {
str = text_to_cstring(PG_GETARG_TEXT_PP(2));
len = strlen(str);
if (unlikely(len == 0)) {
ereport(ERROR, (errmodule(MOD_EC), errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("Invalid string length")));
}
GetKeyValueByString(NAME_ENCODING, t_thrd.conn_cxt.value_encoding, str, len);
}
MemoryContextSwitchTo(oldcontext);
}
* copy_ec_info
* copy ec info to instrumentation field for ec view
*
* @IN field: char, instrumentation field
* @IN ecInfo: char *, ec info
* @IN fieldMaxLen: int, instrumentation field max len
* @return void
*/
static void copy_ec_info(char* field, const char* ecInfo, int fieldMaxLen)
{
errno_t rc = EOK;
if (ecInfo != NULL) {
rc = strncpy_s(field, fieldMaxLen, ecInfo, fieldMaxLen - 1);
securec_check(rc, "\0", "\0");
field[fieldMaxLen - 1] = '\0';
} else {
field[0] = '\0';
}
}
PG_FUNCTION_INFO_V1(exec_on_extension);
PG_FUNCTION_INFO_V1(exec_hadoop_sql);
void check_ec_supported()
{
if (is_feature_disabled(EXTENSION_CONNECTOR) == true) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Extension Connector is not supported in current version.")));
}
}
void record_ec_info(FunctionScanState* node)
{
if (node != NULL && node->ss.ps.instrument) {
node->ss.ps.instrument->ec_status = EC_STATUS_FETCHING;
(node->ss.ps.instrument->ec_fetch_count)++;
}
}
* exec_hadoop_sql:
* Entrance function (old version) of the connector by odbc
*
* @IN text: "DSN"
* @IN text: "SQL" (query string)
* @IN text: "Encoding_Name" (to/from server, include sql and results,
* default value is "UTF8")
*
* @RETURN: set of record, and vary with query string
*
* Note: here we just keep the old fashion of the connector
*/
Datum exec_hadoop_sql(PG_FUNCTION_ARGS)
{
if (!superuser()) {
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be system admin to use this function"),
errhint("This function may have security risk, please use exec_on_extension instead.")));
}
if (u_sess->attr.attr_sql.sql_compatibility == B_FORMAT) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-support feature in B compatibility")));
}
FuncCallContext* funcctx = NULL;
Datum result;
bool isend = false;
FunctionScanState* node = (FunctionScanState*)fcinfo->context;
check_ec_supported();
if (SRF_IS_FIRSTCALL()) {
funcctx = SRF_FIRSTCALL_INIT();
check_args3(funcctx, fcinfo);
if (node != NULL && node->ss.ps.instrument != NULL) {
node->ss.ps.instrument->ec_operator = IS_EC_OPERATOR;
node->ss.ps.instrument->ec_status = EC_STATUS_INIT;
node->ss.ps.instrument->ec_fetch_count = 0;
}
connect_odbc((const char*)t_thrd.conn_cxt.value_dsn,
(const char*)t_thrd.conn_cxt.value_username,
(const char*)t_thrd.conn_cxt.value_password,
funcctx,
fcinfo);
if (node != NULL && node->ss.ps.instrument) {
node->ss.ps.instrument->ec_status = EC_STATUS_CONNECTED;
copy_ec_info(
node->ss.ps.instrument->ec_execute_datanode, g_instance.attr.attr_common.PGXCNodeName, NAMEDATALEN + 1);
copy_ec_info(node->ss.ps.instrument->ec_dsn, t_thrd.conn_cxt.value_dsn, NAMEDATALEN + 1);
copy_ec_info(node->ss.ps.instrument->ec_username, t_thrd.conn_cxt.value_username, NAMEDATALEN + 1);
copy_ec_info(node->ss.ps.instrument->ec_query, t_thrd.conn_cxt.value_sql, ECQUERYDATALEN + 1);
}
exec_odbc((const char*)t_thrd.conn_cxt.value_dsn,
(const char*)t_thrd.conn_cxt.value_sql,
(const char*)t_thrd.conn_cxt.value_encoding);
if (node != NULL && node->ss.ps.instrument) {
node->ss.ps.instrument->ec_status = EC_STATUS_EXECUTED;
}
}
funcctx = SRF_PERCALL_SETUP();
result =
fetch_odbc((const char*)t_thrd.conn_cxt.value_dsn, funcctx, isend, (const char*)t_thrd.conn_cxt.value_encoding);
if (!isend) {
record_ec_info(node);
SRF_RETURN_NEXT(funcctx, result);
} else {
end_odbc((const char*)t_thrd.conn_cxt.value_dsn);
if (node != NULL && node->ss.ps.instrument) {
node->ss.ps.instrument->ec_status = EC_STATUS_END;
}
SRF_RETURN_DONE(funcctx);
}
}
* exec_on_extension:
* Entrance Function of the Extension Connector
*
* @IN text: server name
* @IN text: a statement to be executed
* @RETURN: set of record
*
* Note:
* Here we manage the authority by using Data Source Object
*/
Datum exec_on_extension(PG_FUNCTION_ARGS)
{
if (u_sess->attr.attr_sql.sql_compatibility == B_FORMAT) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-support feature in B compatibility")));
}
FuncCallContext* funcctx = NULL;
Datum result;
bool isEnd = false;
FunctionScanState* node = (FunctionScanState*)fcinfo->context;
check_ec_supported();
if (SRF_IS_FIRSTCALL()) {
funcctx = SRF_FIRSTCALL_INIT();
get_conn_info(funcctx, fcinfo);
init_ec(funcctx, fcinfo);
if (node != NULL && node->ss.ps.instrument != NULL) {
node->ss.ps.instrument->ec_operator = IS_EC_OPERATOR;
node->ss.ps.instrument->ec_status = EC_STATUS_INIT;
node->ss.ps.instrument->ec_fetch_count = 0;
}
t_thrd.conn_cxt.ecCtrl->connect();
if (node != NULL && node->ss.ps.instrument) {
node->ss.ps.instrument->ec_status = EC_STATUS_CONNECTED;
copy_ec_info(
node->ss.ps.instrument->ec_execute_datanode, g_instance.attr.attr_common.PGXCNodeName, NAMEDATALEN + 1);
copy_ec_info(node->ss.ps.instrument->ec_dsn, t_thrd.conn_cxt.value_dsn, NAMEDATALEN + 1);
copy_ec_info(node->ss.ps.instrument->ec_username, t_thrd.conn_cxt.value_username, NAMEDATALEN + 1);
copy_ec_info(node->ss.ps.instrument->ec_query, t_thrd.conn_cxt.value_sql, ECQUERYDATALEN + 1);
}
t_thrd.conn_cxt.ecCtrl->execute();
if (node != NULL && node->ss.ps.instrument) {
node->ss.ps.instrument->ec_status = EC_STATUS_EXECUTED;
}
}
funcctx = SRF_PERCALL_SETUP();
result = t_thrd.conn_cxt.ecCtrl->fetch(isEnd);
if (!isEnd) {
record_ec_info(node);
SRF_RETURN_NEXT(funcctx, result);
} else {
t_thrd.conn_cxt.ecCtrl->end();
delete_ec_ctrl();
if (node != NULL && node->ss.ps.instrument) {
node->ss.ps.instrument->ec_status = EC_STATUS_END;
}
SRF_RETURN_DONE(funcctx);
}
}