diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fda71e4cd..8f3ce3de5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -69,13 +69,18 @@ include(SociDependencies) if(EXISTS /etc/os-release) file(READ /etc/os-release OS_RELEASE) - string(REGEX MATCH "ID(_LIKE?)=debian" DEBIAN ${OS_RELEASE}) - string(REGEX MATCH "ID(_LIKE?)=fedora" FEDORA ${OS_RELEASE}) - string(REGEX MATCH "ID(_LIKE?)=ubuntu" UBUNTU ${OS_RELEASE}) + string(REGEX MATCH "ID(_LIKE)?=debian" DEBIAN ${OS_RELEASE}) + string(REGEX MATCH "ID(_LIKE)?=fedora" FEDORA ${OS_RELEASE}) + string(REGEX MATCH "ID(_LIKE)?=ubuntu" UBUNTU ${OS_RELEASE}) +elseif(EXISTS /etc/lsb-release) + file(READ /etc/lsb-release OS_RELEASE) + string(REGEX MATCH "DISTRIB_ID=[Uu]buntu" UBUNTU ${OS_RELEASE}) + string(REGEX MATCH "DISTRIB_ID=[Db]ebian" DEBIAN ${OS_RELEASE}) endif() - -if(DEBIAN) +if(UBUNTU) + set(SOCI_LIBDIR "lib") +elseif(DEBIAN) execute_process(COMMAND uname -m OUTPUT_VARIABLE SOCI_ARCH OUTPUT_STRIP_TRAILING_WHITESPACE) diff --git a/src/backends/firebird/CMakeLists.txt b/src/backends/firebird/CMakeLists.txt new file mode 100644 index 000000000..9af2acd42 --- /dev/null +++ b/src/backends/firebird/CMakeLists.txt @@ -0,0 +1,9 @@ +soci_backend(Firebird + DEPENDS Firebird + HEADERS soci-firebird.h common.h + DESCRIPTION "SOCI backend for Firebird database engine" + AUTHORS "TBD" + MAINTAINERS "TBD") + +add_subdirectory(test) + diff --git a/src/backends/firebird/common.cpp b/src/backends/firebird/common.cpp index a5549244a..5a871c221 100644 --- a/src/backends/firebird/common.cpp +++ b/src/backends/firebird/common.cpp @@ -10,7 +10,9 @@ #include // FireBird #include #include +#include #include +#include #include namespace soci @@ -82,6 +84,7 @@ void tmDecode(short type, void * src, std::tm * dst) void setTextParam(char const * s, std::size_t size, char * buf_, XSQLVAR * var) { + std::cerr << "setTextParam: var->sqltype=" << var->sqltype << std::endl; short sz = 0; if (size < static_cast(var->sqllen)) { @@ -94,17 +97,96 @@ void setTextParam(char const * s, std::size_t size, char * buf_, if ((var->sqltype & ~1) == SQL_VARYING) { - memcpy(buf_, &sz, sizeof(short)); - memcpy(buf_ + sizeof(short), s, sz); + std::memcpy(buf_, &sz, sizeof(short)); + std::memcpy(buf_ + sizeof(short), s, sz); } else if ((var->sqltype & ~1) == SQL_TEXT) { - memcpy(buf_, s, sz); + std::memcpy(buf_, s, sz); if (sz < var->sqllen) { - memset(buf_+sz, ' ', var->sqllen - sz); + std::memset(buf_+sz, ' ', var->sqllen - sz); } } + else if ((var->sqltype & ~1) == SQL_SHORT) + { + unsigned short t1; + short t2; + if (!*str2int(s, t1)) + std::memcpy(buf_, &t1, sizeof(t1)); + else if (!*str2int(s, t2)) + std::memcpy(buf_, &t2, sizeof(t2)); + else + throw soci_error("Could not parse int16 value."); + to_isc(buf_, var); + } + else if ((var->sqltype & ~1) == SQL_LONG) + { + unsigned t1; + int t2; + if (!*str2int(s, t1)) + std::memcpy(buf_, &t1, sizeof(t1)); + else if (!*str2int(s, t2)) + std::memcpy(buf_, &t2, sizeof(t2)); + else + throw soci_error("Could not parse int32 value."); + to_isc(buf_, var); + } + else if ((var->sqltype & ~1) == SQL_INT64) + { + unsigned long long t1; + long long t2; + if (!*str2int(s, t1)) + std::memcpy(buf_, &t1, sizeof(t1)); + else if (!*str2int(s, t2)) + std::memcpy(buf_, &t2, sizeof(t2)); + else + throw soci_error("Could not parse int64 value."); + to_isc(buf_, var); + } + else if ((var->sqltype & ~1) == SQL_TIMESTAMP + or (var->sqltype & ~1) == SQL_TYPE_DATE) + { + unsigned short year, month, day, hour, min, sec; + if (std::sscanf(s, "%hu-%hu-%hu %hu:%hu:%hu", + &year, &month, &day, &hour, &min, &sec) != 6) + { + if (std::sscanf(s, "%hu-%hu-%huT%hu:%hu:%hu", + &year, &month, &day, &hour, &min, &sec) != 6) + { + hour = min = sec = 0; + if (std::sscanf(s, "%hu-%hu-%hu", &year, &month, &day) != 3) + { + throw soci_error("Could not parse timestamp value."); + } + } + } + std::tm t; + std::memset(&t, 0, sizeof(t)); + t.tm_year = year - 1900; + t.tm_mon = month - 1; + t.tm_mday = day; + t.tm_hour = hour; + t.tm_min = min; + t.tm_sec = sec; + std::memcpy(buf_, &t, sizeof(t)); + tmEncode(var->sqltype, &t, buf_); + } + else if ((var->sqltype & ~1) == SQL_TYPE_TIME) + { + unsigned short hour, min, sec; + if (std::sscanf(s, "%hu:%hu:%hu", &hour, &min, &sec) != 3) + { + throw soci_error("Could not parse timestamp value."); + } + std::tm t; + std::memset(&t, 0, sizeof(t)); + t.tm_hour = hour; + t.tm_min = min; + t.tm_sec = sec; + std::memcpy(buf_, &t, sizeof(t)); + tmEncode(var->sqltype, &t, buf_); + } else { throw soci_error("Unexpected string type."); @@ -113,6 +195,7 @@ void setTextParam(char const * s, std::size_t size, char * buf_, std::string getTextParam(XSQLVAR const *var) { + std::cerr << "getTextParam: var->sqltype=" << var->sqltype << std::endl; short size; std::size_t offset = 0; diff --git a/src/backends/firebird/common.h b/src/backends/firebird/common.h index 37e4bdd61..e6c12d714 100644 --- a/src/backends/firebird/common.h +++ b/src/backends/firebird/common.h @@ -37,6 +37,38 @@ void setTextParam(char const * s, std::size_t size, char * buf_, std::string getTextParam(XSQLVAR const *var); +template +const char *str2int(const char * s, IntType &out) +{ + int sign = 1; + if ('+' == *s) + ++s; + else if ('-' == *s) + { + sign = -1; + ++s; + } + IntType res = 0; + for (out = 0; *s; ++s, out = res) + { + int d = *s - '0'; + if (d < 0 || d > 9) + return s; + res = res * 10 + d * sign; + if (1 == sign) + { + if (res < out) + return s; + } + else + { + if (res > out) + return s; + } + } + return s; +} + template void to_isc(void * val, XSQLVAR * var) { @@ -66,21 +98,27 @@ void to_isc(void * val, XSQLVAR * var) break; case SQL_LONG: { - long tmp = static_cast(value*tens); - memcpy(var->sqldata, &tmp, sizeof(long)); + int tmp = static_cast(value*tens); + std::memcpy(var->sqldata, &tmp, sizeof(int)); } break; case SQL_INT64: { - ISC_INT64 tmp = static_cast(value*tens); - memcpy(var->sqldata, &tmp, sizeof(ISC_INT64)); + long long tmp = static_cast(value*tens); + std::memcpy(var->sqldata, &tmp, sizeof(long long)); } break; case SQL_FLOAT: - memcpy(var->sqldata, &value, sizeof(float)); + { + float sql_value = static_cast(value); + std::memcpy(var->sqldata, &sql_value, sizeof(float)); + } break; case SQL_DOUBLE: - memcpy(var->sqldata, &value, sizeof(double)); + { + double sql_value = static_cast(value); + std::memcpy(var->sqldata, &sql_value, sizeof(double)); + } break; default: throw soci_error("Incorrect data type for numeric conversion"); @@ -114,9 +152,9 @@ T1 from_isc(XSQLVAR * var) case SQL_SHORT: return static_cast(*reinterpret_cast(var->sqldata)/tens); case SQL_LONG: - return static_cast(*reinterpret_cast(var->sqldata)/tens); + return static_cast(*reinterpret_cast(var->sqldata)/tens); case SQL_INT64: - return static_cast(*reinterpret_cast(var->sqldata)/tens); + return static_cast(*reinterpret_cast(var->sqldata)/tens); case SQL_FLOAT: return static_cast(*reinterpret_cast(var->sqldata)); case SQL_DOUBLE: diff --git a/src/backends/firebird/soci-firebird.h b/src/backends/firebird/soci-firebird.h index cb8332050..04acf8faf 100644 --- a/src/backends/firebird/soci-firebird.h +++ b/src/backends/firebird/soci-firebird.h @@ -209,6 +209,7 @@ struct firebird_statement_backend : details::statement_backend protected: int rowsFetched_; + bool endOfRowSet_; virtual void exchangeData(bool gotData, int row); virtual void prepareSQLDA(XSQLDA ** sqldap, int size = 10); diff --git a/src/backends/firebird/standard-use-type.cpp b/src/backends/firebird/standard-use-type.cpp index 1511e791e..282e29877 100644 --- a/src/backends/firebird/standard-use-type.cpp +++ b/src/backends/firebird/standard-use-type.cpp @@ -77,6 +77,7 @@ void firebird_standard_use_type_backend::bind_by_name( void firebird_standard_use_type_backend::pre_use(indicator const * ind) { + indISCHolder_ = 0; if (ind) { switch (*ind) @@ -97,6 +98,9 @@ void firebird_standard_use_type_backend::exchangeData() { XSQLVAR *var = statement_.sqlda2p_->sqlvar+position_; + if (0 != indISCHolder_) + return; + switch (type_) { case x_char: diff --git a/src/backends/firebird/statement.cpp b/src/backends/firebird/statement.cpp index c3d9e731e..13ed0ac19 100644 --- a/src/backends/firebird/statement.cpp +++ b/src/backends/firebird/statement.cpp @@ -10,6 +10,7 @@ #include "error-firebird.h" #include #include +#include using namespace soci; using namespace soci::details; @@ -17,7 +18,7 @@ using namespace soci::details::firebird; firebird_statement_backend::firebird_statement_backend(firebird_session_backend &session) : session_(session), stmtp_(0), sqldap_(NULL), sqlda2p_(NULL), - boundByName_(false), boundByPos_(false), rowsFetched_(0), + boundByName_(false), boundByPos_(false), rowsFetched_(0), endOfRowSet_(false), intoType_(eStandard), useType_(eStandard), procedure_(false) {} @@ -286,6 +287,7 @@ void firebird_statement_backend::rewriteQuery( void firebird_statement_backend::prepare(std::string const & query, statement_type /* eType */) { + std::cerr << "prepare: query=" << query << std::endl; // clear named parametes names_.clear(); @@ -460,6 +462,9 @@ firebird_statement_backend::execute(int number) statement_backend::exec_fetch_result firebird_statement_backend::fetch(int number) { + if (endOfRowSet_) + return ef_no_data; + ISC_STATUS stat[stat_size]; for (size_t i = 0; i(sqldap_->sqld); ++i) @@ -483,11 +488,13 @@ firebird_statement_backend::fetch(int number) } else if (fetch_stat == 100L) { + endOfRowSet_ = true; return ef_no_data; } else { // error + endOfRowSet_ = true; throw_iscerror(stat); return ef_no_data; // unreachable, for compiler only } diff --git a/src/backends/firebird/test/CMakeLists.txt b/src/backends/firebird/test/CMakeLists.txt new file mode 100644 index 000000000..d01a60a13 --- /dev/null +++ b/src/backends/firebird/test/CMakeLists.txt @@ -0,0 +1,5 @@ +soci_backend_test( + BACKEND Firebird + SOURCE test-firebird.cpp + CONNSTR "dummy") + diff --git a/src/backends/firebird/test/test-firebird.cpp b/src/backends/firebird/test/test-firebird.cpp index 1283926e8..2542a1913 100644 --- a/src/backends/firebird/test/test-firebird.cpp +++ b/src/backends/firebird/test/test-firebird.cpp @@ -14,6 +14,7 @@ #include #include #include +#include using namespace soci; @@ -86,6 +87,7 @@ void test2() sql << "delete from test2"; } +#if 0 // SOCI doesn't support binding into(char *, ...) anymore, use std::string { char msg[] = "Hello, Firebird!"; char buf1[100], buf2[100], buf3[100]; @@ -96,18 +98,7 @@ void test2() sql << "insert into test2(p1, p2) values (?,?)", use(b1, 100), use(b1, 100); sql << "select p1, p2 from test2", into(b2, 100), into(b3, 100); - assert( - buf2[0] == 'H' && buf3[0] == 'H' && - buf2[1] == 'e' && buf3[1] == 'e' && - buf2[2] == 'l' && buf3[2] == 'l' && - buf2[3] == 'l' && buf3[3] == 'l' && - buf2[4] == 'o' && buf3[4] == 'o' && - buf2[5] == ',' && buf3[5] == ',' && - buf2[6] == ' ' && buf3[6] == ' ' && - buf2[7] == 'F' && buf3[7] == 'F' && - buf2[8] == 'i' && buf3[8] == 'i' && - buf2[9] == 'r' && buf3[9] == 'r' && - buf2[10] == '\0' && buf3[10] == '\0'); + assert(!std::strcmp(buf2, buf3) && !std::strcmp(buf2, "Hello, Fir")); sql << "delete from test2"; } @@ -121,18 +112,19 @@ void test2() use(buf1), use(buf1); sql << "select p1, p2 from test2", into(buf2), into(buf3); - assert( - buf2[0] == 'H' && buf3[0] == 'H' && - buf2[1] == 'e' && buf3[1] == 'e' && - buf2[2] == 'l' && buf3[2] == 'l' && - buf2[3] == 'l' && buf3[3] == 'l' && - buf2[4] == 'o' && buf3[4] == 'o' && - buf2[5] == ',' && buf3[5] == ',' && - buf2[6] == ' ' && buf3[6] == ' ' && - buf2[7] == 'F' && buf3[7] == 'F' && - buf2[8] == 'i' && buf3[8] == 'i' && - buf2[9] == 'r' && buf3[9] == 'r' && - buf2[10] == '\0' && buf3[10] == '\0'); + assert(!std::strcmp(buf2, buf3) && !std::strcmp(buf2, "Hello, Fir")); + + sql << "delete from test2"; + } +#endif + + { + std::string b1("Hello, Firebird!"), b2, b3; + + sql << "insert into test2(p1, p2) values (?,?)", use(b1), use(b1); + sql << "select p1, p2 from test2", into(b2), into(b3); + + assert(b2 == b3 && b2 == "Hello, Fir"); sql << "delete from test2"; } @@ -144,7 +136,9 @@ void test2() sql << "insert into test2(p1) values(\'" << msg << "\')"; char buf[20]; - sql << "select p1 from test2", into(buf); + std::string buf_str; + sql << "select p1 from test2", into(buf_str); + std::strcpy(buf, buf_str.c_str()); assert(std::strncmp(buf, msg, 5) == 0); assert(std::strncmp(buf+5, " ", 5) == 0); @@ -321,9 +315,11 @@ void test5() sql << "select NULL from rdb$database", into(i, ind); assert(ind == i_null); +#if 0 // SOCI doesn't support binding into(char *, ...) anymore, use std::string char buf[4]; sql << "select \'Hello\' from rdb$database", into(buf, ind); assert(ind == i_truncated); +#endif sql << "select 5 from rdb$database where 0 = 1", into(i, ind); assert(sql.got_data() == false); @@ -341,18 +337,9 @@ void test5() "Null value fetched and no indicator defined."); } - try - { - // expect error - sql << "select 5 from rdb$database where 0 = 1", into(i); - assert(false); - } - catch (soci_error const &e) - { - std::string error = e.what(); - assert(error == - "No data fetched and no indicator defined."); - } + // expect no data + sql << "select 5 from rdb$database where 0 = 1", into(i); + assert(!sql.got_data()); } std::cout << "test 5 passed" << std::endl; @@ -1069,9 +1056,9 @@ void test11() // Support for soci Common Tests // -struct table_creator_1 : public tests::table_creator_base +struct TableCreator1 : public tests::table_creator_base { - table_creator_1(session & sql) + TableCreator1(session & sql) : tests::table_creator_base(sql) { sql << "create table soci_test(id integer, val integer, c char, " @@ -1082,28 +1069,28 @@ struct table_creator_1 : public tests::table_creator_base } }; -struct table_creator_2 : public tests::table_creator_base +struct TableCreator2 : public tests::table_creator_base { - table_creator_2(session & sql) + TableCreator2(session & sql) : tests::table_creator_base(sql) { - sql << "create table soci_test(\"num_float\" float, \"num_int\" integer, " - "\"name\" varchar(20), \"sometime\" timestamp, \"chr\" char)"; + sql << "create table soci_test(num_float float, num_int integer, " + "name varchar(20), sometime timestamp, chr char)"; sql.commit(); sql.begin(); } }; -struct table_creator_3 : public tests::table_creator_base +struct TableCreator3 : public tests::table_creator_base { - table_creator_3(session & sql) + TableCreator3(session & sql) : tests::table_creator_base(sql) { // CommonTest uses lower-case column names, // so we need to enforce such names here. // That's why column names are enclosed in "" - sql << "create table soci_test(\"name\" varchar(100) not null, " - "\"phone\" varchar(15))"; + sql << "create table soci_test(name varchar(100) not null, " + "phone varchar(15))"; sql.commit(); sql.begin(); } diff --git a/src/backends/firebird/vector-into-type.cpp b/src/backends/firebird/vector-into-type.cpp index 6f2442480..f53457247 100644 --- a/src/backends/firebird/vector-into-type.cpp +++ b/src/backends/firebird/vector-into-type.cpp @@ -112,7 +112,7 @@ void firebird_vector_into_type_backend::post_fetch( // buffers during fetch() if (gotData) { - std::size_t rows = statement_.inds_[0].size(); + std::size_t rows = statement_.rowsFetched_; for (std::size_t i = 0; i