Skip to content
This repository has been archived by the owner on Aug 2, 2022. It is now read-only.

ODBC: Report error from Excel when executing an invalid query #611

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
99 changes: 98 additions & 1 deletion sql-odbc/src/odfesqlodbc/es_communication.cpp
Expand Up @@ -40,6 +40,8 @@ static const std::string OPENDISTRO_SQL_PLUGIN_NAME = "opendistro_sql";
static const std::string ALLOCATION_TAG = "AWS_SIGV4_AUTH";
static const std::string SERVICE_NAME = "es";
static const std::string ESODBC_PROFILE_NAME = "elasticsearchodbc";
static const std::string ERROR_MSG_PREFIX =
"[Open Distro For Elasticsearch][SQL ODBC Driver][SQL Plugin] ";
static const std::string JSON_SCHEMA =
"{" // This was generated from the example elasticsearch data
"\"type\": \"object\","
Expand Down Expand Up @@ -79,6 +81,33 @@ static const std::string CURSOR_JSON_SCHEMA =
"},"
"\"required\": [\"datarows\"]"
"}";
static const std::string ERROR_RESPONSE_SCHEMA = R"EOF(
{
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"reason": { "type": "string" },
"details": { "type": "string" },
"type": { "type": "string" }
},
"required": [
"reason",
"details",
"type"
]
},
"status": {
"type": "integer"
}
},
"required": [
"error",
"status"
]
}
)EOF";

void ESCommunication::AwsHttpResponseToString(
std::shared_ptr< Aws::Http::HttpResponse > response, std::string& output) {
Expand Down Expand Up @@ -118,6 +147,32 @@ void ESCommunication::PrepareCursorResult(ESResult& es_result) {
}
}

std::shared_ptr< ErrorDetails > ESCommunication::ParseErrorResponse(
ESResult& es_result) {
// Prepare document and validate schema
try {
LogMsg(ES_DEBUG, "Parsing error response (with schema validation)");
es_result.es_result_doc.parse(es_result.result_json,
ERROR_RESPONSE_SCHEMA);

auto error_details = std::make_shared< ErrorDetails >();
error_details->reason =
es_result.es_result_doc["error"]["reason"].as_string();
error_details->details =
es_result.es_result_doc["error"]["details"].as_string();
error_details->source_type =
es_result.es_result_doc["error"]["type"].as_string();
return error_details;
} catch (const rabbit::parse_error& e) {
// The exception rabbit gives is quite useless - providing the json
// will aid debugging for users
std::string str = "Exception obtained '" + std::string(e.what())
+ "' when parsing json string '"
+ es_result.result_json + "'.";
throw std::runtime_error(str.c_str());
}
}

void ESCommunication::GetJsonSchema(ESResult& es_result) {
// Prepare document and validate schema
try {
Expand All @@ -139,6 +194,7 @@ ESCommunication::ESCommunication()
#pragma clang diagnostic ignored "-Wreorder"
#endif // __APPLE__
: m_status(ConnStatusType::CONNECTION_BAD),
m_error_type(ConnErrorType::ERROR_SUCCESS),
m_valid_connection_options(false),
m_is_retrieving(false),
m_error_message(""),
Expand All @@ -159,7 +215,14 @@ ESCommunication::~ESCommunication() {

std::string ESCommunication::GetErrorMessage() {
// TODO #35 - Check if they expect NULL or "" when there is no error.
return m_error_message;
m_error_details->details = std::regex_replace(m_error_details->details,
std::regex("\\n"), "\\\\n");
return ERROR_MSG_PREFIX + m_error_details->reason + ": "
+ m_error_details->details;
}

ConnErrorType ESCommunication::GetErrorType() {
return m_error_type;
}

bool ESCommunication::ConnectionOptions(runtime_options& rt_opts,
Expand All @@ -180,6 +243,7 @@ bool ESCommunication::ConnectDBStart() {
LogMsg(ES_ALL, "Starting DB connection.");
m_status = ConnStatusType::CONNECTION_BAD;
if (!m_valid_connection_options) {
m_error_type = ConnErrorType::ERROR_COMM_LINK_FAILURE;
m_error_message =
"Invalid connection options, unable to connect to DB.";
LogMsg(ES_ERROR, m_error_message.c_str());
Expand All @@ -189,6 +253,7 @@ bool ESCommunication::ConnectDBStart() {

m_status = ConnStatusType::CONNECTION_NEEDED;
if (!EstablishConnection()) {
m_error_type = ConnErrorType::ERROR_COMM_LINK_FAILURE;
m_error_message = "Failed to establish connection to DB.";
LogMsg(ES_ERROR, m_error_message.c_str());
DropDBConnection();
Expand Down Expand Up @@ -222,14 +287,17 @@ bool ESCommunication::CheckConnectionOptions() {
if (m_rt_opts.auth.auth_type == AUTHTYPE_BASIC) {
if (m_rt_opts.auth.username.empty()
|| m_rt_opts.auth.password.empty()) {
m_error_type = ERROR_INVALID_AUTH;
m_error_message = AUTHTYPE_BASIC
" authentication requires a username and password.";
}
} else {
m_error_type = ERROR_INVALID_AUTH;
m_error_message = "Unknown authentication type: '"
+ m_rt_opts.auth.auth_type + "'";
}
} else if (m_rt_opts.conn.server == "") {
m_error_type = ERROR_UNABLE_TO_ESTABLISH;
m_error_message = "Host connection option was not specified.";
}

Expand Down Expand Up @@ -340,6 +408,7 @@ bool ESCommunication::IsSQLPluginInstalled(const std::string& plugin_response) {
return true;
}
} else {
m_error_type = ConnErrorType::ERROR_COMM_LINK_FAILURE;
m_error_message =
"Could not find all necessary fields in the plugin "
"response object. "
Expand All @@ -348,15 +417,19 @@ bool ESCommunication::IsSQLPluginInstalled(const std::string& plugin_response) {
}
}
} catch (const rabbit::type_mismatch& e) {
m_error_type = ConnErrorType::ERROR_COMM_LINK_FAILURE;
m_error_message =
"Error parsing endpoint response: " + std::string(e.what());
} catch (const rabbit::parse_error& e) {
m_error_type = ConnErrorType::ERROR_COMM_LINK_FAILURE;
m_error_message =
"Error parsing endpoint response: " + std::string(e.what());
} catch (const std::exception& e) {
m_error_type = ConnErrorType::ERROR_COMM_LINK_FAILURE;
m_error_message =
"Error parsing endpoint response: " + std::string(e.what());
} catch (...) {
m_error_type = ConnErrorType::ERROR_COMM_LINK_FAILURE;
m_error_message =
"Unknown exception thrown when parsing plugin endpoint response.";
}
Expand All @@ -379,12 +452,14 @@ bool ESCommunication::EstablishConnection() {
IssueRequest(PLUGIN_ENDPOINT_FORMAT_JSON,
Aws::Http::HttpMethod::HTTP_GET, "", "", "");
if (response == nullptr) {
m_error_type = ConnErrorType::ERROR_COMM_LINK_FAILURE;
m_error_message =
"The SQL plugin must be installed in order to use this driver. "
"Received NULL response.";
} else {
AwsHttpResponseToString(response, m_response_str);
if (response->GetResponseCode() != Aws::Http::HttpResponseCode::OK) {
m_error_type = ConnErrorType::ERROR_COMM_LINK_FAILURE;
m_error_message =
"The SQL plugin must be installed in order to use this driver.";
if (response->HasClientError())
Expand All @@ -396,6 +471,7 @@ bool ESCommunication::EstablishConnection() {
if (IsSQLPluginInstalled(m_response_str)) {
return true;
} else {
m_error_type = ConnErrorType::ERROR_COMM_LINK_FAILURE;
m_error_message =
"The SQL plugin must be installed in order to use this "
"driver. Response body: '"
Expand All @@ -411,6 +487,7 @@ std::vector< std::string > ESCommunication::GetColumnsWithSelectQuery(
const std::string table_name) {
std::vector< std::string > list_of_column;
if (table_name.empty()) {
m_error_type = ConnErrorType::ERROR_INVALID_NULL_PTR;
m_error_message = "Query is NULL";
LogMsg(ES_ERROR, m_error_message.c_str());
return list_of_column;
Expand All @@ -428,6 +505,7 @@ std::vector< std::string > ESCommunication::GetColumnsWithSelectQuery(

// Validate response
if (response == nullptr) {
m_error_type = ConnErrorType::ERROR_COMM_LINK_FAILURE;
m_error_message =
"Failed to receive response from query. "
"Received NULL response.";
Expand All @@ -441,6 +519,7 @@ std::vector< std::string > ESCommunication::GetColumnsWithSelectQuery(

// If response was not valid, set error
if (response->GetResponseCode() != Aws::Http::HttpResponseCode::OK) {
m_error_type = ConnErrorType::ERROR_QUERY_SYNTAX;
m_error_message =
"Http response code was not OK. Code received: "
+ std::to_string(static_cast< long >(response->GetResponseCode()))
Expand Down Expand Up @@ -469,11 +548,14 @@ std::vector< std::string > ESCommunication::GetColumnsWithSelectQuery(
}

int ESCommunication::ExecDirect(const char* query, const char* fetch_size_) {
m_error_details.reset();
if (!query) {
m_error_type = ConnErrorType::ERROR_INVALID_NULL_PTR;
m_error_message = "Query is NULL";
LogMsg(ES_ERROR, m_error_message.c_str());
return -1;
} else if (!m_http_client) {
m_error_type = ConnErrorType::ERROR_COMM_LINK_FAILURE;
m_error_message = "Unable to connect. Please try connecting again.";
LogMsg(ES_ERROR, m_error_message.c_str());
return -1;
Expand All @@ -492,6 +574,7 @@ int ESCommunication::ExecDirect(const char* query, const char* fetch_size_) {

// Validate response
if (response == nullptr) {
m_error_type = ConnErrorType::ERROR_QUERY_SYNTAX;
m_error_message =
"Failed to receive response from query. "
"Received NULL response.";
Expand All @@ -505,6 +588,7 @@ int ESCommunication::ExecDirect(const char* query, const char* fetch_size_) {

// If response was not valid, set error
if (response->GetResponseCode() != Aws::Http::HttpResponseCode::OK) {
m_error_type = ConnErrorType::ERROR_QUERY_SYNTAX;
m_error_message =
"Http response code was not OK. Code received: "
+ std::to_string(static_cast< long >(response->GetResponseCode()))
Expand All @@ -513,6 +597,7 @@ int ESCommunication::ExecDirect(const char* query, const char* fetch_size_) {
m_error_message +=
" Client error: '" + response->GetClientErrorMessage() + "'.";
if (!result->result_json.empty()) {
m_error_details = ParseErrorResponse(*result.get());
m_error_message +=
" Response error: '" + result->result_json + "'.";
}
Expand All @@ -524,6 +609,7 @@ int ESCommunication::ExecDirect(const char* query, const char* fetch_size_) {
try {
ConstructESResult(*result);
} catch (std::runtime_error& e) {
m_error_type = ConnErrorType::ERROR_QUERY_SYNTAX;
m_error_message =
"Received runtime exception: " + std::string(e.what());
if (!result->result_json.empty()) {
Expand Down Expand Up @@ -563,6 +649,7 @@ void ESCommunication::SendCursorQueries(std::string cursor) {
SQL_ENDPOINT_FORMAT_JDBC, Aws::Http::HttpMethod::HTTP_POST,
ctype, "", "", cursor);
if (response == nullptr) {
m_error_type = ConnErrorType::ERROR_QUERY_SYNTAX;
m_error_message =
"Failed to receive response from cursor. "
"Received NULL response.";
Expand Down Expand Up @@ -591,6 +678,7 @@ void ESCommunication::SendCursorQueries(std::string cursor) {
result.release();
}
} catch (std::runtime_error& e) {
m_error_type = ConnErrorType::ERROR_QUERY_SYNTAX;
m_error_message =
"Received runtime exception: " + std::string(e.what());
LogMsg(ES_ERROR, m_error_message.c_str());
Expand All @@ -608,6 +696,7 @@ void ESCommunication::SendCloseCursorRequest(const std::string& cursor) {
IssueRequest(SQL_ENDPOINT_CLOSE_CURSOR,
Aws::Http::HttpMethod::HTTP_POST, ctype, "", "", cursor);
if (response == nullptr) {
m_error_type = ConnErrorType::ERROR_QUERY_SYNTAX;
m_error_message =
"Failed to receive response from cursor. "
"Received NULL response.";
Expand Down Expand Up @@ -693,6 +782,7 @@ std::string ESCommunication::GetServerVersion() {
std::shared_ptr< Aws::Http::HttpResponse > response =
IssueRequest("", Aws::Http::HttpMethod::HTTP_GET, "", "", "");
if (response == nullptr) {
m_error_type = ConnErrorType::ERROR_COMM_LINK_FAILURE;
m_error_message =
"Failed to receive response from query. "
"Received NULL response.";
Expand All @@ -711,14 +801,17 @@ std::string ESCommunication::GetServerVersion() {
}

} catch (const rabbit::type_mismatch& e) {
m_error_type = ConnErrorType::ERROR_COMM_LINK_FAILURE;
m_error_message = "Error parsing main endpoint response: "
+ std::string(e.what());
LogMsg(ES_ERROR, m_error_message.c_str());
} catch (const rabbit::parse_error& e) {
m_error_type = ConnErrorType::ERROR_COMM_LINK_FAILURE;
m_error_message = "Error parsing main endpoint response: "
+ std::string(e.what());
LogMsg(ES_ERROR, m_error_message.c_str());
} catch (const std::exception& e) {
m_error_type = ConnErrorType::ERROR_COMM_LINK_FAILURE;
m_error_message = "Error parsing main endpoint response: "
+ std::string(e.what());
LogMsg(ES_ERROR, m_error_message.c_str());
Expand All @@ -741,6 +834,7 @@ std::string ESCommunication::GetClusterName() {
std::shared_ptr< Aws::Http::HttpResponse > response =
IssueRequest("", Aws::Http::HttpMethod::HTTP_GET, "", "", "");
if (response == nullptr) {
m_error_type = ConnErrorType::ERROR_COMM_LINK_FAILURE;
m_error_message =
"Failed to receive response from query. "
"Received NULL response.";
Expand All @@ -759,14 +853,17 @@ std::string ESCommunication::GetClusterName() {
}

} catch (const rabbit::type_mismatch& e) {
m_error_type = ConnErrorType::ERROR_COMM_LINK_FAILURE;
m_error_message = "Error parsing main endpoint response: "
+ std::string(e.what());
LogMsg(ES_ERROR, m_error_message.c_str());
} catch (const rabbit::parse_error& e) {
m_error_type = ConnErrorType::ERROR_COMM_LINK_FAILURE;
m_error_message = "Error parsing main endpoint response: "
+ std::string(e.what());
LogMsg(ES_ERROR, m_error_message.c_str());
} catch (const std::exception& e) {
m_error_type = ConnErrorType::ERROR_COMM_LINK_FAILURE;
m_error_message = "Error parsing main endpoint response: "
+ std::string(e.what());
LogMsg(ES_ERROR, m_error_message.c_str());
Expand Down
5 changes: 5 additions & 0 deletions sql-odbc/src/odfesqlodbc/es_communication.h
Expand Up @@ -21,6 +21,7 @@
#include <memory>
#include <queue>
#include <future>
#include <regex>
#include "es_types.h"
#include "es_result_queue.h"

Expand Down Expand Up @@ -50,6 +51,7 @@ class ESCommunication {

// Create function for factory
std::string GetErrorMessage();
ConnErrorType GetErrorType();
bool ConnectionOptions(runtime_options& rt_opts, bool use_defaults,
int expand_dbname, unsigned int option_count);
bool ConnectionOptions2();
Expand Down Expand Up @@ -84,12 +86,15 @@ class ESCommunication {
void ConstructESResult(ESResult& result);
void GetJsonSchema(ESResult& es_result);
void PrepareCursorResult(ESResult& es_result);
std::shared_ptr< ErrorDetails > ParseErrorResponse(ESResult& es_result);

// TODO #35 - Go through and add error messages on exit conditions
std::string m_error_message;
const std::vector< std::string > m_supported_client_encodings = {"UTF8"};

ConnStatusType m_status;
ConnErrorType m_error_type;
std::shared_ptr< ErrorDetails > m_error_details;
bool m_valid_connection_options;
bool m_is_retrieving;
ESResultQueue m_result_queue;
Expand Down
5 changes: 5 additions & 0 deletions sql-odbc/src/odfesqlodbc/es_helper.cpp
Expand Up @@ -57,6 +57,11 @@ std::string GetErrorMsg(void* es_conn) {
: NULL;
}

ConnErrorType GetErrorType(void* es_conn) {
return es_conn ? static_cast< ESCommunication* >(es_conn)->GetErrorType()
: ConnErrorType::ERROR_SUCCESS;
}

std::string GetServerVersion(void* es_conn) {
return es_conn
? static_cast< ESCommunication* >(es_conn)->GetServerVersion()
Expand Down
1 change: 1 addition & 0 deletions sql-odbc/src/odfesqlodbc/es_helper.h
Expand Up @@ -30,6 +30,7 @@ void* ESConnectDBParams(runtime_options& rt_opts, int expand_dbname,
std::string GetServerVersion(void* es_conn);
std::string GetClusterName(void* es_conn);
std::string GetErrorMsg(void* es_conn);
ConnErrorType GetErrorType(void* es_conn);
std::vector< std::string > ESGetColumnsWithSelectQuery(
void* es_conn, const std::string table_name);

Expand Down