diff --git a/Makefile_12.5 b/Makefile_12.5 index 5d075ad..ac39477 100644 --- a/Makefile_12.5 +++ b/Makefile_12.5 @@ -8,7 +8,7 @@ INCLUDES = -I/opt/sybase12_5_64/OCS/include # compiler path and flags CC = /opt/gcc/bin/g++ -CFLAGS = -std=c++11 -Wall -Wno-unused -Wno-sequence-point -Wno-parentheses -c -ggdb3 -m64 -pthread -DSYB_LP64 -D_REENTRANT +CFLAGS = -std=c++1y -Wall -Wno-unused -Wno-sequence-point -Wno-parentheses -c -ggdb3 -m64 -pthread -DSYB_LP64 -D_REENTRANT # sybase libraries to include in the build CTLIB = -lct_r64 # client library diff --git a/Makefile_15.0 b/Makefile_15.0 index 3dc1f28..bb83a28 100644 --- a/Makefile_15.0 +++ b/Makefile_15.0 @@ -8,7 +8,7 @@ INCLUDES = -I/opt/sybase15/OCS-15_0/include # compiler path and flags CC = /opt/gcc/bin/g++ -CFLAGS = -std=c++11 -Wall -Wno-unused -Wno-sequence-point -Wno-parentheses -c -ggdb3 -m64 -pthread -DSYB_LP64 -D_REENTRANT +CFLAGS = -std=c++1y -Wall -Wno-unused -Wno-sequence-point -Wno-parentheses -c -ggdb3 -m64 -pthread -DSYB_LP64 -D_REENTRANT # sybase libraries to include in the build CTLIB = -lsybct64 # client library diff --git a/README.md b/README.md index 14a3179..ca30d57 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,7 @@ Header files based C++ database connection library. This project is to create an easy to use, database agnostic, modular, and easily extendable C++ database connection library. -At the moment this is work in progress and not a production-ready code that may have bugs! - -Currently only a limited featured native Sybase driver is implemented, see sybase_example.cpp +Currently only a native Sybase ASE driver is implemented, see sybase_example.cpp If someone would like to contribute please ping me. -TODO: -- add support for more data types, stored procs, and cursor to Sybase driver -- write unit tests -- eventually add support for more databases diff --git a/connection.hpp b/connection.hpp index 1a47d42..8d4909c 100644 --- a/connection.hpp +++ b/connection.hpp @@ -20,8 +20,6 @@ #define CONNECTION_HPP #include -#include "utilities.hpp" -#include "driver.hpp" #include "statement.hpp" // forward declaration @@ -110,8 +108,8 @@ class connection { } /** - * Function sets autocommit option, default is 'true' - * @param ac - true to set autocommit ON, false - OFF + * Function sets auto-commit option, default is 'true' + * @param ac - true to set auto-commit ON, false - OFF */ void autocommit(bool ac) { diff --git a/driver.hpp b/driver.hpp index 8ad2c5f..4c37a0a 100644 --- a/driver.hpp +++ b/driver.hpp @@ -43,7 +43,7 @@ template class driver : public T { public: - // c++11 guaranties no threading issues + // c++1y guarantees no threading issues static T& load() { static driver d; diff --git a/result_set.hpp b/result_set.hpp index 48b5d21..acfa3b2 100644 --- a/result_set.hpp +++ b/result_set.hpp @@ -19,6 +19,8 @@ #ifndef RESULT_SET_HPP #define RESULT_SET_HPP +#include "utilities.hpp" + namespace vgi { namespace dbconn { namespace dbi { class statement; @@ -37,6 +39,9 @@ struct iresult_set virtual size_t rows_affected() const = 0; virtual size_t column_count() const = 0; virtual bool next() = 0; + virtual bool prev() = 0; + virtual bool first() = 0; + virtual bool last() = 0; virtual std::string column_name(size_t col_idx) = 0; virtual int column_index(const std::string& col_name) = 0; virtual bool is_null(size_t col_idx) = 0; @@ -48,7 +53,6 @@ struct iresult_set virtual uint64_t get_ulong(size_t col_idx) = 0; virtual float get_float(size_t col_idx) = 0; virtual double get_double(size_t col_idx) = 0; - virtual long double get_ldouble(size_t col_idx) = 0; virtual bool get_bool(size_t col_idx) = 0; virtual char get_char(size_t col_idx) = 0; virtual std::string get_string(size_t col_idx) = 0; @@ -57,8 +61,7 @@ struct iresult_set virtual time_t get_datetime(size_t col_idx) = 0; virtual char16_t get_unichar(size_t col_idx) = 0; virtual std::u16string get_unistring(size_t col_idx) = 0; - virtual std::vector get_image(size_t col_idx) = 0; - // TODO: add xml, binary, money, lob + virtual std::vector get_binary(size_t col_idx) = 0; }; @@ -165,8 +168,8 @@ class result_set /** * Function returns column name by column index or throws an exception if - * index is invalid - * @param col_idx + * index is invalid + * @param col_idx * @return column name string or exception is thrown if index is invalid */ const std::string column_name(size_t col_idx) @@ -194,6 +197,36 @@ class result_set return rs_impl->next(); } + /** + * Function moves iterator to the previous row of the current result data set + * This function can only be used with scrollable cursor. + * @return true on success, or false if there is no more rows + */ + bool prev() + { + return rs_impl->prev(); + } + + /** + * Function moves iterator to the first row of the current result data set + * This function can only be used with scrollable cursor. + * @return true on success, or false if there is no more rows + */ + bool first() + { + return rs_impl->first(); + } + + /** + * Function moves iterator to the last row of the current result data set + * This function can only be used with scrollable cursor. + * @return true on success, or false if there is no more rows + */ + bool last() + { + return rs_impl->last(); + } + /** * Function checks if cell data is NULL by column index or throws an exception * if index is invalid. @@ -235,8 +268,6 @@ class result_set float get_type_by_name(float); double get_type_by_index(double); double get_type_by_name(double); - long double get_type_by_index(ldouble); - long double get_type_by_name(ldouble); bool get_type_by_index(bool); bool get_type_by_name(bool); char get_type_by_index(char); @@ -253,8 +284,8 @@ class result_set char16_t get_type_by_name(unichar); std::u16string get_type_by_index(unistring); std::u16string get_type_by_name(unistring); - std::vector get_type_by_index(image); - std::vector get_type_by_name(image); + std::vector get_type_by_index(binary); + std::vector get_type_by_name(binary); private: diff --git a/statement.hpp b/statement.hpp index afc0207..81ea0ac 100644 --- a/statement.hpp +++ b/statement.hpp @@ -33,10 +33,11 @@ struct istatement { virtual ~istatement() { }; virtual void prepare(const std::string& sql) = 0; - virtual iresult_set* execute(const std::string& sql) = 0; + virtual void call(const std::string& sql) = 0; + virtual iresult_set* execute(const std::string& sql, bool cursor = false, bool scrollable = false) = 0; virtual iresult_set* execute() = 0; virtual bool cancel() = 0; - virtual bool cancel_all() = 0; + virtual int proc_retval() = 0; virtual void set_null(size_t col_idx) = 0; virtual void set_short(size_t col_idx, int16_t val) = 0; virtual void set_ushort(size_t col_idx, uint16_t val) = 0; @@ -46,7 +47,6 @@ struct istatement virtual void set_ulong(size_t col_idx, uint64_t val) = 0; virtual void set_float(size_t col_idx, float val) = 0; virtual void set_double(size_t col_idx, double val) = 0; - virtual void set_ldouble(size_t col_idx, long double val) = 0; virtual void set_bool(size_t col_idx, bool val) = 0; virtual void set_char(size_t col_idx, char val) = 0; virtual void set_string(size_t col_idx, const std::string& val) = 0; @@ -55,7 +55,7 @@ struct istatement virtual void set_datetime(size_t col_idx, time_t val) = 0; virtual void set_unichar(size_t col_idx, char16_t val) = 0; virtual void set_unistring(size_t col_idx, const std::u16string& val) = 0; - virtual void set_image(size_t col_idx, const std::vector& val) = 0; + virtual void set_binary(size_t col_idx, const std::vector& val) = 0; }; @@ -101,6 +101,15 @@ class statement stmt_impl->prepare(sql); } + /** + * Function prepares SQL stored procedure + * @param sql stored procedure to be executed + */ + void call(const std::string& proc) + { + stmt_impl->call(proc); + } + /** * Function runs last executed SQL statement * @return result set object @@ -113,15 +122,17 @@ class statement /** * Function runs SQL statement * @param sql statement to be executed + * @param cursor = true to use cursor with select statements + * @param scrollable = true to use scrollable cursor * @return result set object */ - result_set execute(const std::string& sql) + result_set execute(const std::string& sql, bool cursor = false, bool scrollable = false) { - return result_set(stmt_impl->execute(sql)); + return result_set(stmt_impl->execute(sql, cursor, scrollable)); } /** - * Function cancels currently running SQL statement + * Function cancels currently running SQL statements * @return true if canceled, false otherwise */ bool cancel() @@ -129,6 +140,17 @@ class statement return stmt_impl->cancel(); } + /** + * Function returns stored procedure return value. + * This function must be called after all result sets from stored proc select + * statements had been processed + * @return int + */ + int proc_retval() + { + return stmt_impl->proc_retval(); + } + virtual void set_null(size_t col_idx) { stmt_impl->set_null(col_idx); @@ -174,11 +196,6 @@ class statement stmt_impl->set_double(col_idx, val); } - virtual void set_ldouble(size_t col_idx, long double val) - { - stmt_impl->set_ldouble(col_idx, val); - } - virtual void set_bool(size_t col_idx, bool val) { stmt_impl->set_bool(col_idx, val); @@ -219,11 +236,10 @@ class statement stmt_impl->set_unistring(col_idx, val); } - virtual void set_image(size_t col_idx, const std::vector& val) + virtual void set_binary(size_t col_idx, const std::vector& val) { - stmt_impl->set_image(col_idx, val); + stmt_impl->set_binary(col_idx, val); } - // TODO: add xml, binary, money, lob private: friend class connection; diff --git a/sybase_driver.hpp b/sybase_driver.hpp index c024fd4..0f963ce 100644 --- a/sybase_driver.hpp +++ b/sybase_driver.hpp @@ -26,7 +26,6 @@ #include #include #include -#include "utilities.hpp" #include "driver.hpp" namespace vgi { namespace dbconn { namespace dbd { namespace sybase { @@ -100,7 +99,7 @@ class result_set : public dbi::iresult_set void allocate(const size_t size) { - data.resize(size + 1, '\0'); + data.resize(size, '\0'); } operator char*() @@ -131,7 +130,7 @@ class result_set : public dbi::iresult_set virtual size_t row_count() const { - return row_cnt; + return std::abs(row_cnt); } virtual size_t rows_affected() const @@ -161,19 +160,41 @@ class result_set : public dbi::iresult_set return -1; } + virtual bool prev() + { + return scroll_fetch(CS_PREV); + } + + virtual bool first() + { + return scroll_fetch(CS_FIRST); + } + + virtual bool last() + { + return scroll_fetch(CS_LAST); + } + virtual bool next() { if (columndata.size() > 0) { - if ((CS_SUCCEED == (retcode = ct_fetch(cscommand, CS_UNUSED, CS_UNUSED, CS_UNUSED, &result))) || CS_ROW_FAIL == retcode) + if (scrollable) { - if (CS_ROW_FAIL == retcode) - throw std::runtime_error(std::string(__FUNCTION__).append(": Error fetching row ").append(std::to_string(result))); - row_cnt += result; - return true; + return scroll_fetch(CS_NEXT); } else - next_result(); + { + if ((CS_SUCCEED == (retcode = ct_fetch(cscommand, CS_UNUSED, CS_UNUSED, CS_UNUSED, &result))) || CS_ROW_FAIL == retcode) + { + if (CS_ROW_FAIL == retcode) + throw std::runtime_error(std::string(__FUNCTION__).append(": Error fetching row ").append(std::to_string(result))); + row_cnt += result; + return true; + } + else + next_result(); + } } return false; } @@ -196,13 +217,19 @@ class result_set : public dbi::iresult_set virtual uint16_t get_ushort(size_t col_idx) { - if (CS_TINYINT_TYPE == columns[col_idx].datatype) - return get(col_idx); + switch (columns[col_idx].datatype) + { + case CS_TINYINT_TYPE: + return get(col_idx); + case CS_USHORT_TYPE: + return get(col_idx); #ifdef CS_USMALLINT_TYPE - if (CS_USMALLINT_TYPE == columns[col_idx].datatype) - return get(col_idx); + case CS_USMALLINT_TYPE: + return get(col_idx); #endif - throw std::runtime_error(std::string(__FUNCTION__).append(": Invalid column data type (supported: tinyint, usmallint)")); + default: + throw std::runtime_error(std::string(__FUNCTION__).append(": Invalid column data type (supported: tinyint, usmallint)")); + } } virtual int32_t get_int(size_t col_idx) @@ -214,8 +241,8 @@ class result_set : public dbi::iresult_set case CS_SMALLINT_TYPE: return get(col_idx); #ifdef CS_USMALLINT_TYPE - if (CS_USMALLINT_TYPE == columns[col_idx].datatype) - return get(col_idx); + case CS_USMALLINT_TYPE: + return get(col_idx); #endif case CS_INT_TYPE: return get(col_idx); @@ -234,6 +261,8 @@ class result_set : public dbi::iresult_set case CS_USMALLINT_TYPE: return get(col_idx); #endif + case CS_USHORT_TYPE: + return get(col_idx); #ifdef CS_UINT_TYPE case CS_UINT_TYPE: return get(col_idx); @@ -261,13 +290,15 @@ class result_set : public dbi::iresult_set case CS_UINT_TYPE: return get(col_idx); #endif + case CS_LONG_TYPE: + return get(col_idx); #ifdef CS_BIGINT_TYPE case CS_BIGINT_TYPE: return get(col_idx); #endif case CS_DECIMAL_TYPE: case CS_NUMERIC_TYPE: - return getnum(col_idx); + return get(col_idx); default: throw std::runtime_error(std::string(__FUNCTION__).append(": Invalid column data type (supported: tinyint, smallint, usmallint, int, uint, bigint)")); } @@ -293,7 +324,7 @@ class result_set : public dbi::iresult_set #endif case CS_DECIMAL_TYPE: case CS_NUMERIC_TYPE: - return getnum(col_idx); + return get(col_idx); default: throw std::runtime_error(std::string(__FUNCTION__).append(": Invalid column data type (supported: tinyint, usmallint, uint, ubigint)")); } @@ -316,30 +347,16 @@ class result_set : public dbi::iresult_set return get(col_idx); case CS_FLOAT_TYPE: return get(col_idx); + case CS_MONEY_TYPE: + case CS_MONEY4_TYPE: case CS_DECIMAL_TYPE: case CS_NUMERIC_TYPE: - return getnum(col_idx); + return get(col_idx); default: throw std::runtime_error(std::string(__FUNCTION__).append(": Invalid column data type (supported: real, float, numeric/decimal)")); } } - virtual long double get_ldouble(size_t col_idx) - { - switch (columns[col_idx].datatype) - { - case CS_REAL_TYPE: - return get(col_idx); - case CS_FLOAT_TYPE: - return get(col_idx); - case CS_DECIMAL_TYPE: - case CS_NUMERIC_TYPE: - return getnum(col_idx); - default: - throw std::runtime_error(std::string(__FUNCTION__).append(": Invalid column data type (supported: real, float, numeric, decimal)")); - } - } - virtual bool get_bool(size_t col_idx) { if (CS_BIT_TYPE != columns[col_idx].datatype) @@ -355,6 +372,11 @@ class result_set : public dbi::iresult_set case CS_LONGCHAR_TYPE: case CS_TEXT_TYPE: case CS_VARCHAR_TYPE: + case CS_BOUNDARY_TYPE: + case CS_SENSITIVITY_TYPE: +#ifdef CS_XML_TYPE + case CS_XML_TYPE: +#endif break; default: throw std::runtime_error(std::string(__FUNCTION__).append(": Invalid column data type (supported: char, varchar, text)")); @@ -370,6 +392,11 @@ class result_set : public dbi::iresult_set case CS_LONGCHAR_TYPE: case CS_TEXT_TYPE: case CS_VARCHAR_TYPE: + case CS_BOUNDARY_TYPE: + case CS_SENSITIVITY_TYPE: +#ifdef CS_XML_TYPE + case CS_XML_TYPE: +#endif break; default: throw std::runtime_error(std::string(__FUNCTION__).append(": Invalid column data type (supported: char, varchar, text)")); @@ -379,29 +406,35 @@ class result_set : public dbi::iresult_set virtual int get_date(size_t col_idx) { - if (CS_TIME_TYPE == columns[col_idx].datatype -#ifdef CS_BIGTIME_TYPE - || CS_BIGTIME_TYPE == columns[col_idx].datatype +#ifdef CS_TIME_TYPE + if (CS_TIME_TYPE == columns[col_idx].datatype) + throw std::runtime_error(std::string(__FUNCTION__).append(": Invalid column data type (supported: date, datetime, smalldatetime, bigdatetime)")); #endif - ) +#ifdef CS_BIGTIME_TYPE + if (CS_BIGTIME_TYPE == columns[col_idx].datatype) throw std::runtime_error(std::string(__FUNCTION__).append(": Invalid column data type (supported: date, datetime, smalldatetime, bigdatetime)")); - getdtrec(col_idx); +#endif + getdt(col_idx); return daterec.dateyear * 10000 + (daterec.datemonth + 1) * 100 + daterec.datedmonth; } virtual double get_time(size_t col_idx) { +#ifdef CS_DATE_TYPE if (CS_DATE_TYPE == columns[col_idx].datatype) throw std::runtime_error(std::string(__FUNCTION__).append(": Invalid column data type (supported: time, datetime, smalldatetime, bigdatetime)")); - getdtrec(col_idx); +#endif + getdt(col_idx); return (double)(daterec.datehour * 10000 + daterec.dateminute * 100 + daterec.datesecond) + (double)daterec.datemsecond / 1000.0; } virtual time_t get_datetime(size_t col_idx) { +#ifdef CS_TIME_TYPE if (CS_TIME_TYPE == columns[col_idx].datatype) throw std::runtime_error(std::string(__FUNCTION__).append(": Invalid column data type (supported: date, datetime, smalldatetime, bigdatetime)")); - getdtrec(col_idx); +#endif + getdt(col_idx); std::memset(&stm, 0, sizeof(stm)); stm.tm_sec = daterec.datesecond; stm.tm_min = daterec.dateminute; @@ -423,7 +456,7 @@ class result_set : public dbi::iresult_set #endif break; default: - throw std::runtime_error(std::string(__FUNCTION__).append(": Invalid column data type")); + throw std::runtime_error(std::string(__FUNCTION__).append(": Invalid column data type: ").append(std::to_string(columns[col_idx].datatype))); } return get(col_idx); } @@ -438,41 +471,35 @@ class result_set : public dbi::iresult_set #endif break; default: - throw std::runtime_error(std::string(__FUNCTION__).append(": Invalid column data type")); + throw std::runtime_error(std::string(__FUNCTION__).append(": Invalid column data type: ").append(std::to_string(columns[col_idx].datatype))); } return std::u16string(reinterpret_cast((char*)columndata[col_idx])); } - virtual std::vector get_image(size_t col_idx) + virtual std::vector get_binary(size_t col_idx) { - if (CS_IMAGE_TYPE == columns[col_idx].datatype) + switch (columns[col_idx].datatype) { - std::vector t(columndata[col_idx].length); - std::memcpy(reinterpret_cast(t.data()), columndata[col_idx], columndata[col_idx].length); - return t; + case CS_IMAGE_TYPE: + case CS_BINARY_TYPE: + case CS_VARBINARY_TYPE: + case CS_LONGBINARY_TYPE: +#ifdef CS_BLOB_TYPE + case CS_BLOB_TYPE: +#endif + break; + default: + throw std::runtime_error(std::string(__FUNCTION__).append(": Invalid column data type: ").append(std::to_string(columns[col_idx].datatype))); } - throw std::runtime_error(std::string(__FUNCTION__).append(": Invalid column data type")); + std::vector t(columndata[col_idx].length); + std::memcpy(reinterpret_cast(t.data()), columndata[col_idx], columndata[col_idx].length); + return std::move(t); } bool cancel() { - if (true == do_cancel) - { - do_cancel = false; - if (CS_SUCCEED != ct_cancel(nullptr, cscommand, CS_CANCEL_CURRENT)) - return false; - } - return true; - } - - bool cancel_all() - { - if (true == do_cancel) // TODO - { - do_cancel = false; - if (CS_SUCCEED != ct_cancel(nullptr, cscommand, CS_CANCEL_ALL)) - return false; - } + if (CS_SUCCEED != ct_cancel(nullptr, cscommand, CS_CANCEL_ALL)) + return false; return true; } @@ -483,6 +510,10 @@ class result_set : public dbi::iresult_set result_set(const result_set& rs) = delete; result_set& operator=(const result_set& rs) = delete; + void set_scrollable(bool scroll) + { + scrollable = scroll; + } bool next_result() { @@ -533,8 +564,8 @@ class result_set : public dbi::iresult_set { case CS_PARAM_RESULT: case CS_STATUS_RESULT: - case CS_CURSOR_RESULT: case CS_ROW_RESULT: + case CS_CURSOR_RESULT: process_result(false, true); return true; case CS_COMPUTE_RESULT: @@ -612,10 +643,32 @@ class result_set : public dbi::iresult_set } } } - - void process_param_result() + + bool scroll_fetch(CS_INT type) { - // TODO + if (scrollable) + { + switch (ct_scroll_fetch(cscommand, type, CS_UNUSED, CS_TRUE, &result)) + { + case CS_END_DATA: + case CS_CURSOR_BEFORE_FIRST: + row_cnt = 0; + return false; + case CS_CURSOR_AFTER_LAST: + row_cnt += 1; + return false; + } + switch (type) + { + case CS_FIRST: row_cnt = 1; break; + case CS_LAST: row_cnt = -1; break; + case CS_PREV: row_cnt -= 1; break; + case CS_NEXT: row_cnt += 1; break; + } + return true; + } + else + throw std::runtime_error(std::string(__FUNCTION__).append(": The function can only be called if scrollable cursor is used ")); } template @@ -623,35 +676,43 @@ class result_set : public dbi::iresult_set { if (is_null(col_idx)) throw std::runtime_error(std::string(__FUNCTION__).append(": Can't convert NULL data")); + switch (columns[col_idx].datatype) + { + case CS_NUMERIC_TYPE: + case CS_DECIMAL_TYPE: + case CS_MONEY_TYPE: + case CS_MONEY4_TYPE: + { + double num = 0.0; + std::memset(&destfmt, 0, sizeof(destfmt)); + destfmt.maxlength = sizeof(num); + destfmt.datatype = CS_FLOAT_TYPE; + destfmt.format = CS_FMT_UNUSED; + destfmt.locale = nullptr; + if (CS_SUCCEED != cs_convert(cscontext, &columns[col_idx], static_cast(columndata[col_idx]), &destfmt, &num, 0)) + throw std::runtime_error(std::string(__FUNCTION__).append(": cs_convert failed")); + return num; + } + } + if (sizeof(T) < columndata[col_idx].data.size()) + throw std::runtime_error(std::string(__FUNCTION__).append(": Sybase data type is larger than the primitive data type")); return *(reinterpret_cast((char*)columndata[col_idx])); } - template - T getnum(size_t col_idx) - { - if (is_null(col_idx)) - throw std::runtime_error(std::string(__FUNCTION__).append(": Can't convert NULL data")); - auto num = 0.0D; - std::memset(&destfmt, 0, sizeof(destfmt)); - destfmt.maxlength = sizeof(double); - destfmt.datatype = CS_FLOAT_TYPE; - destfmt.format = CS_FMT_UNUSED; - destfmt.locale = nullptr; - if (CS_SUCCEED != cs_convert(cscontext, &columns[col_idx], static_cast(columndata[col_idx]), &destfmt, &num, 0)) - throw std::runtime_error(std::string(__FUNCTION__).append(": cs_convert failed")); - return num; - } - - void getdtrec(size_t col_idx) + void getdt(size_t col_idx) { if (is_null(col_idx)) throw std::runtime_error(std::string(__FUNCTION__).append(": Can't convert NULL data")); switch (columns[col_idx].datatype) { - case CS_DATE_TYPE: case CS_DATETIME_TYPE: case CS_DATETIME4_TYPE: +#ifdef CS_DATE_TYPE + case CS_DATE_TYPE: +#endif +#ifdef CS_TIME_TYPE case CS_TIME_TYPE: +#endif #ifdef CS_BIGDATETIME_TYPE case CS_BIGDATETIME_TYPE: #endif @@ -668,8 +729,9 @@ class result_set : public dbi::iresult_set } private: + bool scrollable = false; bool do_cancel = false; - size_t row_cnt = 0; + long row_cnt = 0; size_t affected_rows = 0; bool more_res = false; Context* cscontext = nullptr; @@ -734,8 +796,8 @@ class connection : public dbi::iconnection if (true == connected()) disconnect(); if (nullptr != csconnection && CS_SUCCEED == ct_connect(csconnection, (server.empty() ? nullptr : const_cast(server.c_str())), server.empty() ? 0 : CS_NULLTERM)) - return alive(); - return false; + is_server_ase(); + return alive(); } virtual void disconnect() @@ -849,6 +911,11 @@ class connection : public dbi::iconnection { return csconnection; } + + bool is_ase() + { + return ase; + } private: friend class driver; @@ -881,8 +948,18 @@ class connection : public dbi::iconnection } cscontext = nullptr; } + + void is_server_ase() + { + std::unique_ptr stmt(get_statement(*this)); + dbi::iresult_set* rs = stmt->execute("if object_id('dbo.sysobjects') is not null and object_id('dbo.syscolumns') is not null select 1 else select 0"); + if (!rs->next()) + throw std::runtime_error(std::string(__FUNCTION__).append(": Failed to get server type")); + ase = rs->get_int(0); + } private: + bool ase = false; CS_BOOL is_autocommit = CS_TRUE; Context* cscontext = nullptr; Connection* csconnection = nullptr; @@ -905,8 +982,6 @@ class driver : public idriver public: ~driver() { - if (cslocale != nullptr) - cs_loc_drop(cscontext, cslocale); destroy(cscontext); } @@ -1133,6 +1208,8 @@ class driver : public idriver void destroy(Context*& cs_context) { + if (cslocale != nullptr) + cs_loc_drop(cscontext, cslocale); if (CS_SUCCEED != ct_exit(cs_context, CS_UNUSED)) ct_exit(cs_context, CS_FORCE_EXIT); cs_ctx_drop(cs_context); @@ -1234,135 +1311,179 @@ class statement : public dbi::istatement public: ~statement() { - cancel_all(); + cancel(); + close_cursor(); ct_cmd_drop(cscommand); } + virtual bool cancel() + { + return rs.cancel(); + } + + virtual dbi::iresult_set* execute() + { + if (false == conn.alive()) + throw std::runtime_error(std::string(__FUNCTION__).append(": Database connection is dead")); + if (CS_SUCCEED != ct_send(cscommand)) + throw std::runtime_error(std::string(__FUNCTION__).append(": Failed to send command: ").append(command)); + rs.cscontext = conn.cs_context(); + rs.cscommand = cscommand; + rs.next_result(); + return &rs; + } + + virtual dbi::iresult_set* execute(const std::string& cmd, bool usecursor = false, bool scrollable = false) + { + if (cmd.empty()) + throw std::runtime_error(std::string(__FUNCTION__).append(": SQL command is not set")); + set_command(cmd, CS_LANG_CMD); + usecursor && init_cursor(scrollable) || init_command(); + return execute(); + } + virtual void prepare(const std::string& cmd) { - rs.cancel_all(); - command = cmd; - static std::atomic_int cnt(1); - std::string id = "p"; - id.append(std::to_string(cnt++)).append(std::to_string(std::hash()(command))); - auto csid = const_cast(id.c_str()); + set_command(cmd, CS_LANG_CMD); + std::string csid = genid("proc"); if (false == conn.alive()) throw std::runtime_error(std::string(__FUNCTION__).append(": Database connection is dead")); - if (CS_SUCCEED != ct_dynamic(cscommand, CS_PREPARE, csid, CS_NULLTERM, const_cast(command.c_str()), CS_NULLTERM)) + if (CS_SUCCEED != ct_dynamic(cscommand, CS_PREPARE, const_cast(csid.c_str()), CS_NULLTERM, const_cast(command.c_str()), CS_NULLTERM)) throw std::runtime_error(std::string(__FUNCTION__).append(": Failed to prepare command")); execute(); - if (CS_SUCCEED != ct_dynamic(cscommand, CS_DESCRIBE_INPUT, csid, CS_NULLTERM, nullptr, CS_UNUSED)) + if (CS_SUCCEED != ct_dynamic(cscommand, CS_DESCRIBE_INPUT, const_cast(csid.c_str()), CS_NULLTERM, nullptr, CS_UNUSED)) throw std::runtime_error(std::string(__FUNCTION__).append(": Failed to get input params decription")); execute(); - if (CS_SUCCEED != ct_dynamic(cscommand, CS_EXECUTE, csid, CS_NULLTERM, nullptr, CS_UNUSED)) + if (CS_SUCCEED != ct_dynamic(cscommand, CS_EXECUTE, const_cast(csid.c_str()), CS_NULLTERM, nullptr, CS_UNUSED)) throw std::runtime_error(std::string(__FUNCTION__).append(": Failed to set command for execution")); - prep_datafmt.resize(rs.columns.size()); - prep_data.resize(rs.columns.size()); + param_datafmt.resize(rs.columns.size()); + param_data.resize(rs.columns.size()); for (auto i = 0U; i < rs.columns.size(); ++i) { - prep_datafmt[i] = rs.columns[i]; - prep_data[i].allocate(rs.columns[i].maxlength); - if (CS_SUCCEED != ct_setparam(cscommand, &(prep_datafmt[i]), prep_data[i], &(prep_data[i].length), &(prep_data[i].indicator))) + param_datafmt[i] = rs.columns[i]; + param_data[i].allocate(rs.columns[i].maxlength); + if (CS_SUCCEED != ct_setparam(cscommand, &(param_datafmt[i]), param_data[i], &(param_data[i].length), &(param_data[i].indicator))) + throw std::runtime_error(std::string(__FUNCTION__).append(": Failed to set param, index ").append(std::to_string(i))); + } + } + + virtual void call(const std::string& cmd) + { + get_proc_params(cmd);; + set_command(cmd, CS_RPC_CMD); + if (CS_SUCCEED != ct_command(cscommand, CS_RPC_CMD, const_cast(cmd.c_str()), CS_NULLTERM, CS_NO_RECOMPILE)) + throw std::runtime_error(std::string(__FUNCTION__).append(": Failed to set stored proc command")); + for (auto i = 0U; i < param_datafmt.size(); ++i) + { + if (CS_SUCCEED != ct_setparam(cscommand, &(param_datafmt[i]), param_data[i], &(param_data[i].length), &(param_data[i].indicator))) throw std::runtime_error(std::string(__FUNCTION__).append(": Failed to set param, index ").append(std::to_string(i))); } } - virtual void set_null(size_t col_idx) + virtual int proc_retval() { - if (col_idx >= prep_data.size()) - throw std::runtime_error(std::string(__FUNCTION__).append(": Invalid index")); - prep_data[col_idx].length = 0; - prep_data[col_idx].indicator = -1; + if (CS_RPC_CMD == cmdtype && rs.more_results() && rs.next() && rs.column_count() == 1) + { + int ret = rs.get_int(0); + rs.more_results() && rs.next(); + rs.more_results() && rs.next() && rs.cancel(); + return ret; + } + throw std::runtime_error(std::string(__FUNCTION__).append(": Invalid function call, it must be called after all result sets from stored procedure are processed")); } - virtual void set_short(size_t col_idx, int16_t val) + virtual void set_null(size_t param_idx) { - set(col_idx, std::to_string(val)); + if (param_idx >= param_data.size()) + throw std::runtime_error(std::string(__FUNCTION__).append(": Invalid index")); + param_data[param_idx].length = 0; + param_data[param_idx].indicator = -1; } - virtual void set_ushort(size_t col_idx, uint16_t val) + virtual void set_short(size_t param_idx, int16_t val) { - set(col_idx, std::to_string(val)); + set(param_idx, val); } - virtual void set_int(size_t col_idx, int32_t val) + virtual void set_ushort(size_t param_idx, uint16_t val) { - set(col_idx, std::to_string(val)); + set(param_idx, val); } - virtual void set_uint(size_t col_idx, uint32_t val) + virtual void set_int(size_t param_idx, int32_t val) { - set(col_idx, std::to_string(val)); + set(param_idx, val); } - virtual void set_long(size_t col_idx, int64_t val) + virtual void set_uint(size_t param_idx, uint32_t val) { - set(col_idx, std::to_string(val)); + set(param_idx, val); } - virtual void set_ulong(size_t col_idx, uint64_t val) + virtual void set_long(size_t param_idx, int64_t val) { - set(col_idx, std::to_string(val)); + set(param_idx, val); } - virtual void set_float(size_t col_idx, float val) + virtual void set_ulong(size_t param_idx, uint64_t val) { - set(col_idx, std::to_string(val)); + set(param_idx, val); } - virtual void set_double(size_t col_idx, double val) + virtual void set_float(size_t param_idx, float val) { - set(col_idx, std::to_string(val)); + set(param_idx, val); } - virtual void set_ldouble(size_t col_idx, long double val) + virtual void set_double(size_t param_idx, double val) { - set(col_idx, std::to_string(val)); + set(param_idx, val); } - virtual void set_bool(size_t col_idx, bool val) + virtual void set_bool(size_t param_idx, bool val) { - set(col_idx, std::to_string(val)); + set(param_idx, val); } - virtual void set_char(size_t col_idx, char val) + virtual void set_char(size_t param_idx, char val) { - if (col_idx >= prep_data.size()) - throw std::runtime_error(std::string(__FUNCTION__).append(": Invalid index")); - prep_data[col_idx].indicator = 0; - prep_data[col_idx].length = 1; - prep_data[col_idx][0] = val; + set(param_idx, val); } - virtual void set_string(size_t col_idx, const std::string& val) + virtual void set_string(size_t param_idx, const std::string& val) { - if (col_idx >= prep_data.size()) + if (param_idx >= param_data.size()) throw std::runtime_error(std::string(__FUNCTION__).append(": Invalid index")); - prep_data[col_idx].length = val.length(); - if (prep_data[col_idx].length > prep_datafmt[col_idx].maxlength) + param_data[param_idx].length = val.length(); + if (param_data[param_idx].length > param_datafmt[param_idx].maxlength) throw std::runtime_error(std::string(__FUNCTION__).append(": Data length is greater than maximum field size")); - prep_data[col_idx].indicator = 0; - std::memcpy(prep_data[col_idx], val.c_str(), val.length()); + param_data[param_idx].indicator = 0; + std::memcpy(param_data[param_idx], val.c_str(), val.length()); } - virtual void set_date(size_t col_idx, int val) + virtual void set_date(size_t param_idx, int val) { - set(col_idx, std::to_string(val)); + int yr = val / 10000; + int mon = (val % 10000) / 100; + int day = val % 100; + std::vector dt(22); + std::sprintf(dt.data(), "%4d%02d%02d 00:00:00.000", yr, mon, day); + setdt(param_idx, dt.data()); } - virtual void set_time(size_t col_idx, double val) + virtual void set_time(size_t param_idx, double val) { int t = static_cast(val); int hr = t / 10000; int min = (t % 10000) / 100; int sec = t % 100; - int ms = (val - t) * 1000; - std::vector dt(23); - std::sprintf(dt.data(), "1900-01-01 %02d:%02d:%02d.%03d", hr, min, sec, ms); - set(col_idx, dt.data()); + int ms = floor((val - t) * 1000 + 0.5); + std::vector dt(22); + std::sprintf(dt.data(), "19000101 %02d:%02d:%02d.%03d", hr, min, sec, ms); + setdt(param_idx, dt.data()); } - virtual void set_datetime(size_t col_idx, time_t val) + virtual void set_datetime(size_t param_idx, time_t val) { #if defined(_WIN32) || defined(_WIN64) ::localtime_s(&stm, &val); @@ -1370,110 +1491,307 @@ class statement : public dbi::istatement ::localtime_r(&val, &stm); #endif stm.tm_year += 1900; - std::vector dt(23); - std::sprintf(dt.data(), "%04d-%02d-%02d %02d:%02d:%02d.000", stm.tm_year, stm.tm_mon, stm.tm_mday, stm.tm_hour, stm.tm_min, stm.tm_sec); - set(col_idx, dt.data()); + std::vector dt(22); + std::sprintf(dt.data(), "%04d%02d%02d %02d:%02d:%02d.000", stm.tm_year, stm.tm_mon + 1, stm.tm_mday, stm.tm_hour, stm.tm_min, stm.tm_sec); + setdt(param_idx, dt.data()); } - virtual void set_unichar(size_t col_idx, char16_t val) + virtual void set_unichar(size_t param_idx, char16_t val) { - if (col_idx >= prep_data.size()) + if (param_idx >= param_data.size()) throw std::runtime_error(std::string(__FUNCTION__).append(": Invalid index")); - prep_data[col_idx].length = sizeof(char16_t); - if (prep_data[col_idx].length > prep_datafmt[col_idx].maxlength) + param_data[param_idx].length = sizeof(char16_t); + if (param_data[param_idx].length > param_datafmt[param_idx].maxlength) throw std::runtime_error(std::string(__FUNCTION__).append(": Data length is greater than maximum field size")); - prep_data[col_idx].indicator = 0; - std::memcpy(prep_data[col_idx], &val, prep_data[col_idx].length); + param_data[param_idx].indicator = 0; + std::memcpy(param_data[param_idx], &val, param_data[param_idx].length); } - virtual void set_unistring(size_t col_idx, const std::u16string& val) + virtual void set_unistring(size_t param_idx, const std::u16string& val) { - if (col_idx >= prep_data.size()) + if (param_idx >= param_data.size()) throw std::runtime_error(std::string(__FUNCTION__).append(": Invalid index")); - prep_data[col_idx].length = sizeof(char16_t) * val.length(); - if (prep_data[col_idx].length > prep_datafmt[col_idx].maxlength) + param_data[param_idx].length = sizeof(char16_t) * val.length(); + if (param_data[param_idx].length > param_datafmt[param_idx].maxlength) throw std::runtime_error(std::string(__FUNCTION__).append(": Data length is greater than maximum field size")); - prep_data[col_idx].indicator = 0; - std::memcpy(prep_data[col_idx], &val[0], prep_data[col_idx].length); + param_data[param_idx].indicator = 0; + std::memcpy(param_data[param_idx], &val[0], param_data[param_idx].length); } - virtual void set_image(size_t col_idx, const std::vector& val) + virtual void set_binary(size_t param_idx, const std::vector& val) { - if (col_idx >= prep_data.size()) + if (param_idx >= param_data.size()) throw std::runtime_error(std::string(__FUNCTION__).append(": Invalid index")); - prep_data[col_idx].length = val.size(); - if (prep_data[col_idx].length > prep_datafmt[col_idx].maxlength) + param_data[param_idx].length = val.size(); + if (param_data[param_idx].length > param_datafmt[param_idx].maxlength) throw std::runtime_error(std::string(__FUNCTION__).append(": Data length is greater than maximum field size")); - prep_data[col_idx].indicator = 0; - std::memcpy(prep_data[col_idx], &val[0], prep_data[col_idx].length); + param_data[param_idx].indicator = 0; + std::memcpy(param_data[param_idx], &val[0], param_data[param_idx].length); } - virtual dbi::iresult_set* execute() +private: + friend class connection; + statement() = delete; + statement(connection& conn) : conn(conn) { - if (false == conn.alive()) - throw std::runtime_error(std::string(__FUNCTION__).append(": Database connection is dead")); - if (CS_SUCCEED != ct_send(cscommand)) - throw std::runtime_error(std::string(__FUNCTION__).append(": Failed to send command: ").append(command)); - rs.cscontext = conn.cs_context(); - rs.cscommand = cscommand; - rs.next_result(); - return &rs; + if (CS_SUCCEED != ct_cmd_alloc(conn.cs_connection(), &cscommand)) + throw std::runtime_error(std::string(__FUNCTION__).append(": Failed to allocate command struct")); } - - virtual dbi::iresult_set* execute(const std::string& cmd) + + std::string genid(const std::string& type) + { + static std::atomic_int cnt(1); + std::string id = type; + id.append(std::to_string(cnt++)).append(std::to_string(std::hash()(command))); + return id; + } + + void set_command(const std::string& cmd, CS_INT type) { - if (cmd.empty()) - throw std::runtime_error(std::string(__FUNCTION__).append(": SQL command is not set")); command = cmd; - rs.cancel_all(); + cmdtype = type; + rs.cancel(); + rs.set_scrollable(false); + close_cursor(); + } + + bool init_command() + { if (CS_SUCCEED != ct_command(cscommand, CS_LANG_CMD, const_cast(command.c_str()), CS_NULLTERM, CS_UNUSED)) throw std::runtime_error(std::string(__FUNCTION__).append(": Failed to set command")); - return execute(); + return true; } - - virtual bool cancel() + + bool init_cursor(bool scrollable) + { + cursor = true; + rs.set_scrollable(scrollable); + std::string csid = genid("cursor"); + if (CS_SUCCEED != ct_cursor(cscommand, CS_CURSOR_DECLARE, const_cast(csid.c_str()), CS_NULLTERM, const_cast(command.c_str()), CS_NULLTERM, (scrollable ? CS_SCROLL_CURSOR : CS_READ_ONLY))) + throw std::runtime_error(std::string(__FUNCTION__).append(": Failed to declare cursor")); + if (CS_SUCCEED != ct_cursor(cscommand, CS_CURSOR_OPEN, nullptr, CS_UNUSED, nullptr, CS_UNUSED, CS_UNUSED)) + throw std::runtime_error(std::string(__FUNCTION__).append(": Failed to open cursor")); + return true; + } + + void close_cursor() { - return rs.cancel(); + if (cursor) + { + ct_cursor(cscommand, CS_CURSOR_CLOSE, nullptr, CS_UNUSED, nullptr, CS_UNUSED, CS_DEALLOC); + cursor = false; + execute(); + } } - - virtual bool cancel_all() + + void get_proc_params(std::string procname) + { + param_datafmt.clear(); + param_data.clear(); + std::string sql; + if (conn.is_ase()) + { + std::string procnum = "1"; + std::string sysprefix = "dbo"; + if (2 == std::count(procname.begin(), procname.end(), '.')) + sysprefix.insert(0, procname.substr(0, procname.find('.') + 1)); + std::size_t pos = procname.find(';'); + if (std::string::npos != pos) + { + procnum = procname.substr(pos + 1); + procname.erase(pos); + } + sql = "select c.name, c.usertype, c.length, c.prec, c.scale, c.status2 from " + + sysprefix + ".sysobjects o, " + sysprefix + ".syscolumns c where o.id = object_id('" + + procname + "') and o.type = 'P' and o.id = c.id and c.number = " + + procnum + " order by c.colid"; + } + else + { + sql = "select m.parm_name, m.domain_id, m.width, m.width, m.scale, case m.parm_mode_out when 'Y' then 2 else 1 end from sysobjects o, sysprocedure p, sysprocparm m where o.id = object_id('" + + procname + "') and o.type = 'P' and o.name = p.proc_name and o.uid = p.creator and m.proc_id = p.proc_id and m.parm_type = 0 order by m.parm_id"; + } + execute(sql); + while (rs.next()) + { + param_datafmt.push_back(CS_DATAFMT()); + CS_DATAFMT& datafmt = *param_datafmt.rbegin(); + memset(&datafmt, 0, sizeof(datafmt)); + strcpy(datafmt.name, rs.get_string(0).c_str()); + datafmt.namelen = CS_NULLTERM; + datafmt.datatype = ctlib_datatype(rs.get_int(1)); + datafmt.format = CS_FMT_UNUSED; + datafmt.maxlength = rs.get_int(2); + if (!rs.is_null(3)) + datafmt.precision = rs.get_int(3); + if (!rs.is_null(4)) + datafmt.scale = rs.get_int(4); + datafmt.status = (rs.get_int(5) == 1 ? CS_INPUTVALUE : CS_RETURN); + datafmt.locale = NULL; + param_data.push_back(result_set::column_data()); + result_set::column_data& coldata = *param_data.rbegin(); + coldata.allocate(datafmt.maxlength); + coldata.length = datafmt.maxlength; + } + } + + template + void set(size_t param_idx, T val) { - return rs.cancel_all(); + if (param_idx >= param_data.size()) + throw std::runtime_error(std::string(__FUNCTION__).append(": Invalid index")); + switch (param_datafmt[param_idx].datatype) + { + case CS_NUMERIC_TYPE: + case CS_DECIMAL_TYPE: + case CS_MONEY_TYPE: + case CS_MONEY4_TYPE: + { + double t = val; + std::memset(&srcfmt, 0, sizeof(srcfmt)); + srcfmt.datatype = CS_FLOAT_TYPE; + srcfmt.format = CS_FMT_UNUSED; + srcfmt.locale = nullptr; + srcfmt.maxlength = sizeof(t); + if (CS_SUCCEED != cs_convert(conn.cs_context(), &srcfmt, &t, ¶m_datafmt[param_idx], param_data[param_idx], 0)) + throw std::runtime_error(std::string(__FUNCTION__).append(": cs_convert failed")); + } + break; + default: + { + if (sizeof(T) > param_data[param_idx].data.size() && CS_TINYINT_TYPE != param_datafmt[param_idx].datatype) + throw std::runtime_error(std::string(__FUNCTION__).append(": Primitive data type is larger than the Sybase data type")); + *(reinterpret_cast((char*)param_data[param_idx])) = val; + } + } + param_data[param_idx].length = param_datafmt[param_idx].maxlength; + param_data[param_idx].indicator = 0; } -private: - friend class connection; - statement() = delete; - statement(connection& conn) : conn(conn) + void setdt(size_t param_idx, const std::string& val) { + if (param_idx >= param_data.size()) + throw std::runtime_error(std::string(__FUNCTION__).append(": Invalid index")); std::memset(&srcfmt, 0, sizeof(srcfmt)); srcfmt.datatype = CS_CHAR_TYPE; - srcfmt.format = CS_FMT_NULLTERM; + srcfmt.format = CS_FMT_UNUSED; srcfmt.locale = nullptr; - if (CS_SUCCEED != ct_cmd_alloc(conn.cs_connection(), &cscommand)) - throw std::runtime_error(std::string(__FUNCTION__).append(": Failed to allocate command struct")); - } - - void set(size_t col_idx, const std::string& val) - { - if (col_idx >= prep_data.size()) - throw std::runtime_error(std::string(__FUNCTION__).append(": Invalid index")); srcfmt.maxlength = val.length(); - if (CS_SUCCEED != cs_convert(conn.cs_context(), &srcfmt, const_cast(val.c_str()), &prep_datafmt[col_idx], prep_data[col_idx], 0)) + if (CS_SUCCEED != cs_convert(conn.cs_context(), &srcfmt, const_cast(val.c_str()), ¶m_datafmt[param_idx], param_data[param_idx], 0)) throw std::runtime_error(std::string(__FUNCTION__).append(": cs_convert failed")); - prep_data[col_idx].length = prep_datafmt[col_idx].maxlength; - prep_data[col_idx].indicator = 0; + param_data[param_idx].length = param_datafmt[param_idx].maxlength; + param_data[param_idx].indicator = 0; + } + + CS_INT ctlib_datatype(int dbt) + { + if (conn.is_ase()) + { + switch(dbt) + { + case 1: return CS_CHAR_TYPE; // char + case 2: return CS_VARCHAR_TYPE; // varchar + case 3: return CS_BINARY_TYPE; // binary + case 4: return CS_VARBINARY_TYPE; // varbinary + case 5: return CS_TINYINT_TYPE; // tinyint + case 6: return CS_SMALLINT_TYPE; // smallint + case 7: return CS_INT_TYPE; // int + case 8: return CS_FLOAT_TYPE; // float + case 10: return CS_NUMERIC_TYPE; // numeric + case 11: return CS_MONEY_TYPE; // money + case 12: return CS_DATETIME_TYPE; // datetime + case 13: return CS_INT_TYPE; // intn + case 14: return CS_FLOAT_TYPE; // floatn + case 15: return CS_DATETIME_TYPE; // datetimn + case 16: return CS_BIT_TYPE; // bit + case 17: return CS_MONEY_TYPE; // moneyn + case 19: return CS_TEXT_TYPE; // text + case 20: return CS_IMAGE_TYPE; // image + case 21: return CS_MONEY4_TYPE; // smallmoney + case 22: return CS_DATETIME4_TYPE; // smalldatetime + case 23: return CS_REAL_TYPE; // real + case 24: return CS_CHAR_TYPE; // nchar + case 25: return CS_VARCHAR_TYPE; // nvarchar + case 26: return CS_DECIMAL_TYPE; // decimal + case 27: return CS_DECIMAL_TYPE; // decimaln + case 28: return CS_NUMERIC_TYPE; // numericn + case 34: return CS_UNICHAR_TYPE; // unichar + case 35: return CS_UNICHAR_TYPE; // univarchar +#ifdef CS_UNITEXT_TYPE + case 36: return CS_UNITEXT_TYPE; // unitext +#endif + case 37: return CS_DATE_TYPE; // date + case 38: return CS_TIME_TYPE; // time + case 39: return CS_DATE_TYPE; // daten + case 40: return CS_TIME_TYPE; // timen +#ifdef CS_BIGINT_TYPE + case 43: return CS_BIGINT_TYPE; // bigint +#endif +#ifdef CS_USMALLINT_TYPE + case 44: return CS_USMALLINT_TYPE; // usmallint +#endif +#ifdef CS_UINT_TYPE + case 45: return CS_UINT_TYPE; // uint +#endif +#ifdef CS_UBIGINT_TYPE + case 46: return CS_UBIGINT_TYPE; // ubigint +#endif +#ifdef CS_UINT_TYPE + case 47: return CS_UINT_TYPE; // uintn +#endif + case 80: return CS_DATETIME_TYPE; // timestamp + } + } + else + { + switch (dbt) + { + case 1: return CS_SMALLINT_TYPE; // smallint + case 2: return CS_INT_TYPE; // int + case 3: return CS_NUMERIC_TYPE; // numeric + case 4: return CS_FLOAT_TYPE; // float + case 5: return CS_REAL_TYPE; // real + case 6: return CS_DATE_TYPE; // date + case 7: return CS_CHAR_TYPE; // char + case 8: return CS_CHAR_TYPE; // char + case 9: return CS_VARCHAR_TYPE; // varchar + case 10: return CS_LONGCHAR_TYPE; // long varchar + case 11: return CS_BINARY_TYPE; // binary + case 12: return CS_LONGBINARY_TYPE;// longbinary + case 13: return CS_DATETIME_TYPE; // timestamp + case 14: return CS_TIME_TYPE; // time + case 19: return CS_TINYINT_TYPE; // tinyint +#ifdef CS_BIGINT_TYPE + case 20: return CS_BIGINT_TYPE; // bigint +#endif +#ifdef CS_UINT_TYPE + case 21: return CS_UINT_TYPE; // uint +#endif +#ifdef CS_USMALLINT_TYPE + case 22: return CS_USMALLINT_TYPE; // usmallint +#endif +#ifdef CS_UBIGINT_TYPE + case 23: return CS_UBIGINT_TYPE; // ubigint +#endif + case 24: return CS_BIT_TYPE; // bit + case 27: return CS_DECIMAL_TYPE; // decimal + case 28: return CS_VARBINARY_TYPE; // varbinary + } + } + throw std::runtime_error(std::string(__FUNCTION__).append(": Unknown data type: ").append(std::to_string(dbt))); } private: CS_COMMAND* cscommand = nullptr; connection& conn; + bool cursor = false; + CS_INT cmdtype = CS_RPC_CMD; std::string command; result_set rs; CS_DATAFMT srcfmt; struct tm stm; - std::vector prep_datafmt; - std::vector prep_data; + std::vector param_datafmt; + std::vector param_data; }; diff --git a/sybase_example.cpp b/sybase_example.cpp index 089e469..c02c7f2 100644 --- a/sybase_example.cpp +++ b/sybase_example.cpp @@ -7,6 +7,10 @@ using namespace std; using namespace vgi::dbconn::dbi; using namespace vgi::dbconn::dbd; +constexpr auto DBNAME = "DBSYB1"; +constexpr auto DBUSER = "sa"; +constexpr auto DBPASS = "sybase"; + int main(int argc, char** argv) { /************************ @@ -14,49 +18,110 @@ int main(int argc, char** argv) ************************/ try { + cout.precision(12); + cout.setf(ios_base::fixed, ios::floatfield); + cout << endl << "============= Simple example =============" << endl; - connection conn = driver::load().get_connection("DB_SYB1", "sa", "sybase"); + cout << "===== connecting to database server\n"; + connection conn = driver::load().get_connection(DBNAME, DBUSER, DBPASS); if (conn.connect()) { - cout << "connected...\n"; + cout << "===== done...\n\n"; statement stmt = conn.get_statement(); + + cout << "===== switching database\n"; + stmt.execute("use tempdb"); + cout << "===== done...\n\n"; + + try + { + stmt.execute("if object_id('tempdb..test') is not null drop table test"); + } + catch (const exception&) + { + // ignore exception if table doesn't exist + } + cout << "===== creating table\n"; + stmt.execute("create table test ( " + "id int not null, " + "txt1 char(25) not null, " + "txt2 varchar(10) not null, " +#ifndef CS_VERSION_150 // for using old sybase 12.5 client with sybase 12 or 15 server + "txt3 varchar(256) null, " + "txt4 varchar(256) null, " +#else + "txt3 text null, " + "txt4 text null, " +#endif + "bool bit not null, " + "flag1 char(1) not null, " + "flag2 varchar(1) not null, " + "short1 tinyint not null, " + "short2 smallint not null, " + "long1 decimal not null, " + "long2 numeric not null, " + "float1 real not null, " + "float2 float(15) not null, " + "double1 float(16) not null, " +#ifndef CS_VERSION_150 + "double2 float(18) not null, " +#else + "double2 double precision not null, " +#endif + "double3 numeric(18, 8) not null, " + "date1 date not null, " + "date2 datetime not null, " + "date3 smalldatetime not null, " +#ifndef CS_VERSION_150 + "date4 datetime not null, " +#else + "date4 bigdatetime not null, " +#endif + + "datetime1 date not null, " + "datetime2 datetime not null, " + "datetime3 smalldatetime not null, " + +#ifndef CS_VERSION_150 + "datetime4 datetime not null, " +#else + "datetime4 bigdatetime not null, " +#endif + "time1 time not null, " +#ifndef CS_VERSION_150 + "time2 time not null, " +#else + "time2 bigtime not null, " +#endif + "time3 datetime not null, " + "time4 smalldatetime not null, " +#ifndef CS_VERSION_150 + "time5 datetime not null, " +#else + "time5 bigdatetime not null, " +#endif + "u16str1 univarchar(20) not null, " + "u16str2 unichar(20) not null, " + "u16char unichar(1) not null, " + + "nstr1 nchar(20) not null, " + "nstr2 nvarchar(20) not null, " + "nchar nchar(1) not null, " + +#ifndef CS_VERSION_150 + "img binary(250), " +#else + "img image, " +#endif + "bin1 binary(25), " + "bin2 varbinary(25), " + "mon1 smallmoney, " + "mon2 money, " + "primary key(id) ) "); + cout << "===== done...\n\n"; - stmt.execute("create table tempdb..test ( \ - id int not null, \ - txt1 char(25) not null, \ - txt2 varchar(10) not null, \ - txt3 text null, \ - txt4 text null, \ - bool bit not null, \ - flag1 char(1) not null, \ - flag2 varchar(1) not null, \ - short1 tinyint not null, \ - short2 smallint not null, \ - long1 decimal not null, \ - long2 numeric(18, 0) not null, \ - float1 real not null, \ - float2 float(15) not null, \ - double1 float(16) not null, \ - double2 double precision not null, \ - double3 numeric(18, 8) not null, \ - date1 date not null, \ - date2 datetime not null, \ - date3 smalldatetime not null, \ - date4 bigdatetime not null, \ - datetime1 date not null, \ - datetime2 datetime not null, \ - datetime3 smalldatetime not null, \ - datetime4 bigdatetime not null, \ - time1 time not null, \ - time2 bigtime not null, \ - time3 datetime not null, \ - time4 smalldatetime not null, \ - time5 bigdatetime not null, \ - u16str unichar(20) not null, \ - u16char unichar(1) not null, \ - primary key(id) )"); - - stmt.execute("insert into tempdb..test values (\ + cout << "===== inserting row into the table\n"; + stmt.execute("insert into test values (\ 1, \ 'text1', \ 'text2', \ @@ -88,19 +153,39 @@ int main(int argc, char** argv) getdate(), \ getdate(), \ '\u041F\u0441\u0438\u0445', \ - '\u0414')"); + '\u041F\u0441\u0438\u0445', \ + '\u0414', \ + '\u041F\u0441\u0438\u0445', \ + '\u041F\u0441\u0438\u0445', \ + '\u0414', \ + 0x0000008300000000000100000000013c, \ + 0x01234, \ + 0x0123456789, \ + 214748.3647, \ + 122337203685477.58)"); + cout << "===== done...\n\n"; - result_set rs = stmt.execute("select * from tempdb..test"); + cout << "===== selecting data from the table\n"; + result_set rs = stmt.execute("select * from test"); + + int date; + time_t datetime; + double time; + std::u16string unistr; + std::string nstr; + std::vector imgvec; + std::vector binvec1; + std::vector binvec2; time_t tm; while (rs.next()) { - cout << "-------------- data by index ----------------\n"; + cout << "-------------- data by index\n"; cout << rs.column_name(0) << ": >" << rs.get_int(0) << "<\n"; cout << rs.column_name(1) << ": >" << rs.get_string(1) << "<\n"; cout << rs.column_name(2) << ": >" << rs.get_string(2) << "<\n"; - cout << rs.column_name(3) << ": >" << (rs.is_null(3) ? "NULL" : rs.get_string(4)) << "<\n"; - cout << rs.column_name(4) << ": >" << (rs.is_null(4) ? "NULL" : rs.get_string(5)) << "<\n"; + cout << rs.column_name(3) << ": >" << (rs.is_null(3) ? "NULL" : rs.get_string(3)) << "<\n"; + cout << rs.column_name(4) << ": >" << (rs.is_null(4) ? "NULL" : rs.get_string(4)) << "<\n"; cout << rs.column_name(5) << ": >" << rs.get_bool(5) << "<\n"; cout << rs.column_name(6) << ": >" << rs.get_char(6) << "<\n"; cout << rs.column_name(7) << ": >" << rs.get_char(7) << "<\n"; @@ -110,27 +195,61 @@ int main(int argc, char** argv) cout << rs.column_name(11) << ": >" << rs.get_long(11) << "<\n"; cout << rs.column_name(12) << ": >" << rs.get_float(12) << "<\n"; cout << rs.column_name(13) << ": >" << rs.get_float(13) << "<\n"; - cout << rs.column_name(14) << ": >" << setprecision(12) << rs.get_double(14) << "<\n"; - cout << rs.column_name(15) << ": >" << setprecision(12) << rs.get_double(15) << "<\n"; - cout << rs.column_name(16) << ": >" << setprecision(12) << rs.get_double(16) << "<\n"; - cout << rs.column_name(17) << ": >" << rs.get_date(17) << "<\n"; + cout << rs.column_name(14) << ": >" << rs.get_double(14) << "<\n"; + cout << rs.column_name(15) << ": >" << rs.get_double(15) << "<\n"; + cout << rs.column_name(16) << ": >" << rs.get_double(16) << "<\n"; + cout << rs.column_name(17) << ": >" << rs.get_date(17) << "<\n"; date = rs.get_date(17); cout << rs.column_name(18) << ": >" << rs.get_date(18) << "<\n"; cout << rs.column_name(19) << ": >" << rs.get_date(19) << "<\n"; cout << rs.column_name(20) << ": >" << rs.get_date(20) << "<\n"; cout << rs.column_name(21) << ": >" << ctime(&(tm = rs.get_datetime(21))); - cout << rs.column_name(22) << ": >" << ctime(&(tm = rs.get_datetime(22))); + cout << rs.column_name(22) << ": >" << ctime(&(tm = rs.get_datetime(22))); datetime = rs.get_datetime(22); cout << rs.column_name(23) << ": >" << ctime(&(tm = rs.get_datetime(23))); cout << rs.column_name(24) << ": >" << ctime(&(tm = rs.get_datetime(24))); - cout << rs.column_name(25) << ": >" << rs.get_time(25) << "<\n"; + cout << rs.column_name(25) << ": >" << rs.get_time(25) << "<\n"; time = rs.get_time(25); cout << rs.column_name(26) << ": >" << rs.get_time(26) << "<\n"; cout << rs.column_name(27) << ": >" << rs.get_time(27) << "<\n"; cout << rs.column_name(28) << ": >" << rs.get_time(28) << "<\n"; cout << rs.column_name(29) << ": >" << rs.get_time(29) << "<\n"; //wstring_convert, char16_t> cv; //cout << rs.column_name(30) << ": >" << cv.to_bytes(rs.get_unistring(30)) << "<\n"; - cout << rs.column_name(30) << ": >" << rs.get_unistring(30).data() << "<\n"; - cout << rs.column_name(31) << ": >" << rs.get_unichar(31) << "<\n"; - cout << "-------------- data by name ----------------\n"; + //cout << rs.column_name(32) << ": >" << cv.to_bytes(rs.get_unistring(31)) << "<\n"; + //cout << rs.column_name(31) << ": >" << cv.to_bytes(rs.get_unichar(32)) << "<\n"; + cout << rs.column_name(30) << ": >" << rs.get_unistring(30).data() << "<\n"; // this is utf-16 + unistr = rs.get_unistring(30); + cout << rs.column_name(31) << ": >" << rs.get_unistring(31).data() << "<\n"; // this is utf-16 + cout << rs.column_name(32) << ": >" << rs.get_unichar(32) << "<\n"; // this is utf-16 + cout << rs.column_name(33) << ": >" << rs.get_string(33) << "<\n"; // this is utf-8 + nstr = rs.get_string(33); + cout << rs.column_name(34) << ": >" << rs.get_string(34) << "<\n"; // this is utf-8 + cout << rs.column_name(35) << ": >" << rs.get_char(35) << "<\n"; // this is utf-8 + auto idata1 = rs.get_binary(36); + imgvec.insert(imgvec.begin(), idata1.begin(), idata1.end()); + cout << rs.column_name(36) << ": >"; + cout.setf(ios_base::hex, ios::basefield); + for (uint16_t i : idata1) + cout << setfill('0') << setw(2) << i; + cout.setf(ios_base::dec, ios::basefield); + cout << "<\n"; + auto bdata1 = rs.get_binary(37); + binvec1.insert(binvec1.begin(), bdata1.begin(), bdata1.end()); + cout << rs.column_name(37) << ": >"; + cout.setf(ios_base::hex, ios::basefield); + for (uint16_t i : bdata1) + cout << setfill('0') << setw(2) << i; + cout.setf(ios_base::dec, ios::basefield); + cout << "<\n"; + auto bbdata1 = rs.get_binary(38); + binvec2.insert(binvec2.begin(), bbdata1.begin(), bbdata1.end()); + cout << rs.column_name(38) << ": >"; + cout.setf(ios_base::hex, ios::basefield); + for (uint16_t i : bbdata1) + cout << setfill('0') << setw(2) << i; + cout.setf(ios_base::dec, ios::basefield); + cout << "<\n"; + cout << rs.column_name(39) << ": >" << rs.get_double(39) << "<\n"; + cout << rs.column_name(40) << ": >" << rs.get_double(40) << "<\n"; + cout << "-------------- data by name\n"; cout << "column id: >" << rs.get_int("id") << "<\n"; cout << "column txt1: >" << rs.get_string("txt1") << "<\n"; cout << "column txt2: >" << rs.get_string("txt2") << "<\n"; @@ -139,15 +258,15 @@ int main(int argc, char** argv) cout << "column bool: >" << rs.get_bool("bool") << "<\n"; cout << "column flag1: >" << rs.get_char("flag1") << "<\n"; cout << "column flag2: >" << rs.get_char("flag2") << "<\n"; - cout << "column short1: >" << rs.get_short("short1") << "<\n"; - cout << "column short2: >" << rs.get_short("short2") << "<\n"; + cout << "column short1: >" << rs.get_long("short1") << "<\n"; + cout << "column short2: >" << rs.get_long("short2") << "<\n"; cout << "column long1: >" << rs.get_long("long1") << "<\n"; cout << "column long2: >" << rs.get_long("long2") << "<\n"; cout << "column float1: >" << rs.get_float("float1") << "<\n"; cout << "column float2: >" << rs.get_float("float2") << "<\n"; - cout << "column double1: >" << setprecision(12) << rs.get_double("double1") << "<\n"; - cout << "column double2: >" << setprecision(12) << rs.get_double("double2") << "<\n"; - cout << "column double3: >" << setprecision(12) << rs.get_double("double3") << "<\n"; + cout << "column double1: >" << rs.get_double("double1") << "<\n"; + cout << "column double2: >" << rs.get_double("double2") << "<\n"; + cout << "column double3: >" << rs.get_double("double3") << "<\n"; cout << "column date1: >" << rs.get_date("date1") << "<\n"; cout << "column date2: >" << rs.get_date("date1") << "<\n"; cout << "column date3: >" << rs.get_date("date1") << "<\n"; @@ -161,25 +280,105 @@ int main(int argc, char** argv) cout << "column time3: >" << rs.get_time("time3") << "<\n"; cout << "column time4: >" << rs.get_time("time4") << "<\n"; cout << "column time5: >" << rs.get_time("time5") << "<\n"; - cout << "column u16str: >" << rs.get_unistring("u16str").data() << "<\n"; - cout << "column u16char: >" << rs.get_unichar("u16char") << "<\n"; + cout << "column u16str1: >" << rs.get_unistring("u16str1").data() << "<\n"; // this is utf-16 + cout << "column u16str2: >" << rs.get_unistring("u16str2").data() << "<\n"; // this is utf-16 + cout << "column u16char: >" << rs.get_unichar("u16char") << "<\n"; // this is utf-16 + cout << "column nstr1: >" << rs.get_string("nstr1") << "<\n"; // this is utf-8 + cout << "column nstr2: >" << rs.get_string("nstr2") << "<\n"; // this is utf-8 + cout << "column nchar: >" << rs.get_char("nchar") << "<\n"; // this is utf-8 + cout << setfill('0') << setw(2) << hex; + auto idata2 = rs.get_binary("img"); + cout << "column img: >"; + cout.setf(ios_base::hex, ios::basefield); + for (uint16_t i : idata2) + cout << setfill('0') << setw(2) << i; + cout.setf(ios_base::dec, ios::basefield); + cout << "<\n"; + auto bdata2 = rs.get_binary("bin1"); + cout << "column bin: >"; + cout.setf(ios_base::hex, ios::basefield); + for (uint16_t i : bdata2) + cout << setfill('0') << setw(2) << i; + cout.setf(ios_base::dec, ios::basefield); + cout << "<\n"; + auto bbdata2 = rs.get_binary("bin2"); + cout << "column bin: >"; + cout.setf(ios_base::hex, ios::basefield); + for (uint16_t i : bbdata2) + cout << setfill('0') << setw(2) << i; + cout.setf(ios_base::dec, ios::basefield); + cout << "<\n"; + cout << "column mon1: >" << rs.get_double("mon1") << "<\n"; + cout << "column mon2: >" << rs.get_double("mon2") << "<\n"; } - - stmt.execute("update tempdb..test set txt4 = 'text4' where id = 1"); - - stmt.execute("delete from tempdb..test where id = 1"); + cout << "===== done...\n\n"; - stmt.execute("drop table tempdb..test"); + cout << "===== testing prepared statement (stored proc would be the same) data types binding\n"; + stmt.prepare("insert into test values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + stmt.set_int(0, 2); + stmt.set_string(1, "text1"); + stmt.set_string(2, "text2"); + stmt.set_string(3, "text3"); + stmt.set_null(4); + stmt.set_bool(5, false); + stmt.set_char(6, 'Y'); + stmt.set_char(7, 'N'); + stmt.set_short(8, 1); + stmt.set_short(9, 2); + stmt.set_long(10, 167000000); + stmt.set_long(11, 167890000); + stmt.set_float(12, 1.45); + stmt.set_float(13, 2.56); + stmt.set_double(14, 12345.123456); + stmt.set_double(15, 12345.123456); + stmt.set_double(16, 12345.123456); + stmt.set_date(17, date); + stmt.set_date(18, date); + stmt.set_date(19, date); + stmt.set_date(20, date); + stmt.set_datetime(21, datetime); + stmt.set_datetime(22, datetime); + stmt.set_datetime(23, datetime); + stmt.set_datetime(24, datetime); + stmt.set_time(25, time); + stmt.set_time(26, time); + stmt.set_time(27, time); + stmt.set_time(28, time); + stmt.set_time(29, time); + stmt.set_unistring(30, unistr); + stmt.set_unistring(31, unistr); + stmt.set_unichar(32, unistr[0]); + stmt.set_string(33, nstr); + stmt.set_string(34, nstr); + stmt.set_char(35, nstr[0]); + stmt.set_binary(36, imgvec); + stmt.set_binary(37, binvec1); + stmt.set_binary(38, binvec2); + stmt.set_double(39, 214748.3647); + stmt.set_double(40, 122337203685477.58); + stmt.execute(); + cout << "===== done...\n\n"; + + cout << "===== updating row in the table\n"; + stmt.execute("update test set txt4 = 'text4' where id = 1"); + cout << "===== done...\n\n"; + + cout << "===== deleting from the table\n"; + stmt.execute("delete from test where id = 1"); + cout << "===== done...\n\n"; + + cout << "===== dropping the table\n"; + stmt.execute("drop table test"); + cout << "===== done...\n\n"; } else - cout << "failed to connect!\n"; + cout << "===== failed to connect!\n"; } catch (const exception& e) { - cout << "simple example exception: " << e.what() << endl; + cout << "===== simple example exception: " << e.what() << endl; } - /************************ * advanced usage ************************/ @@ -192,8 +391,9 @@ int main(int argc, char** argv) string my_context_info = "A: 1"; string my_conn_info = "B: 2"; + cout << "===== connecting to database server using custom settings\n"; /* - * Initialize sybase driver, get driver information, and set custom properties + * Initialize Sybase driver, get driver information, and set custom properties */ sybase::driver& sybdriver = driver::load(). // debug works only if compiled with debug version of sybase libraries @@ -227,7 +427,7 @@ int main(int argc, char** argv) ", number -" << CS_NUMBER(msg->msgnumber) << ", message - " << msg->msgstring << endl; if (msg->osstringlen > 0) cout << __FUNCTION__ << ": Operating System Message: " << msg->osstring << endl; - return (CS_SUCCEED); + return CS_SUCCEED; }). // set custom client callback ct_msg_callback([](sybase::Context* context, sybase::Connection* conn, sybase::ClientMessage* msg) @@ -267,8 +467,7 @@ int main(int argc, char** argv) /* * Get connection */ - connection conn = sybdriver.get_connection("DB_DEVSYB101", "brass_ro", "brass_ro"); - + connection conn = sybdriver.get_connection(DBNAME, DBUSER, DBPASS); /* * Print information from the driver @@ -305,13 +504,19 @@ int main(int argc, char** argv) */ if (conn.connect()) { - cout << "connected...\n\n"; + cout << "===== done...\n\n"; statement stmt = conn.get_statement(); + + cout << "===== switching database\n"; + stmt.execute("use tempdb"); + cout << "===== done...\n\n"; + + cout << "===== creating table\n"; // By default, columns in Adaptive Server Enterprise default to NOT NULL, whereas in Sybase IQ the default setting is NULL // To allow NULL values we explicitly state so for the column - result_set rs = stmt.execute("create table tempdb..test ( id int, txt varchar(10) NULL, dt numeric(18, 8) NULL, primary key(id) )"); - cout << "--- created table ---\n\n"; + result_set rs = stmt.execute("create table test ( id int, txt varchar(10) NULL, dt numeric(18, 8) NULL, primary key(id) )"); + cout << "===== done...\n\n"; /******************************************************************** * Use multiple insert statement to populate table. @@ -320,47 +525,49 @@ int main(int argc, char** argv) * Note that Sybase handles errors differently depending on type of error */ + cout << "===== using multiple statements to insert rows into the table - all will fail on invalid table name in one of the statements\n"; /* * Sybase will fail execution of whole chain of sql statements * since table does not exist */ try { - rs = stmt.execute( "insert into tempdb..test (id, txt) values (1, 'hello1') \ - insert into tempdb..test (id, txt) values (1, 'hello1') \ - insert into tempdb..test (id, txt) values (2, 'hello2') \ - insert into tempdb..bogus (id, txt) values (7, 'hello2') \ - insert into tempdb..test (id, txt) values (3, 'hello2') \ - insert into tempdb..test (id, txt) values (4, 'hello3') "); + rs = stmt.execute( "insert into test (id, txt) values (1, 'hello1') \ + insert into test (id, txt) values (1, 'hello1') \ + insert into test (id, txt) values (2, 'hello2') \ + insert into bogus (id, txt) values (7, 'hello2') \ + insert into test (id, txt) values (3, 'hello2') \ + insert into test (id, txt) values (4, 'hello3') "); } catch (const exception& e) { cout << e.what() << "\n"; } cout << "rows affected = " << rs.rows_affected() << "\n"; - cout << "--- insert data - example 1 ---\n\n"; + cout << "===== done...\n\n"; + cout << "===== using multiple statements to insert rows into the table - two will fail on broken unique index\n"; /* * Sybase will fail only execution of sql statements that breaks * unique indexing */ try { - rs = stmt.execute( "insert into tempdb..test (id, txt) values (1, 'hello1') \ - insert into tempdb..test (id, txt) values (1, 'hello1') \ - insert into tempdb..test (id, txt) values (2, 'hello2') \ - insert into tempdb..test (id, txt) values (3, 'hello2') \ - insert into tempdb..test (id, txt) values (4, 'hello3') \ - insert into tempdb..test (id, txt) values (4, 'hello3') \ - insert into tempdb..test (id, txt) values (5, 'hello5') "); + rs = stmt.execute( "insert into test (id, txt) values (1, 'hello1') \ + insert into test (id, txt) values (1, 'hello1') \ + insert into test (id, txt) values (2, 'hello2') \ + insert into test (id, txt) values (3, 'hello2') \ + insert into test (id, txt) values (4, 'hello3') \ + insert into test (id, txt) values (4, 'hello3') \ + insert into test (id, txt) values (5, 'hello5') "); } catch (const exception& e) { cout << e.what() << "\n"; } cout << "rows affected = " << rs.rows_affected() << "\n"; - cout << "--- insert data - example 2 ---\n\n"; + cout << "===== done...\n\n"; /******************************************************************** @@ -368,37 +575,39 @@ int main(int argc, char** argv) * Some statements are invalid. */ + cout << "===== using multiple statements to update rows in the table - all will fail on invalid data type for the column in one of the statements\n"; /* * Sybase will fail execution of whole chain of sql statements * since data type is wrong for the column */ try { - rs = stmt.execute( "update tempdb..test set txt = 'boom' where id = 5 \ - update tempdb..test set txt = 'test2' where id = 6 \ - update tempdb..test set txt = 3 where id = 1 "); + rs = stmt.execute( "update test set txt = 'boom' where id = 5 \ + update test set txt = 'test2' where id = 6 \ + update test set txt = 3 where id = 1 "); } catch (const exception& e) { cout << e.what() << "\n"; } cout << "rows affected = " << rs.rows_affected() << "\n"; - cout << "--- update data - example 1 ---\n\n"; + cout << "===== done...\n\n"; + cout << "===== using multiple statements to update rows in the table\n"; /* * Sybase will update all matching records */ try { - rs = stmt.execute( "update tempdb..test set txt = 'boom' where id = 5 \ - update tempdb..test set txt = 'test2' where id = 6 "); + rs = stmt.execute( "update test set txt = 'boom' where id = 5 \ + update test set txt = 'test2' where id = 6 "); } catch (const exception& e) { cout << e.what() << "\n"; } cout << "rows affected = " << rs.rows_affected() << "\n"; - cout << "--- update data - example 2 ---\n\n"; + cout << "===== done...\n\n"; /******************************************************************** @@ -409,17 +618,18 @@ int main(int argc, char** argv) */ + cout << "===== using multiple statements to select data from the table - all will fail on invalid table name in one of the statements\n"; /* * Here Sybase will fail execution of whole chain of sql statements * as table doesn't exist */ try { - rs = stmt.execute( "select id from tempdb..test where txt = 'hello1' \ - select id, txt from tempdb..test where txt = 'hello2' \ - select id from tempdb..test where txt = 'hello4' \ - select id, txt from tempdb..bogus where txt = 'aa' \ - select id, txt from tempdb..test where txt = 'hello3' "); + rs = stmt.execute( "select id from test where txt = 'hello1' \ + select id, txt from test where txt = 'hello2' \ + select id from test where txt = 'hello4' \ + select id, txt from bogus where txt = 'aa' \ + select id, txt from test where txt = 'hello3' "); } catch (const exception& e) { @@ -427,9 +637,10 @@ int main(int argc, char** argv) } cout << "has data = " << rs.has_data() << "\n"; cout << "more results = " << rs.more_results() << "\n"; - cout << "--- select data - example 1 ---\n\n"; + cout << "===== done...\n\n"; + cout << "===== using multiple statements to select data from the table - one will fail on invalid data type for the column (unlike isert/update which fail all)\n"; /******************************************************************** * Here Sybase will fail only execution of sql statements that has * specified invalid data type for column in where clause @@ -437,11 +648,11 @@ int main(int argc, char** argv) */ try { - rs = stmt.execute( "select id from tempdb..test where txt = 'hello1' \ - select id, txt from tempdb..test where txt = 'hello2' \ - select id from tempdb..test where txt = 'hello4' \ - select id, txt from tempdb..test where txt = 1 \ - select id, txt from tempdb..test where txt = 'hello3' "); + rs = stmt.execute( "select id from test where txt = 'hello1' \ + select id, txt from test where txt = 'hello2' \ + select id from test where txt = 'hello4' \ + select id, txt from test where txt = 1 \ + select id, txt from test where txt = 'hello3' "); } catch (const exception& e) { @@ -471,16 +682,16 @@ int main(int argc, char** argv) cout << "---------\n"; } } - cout << "--- select data - example 2 ---\n\n"; - + cout << "===== done...\n\n"; + cout << "===== using compute select statements\n"; /******************************************************************** * Using compute in select */ try { - rs = stmt.execute( "select id, txt, dt from tempdb..test order by id, txt compute count(txt) by id, txt "); + rs = stmt.execute( "select id, txt, dt from test order by id, txt compute count(txt) by id, txt "); } catch (const exception& e) { @@ -510,7 +721,7 @@ int main(int argc, char** argv) } } cout << "rows affected = " << rs.rows_affected() << "\n"; - cout << "--- select data - example 3 ---\n\n"; + cout << "===== done...\n\n"; /******************************************************************** @@ -518,10 +729,11 @@ int main(int argc, char** argv) */ + cout << "===== using prepared select statements - repeated execution setting new values\n"; /* * Prepared SQL statements for select */ - stmt.prepare("select id, txt from tempdb..test where id = ? or txt = ?"); + stmt.prepare("select id, txt from test where id = ? or txt = ?"); for (int i = 0; i < 6; ++i) { cout << "--------------\n"; @@ -542,112 +754,230 @@ int main(int argc, char** argv) cout << "no data\n"; cout << "rows affected = " << rs.rows_affected() << "\n"; } - cout << "--- prepared stmt select 1 ---\n\n"; - - stmt.prepare("select min(id) from tempdb..test where txt = ?"); - cout << "--------------\n"; - stmt.set_string(0, "hello2"); - rs = stmt.execute(); - if (rs.has_data()) - { - while (rs.next()) - { - cout << "\trow " << rs.row_count() << ":\n"; - cout << "\t" << rs.column_name(0) << ": >" << (rs.is_null(0) ? -1 : rs.get_int(0)) << "<\n"; - } - cout << "done\n"; - } - else - cout << "no data\n"; - cout << "rows affected = " << rs.rows_affected() << "\n"; - cout << "--- prepared stmt select 2 ---\n\n"; - + cout << "===== done...\n\n"; + + cout << "===== using prepared update statements - repeated execution setting new values (no affected row count is available)\n"; /* * Prepared SQL statements for update * - * NOTE!!! weirdly the Sybase does not return row count for prepared + * NOTE!!! weirdly the Sybase does not return affected row count for prepared * update SQL statements */ - stmt.prepare("update tempdb..test set txt = ?, dt = ? where id = ?"); + stmt.prepare("update test set txt = ?, dt = ? where id = ?"); for (int i = 4; i < 6; ++i) { cout << "--------------\n"; stmt.set_null(0); - stmt.set_ldouble(1, 12345.123456); + stmt.set_double(1, 12345.123456); stmt.set_int(2, i); rs = stmt.execute(); cout << "rows affected = " << rs.rows_affected() << "\n"; } - cout << "--- prepared stmt update - example 1 ---\n\n"; - - stmt.prepare("update tempdb..test set txt = ? where id = ?"); - for (int i = 5; i < 7; ++i) - { - cout << "--------------\n"; - string txt("aaqqq"); - txt.append(std::to_string(i)); - stmt.set_string(0, txt); - stmt.set_int(1, i); - rs = stmt.execute(); - cout << "rows affected = " << rs.rows_affected() << "\n"; - } - cout << "--- prepared stmt update - example 2 ---\n\n"; + cout << "===== done...\n\n"; + cout << "===== using prepared delete statements - repeated execution setting new values (no affected row count is available)\n"; /* * Prepared SQL statements for delete * * NOTE!!! weirdly the Sybase does not return row count for prepared * delete SQL statements */ - stmt.prepare("delete from tempdb..test where id = ?"); + stmt.prepare("delete from test where id = ?"); cout << "--------------\n"; stmt.set_int(0, 4); rs = stmt.execute(); cout << "rows affected = " << rs.rows_affected() << "\n"; - cout << "--- prepared stmt delete - example ---\n\n"; + cout << "===== done...\n\n"; + + + /******************************************************************** + * Use SQL stored procedures + */ + try + { + stmt.execute("drop procedure test_proc"); + } + catch (const exception&) + { + // ignore exception if procedure doesn't exist + } + cout << "===== creating stored procedure\n"; + stmt.execute("CREATE PROCEDURE test_proc @id INT, @error VARCHAR(128) output, @status VARCHAR(20) output AS \ + BEGIN \ + DECLARE @ret int \ + SET @error = NULL \ + SET @ret = 0 \ + IF @id IS NULL \ + BEGIN \ + SET @error = 'You need to pass in an id number' \ + SET @status = 'Ooops' \ + SET @id = -2222222 \ + SET @ret = 1 \ + END \ + ELSE IF @id = 5 \ + BEGIN \ + SET @error = 'ID 5 is not allowed' \ + SET @status = 'Exception' \ + SET @id = -2222222 \ + SET @ret = 2 \ + RAISERROR 90001 @error \ + END \ + ELSE \ + SET @status = 'OK' \ + PRINT @status \ + SELECT txt FROM test WHERE id = @id \ + RETURN @ret \ + END"); + cout << "===== done...\n\n"; + + + cout << "===== using stored procedure\n"; + stmt.call("test_proc"); + stmt.set_int(0, 3); + cout << "--------------\n"; + rs = stmt.execute(); + while (rs.next()) + { + cout << "\trow " << rs.row_count() << ":\n"; + cout << "\t" << rs.column_name(0) << ": >" << (rs.is_null(0) ? "NULL" : rs.get_string(0)) << "<\n"; + } + cout << "rows affected = " << rs.rows_affected() << "\n"; + cout << "stored proc return = " << stmt.proc_retval() << "\n"; + cout << "@error: >" << (rs.is_null(0) ? "NULL" : rs.get_string("@error")) << "<\n"; + cout << "@status: >" << (rs.is_null(1) ? "NULL" : rs.get_string("@status")) << "<\n"; + + stmt.set_null(0); + cout << "--------------\n"; + rs = stmt.execute(); + while (rs.next()) + { + cout << "\trow " << rs.row_count() << ":\n"; + cout << "\t" << rs.column_name(0) << ": >" << (rs.is_null(0) ? "NULL" : rs.get_string(0)) << "<\n"; + } + cout << "rows affected = " << rs.rows_affected() << "\n"; + cout << "stored proc return = " << stmt.proc_retval() << "\n"; + cout << "@error: >" << (rs.is_null(0) ? "NULL" : rs.get_string("@error")) << "<\n"; + cout << "@status: >" << (rs.is_null(1) ? "NULL" : rs.get_string("@status")) << "<\n"; + stmt.set_int(0, 5); + cout << "--------------\n"; + rs = stmt.execute(); + while (rs.next()) + { + cout << "\trow " << rs.row_count() << ":\n"; + cout << "\t" << rs.column_name(0) << ": >" << (rs.is_null(0) ? "NULL" : rs.get_string(0)) << "<\n"; + } + cout << "rows affected = " << rs.rows_affected() << "\n"; + cout << "stored proc return = " << stmt.proc_retval() << "\n"; + cout << "@error: >" << (rs.is_null(0) ? "NULL" : rs.get_string("@error")) << "<\n"; + cout << "@status: >" << (rs.is_null(1) ? "NULL" : rs.get_string("@status")) << "<\n"; + cout << "===== done...\n\n"; + cout << "===== using delete with number of affected rows\n"; /******************************************************************** * Use delete SQL statement. */ - rs = stmt.execute("delete from tempdb..test where id = 1 or id = 2"); + rs = stmt.execute("delete from test where id = 1 or id = 2"); cout << "delete : rows affected = " << rs.rows_affected() << "\n"; - cout << "--- deleted data ---\n\n"; - - + cout << "===== done...\n\n"; + cout << "===== using drop with number of affected rows\n"; /******************************************************************** * Use drop table SQL statement. */ - rs = stmt.execute("drop table tempdb..test"); + rs = stmt.execute("drop table test"); cout << "drop : rows affected = " << rs.rows_affected() << "\n"; - cout << "--- dropped table ---\n\n"; - + cout << "===== done...\n\n"; + cout << "===== using explicit commit\n"; /******************************************************************** * Use autocommit OFF mode */ - stmt.execute("create table tempdb..test (id int, txt varchar(10) NULL, primary key(id) )"); + stmt.execute("create table test (id int, txt varchar(10) NULL, primary key(id) )"); conn.autocommit(false); cout << "autocommit is OFF\n"; - cout << "insert and commit 1 row\n"; - stmt.execute("insert into tempdb..test values (1, 'test1')"); + cout << "insert 1 row\n"; + stmt.execute("insert into test values (1, 'test1')"); + cout << "run commit\n"; conn.commit(); - cout << "insert and rollback 2 rows\n"; - stmt.execute("insert into tempdb..test values (2, 'test2')"); - stmt.execute("insert into tempdb..test values (3, 'test3')"); + cout << "insert 2 rows\n"; + stmt.execute("insert into test values (2, 'test2')"); + stmt.execute("insert into test values (3, 'test3')"); + cout << "run rollback\n"; conn.rollback(); conn.autocommit(true); cout << "autocommit is ON\n"; cout << "check row count to make sure rollback worked\n"; - rs = stmt.execute( "select count(*) from tempdb..test"); + rs = stmt.execute( "select count(*) from test"); rs.next(); cout << "\tTable has >" << rs.get_int(0) << "< rows\n"; - stmt.execute("drop table tempdb..test"); + cout << "===== done...\n\n"; + + + /******************************************************************** + * Use cursors for SELECT statements + */ + stmt.execute("insert into test values (2, 'test2')"); + stmt.execute("insert into test values (3, 'test3')"); + stmt.execute("insert into test values (4, 'test4')"); + stmt.execute("insert into test values (5, 'test5')"); + + cout << "===== using cursor for select statements - read only cursor (note that rows affected is not returned by sybase)\n"; + rs = stmt.execute("select id, txt from test", true); + while (rs.next()) + { + cout << "\trow " << rs.row_count() << ":\n"; + cout << "\t" << rs.column_name(0) << ": >" << (rs.is_null(0) ? -1 : rs.get_int(0)) << "<\n"; + cout << "\t" << rs.column_name(1) << ": >" << (rs.is_null(1) ? "NULL" : rs.get_string(1)) << "<\n"; + } + cout << "done\n"; + cout << "rows affected = " << rs.rows_affected() << "\n"; + cout << "===== done...\n\n"; + + cout << "===== using cursor for select statements - scrollable cursor (note that rows affected is not returned by sybase)\n"; + rs = stmt.execute("select id, txt from test", true, true); + while (rs.next()) + { + cout << "row " << rs.row_count() << ":\n"; + cout << "\t" << rs.column_name(0) << ": >" << (rs.is_null(0) ? -1 : rs.get_int(0)) << "<\n"; + cout << "\t" << rs.column_name(1) << ": >" << (rs.is_null(1) ? "NULL" : rs.get_string(1)) << "<\n"; + } + + cout << "-------------- rewinding the cursor to the start\n"; + rs.first(); + do + { + cout << "row " << rs.row_count() << ":\n"; + cout << "\t" << rs.column_name(0) << ": >" << (rs.is_null(0) ? -1 : rs.get_int(0)) << "<\n"; + cout << "\t" << rs.column_name(1) << ": >" << (rs.is_null(1) ? "NULL" : rs.get_string(1)) << "<\n"; + } + while (rs.next()); + + cout << "-------------- iterating from end to start\n"; + while (rs.prev()) + { + cout << "row " << rs.row_count() << ":\n"; + cout << "\t" << rs.column_name(0) << ": >" << (rs.is_null(0) ? -1 : rs.get_int(0)) << "<\n"; + cout << "\t" << rs.column_name(1) << ": >" << (rs.is_null(1) ? "NULL" : rs.get_string(1)) << "<\n"; + } + + cout << "-------------- rewinding the cursor to the end - resets row count and starts increasing the count\n"; + rs.last(); + do + { + cout << "row " << rs.row_count() << ":\n"; + cout << "\t" << rs.column_name(0) << ": >" << (rs.is_null(0) ? -1 : rs.get_int(0)) << "<\n"; + cout << "\t" << rs.column_name(1) << ": >" << (rs.is_null(1) ? "NULL" : rs.get_string(1)) << "<\n"; + } + while (rs.prev()); + cout << "rows affected = " << rs.rows_affected() << "\n"; + cout << "===== done...\n\n"; + + stmt.execute("drop table test"); } else cout << "failed to connect!\n";