diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6fc7dc9..2b4086d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -21,6 +21,7 @@ test-ubuntu: image: $CI_REGISTRY/infrastructure/qore-test-base/qore-test-base:develop script: - | + printf "[rippy]\n host = rippy\n port = 1433\n tds version = 7.4" > /etc/freetds/freetds.conf if test/docker_test/test-ubuntu.sh; then curl "https://api.github.com/repos/qorelanguage/${REPO_NAME}/statuses/${CI_COMMIT_SHA}" \ -X POST -u omusil24:${GITHUB_ACCESS_TOKEN} -H "Content-Type: application/json" \ @@ -38,6 +39,7 @@ test-alpine: image: $CI_REGISTRY/infrastructure/qore-test-base/qore-test-base:develop-alpine script: - | + printf "[rippy]\n host = rippy\n port = 1433\n tds version = 7.4" > /etc/freetds.conf if test/docker_test/test-alpine.sh; then curl "https://api.github.com/repos/qorelanguage/${REPO_NAME}/statuses/${CI_COMMIT_SHA}" \ -X POST -u omusil24:${GITHUB_ACCESS_TOKEN} -H "Content-Type: application/json" \ diff --git a/configure.ac b/configure.ac index 2394432..8b17cbc 100644 --- a/configure.ac +++ b/configure.ac @@ -1,7 +1,7 @@ # Process this file with autoconf to produce a configure script. # AC_PREREQ(2.59) -AC_INIT([qore-sybase-modules], [1.1], +AC_INIT([qore-sybase-modules], [1.2], [David Nichols ], [qore-sybase-modules]) AM_INIT_AUTOMAKE([no-dist-gzip dist-bzip2 tar-ustar]) diff --git a/docs/mainpage.dox.tmpl b/docs/mainpage.dox.tmpl index eb3e027..3fb17b4 100644 --- a/docs/mainpage.dox.tmpl +++ b/docs/mainpage.dox.tmpl @@ -252,10 +252,15 @@ exec get_values :string output, :int output"); version of this driver should offer an alternative Datasource method allowing explicit stored procedure execution through ct-lib RPC functions, which will allow output parameters to be returned.\n\n There are other issues with data types, character encoding, and more when using this driver; please see - http://www.freetds.org for more information. + http://www.freetds.org for more information @section sybasereleasenotes Release Notes + @subsection sybase_1_2 sybase Driver Version 1.2 + - detect and automatically set the server character encoding for MS SQL server connections to ensure that strings + with invalid encodings are never sent to or retrieved from the server + (issue 4710) + @subsection sybase_1_1 sybase Driver Version 1.1 - fixed a bug handliing \c DATETIME2 column data (issue 4401) diff --git a/src/command.cpp b/src/command.cpp index 3a38c88..91a9371 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -468,16 +468,16 @@ QoreValue command::readOutput(connection& conn, command& cmd, bool list, bool& c } int command::retr_colinfo(ExceptionSink* xsink) { - unsigned columns = get_column_count(xsink); - if (*xsink) - return -1; + unsigned columns = get_column_count(xsink); + if (*xsink) + return -1; - colinfo.reset(); - get_row_description(colinfo.datafmt, columns, xsink); - setup_output_buffers(colinfo.datafmt, xsink); - colinfo.dirty = false; + colinfo.reset(); + get_row_description(colinfo.datafmt, columns, xsink); + setup_output_buffers(colinfo.datafmt, xsink); + colinfo.dirty = false; - return 0; + return 0; } void command::setupColumns(QoreHashNode& h, const Placeholders *ph) { @@ -598,14 +598,15 @@ int command::get_row_description(row_result_t &result, unsigned column_count, Ex CS_RETCODE err = ct_describe(m_cmd, i + 1, &datafmt); if (err != CS_SUCCEED) { - m_conn.do_exception(xsink, "TDS-EXEC-ERROR", "ct_describe() failed with error %d", (int)err); + m_conn.do_exception(xsink, "TDS-EXEC-ERROR", "ct_describe() failed with error %d on column %u / %u", + (int)err, i + 1, column_count); return -1; } datafmt.count = 1; // fetch just single row per every ct_fetch() bool is_multi_byte = m_conn.getEncoding()->isMultiByte(); - printd(5, "command::get_row_description(): name=%s type=%d usertype=%d\n", - datafmt.name, datafmt.datatype, datafmt.usertype); + printd(5, "command::get_row_description(): name: %s type: %d usertype: %d\n", + datafmt.name, datafmt.datatype, datafmt.usertype); datafmt.origin_datatype = datafmt.datatype; switch (datafmt.datatype) { diff --git a/src/connection.cpp b/src/connection.cpp index 7e73003..d9c37da 100644 --- a/src/connection.cpp +++ b/src/connection.cpp @@ -1,12 +1,12 @@ /* - sybase_connection.cpp + connection.cpp Sybase DB layer for QORE uses Sybase OpenClient C library or FreeTDS's ct-lib Qore Programming Language - Copyright (C) 2007 - 2022 Qore Technolgoies s.r.o. + Copyright (C) 2007 - 2023 Qore Technolgoies s.r.o. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -44,117 +44,114 @@ QoreThreadLock ct_lock; QoreThreadLock cs_lock; #endif -connection::connection(Datasource *n_ds, ExceptionSink *xsink) - : m_context(xsink), - m_connection(0), - connected(false), - ds(n_ds), - numeric_support(OPT_NUM_OPTIMAL), - server_tz(0), - stmt(0) -{} +connection::connection(Datasource *n_ds, ExceptionSink *xsink) : + m_context(xsink), + ds(n_ds) { +} connection::~connection() { - invalidateStatement(); - CS_RETCODE ret = CS_SUCCEED; - - if (m_connection) { - if (connected) { - ret = ct_close(m_connection, CS_UNUSED); - if (ret != CS_SUCCEED) { - ret = ct_close(m_connection, CS_FORCE_CLOSE); - assert(ret == CS_SUCCEED); - } - } - ret = ct_con_drop(m_connection); - assert(ret == CS_SUCCEED); - } + invalidateStatement(); + CS_RETCODE ret = CS_SUCCEED; + + if (m_connection) { + if (connected) { + ret = ct_close(m_connection, CS_UNUSED); + if (ret != CS_SUCCEED) { + ret = ct_close(m_connection, CS_FORCE_CLOSE); + assert(ret == CS_SUCCEED); + } + } + ret = ct_con_drop(m_connection); + assert(ret == CS_SUCCEED); + } } // FIXME: check for auto-reconnect here if ct_command fails int connection::direct_execute(const char* sql_text, ExceptionSink* xsink) { - assert(sql_text && sql_text[0]); + assert(sql_text && sql_text[0]); - CS_COMMAND* cmd = 0; + CS_COMMAND* cmd = 0; - CS_RETCODE err = ct_cmd_alloc(m_connection, &cmd); - if (err != CS_SUCCEED) - do_exception(xsink, "TDS-EXEC-ERROR", "ct_cmd_alloc() failed"); + CS_RETCODE err = ct_cmd_alloc(m_connection, &cmd); + if (err != CS_SUCCEED) + do_exception(xsink, "TDS-EXEC-ERROR", "ct_cmd_alloc() failed"); - ON_BLOCK_EXIT(ct_cmd_drop, cmd); - ScopeGuard canceller = MakeGuard(ct_cancel, (CS_CONNECTION*)0, cmd, CS_CANCEL_ALL); + ON_BLOCK_EXIT(ct_cmd_drop, cmd); + ScopeGuard canceller = MakeGuard(ct_cancel, (CS_CONNECTION*)0, cmd, CS_CANCEL_ALL); - err = ct_command(cmd, CS_LANG_CMD, (CS_CHAR*)sql_text, strlen(sql_text), CS_END); - if (err != CS_SUCCEED) - do_exception(xsink, "TDS-EXEC-ERROR", "ct_command() failed"); + err = ct_command(cmd, CS_LANG_CMD, (CS_CHAR*)sql_text, strlen(sql_text), CS_END); + if (err != CS_SUCCEED) + do_exception(xsink, "TDS-EXEC-ERROR", "ct_command() failed"); - err = ct_send(cmd); - if (err != CS_SUCCEED) - do_exception(xsink, "TDS-EXEC-ERROR", "ct_send() failed"); + err = ct_send(cmd); + if (err != CS_SUCCEED) + do_exception(xsink, "TDS-EXEC-ERROR", "ct_send() failed"); - // no results expected - CS_INT result_type; - err = ct_results(cmd, &result_type); - if (err != CS_SUCCEED) - do_exception(xsink, "TDS-EXEC-ERROR", - "connection::direct_execute(): ct_results()" - " returned error code %d", err); + // no results expected + CS_INT result_type; + err = ct_results(cmd, &result_type); + if (err != CS_SUCCEED) + do_exception(xsink, "TDS-EXEC-ERROR", + "connection::direct_execute(): ct_results()" + " returned error code %d", err); - if (result_type != CS_CMD_SUCCEED) - do_check_exception(xsink, true, "TDS-EXEC-ERROR", - "connection::direct_execute(): ct_results()" - " failed with result_type = %d", result_type); + if (result_type != CS_CMD_SUCCEED) + do_check_exception(xsink, true, "TDS-EXEC-ERROR", + "connection::direct_execute(): ct_results()" + " failed with result_type = %d", result_type); - while ((err = ct_results(cmd, &result_type)) == CS_SUCCEED); - canceller.Dismiss(); + while ((err = ct_results(cmd, &result_type)) == CS_SUCCEED); + canceller.Dismiss(); - return purge_messages(xsink); + return purge_messages(xsink); } static inline bool wasInTransaction(Datasource *ds) { #ifdef _QORE_HAS_DATASOURCE_ACTIVETRANSACTION - return ds->activeTransaction(); + return ds->activeTransaction(); #else - return ds->isInTransaction(); + return ds->isInTransaction(); #endif } bool connection::ping() const { - // check if the connection is up - CS_INT up; - CS_INT outlen; - CS_RETCODE rc = ct_con_props(m_connection, CS_GET, CS_CON_STATUS, &up, sizeof(up), &outlen); - printd(0, "ping() rc: %d raw: %d up: %d\n", rc, up, up == CS_CONSTAT_CONNECTED); - return (rc == CS_SUCCEED) && (up == CS_CONSTAT_CONNECTED); + // check if the connection is up + CS_INT up; + CS_INT outlen; + CS_RETCODE rc = ct_con_props(m_connection, CS_GET, CS_CON_STATUS, &up, sizeof(up), &outlen); + printd(5, "ping() rc: %d raw: %d up: %d\n", rc, up, up == CS_CONSTAT_CONNECTED); + return (rc == CS_SUCCEED) && (up == CS_CONSTAT_CONNECTED); } -command* connection::setupCommand(const QoreString* cmd_text, const QoreListNode* args, bool raw, ExceptionSink* xsink) { - while (true) { - std::unique_ptr query(new sybase_query); - if (!raw) { - if (query->init(cmd_text, args, xsink)) - return 0; - } else { - assert(!args); - query->init(cmd_text); - } +command* connection::setupCommand(const QoreString* cmd_text, const QoreListNode* args, bool raw, + ExceptionSink* xsink) { + while (true) { + std::unique_ptr query(new sybase_query); + if (!raw) { + if (query->init(cmd_text, args, xsink)) + return nullptr; + } else { + assert(!args); + query->init(cmd_text); + } - std::unique_ptr cmd(new command(*this, xsink)); - cmd->bind_query(query, args, xsink); + std::unique_ptr cmd(new command(*this, xsink)); + cmd->bind_query(query, args, xsink); - try { - cmd->send(xsink); - } catch (const ss::Error& e) { - // if the connection is down and we can reconnect transparently, then we do so - if (!ping() && !closeAndReconnect(xsink, *cmd.get(), true)) - continue; - throw; - } - return cmd.release(); - } + try { + cmd->send(xsink); + } catch (const ss::Error& e) { + // if the connection is down and we can reconnect transparently, then we do so + if (!ping() && !closeAndReconnect(xsink, *cmd.get(), true)) + continue; + throw; + } + return cmd.release(); + } } -QoreValue connection::execReadOutput(QoreString* cmd_text, const QoreListNode* qore_args, bool need_list, bool doBinding, bool cols, ExceptionSink* xsink, bool single_row) { +QoreValue connection::execReadOutput(QoreString* cmd_text, const QoreListNode* qore_args, bool need_list, + bool doBinding, bool cols, ExceptionSink* xsink, bool single_row) { // cancel any active statement invalidateStatement(); @@ -181,51 +178,55 @@ QoreValue connection::execReadOutput(QoreString* cmd_text, const QoreListNode* q } int connection::closeAndReconnect(ExceptionSink* xsink, command& cmd, bool try_reconnect) { - // cancel the current command - cmd.cancelDisconnect(); + // cancel the current command + cmd.cancelDisconnect(); - // cancel any current statement - invalidateStatement(); + // cancel any current statement + invalidateStatement(); - // see if we need to reconnect and try again - ct_close(m_connection, CS_FORCE_CLOSE); - connected = false; + // see if we need to reconnect and try again + ct_close(m_connection, CS_FORCE_CLOSE); + connected = false; - // discard all current messages - discard_messages(); + // discard all current messages + discard_messages(); - if (wasInTransaction(ds)) - xsink->raiseException("TDS-TRANSACTION-ERROR", "connection to server lost while in a transaction; transaction has been lost"); + if (wasInTransaction(ds)) + xsink->raiseException("TDS-TRANSACTION-ERROR", "connection to server lost while in a transaction; " + "transaction has been lost"); - // otherwise try to reconnect - ct_con_drop(m_connection); - m_connection = 0; + // otherwise try to reconnect + ct_con_drop(m_connection); + m_connection = nullptr; - int port = ds->getPort(); + int port = ds->getPort(); - printd(5, "connection::closeAndReconnect() this: %p reconnecting to %s@%s (trans: %d, try_reconnect: %d)\n", this, ds->getUsername(), ds->getDBName(), wasInTransaction(ds), try_reconnect); + printd(5, "connection::closeAndReconnect() this: %p reconnecting to %s@%s (trans: %d, try_reconnect: %d)\n", this, + ds->getUsername(), ds->getDBName(), wasInTransaction(ds), try_reconnect); - // make the actual connection to the database - if (init(ds->getUsername(), ds->getPassword() ? ds->getPassword() : "", ds->getDBName(), ds->getDBEncoding(), ds->getQoreEncoding(), ds->getHostName(), port, xsink)) { - // make sure and mark Datasource as closed - ds->connectionAborted(); - return -1; - } + // make the actual connection to the database + if (init(ds->getUsername(), ds->getPassword() ? ds->getPassword() : "", ds->getDBName(), ds->getDBEncoding(), + ds->getQoreEncoding(), ds->getHostName(), port, xsink)) { + // make sure and mark Datasource as closed + ds->connectionAborted(); + return -1; + } - printd(5, "connection::closeAndReconnect() this: %p auto reconnected to %s@%s m_connection: %p\n", this, ds->getUsername(), ds->getDBName(), m_connection); + printd(5, "connection::closeAndReconnect() this: %p auto reconnected to %s@%s m_connection: %p\n", this, + ds->getUsername(), ds->getDBName(), m_connection); - if (!try_reconnect || wasInTransaction(ds)) - return -1; + if (!try_reconnect || wasInTransaction(ds)) + return -1; #ifdef DEBUG - // otherwise show the exception on stdout in debug mode - xsink->handleExceptions(); + // otherwise show the exception on stdout in debug mode + xsink->handleExceptions(); #endif - // clear any exceptions that have been ignored - xsink->clear(); + // clear any exceptions that have been ignored + xsink->clear(); - return 0; + return 0; } command::ResType connection::readNextResult(command& cmd, bool& connection_reset, ExceptionSink* xsink) { @@ -374,8 +375,9 @@ QoreValue connection::exec_rows(const QoreString *cmd, const QoreListNode *param QoreValue connection::exec_row(const QoreString *cmd, const QoreListNode *parameters, ExceptionSink *xsink) { // copy the string here for intrusive editing, convert encoding too if necessary QoreString *query = cmd->convertEncoding(enc, xsink); - if (!query) + if (!query) { return QoreValue(); + } std::unique_ptr tmp(query); ValueHolder rv(execReadOutput(query, parameters, true, true, false, xsink, true), xsink); @@ -397,33 +399,36 @@ int connection::rollback(ExceptionSink *xsink) { return direct_execute("rollback", xsink); } +#define CHAR_ENC_SIZE 256 + // Post-constructor initialization int connection::init(const char* username, const char* password, const char* dbname, const char* db_encoding, - const QoreEncoding *n_enc, - const char *hostname, + const QoreEncoding* n_enc, + const char* hostname, int port, ExceptionSink* xsink) { assert(!m_connection); - printd(5, "connection::init() user: %s pass: %s dbname: %s, db_enc: %s\n", username, password ? password : "", dbname, db_encoding ? db_encoding : ""); + printd(5, "connection::init() user: %s pass: %s dbname: %s, db_enc: %s\n", username, + password ? password : "", dbname, db_encoding ? db_encoding : ""); enc = n_enc; -/* -// add callbacks -ret = ct_callback(m_context, 0, CS_SET, CS_CLIENTMSG_CB, (CS_VOID*)clientmsg_callback); -if (ret != CS_SUCCEED) { -xsink->raiseException("TDS-CTLIB-SET-CALLBACK", "ct_callback(CS_CLIENTMSG_CB) failed with error %d", ret); -return -1; -} -ret = ct_callback(m_context, 0, CS_SET, CS_SERVERMSG_CB, (CS_VOID*)servermsg_callback); -if (ret != CS_SUCCEED) { -xsink->raiseException("TDS-CTLIB-SET-CALLBACK", "ct_callback(CS_SERVERMSG_CB) failed with error %d", ret); -return -1; -} -*/ + /* + // add callbacks + ret = ct_callback(m_context, 0, CS_SET, CS_CLIENTMSG_CB, (CS_VOID*)clientmsg_callback); + if (ret != CS_SUCCEED) { + xsink->raiseException("TDS-CTLIB-SET-CALLBACK", "ct_callback(CS_CLIENTMSG_CB) failed with error %d", ret); + return -1; + } + ret = ct_callback(m_context, 0, CS_SET, CS_SERVERMSG_CB, (CS_VOID*)servermsg_callback); + if (ret != CS_SUCCEED) { + xsink->raiseException("TDS-CTLIB-SET-CALLBACK", "ct_callback(CS_SERVERMSG_CB) failed with error %d", ret); + return -1; + } + */ CS_RETCODE ret = ct_con_alloc(m_context.get_context(), &m_connection); if (ret != CS_SUCCEED) { @@ -465,45 +470,45 @@ return -1; QoreString hn(hostname); hn.sprintf(" %d", port); - ret = ct_con_props(m_connection, CS_SET, CS_SERVERADDR, (CS_VOID*)hn.getBuffer(), CS_NULLTERM, 0); + ret = ct_con_props(m_connection, CS_SET, CS_SERVERADDR, (CS_VOID*)hn.c_str(), CS_NULLTERM, 0); if (ret != CS_SUCCEED) { - xsink->raiseException("TDS-CTLIB-SET-SERVERADDR", "ct_con_props(CS_SERVERADDR, '%s') failed with error %d", hn.getBuffer(), ret); + xsink->raiseException("TDS-CTLIB-SET-SERVERADDR", "ct_con_props(CS_SERVERADDR, '%s') failed with error %d", hn.c_str(), ret); return -1; } } #if defined(SYBASE) || defined(FREETDS_LOCALE) - CS_LOCALE *m_charset_locale = 0; + CS_LOCALE* m_charset_locale = nullptr; ret = cs_loc_alloc(m_context.get_context(), &m_charset_locale); if (ret != CS_SUCCEED) { xsink->raiseException("TDS-EXEC-EXCEPTION", "cs_loc_alloc() returned error %d", (int)ret); return -1; } + ON_BLOCK_EXIT(cs_loc_drop, m_context.get_context(), m_charset_locale); ret = cs_locale(m_context.get_context(), CS_SET, m_charset_locale, CS_LC_ALL, 0, CS_NULLTERM, 0); if (ret != CS_SUCCEED) { xsink->raiseException("TDS-EXEC-EXCEPTION", "cs_locale(CS_LC_ALL) returned error %d", (int)ret); return -1; } - ret = cs_locale(m_context.get_context(), CS_SET, m_charset_locale, CS_SYB_CHARSET, (CS_CHAR*)db_encoding, CS_NULLTERM, 0); + ret = cs_locale(m_context.get_context(), CS_SET, m_charset_locale, CS_SYB_CHARSET, (CS_CHAR*)db_encoding, + CS_NULLTERM, 0); if (ret != CS_SUCCEED) { - xsink->raiseException("TDS-EXEC-EXCEPTION", "cs_locale(CS_SYB_CHARSET, '%s') failed with error %d", db_encoding, (int)ret); + xsink->raiseException("TDS-EXEC-EXCEPTION", "cs_locale(CS_SYB_CHARSET, '%s') failed with error %d", + db_encoding, (int)ret); return -1; } ret = ct_con_props(m_connection, CS_SET, CS_LOC_PROP, m_charset_locale, CS_UNUSED, 0); if (ret !=CS_SUCCEED) { - xsink->raiseException("TDS-EXEC-EXCEPTION", "ct_con_props(CS_SET, CS_LOC_PROP) failed with error %d", (int)ret); + xsink->raiseException("TDS-EXEC-EXCEPTION", "ct_con_props(CS_SET, CS_LOC_PROP) failed with error %d", + (int)ret); return -1; } - - ret = cs_loc_drop(m_context.get_context(), m_charset_locale); - assert(ret == CS_SUCCEED); #endif ret = ct_connect(m_connection, (CS_CHAR*)dbname, strlen(dbname)); if (ret != CS_SUCCEED) { do_exception(xsink, "TDS-CTLIB-CONNECT-ERROR", "ct_connect() failed with error %d", ret); - return -1; } connected = true; @@ -511,23 +516,94 @@ return -1; // - in autocommit mode qore executes a commit after every request manually CS_BOOL cs_bool = CS_TRUE; ret = ct_options(m_connection, CS_SET, CS_OPT_CHAINXACTS, &cs_bool, CS_UNUSED, 0); - if (ret != CS_SUCCEED) + if (ret != CS_SUCCEED) { do_exception(xsink, "TDS-INIT-ERROR", "ct_options(CS_OPT_CHAINXACTS) failed"); + } // returns up to 1MB images or text values CS_INT cs_size = 1024 * 1024; ret = ct_options(m_connection, CS_SET, CS_OPT_TEXTSIZE, &cs_size, CS_UNUSED, 0); - if (ret != CS_SUCCEED) + if (ret != CS_SUCCEED) { do_exception(xsink, "TDS-INIT-ERROR", "ct_options(CS_OPT_TEXTSIZE) failed"); + } // Set default type of string representation of DATETIME to long (like Jan 1 1990 12:32:55:0000 PM) // Without this some routines in conversions.cpp would fail. CS_INT aux = CS_DATES_LONG; ret = cs_dt_info(m_context.get_context(), CS_SET, NULL, CS_DT_CONVFMT, CS_UNUSED, (CS_VOID*)&aux, sizeof(aux), 0); - if (ret != CS_SUCCEED) + if (ret != CS_SUCCEED) { do_exception(xsink, "TDS-INIT-ERROR", "cs_dt_info(CS_DT_CONVFMT) failed"); + } - return purge_messages(xsink); + // issue #4710: determine DB character encoding + cs_bool = CS_FALSE; + ret = ct_con_props(m_connection, CS_SET, CS_CHARSETCNV, &cs_bool, CS_UNUSED, 0); + if (ret != CS_SUCCEED) { + do_exception(xsink, "TDS-INIT-ERROR", "ct_options(CS_CHARSETCNV) failed"); + } + + if (!cs_bool) { + // issue #4710: determine DB character encoding + // see if we have Sybase or MS SQL + ValueHolder v(get_server_version(xsink), xsink); + if (!*xsink) { + if (v->getType() == NT_STRING) { + const QoreStringNode* str = v->get(); + if (str->find("Adaptive Server") >= 0) { + sybase = true; + } + } + + // issue #4710: determine DB character encoding + if (!sybase) { + QoreString sql("select convert(varchar, serverproperty('collation')) as 'coll'"); + ValueHolder holder(exec_row(&sql, nullptr, xsink), xsink); + if (holder->getType() == NT_HASH) { + const QoreStringNode* coll = holder->get() + ->getKeyValue("coll") + .get(); + assert(coll); + printd(5, "MS SQL Server collation: '%s'\n", coll->c_str()); + QoreString c(coll); + c.tolwr(); + if (c.find("utf8") >= 0) { + // set character encoding to UTF-8 + enc = QCS_UTF8; + } else { + sql = "select cast(collationproperty(%v, 'CodePage') as varchar) as 'cp'"; + try { + ReferenceHolder args(new QoreListNode(autoTypeInfo), xsink); + args->push(coll->refSelf(), xsink); + holder = exec_row(&sql, *args, xsink); + if (*xsink) { + purge_messages(xsink); + xsink->clear(); + } else { + if (holder->getType() == NT_HASH) { + QoreStringNode* cp = holder->get() + ->getKeyValue("cp") + .get(); + if (cp && isdigit((*cp)[0])) { + printd(5, "MS SQL Server code page: '%s'\n", cp->c_str()); + cp->prepend("WINDOWS-"); + enc = QEM.findCreate(cp->c_str()); + printd(5, "set connection encoding to '%s'\n", cp->c_str()); + } + } + } + + } catch (const ss::Error& e) { + printd(5, "ignoring error trying to determine server character encoding: %s: %s\n", + e.getErr(), e.getDesc()); + } + } + } + } + } + } + + purge_messages(xsink); + return *xsink ? -1 : 0; } void connection::discard_messages() { @@ -662,7 +738,7 @@ void connection::do_check_exception(ExceptionSink *xsink, bool check, const char if (!count) estr->terminate(estr->size() - 2); - throw ss::Error(err, estr->getBuffer()); + throw ss::Error(err, estr->c_str()); } void connection::do_check_exception(ExceptionSink *xsink, bool check, const char *err, const char *fmt, ...) { @@ -753,8 +829,9 @@ QoreValue connection::get_server_version(ExceptionSink *xsink) { QoreValue rv; { ValueHolder res(execReadOutput(&ver_str, 0, true, true, false, xsink), xsink); - if (!res) + if (!res) { return QoreValue(); + } assert(res->getType() == NT_HASH); HashIterator hi(res->get()); hi.next(); @@ -762,8 +839,9 @@ QoreValue connection::get_server_version(ExceptionSink *xsink) { } QoreStringNode* str = rv.getType() == NT_STRING ? rv.get() : nullptr; - if (str) + if (str) { str->trim_trailing('\n'); + } return rv; } diff --git a/src/connection.h b/src/connection.h index 2a7635d..c6ea0bd 100644 --- a/src/connection.h +++ b/src/connection.h @@ -1,12 +1,12 @@ /* - sybase_connection.h + connection.h Sybase DB layer for QORE uses Sybase OpenClient C library Qore Programming language - Copyright (C) 2007 - 2022 Qore Technologies, s.r.o. + Copyright (C) 2007 - 2023 Qore Technologies, s.r.o. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -57,39 +57,6 @@ class AbstractQoreZoneInfo; typedef ss::DBModuleWrap::ModuleWrap stmt_t; class context { -private: - CS_CONTEXT *m_context; - - DLLLOCAL void del() { - //printd(5, "context::del() this=%p deleting %p\n", this, m_context); - - CS_RETCODE ret; -#ifdef SYBASE - { - AutoLocker al(ct_lock); -#endif - ret = ct_exit(m_context, CS_UNUSED); -#ifdef SYBASE - } -#endif - if (ret != CS_SUCCEED) { -#ifdef SYBASE - { - AutoLocker al(ct_lock); -#endif - ret = ct_exit(m_context, CS_FORCE_EXIT); -#ifdef SYBASE - } -#endif - assert(ret == CS_SUCCEED); - } -#ifdef SYBASE - AutoLocker al(cs_lock); -#endif - ret = cs_ctx_drop(m_context); - assert(ret == CS_SUCCEED); - } - public: DLLLOCAL context(ExceptionSink *xsink) { CS_RETCODE ret; @@ -147,6 +114,39 @@ class context { //printd(5, "client version=%s (olen=%d, strlen=%d)\n", buf, olen, strlen(buf)); return new QoreStringNode(buf, olen - 1, CLIENT_VER_LEN, QCS_DEFAULT); } + +private: + CS_CONTEXT* m_context; + + DLLLOCAL void del() { + //printd(5, "context::del() this=%p deleting %p\n", this, m_context); + + CS_RETCODE ret; +#ifdef SYBASE + { + AutoLocker al(ct_lock); +#endif + ret = ct_exit(m_context, CS_UNUSED); +#ifdef SYBASE + } +#endif + if (ret != CS_SUCCEED) { +#ifdef SYBASE + { + AutoLocker al(ct_lock); +#endif + ret = ct_exit(m_context, CS_FORCE_EXIT); +#ifdef SYBASE + } +#endif + assert(ret == CS_SUCCEED); + } +#ifdef SYBASE + AutoLocker al(cs_lock); +#endif + ret = cs_ctx_drop(m_context); + assert(ret == CS_SUCCEED); + } }; // Instantiated class is kept as private data of the Datasource @@ -245,15 +245,16 @@ class connection { private: context m_context; - CS_CONNECTION* m_connection; - bool connected; - const QoreEncoding *enc; - Datasource *ds; - int numeric_support; - const AbstractQoreZoneInfo* server_tz; + CS_CONNECTION* m_connection = nullptr; + bool connected = false; + bool sybase = false; + const QoreEncoding* enc = nullptr; + Datasource* ds; + int numeric_support = OPT_NUM_OPTIMAL; + const AbstractQoreZoneInfo* server_tz = nullptr; bool optimized_date_binds = false; - stmt_t* stmt; + stmt_t* stmt = nullptr; // returns -1 if an exception was thrown, 0 if all errors were ignored DLLLOCAL void do_check_exception(ExceptionSink *xsink, bool check, const char *err, const char *fmt, ...); diff --git a/src/error.h b/src/error.h index 72f7942..3d15980 100644 --- a/src/error.h +++ b/src/error.h @@ -9,15 +9,26 @@ namespace ss { class Error { public: - Error(const std::string &e1, const std::string &e2) : - e1(e1), e2(e2) - {} + DLLLOCAL Error(const std::string& e1, const std::string& e2) : + e1(e1), e2(e2) { - void raise(ExceptionSink *xsink) const { - if (xsink->isException()) return; + } + + DLLLOCAL void raise(ExceptionSink* xsink) const { + if (xsink->isException()) { + return; + } xsink->raiseException(e1.c_str(), e2.c_str()); } + DLLLOCAL const char* getErr() const { + return e1.c_str(); + } + + DLLLOCAL const char* getDesc() const { + return e2.c_str(); + } + private: std::string e1; std::string e2; diff --git a/src/sybase.cpp b/src/sybase.cpp index 690d638..3e66398 100644 --- a/src/sybase.cpp +++ b/src/sybase.cpp @@ -190,7 +190,7 @@ static int sybase_close(Datasource *ds) { static QoreValue sybase_select(Datasource *ds, const QoreString *qstr, const QoreListNode *args, ExceptionSink *xsink) { BEGIN_CALLBACK; - connection *conn = (connection*)ds->getPrivateData(); + connection* conn = (connection*)ds->getPrivateData(); return conn->select(qstr, args, xsink); END_CALLBACK(0); }