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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 98 additions & 1 deletion sql-odbc/src/odfesqlodbc/es_communication.cpp
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Loading