Warning, file /sdk/codevis/thirdparty/soci/tests/firebird/test-firebird.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 //
0002 // Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, Rafal Bobrowski
0003 // Distributed under the Boost Software License, Version 1.0.
0004 // (See accompanying file LICENSE_1_0.txt or copy at
0005 // http://www.boost.org/LICENSE_1_0.txt)
0006 //
0007 //
0008 
0009 #include "soci/soci.h"
0010 #include "soci/firebird/soci-firebird.h"
0011 #include "soci-compiler.h"
0012 #include "firebird/error-firebird.h"            // soci::details::Firebird::throw_iscerror()
0013 #include "firebird/common.h"
0014 #include "common-tests.h"
0015 #include <iostream>
0016 #include <string>
0017 #include <ctime>
0018 #include <cstring>
0019 #include <cmath>
0020 
0021 using namespace soci;
0022 
0023 std::string connectString;
0024 soci::backend_factory const &backEnd = *factory_firebird();
0025 
0026 // fundamental tests - transactions in Firebird
0027 TEST_CASE("Firebird transactions", "[firebird][transaction]")
0028 {
0029     soci::session sql(backEnd, connectString);
0030 
0031     // In Firebird transaction is always required and is started
0032     // automatically when session is opened. There is no need to
0033     // call session::begin(); it will do nothing if there is active
0034     // transaction.
0035 
0036     // sql.begin();
0037 
0038     try
0039     {
0040         sql << "drop table test1";
0041     }
0042     catch (soci_error const &)
0043     {} // ignore if error
0044 
0045     sql << "create table test1 (id integer)";
0046 
0047     // After DDL statement transaction must be commited or changes
0048     // won't be visible to active transaction.
0049     sql.commit();
0050 
0051     // After commit or rollback, transaction must be started manually.
0052     sql.begin();
0053 
0054     sql << "insert into test1(id) values(5)";
0055     sql << "drop table test1";
0056 
0057     // Transaction is automatically commited in session's destructor
0058 }
0059 
0060 // character types
0061 TEST_CASE("Firebird char types", "[firebird][string]")
0062 {
0063     soci::session sql(backEnd, connectString);
0064 
0065     try
0066     {
0067         sql << "drop table test2";
0068     }
0069     catch (soci_error const &)
0070     {} // ignore if error
0071 
0072     sql << "create table test2 (p1 char(10) character set none, p2 varchar(10) character set none)";
0073     sql.commit();
0074 
0075     sql.begin();
0076 
0077     {
0078         char a('a'), b('b'), c1, c2;
0079 
0080         sql << "insert into test2(p1,p2) values(?,?)", use(a), use(b);
0081 
0082         sql << "select p1,p2 from test2", into(c1), into(c2);
0083         CHECK(c1 == 'a');
0084         CHECK(c2 == 'b');
0085 
0086         sql << "delete from test2";
0087     }
0088 
0089 #if 0 // SOCI doesn't support binding into(char *, ...) anymore, use std::string
0090     {
0091         char msg[] = "Hello, Firebird!";
0092         char buf1[100], buf2[100], buf3[100];
0093         char *b1 = buf1, *b2 = buf2, *b3 = buf3;
0094 
0095         strcpy(b1, msg);
0096 
0097         sql << "insert into test2(p1, p2) values (?,?)", use(b1, 100), use(b1, 100);
0098         sql << "select p1, p2 from test2", into(b2, 100), into(b3, 100);
0099 
0100         CHECK(!std::strcmp(buf2, buf3));
0101         CHECK(!std::strcmp(buf2, "Hello, Fir"));
0102 
0103         sql << "delete from test2";
0104     }
0105 
0106     {
0107         char msg[] = "Hello, Firebird!";
0108         char buf1[100], buf2[100], buf3[100];
0109         strcpy(buf1, msg);
0110 
0111         sql << "insert into test2(p1, p2) values (?,?)",
0112         use(buf1), use(buf1);
0113         sql << "select p1, p2 from test2", into(buf2), into(buf3);
0114 
0115         CHECK(!std::strcmp(buf2, buf3));
0116         CHECK(!std::strcmp(buf2, "Hello, Fir"));
0117 
0118         sql << "delete from test2";
0119     }
0120 #endif
0121 
0122     {
0123         // The test string is exactly 10 bytes long, i.e. same as column length.
0124         std::string b1("Hello, FB!"), b2, b3;
0125 
0126         sql << "insert into test2(p1, p2) values (?,?)", use(b1), use(b1);
0127         sql << "select p1, p2 from test2", into(b2), into(b3);
0128 
0129         CHECK(b2 == b3);
0130         CHECK(b2 == "Hello, FB!");
0131 
0132         sql << "delete from test2";
0133     }
0134 
0135     {
0136         // verify blank padding in CHAR fields
0137         // In Firebird, CHAR fields are always padded with whitespaces.
0138         char msg[] = "Hello";
0139         sql << "insert into test2(p1) values(\'" << msg << "\')";
0140 
0141         char buf[20];
0142         std::string buf_str;
0143         sql << "select p1 from test2", into(buf_str);
0144         std::strcpy(buf, buf_str.c_str());
0145 
0146         CHECK(std::strncmp(buf, msg, 5) == 0);
0147         // This test works only for charset none
0148         CHECK(std::strncmp(buf+5, "     ", 5) == 0);
0149 
0150         sql << "delete from test2";
0151     }
0152 
0153     sql << "drop table test2";
0154 }
0155 
0156 // date and time
0157 TEST_CASE("Firebird date and time", "[firebird][datetime]")
0158 {
0159     soci::session sql(backEnd, connectString);
0160 
0161     try
0162     {
0163         sql << "drop table test3";
0164     }
0165     catch (soci_error const &)
0166     {} // ignore if error
0167 
0168     sql << "create table test3 (p1 timestamp, p2 date, p3 time)";
0169     sql.commit();
0170 
0171     sql.begin();
0172 
0173     std::tm t1 = std::tm();
0174     std::tm t2 = std::tm();
0175     std::tm t3 = std::tm();
0176     std::time_t now = std::time(NULL);
0177     std::tm t = *std::localtime(&now);
0178     sql << "insert into test3(p1, p2, p3) "
0179     << "values (?,?,?)", use(t), use(t), use(t);
0180 
0181     sql << "select p1, p2, p3 from test3", into(t1), into(t2), into(t3);
0182 
0183     // timestamp
0184     CHECK(t1.tm_year == t.tm_year);
0185     CHECK(t1.tm_mon  == t.tm_mon);
0186     CHECK(t1.tm_mday == t.tm_mday);
0187     CHECK(t1.tm_hour == t.tm_hour);
0188     CHECK(t1.tm_min  == t.tm_min);
0189     CHECK(t1.tm_sec  == t.tm_sec);
0190 
0191     // date
0192     CHECK(t2.tm_year == t.tm_year);
0193     CHECK(t2.tm_mon  == t.tm_mon);
0194     CHECK(t2.tm_mday == t.tm_mday);
0195     CHECK(t2.tm_hour == 0);
0196     CHECK(t2.tm_min  == 0);
0197     CHECK(t2.tm_sec  == 0);
0198 
0199     // time
0200     CHECK(t3.tm_year == 0);
0201     CHECK(t3.tm_mon  == 0);
0202     CHECK(t3.tm_mday == 0);
0203     CHECK(t3.tm_hour == t.tm_hour);
0204     CHECK(t3.tm_min  == t.tm_min);
0205     CHECK(t3.tm_sec  == t.tm_sec);
0206 
0207     sql << "drop table test3";
0208 }
0209 
0210 // floating points
0211 TEST_CASE("Firebird floating point", "[firebird][float]")
0212 {
0213     soci::session sql(backEnd, connectString);
0214 
0215     try
0216     {
0217         sql << "drop table test4";
0218     }
0219     catch (soci_error const &)
0220     {} // ignore if error
0221 
0222     sql << "create table test4 (p1 numeric(8,2), "
0223     << "p2 decimal(14,8), p3 double precision, p4 integer)";
0224     sql.commit();
0225 
0226     sql.begin();
0227 
0228     double d1 = 1234.23, d2 = 1e8, d3 = 1.0/1440.0,
0229                                         d4, d5, d6;
0230 
0231     sql << "insert into test4(p1, p2, p3) values (?,?,?)",
0232     use(d1), use(d2), use(d3);
0233 
0234     sql << "select p1, p2, p3 from test4",
0235     into(d4), into(d5), into(d6);
0236 
0237     // The doubles should make the round trip unchanged, so use the exact
0238     // comparisons here.
0239     CHECK(tests::are_doubles_exactly_equal(d1, d4));
0240     CHECK(tests::are_doubles_exactly_equal(d2, d5));
0241     CHECK(tests::are_doubles_exactly_equal(d3, d6));
0242 
0243     // test negative doubles too
0244     sql << "delete from test4";
0245     d1 = -d1;
0246     d2 = -d2;
0247     d3 = -d3;
0248 
0249     sql << "insert into test4(p1, p2, p3) values (?,?,?)",
0250     use(d1), use(d2), use(d3);
0251 
0252     sql << "select p1, p2, p3 from test4",
0253     into(d4), into(d5), into(d6);
0254 
0255     CHECK(tests::are_doubles_exactly_equal(d1, d4));
0256     CHECK(tests::are_doubles_exactly_equal(d2, d5));
0257     CHECK(tests::are_doubles_exactly_equal(d3, d6));
0258 
0259     // verify an exception is thrown when fetching non-integral value
0260     // to integral variable
0261     try
0262     {
0263         int i;
0264         sql << "select p1 from test4", into(i);
0265 
0266         // expecting error
0267         CHECK(false);
0268     }
0269     catch (soci_error const &e)
0270     {
0271         CHECK(e.get_error_message() ==
0272                "Can't convert value with scale 2 to integral type");
0273     }
0274 
0275     // verify an exception is thrown when inserting non-integral value
0276     // to integral column
0277     try
0278     {
0279         sql << "insert into test4(p4) values(?)", use(d1);
0280 
0281         // expecting error
0282         CHECK(false);
0283     }
0284     catch (soci_error const &e)
0285     {
0286         CHECK(e.get_error_message() ==
0287                "Can't convert non-integral value to integral column type");
0288     }
0289 
0290     sql << "drop table test4";
0291 }
0292 
0293 // integer types and indicators
0294 TEST_CASE("Firebird integers", "[firebird][int]")
0295 {
0296     soci::session sql(backEnd, connectString);
0297 
0298     {
0299         short sh(0);
0300         sql << "select 3 from rdb$database", into(sh);
0301         CHECK(sh == 3);
0302     }
0303 
0304     {
0305         int i(0);
0306         sql << "select 5 from rdb$database", into(i);
0307         CHECK(i == 5);
0308     }
0309 
0310     {
0311         unsigned long ul(0);
0312         sql << "select 7 from rdb$database", into(ul);
0313         CHECK(ul == 7);
0314     }
0315 
0316     {
0317         // test indicators
0318         indicator ind;
0319         int i;
0320 
0321         sql << "select 2 from rdb$database", into(i, ind);
0322         CHECK(ind == i_ok);
0323 
0324         sql << "select NULL from rdb$database", into(i, ind);
0325         CHECK(ind == i_null);
0326 
0327 #if 0   // SOCI doesn't support binding into(char *, ...) anymore, use std::string
0328         char buf[4];
0329         sql << "select \'Hello\' from rdb$database", into(buf, ind);
0330         CHECK(ind == i_truncated);
0331 #endif
0332 
0333         sql << "select 5 from rdb$database where 0 = 1", into(i, ind);
0334         CHECK(sql.got_data() == false);
0335 
0336         try
0337         {
0338             // expect error
0339             sql << "select NULL from rdb$database", into(i);
0340             CHECK(false);
0341         }
0342         catch (soci_error const &e)
0343         {
0344             CHECK(e.get_error_message() ==
0345                    "Null value fetched and no indicator defined.");
0346         }
0347 
0348         // expect no data
0349         sql << "select 5 from rdb$database where 0 = 1", into(i);
0350         CHECK(!sql.got_data());
0351     }
0352 }
0353 
0354 // repeated fetch and bulk operations for character types
0355 TEST_CASE("Firebird bulk operations", "[firebird][bulk]")
0356 {
0357     soci::session sql(backEnd, connectString);
0358 
0359     try
0360     {
0361         sql << "drop table test6";
0362     }
0363     catch (soci_error const &)
0364     {} // ignore if error
0365 
0366     sql << "create table test6 (p1 char(10) character set none, p2 varchar(10) character set none)";
0367     sql.commit();
0368 
0369     sql.begin();
0370 
0371     for (char c = 'a'; c <= 'z'; ++c)
0372     {
0373         sql << "insert into test6(p1, p2) values(?,?)", use(c), use(c);
0374     }
0375 
0376     {
0377         char c, c1, c2;
0378 
0379         statement st = (sql.prepare <<
0380                         "select p1,p2 from test6 order by p1", into(c1), into(c2));
0381 
0382         // Verify that fetch after re-executing the same statement works.
0383         for (int n = 0; n < 2; ++n)
0384         {
0385             st.execute();
0386 
0387             c='a';
0388             while (st.fetch())
0389             {
0390                 CHECK(c == c1);
0391                 CHECK(c == c2);
0392                 ++c;
0393             }
0394             CHECK(c == 'z'+1);
0395         }
0396     }
0397 
0398     {
0399         char c='a';
0400 
0401         std::vector<char> c1(10), c2(10);
0402 
0403         statement st = (sql.prepare <<
0404                         "select p1,p2 from test6 order by p1", into(c1), into(c2));
0405 
0406         st.execute();
0407         while (st.fetch())
0408         {
0409             for (std::size_t i = 0; i != c1.size(); ++i)
0410             {
0411                 CHECK(c == c1[i]);
0412                 CHECK(c == c2[i]);
0413                 ++c;
0414             }
0415         }
0416         CHECK(c == 'z' + 1);
0417     }
0418 
0419     {
0420         // verify an exception is thrown when empty vector is used
0421         std::vector<char> vec;
0422         try
0423         {
0424             sql << "select p1 from test6", into(vec);
0425             CHECK(false);
0426         }
0427         catch (soci_error const &e)
0428         {
0429             CHECK(e.get_error_message() ==
0430                 "Vectors of size 0 are not allowed.");
0431         }
0432     }
0433 
0434     sql << "delete from test6";
0435 
0436     // verifying std::string
0437     int const rowsToTest = 10;
0438     for (int i = 0; i != rowsToTest; ++i)
0439     {
0440         std::ostringstream ss;
0441         ss << "Hello_" << i;
0442 
0443         std::string const &x = ss.str();
0444 
0445         sql << "insert into test6(p1, p2) values(\'"
0446         << x << "\', \'" << x << "\')";
0447     }
0448 
0449     int count;
0450     sql << "select count(*) from test6", into(count);
0451     CHECK(count == rowsToTest);
0452 
0453     {
0454         int i = 0;
0455         std::string s1, s2;
0456         statement st = (sql.prepare <<
0457                         "select p1, p2 from test6 order by p1", into(s1), into(s2));
0458 
0459         st.execute();
0460         while (st.fetch())
0461         {
0462             std::ostringstream ss;
0463             ss << "Hello_" << i;
0464             std::string const &x = ss.str();
0465 
0466             // Note: CHAR fields are always padded with whitespaces
0467             ss << "   ";
0468             CHECK(s1 == ss.str());
0469             CHECK(s2 == x);
0470             ++i;
0471         }
0472         CHECK(i == rowsToTest);
0473     }
0474 
0475     {
0476         int i = 0;
0477 
0478         std::vector<std::string> s1(4), s2(4);
0479         statement st = (sql.prepare <<
0480                         "select p1, p2 from test6 order by p1", into(s1), into(s2));
0481         st.execute();
0482         while (st.fetch())
0483         {
0484             for (std::size_t j = 0; j != s1.size(); ++j)
0485             {
0486                 std::ostringstream ss;
0487                 ss << "Hello_" << i;
0488                 std::string const &x = ss.str();
0489 
0490                 // Note: CHAR fields are always padded with whitespaces
0491                 ss << "   ";
0492                 CHECK(ss.str() == s1[j]);
0493                 CHECK(x == s2[j]);
0494                 ++i;
0495             }
0496         }
0497         CHECK(i == rowsToTest);
0498     }
0499 
0500     sql << "drop table test6";
0501 }
0502 
0503 // blob test
0504 TEST_CASE("Firebird blobs", "[firebird][blob]")
0505 {
0506     soci::session sql(backEnd, connectString);
0507 
0508     try
0509     {
0510         sql << "drop table test7";
0511     }
0512     catch (std::runtime_error &)
0513     {} // ignore if error
0514 
0515     sql << "create table test7(id integer, img blob)";
0516     sql.commit();
0517 
0518     sql.begin();
0519     {
0520         // verify empty blob
0521         blob b(sql);
0522         indicator ind;
0523 
0524         sql << "insert into test7(id, img) values(1,?)", use(b);
0525         sql << "select img from test7 where id = 1", into(b, ind);
0526 
0527         CHECK(ind == i_ok);
0528         CHECK(b.get_len() == 0);
0529 
0530         sql << "delete from test7";
0531     }
0532 
0533     {
0534         // create a new blob
0535         blob b(sql);
0536 
0537         char str1[] = "Hello";
0538         b.write_from_start(str1, strlen(str1));
0539 
0540         char str2[20];
0541         std::size_t i = b.read_from_start(str2, 2, 3);
0542         str2[i] = '\0';
0543         CHECK(str2[0] == 'l');
0544         CHECK(str2[1] == 'o');
0545         CHECK(str2[2] == '\0');
0546 
0547         char str3[] = ", Firebird!";
0548         b.append(str3, strlen(str3));
0549 
0550         sql << "insert into test7(id, img) values(1,?)", use(b);
0551     }
0552 
0553     {
0554         // read & update blob
0555         blob b(sql);
0556 
0557         sql << "select img from test7 where id = 1", into(b);
0558 
0559         std::vector<char> text(b.get_len());
0560         b.read_from_start(&text[0], b.get_len());
0561         CHECK(strncmp(&text[0], "Hello, Firebird!", b.get_len()) == 0);
0562 
0563         char str1[] = "FIREBIRD";
0564         b.write_from_start(str1, strlen(str1), 7);
0565 
0566         // after modification blob must be written to database
0567         sql << "update test7 set img=? where id=1", use(b);
0568     }
0569 
0570     {
0571         // read blob from database, modify and write to another record
0572         blob b(sql);
0573 
0574         sql << "select img from test7 where id = 1", into(b);
0575 
0576         std::vector<char> text(b.get_len());
0577         b.read_from_start(&text[0], b.get_len());
0578 
0579         char str1[] = "HELLO";
0580         b.write_from_start(str1, strlen(str1));
0581 
0582         b.read_from_start(&text[0], b.get_len());
0583         CHECK(strncmp(&text[0], "HELLO, FIREBIRD!", b.get_len()) == 0);
0584 
0585         b.trim(5);
0586         sql << "insert into test7(id, img) values(2,?)", use(b);
0587     }
0588 
0589     {
0590         blob b(sql);
0591         statement st = (sql.prepare << "select img from test7", into(b));
0592 
0593         st.execute();
0594 
0595         st.fetch();
0596         std::vector<char> text(b.get_len());
0597         b.read_from_start(&text[0], b.get_len());
0598         CHECK(strncmp(&text[0], "Hello, FIREBIRD!", b.get_len()) == 0);
0599 
0600         st.fetch();
0601         text.resize(b.get_len());
0602         b.read_from_start(&text[0], b.get_len());
0603         CHECK(strncmp(&text[0], "HELLO", b.get_len()) == 0);
0604     }
0605 
0606     {
0607         // delete blob
0608         blob b(sql);
0609         indicator ind=i_null;
0610         sql << "update test7 set img=? where id = 1", use(b, ind);
0611 
0612         sql << "select img from test7 where id = 2", into(b, ind);
0613         CHECK(ind==i_ok);
0614 
0615         sql << "select img from test7 where id = 1", into(b, ind);
0616         CHECK(ind==i_null);
0617     }
0618 
0619     {
0620         //create large blob
0621         const int blobSize = 65536; //max segment size is 65535(unsigned short)
0622         std::vector<char> data(blobSize);
0623         blob b(sql);
0624         b.write_from_start(data.data(), blobSize);
0625         sql << "insert into test7(id, img) values(3,?)", use(b);
0626 
0627         //now read blob back from database and make sure it has correct content and size
0628         blob br(sql);
0629         sql << "select img from test7 where id = 3", into(br);
0630         std::vector<char> data2(br.get_len());
0631         if(br.get_len()>0)
0632             br.read_from_start(data2.data(), br.get_len());
0633         CHECK(data == data2);
0634     }
0635 
0636     sql << "drop table test7";
0637 }
0638 
0639 // named parameters
0640 TEST_CASE("Firebird named parameters", "[firebird][named-params]")
0641 {
0642     soci::session sql(backEnd, connectString);
0643 
0644     try
0645     {
0646         sql << "drop table test8";
0647     }
0648     catch (std::runtime_error &)
0649     {} // ignore if error
0650 
0651     sql << "create table test8(id1 integer, id2 integer)";
0652     sql.commit();
0653 
0654     sql.begin();
0655 
0656     int j = 13, k = 4, i, m;
0657     sql << "insert into test8(id1, id2) values(:id1, :id2)",
0658     use(k, "id2"), use(j, "id1");
0659     sql << "select id1, id2 from test8", into(i), into(m);
0660     CHECK(i == j);
0661     CHECK(m == k);
0662 
0663     sql << "delete from test8";
0664 
0665     std::vector<int> in1(3), in2(3);
0666     in1[0] = 3;
0667     in1[1] = 2;
0668     in1[2] = 1;
0669     in2[0] = 4;
0670     in2[1] = 5;
0671     in2[2] = 6;
0672 
0673     {
0674         statement st = (sql.prepare <<
0675                         "insert into test8(id1, id2) values(:id1, :id2)",
0676                         use(k, "id2"), use(j, "id1"));
0677 
0678         std::size_t s = in1.size();
0679         for (std::size_t x = 0; x < s; ++x)
0680         {
0681             j = in1[x];
0682             k = in2[x];
0683             st.execute();
0684         }
0685     }
0686 
0687     {
0688         statement st = (
0689             sql.prepare << "select id1, id2 from test8", into(i), into(m));
0690         st.execute();
0691 
0692         std::size_t x(0);
0693         while (st.fetch())
0694         {
0695             CHECK(i == in1[x]);
0696             CHECK(m == in2[x]);
0697             ++x;
0698         }
0699     }
0700 
0701     sql << "delete from test8";
0702 
0703     // test vectors
0704     sql << "insert into test8(id1, id2) values(:id1, :id2)",
0705     use(in1, "id1"), use(in2, "id2");
0706 
0707     std::vector<int> out1(3), out2(3);
0708 
0709     sql << "select id1, id2 from test8", into(out1), into(out2);
0710     std::size_t s = out1.size();
0711     CHECK(s == 3);
0712 
0713     for (std::size_t x = 0; x<s; ++x)
0714     {
0715         CHECK(out1[x] == in1[x]);
0716         CHECK(out2[x] == in2[x]);
0717     }
0718 
0719     sql << "drop table test8";
0720 }
0721 
0722 // Dynamic binding to row objects
0723 TEST_CASE("Firebird dynamic binding", "[firebird][dynamic]")
0724 {
0725     soci::session sql(backEnd, connectString);
0726 
0727     try
0728     {
0729         sql << "drop table test9";
0730     }
0731     catch (std::runtime_error &)
0732     {} // ignore if error
0733 
0734     sql << "create table test9(id integer, msg varchar(20), ntest numeric(10,2))";
0735     sql.commit();
0736 
0737     sql.begin();
0738 
0739     {
0740         row r;
0741         sql << "select * from test9", into(r);
0742         CHECK(sql.got_data() == false);
0743     }
0744 
0745     std::string msg("Hello");
0746     int i(1);
0747     double d(3.14);
0748     indicator ind(i_ok);
0749 
0750     {
0751         statement st((sql.prepare << "insert into test9(id, msg, ntest) "
0752                 << "values(:id,:msg,:ntest)",
0753                 use(i, "id"), use(msg, "msg"), use(d, ind, "ntest")));
0754 
0755         st.execute(1);
0756 
0757         i = 2;
0758         msg = "Firebird";
0759         ind = i_null;
0760         st.execute(1);
0761     }
0762 
0763     row r;
0764     statement st = (sql.prepare <<
0765                     "select * from test9", into(r));
0766     st.execute(1);
0767 
0768     CHECK(r.size() == 3);
0769 
0770     // get properties by position
0771     CHECK(r.get_properties(0).get_name() == "ID");
0772     CHECK(r.get_properties(1).get_name() == "MSG");
0773     CHECK(r.get_properties(2).get_name() == "NTEST");
0774 
0775     CHECK(r.get_properties(0).get_data_type() == dt_integer);
0776     CHECK(r.get_properties(1).get_data_type() == dt_string);
0777     CHECK(r.get_properties(2).get_data_type() == dt_double);
0778 
0779     // get properties by name
0780     CHECK(r.get_properties("ID").get_name() == "ID");
0781     CHECK(r.get_properties("MSG").get_name() == "MSG");
0782     CHECK(r.get_properties("NTEST").get_name() == "NTEST");
0783 
0784     CHECK(r.get_properties("ID").get_data_type() == dt_integer);
0785     CHECK(r.get_properties("MSG").get_data_type() == dt_string);
0786     CHECK(r.get_properties("NTEST").get_data_type() == dt_double);
0787 
0788     // get values by position
0789     CHECK(r.get<int>(0) == 1);
0790     CHECK(r.get<std::string>(1) == "Hello");
0791     CHECK(tests::are_doubles_exactly_equal(r.get<double>(2), d));
0792 
0793     // get values by name
0794     CHECK(r.get<int>("ID") == 1);
0795     CHECK(r.get<std::string>("MSG") == "Hello");
0796     CHECK(tests::are_doubles_exactly_equal(r.get<double>("NTEST"), d));
0797 
0798     st.fetch();
0799     CHECK(r.get<int>(0) == 2);
0800     CHECK(r.get<std::string>("MSG") == "Firebird");
0801     CHECK(r.get_indicator(2) == i_null);
0802 
0803     // verify default values
0804     CHECK(tests::are_doubles_exactly_equal(r.get<double>("NTEST", 2), 2));
0805 
0806     CHECK_THROWS_AS(r.get<double>("NTEST"), soci_error);
0807 
0808     // verify exception thrown on invalid get<>
0809     CHECK_THROWS_AS(r.get<std::string>(0), std::bad_cast);
0810 
0811     sql << "drop table test9";
0812 }
0813 
0814 // stored procedures
0815 TEST_CASE("Firebird stored procedures", "[firebird][procedure]")
0816 {
0817     soci::session sql(backEnd, connectString);
0818 
0819     try
0820     {
0821         sql << "drop procedure sp_test10";
0822     }
0823     catch (std::runtime_error &)
0824     {} // ignore if error
0825 
0826     try
0827     {
0828         sql << "drop procedure sp_test10a";
0829     }
0830     catch (std::runtime_error &)
0831     {} // ignore if error
0832 
0833     try
0834     {
0835         sql << "drop table test10";
0836     }
0837     catch (std::runtime_error &)
0838     {} // ignore if error
0839 
0840     sql << "create table test10(id integer, id2 integer)";
0841 
0842     sql << "create procedure sp_test10\n"
0843     << "returns (rid integer, rid2 integer)\n"
0844     << "as begin\n"
0845     << "for select id, id2 from test10 into rid, rid2 do begin\n"
0846     << "suspend;\n"
0847     << "end\n"
0848     << "end;\n";
0849 
0850     sql << "create procedure sp_test10a (pid integer, pid2 integer)\n"
0851     << "as begin\n"
0852     << "insert into test10(id, id2) values (:pid, :pid2);\n"
0853     << "end;\n";
0854 
0855     sql.commit();
0856 
0857     sql.begin();
0858 
0859     row r;
0860     int p1 = 3, p2 = 4;
0861 
0862     // calling procedures that do not return values requires
0863     // 'execute procedure ...' statement
0864     sql << "execute procedure sp_test10a ?, ?", use(p1), use(p2);
0865 
0866     // calling procedures that return values requires
0867     // 'select ... from ...' statement
0868     sql << "select * from sp_test10", into(r);
0869 
0870     CHECK(r.get<int>(0) == p1);
0871     CHECK(r.get<int>(1) == p2);
0872 
0873     sql << "delete from test10";
0874 
0875     p1 = 5;
0876     p2 = 6;
0877     {
0878         procedure proc = (
0879                              sql.prepare << "sp_test10a :p1, :p2",
0880                              use(p2, "p2"), use(p1, "p1"));
0881         proc.execute(1);
0882     }
0883 
0884     {
0885         row rw;
0886         procedure proc = (sql.prepare << "sp_test10", into(rw));
0887         proc.execute(1);
0888 
0889         CHECK(rw.get<int>(0) == p1);
0890         CHECK(rw.get<int>(1) == p2);
0891     }
0892 
0893     sql << "delete from test10";
0894 
0895     // test vectors
0896     std::vector<int> in1(3), in2(3);
0897     in1[0] = 3;
0898     in1[1] = 2;
0899     in1[2] = 1;
0900     in2[0] = 4;
0901     in2[1] = 5;
0902     in2[2] = 6;
0903 
0904     {
0905         procedure proc = (
0906                              sql.prepare << "sp_test10a :p1, :p2",
0907                              use(in2, "p2"), use(in1, "p1"));
0908         proc.execute(1);
0909     }
0910 
0911     {
0912         row rw;
0913         procedure proc = (sql.prepare << "sp_test10", into(rw));
0914 
0915         proc.execute(1);
0916         CHECK(rw.get<int>(0) == in1[0]);
0917         CHECK(rw.get<int>(1) == in2[0]);
0918         proc.fetch();
0919         CHECK(rw.get<int>(0) == in1[1]);
0920         CHECK(rw.get<int>(1) == in2[1]);
0921         proc.fetch();
0922         CHECK(rw.get<int>(0) == in1[2]);
0923         CHECK(rw.get<int>(1) == in2[2]);
0924         CHECK(proc.fetch() == false);
0925     }
0926 
0927     {
0928         std::vector<int> out1(3), out2(3);
0929         procedure proc = (sql.prepare << "sp_test10", into(out1), into(out2));
0930         proc.execute(1);
0931 
0932         std::size_t s = out1.size();
0933         CHECK(s == 3);
0934 
0935         for (std::size_t x = 0; x < s; ++x)
0936         {
0937             CHECK(out1[x] == in1[x]);
0938             CHECK(out2[x] == in2[x]);
0939         }
0940     }
0941 
0942     sql.rollback();
0943 
0944     sql.begin();
0945     sql << "drop procedure sp_test10";
0946     sql << "drop procedure sp_test10a";
0947     sql << "drop table test10";
0948 }
0949 
0950 // direct access to Firebird using handles exposed by
0951 // soci::FirebirdStatmentBackend
0952 namespace soci
0953 {
0954     enum eRowCountType
0955     {
0956         eRowsSelected = isc_info_req_select_count,
0957         eRowsInserted = isc_info_req_insert_count,
0958         eRowsUpdated  = isc_info_req_update_count,
0959         eRowsDeleted  = isc_info_req_delete_count
0960     };
0961 
0962     // Returns number of rows afected by last statement
0963     // or -1 if there is no such counter available.
0964     long getRowCount(soci::statement & statement, eRowCountType type)
0965     {
0966         ISC_STATUS stat[20];
0967         char cnt_req[2], cnt_info[128];
0968 
0969         cnt_req[0]=isc_info_sql_records;
0970         cnt_req[1]=isc_info_end;
0971 
0972         firebird_statement_backend* statementBackEnd
0973             = static_cast<firebird_statement_backend*>(statement.get_backend());
0974 
0975         // Note: This is very poorly documented function.
0976         // It can extract number of rows returned by select statement,
0977         // but it appears that this is only number of rows prefetched by
0978         // client library, not total number of selected rows.
0979         if (isc_dsql_sql_info(stat, &statementBackEnd->stmtp_, sizeof(cnt_req),
0980                               cnt_req, sizeof(cnt_info), cnt_info))
0981         {
0982             soci::details::firebird::throw_iscerror(stat);
0983         }
0984 
0985         long count = -1;
0986         char type_ = static_cast<char>(type);
0987         for (char *ptr = cnt_info + 3; *ptr != isc_info_end;)
0988         {
0989             char count_type = *ptr++;
0990             int m = isc_vax_integer(ptr, 2);
0991             ptr += 2;
0992             count = isc_vax_integer(ptr, static_cast<short>(m));
0993 
0994             if (count_type == type_)
0995             {
0996                 // this is requested number
0997                 break;
0998             }
0999             ptr += m;
1000         }
1001 
1002         return count;
1003     }
1004 
1005 } // namespace soci
1006 
1007 TEST_CASE("Firebird direct API use", "[firebird][native]")
1008 {
1009     soci::session sql(backEnd, connectString);
1010 
1011     try
1012     {
1013         sql << "drop table test11";
1014     }
1015     catch (std::runtime_error &)
1016     {} // ignore if error
1017 
1018     sql << "create table test11(id integer)";
1019     sql.commit();
1020 
1021     sql.begin();
1022 
1023     {
1024         std::vector<int> in(3);
1025         in[0] = 3;
1026         in[1] = 2;
1027         in[2] = 1;
1028 
1029         statement st = (sql.prepare << "insert into test11(id) values(?)",
1030                         use(in));
1031         st.execute(1);
1032 
1033         // Note: Firebird backend inserts every row with separate insert
1034         // statement to achieve the effect of inserting vectors of values.
1035         // Since getRowCount() returns number of rows affected by the *last*
1036         // statement, it will return 1 here.
1037         CHECK(getRowCount(st, eRowsInserted) == 1);
1038     }
1039 
1040     {
1041         int i = 5;
1042         statement st = (sql.prepare << "update test11 set id = ? where id<3",
1043                         use(i));
1044         st.execute(1);
1045         CHECK(getRowCount(st, eRowsUpdated) == 2);
1046 
1047         // verify that no rows were deleted
1048         CHECK(getRowCount(st, eRowsDeleted) == 0);
1049     }
1050 
1051     {
1052         std::vector<int> out(3);
1053         statement st = (sql.prepare << "select id from test11", into(out));
1054         st.execute(1);
1055 
1056         CHECK(getRowCount(st, eRowsSelected) == 3);
1057     }
1058 
1059     {
1060         statement st = (sql.prepare << "delete from test11 where id=10");
1061         st.execute(1);
1062         CHECK(getRowCount(st, eRowsDeleted) == 0);
1063     }
1064 
1065     {
1066         statement st = (sql.prepare << "delete from test11");
1067         st.execute(1);
1068         CHECK(getRowCount(st, eRowsDeleted) == 3);
1069     }
1070 
1071     sql << "drop table test11";
1072 }
1073 
1074 TEST_CASE("Firebird string coercions", "[firebird][string]")
1075 {
1076     soci::session sql(backEnd, connectString);
1077 
1078     try
1079     {
1080         sql << "drop table test12";
1081     }
1082     catch (std::runtime_error &)
1083     {} // ignore if error
1084 
1085     sql << "create table test12(a decimal(10,3), b timestamp, c date, d time)";
1086     sql.commit();
1087     sql.begin();
1088 
1089     // Check if passing input parameters as strings works
1090     // for different column types.
1091     {
1092         std::string a = "-3.14150", b = "2013-02-28 23:36:01",
1093             c = "2013-02-28", d = "23:36:01";
1094         statement st = (sql.prepare <<
1095                 "insert into test12(a, b, c, d) values (?, ?, ?, ?)",
1096                 use(a), use(b), use(c), use(d));
1097         st.execute(1);
1098         CHECK(getRowCount(st, eRowsInserted) == 1);
1099     }
1100 
1101     {
1102         double a;
1103         std::tm b = std::tm(), c = std::tm(), d = std::tm();
1104         sql << "select a, b, c, d from test12",
1105             into(a), into(b), into(c), into(d);
1106         CHECK(std::fabs(a - (-3.141)) < 0.000001);
1107         CHECK(b.tm_year == 2013 - 1900);
1108         CHECK(b.tm_mon == 2 - 1);
1109         CHECK(b.tm_mday == 28);
1110         CHECK(b.tm_hour == 23);
1111         CHECK(b.tm_min == 36);
1112         CHECK(b.tm_sec == 1);
1113         CHECK(c.tm_year == 2013 - 1900);
1114         CHECK(c.tm_mon == 2 - 1);
1115         CHECK(c.tm_mday == 28);
1116         CHECK(c.tm_hour == 0);
1117         CHECK(c.tm_min == 0);
1118         CHECK(c.tm_sec == 0);
1119         CHECK(d.tm_hour == 23);
1120         CHECK(d.tm_min == 36);
1121         CHECK(d.tm_sec == 1);
1122     }
1123 
1124     sql << "drop table test12";
1125 }
1126 
1127 // Dynamic binding to row objects: decimals_as_strings
1128 TEST_CASE("Firebird decimals as strings", "[firebird][decimal][string]")
1129 {
1130     using namespace soci::details::firebird;
1131 
1132     int a = -12345678;
1133     CHECK(format_decimal<int>(&a, 1) == "-123456780");
1134     CHECK(format_decimal<int>(&a, 0) == "-12345678");
1135     CHECK(format_decimal<int>(&a, -3) == "-12345.678");
1136     CHECK(format_decimal<int>(&a, -8) == "-0.12345678");
1137     CHECK(format_decimal<int>(&a, -9) == "-0.012345678");
1138 
1139     a = 12345678;
1140     CHECK(format_decimal<int>(&a, 1) == "123456780");
1141     CHECK(format_decimal<int>(&a, 0) == "12345678");
1142     CHECK(format_decimal<int>(&a, -3) == "12345.678");
1143     CHECK(format_decimal<int>(&a, -8) == "0.12345678");
1144     CHECK(format_decimal<int>(&a, -9) == "0.012345678");
1145 
1146     soci::session sql(backEnd, connectString + " decimals_as_strings=1");
1147 
1148     try
1149     {
1150         sql << "drop table test13";
1151     }
1152     catch (std::runtime_error &)
1153     {} // ignore if error
1154 
1155     sql << "create table test13(ntest1 decimal(10,2), "
1156         << "ntest2 decimal(4,4), ntest3 decimal(3,1))";
1157     sql.commit();
1158 
1159     sql.begin();
1160 
1161     {
1162         row r;
1163         sql << "select * from test13", into(r);
1164         CHECK(sql.got_data() == false);
1165     }
1166 
1167     std::string d_str0("+03.140"), d_str1("3.14"),
1168         d_str2("3.1400"), d_str3("3.1");
1169     indicator ind(i_ok);
1170 
1171     {
1172         statement st((sql.prepare <<
1173                     "insert into test13(ntest1, ntest2, ntest3) "
1174                     "values(:ntest1, :ntest2, :ntest3)",
1175                 use(d_str0, ind, "ntest1"), use(d_str0, "ntest2"),
1176                 use(d_str0, "ntest3")));
1177 
1178         st.execute(1);
1179 
1180         ind = i_null;
1181         st.execute(1);
1182     }
1183 
1184     row r;
1185     statement st = (sql.prepare << "select * from test13", into(r));
1186     st.execute(1);
1187 
1188     CHECK(r.size() == 3);
1189 
1190     // get properties by position
1191     CHECK(r.get_properties(0).get_name() == "NTEST1");
1192     CHECK(r.get_properties(0).get_data_type() == dt_string);
1193     CHECK(r.get_properties(1).get_name() == "NTEST2");
1194     CHECK(r.get_properties(1).get_data_type() == dt_string);
1195     CHECK(r.get_properties(2).get_name() == "NTEST3");
1196     CHECK(r.get_properties(2).get_data_type() == dt_string);
1197 
1198     // get properties by name
1199     CHECK(r.get_properties("NTEST1").get_name() == "NTEST1");
1200     CHECK(r.get_properties("NTEST1").get_data_type() == dt_string);
1201     CHECK(r.get_properties("NTEST2").get_name() == "NTEST2");
1202     CHECK(r.get_properties("NTEST2").get_data_type() == dt_string);
1203     CHECK(r.get_properties("NTEST3").get_name() == "NTEST3");
1204     CHECK(r.get_properties("NTEST3").get_data_type() == dt_string);
1205 
1206     // get values by position
1207     CHECK(r.get<std::string>(0) == d_str1);
1208     CHECK(r.get<std::string>(1) == d_str2);
1209     CHECK(r.get<std::string>(2) == d_str3);
1210 
1211     // get values by name
1212     CHECK(r.get<std::string>("NTEST1") == d_str1);
1213     CHECK(r.get<std::string>("NTEST2") == d_str2);
1214     CHECK(r.get<std::string>("NTEST3") == d_str3);
1215 
1216     st.fetch();
1217     CHECK(r.get_indicator(0) == i_null);
1218     CHECK(r.get_indicator(1) == i_ok);
1219     CHECK(r.get_indicator(2) == i_ok);
1220 
1221     sql << "drop table test13";
1222 }
1223 
1224 //
1225 // Support for soci Common Tests
1226 //
1227 
1228 struct TableCreator1 : public tests::table_creator_base
1229 {
1230     TableCreator1(soci::session & sql)
1231             : tests::table_creator_base(sql)
1232     {
1233         sql << "create table soci_test(id integer, val integer, c char, "
1234         "str varchar(20), sh smallint, ul bigint, d double precision, "
1235         "num76 numeric(7,6), "
1236         "tm timestamp, i1 integer, i2 integer, i3 integer, name varchar(20))";
1237         sql.commit();
1238         sql.begin();
1239     }
1240 };
1241 
1242 struct TableCreator2 : public tests::table_creator_base
1243 {
1244     TableCreator2(soci::session & sql)
1245             : tests::table_creator_base(sql)
1246     {
1247         sql  << "create table soci_test(num_float float, num_int integer, "
1248         "name varchar(20), sometime timestamp, chr char)";
1249         sql.commit();
1250         sql.begin();
1251     }
1252 };
1253 
1254 struct TableCreator3 : public tests::table_creator_base
1255 {
1256     TableCreator3(soci::session & sql)
1257             : tests::table_creator_base(sql)
1258     {
1259         sql << "create table soci_test(name varchar(100) not null, "
1260         "phone varchar(15))";
1261         sql.commit();
1262         sql.begin();
1263     }
1264 };
1265 
1266 struct TableCreator4 : public tests::table_creator_base
1267 {
1268     TableCreator4(soci::session & sql)
1269             : tests::table_creator_base(sql)
1270     {
1271         sql << "create table soci_test(val integer)";
1272         sql.commit();
1273         sql.begin();
1274     }
1275 };
1276 
1277 struct TableCreatorCLOB : public tests::table_creator_base
1278 {
1279     TableCreatorCLOB(soci::session & sql)
1280             : tests::table_creator_base(sql)
1281     {
1282         sql << "create table soci_test(id integer, s blob sub_type text)";
1283         sql.commit();
1284         sql.begin();
1285     }
1286 };
1287 
1288 struct TableCreatorXML : public tests::table_creator_base
1289 {
1290     TableCreatorXML(soci::session & sql)
1291             : tests::table_creator_base(sql)
1292     {
1293         sql << "create table soci_test(id integer, x blob sub_type text)";
1294         sql.commit();
1295         sql.begin();
1296     }
1297 };
1298 
1299 class test_context : public tests::test_context_base
1300 {
1301     public:
1302         test_context(backend_factory const &backEnd,
1303                     std::string const &connectString)
1304                 : test_context_base(backEnd, connectString)
1305         {}
1306 
1307         tests::table_creator_base* table_creator_1(soci::session& s) const override
1308         {
1309             return new TableCreator1(s);
1310         }
1311 
1312         tests::table_creator_base* table_creator_2(soci::session& s) const override
1313         {
1314             return new TableCreator2(s);
1315         }
1316 
1317         tests::table_creator_base* table_creator_3(soci::session& s) const override
1318         {
1319             return new TableCreator3(s);
1320         }
1321 
1322         tests::table_creator_base* table_creator_4(soci::session& s) const override
1323         {
1324             return new TableCreator4(s);
1325         }
1326 
1327         tests::table_creator_base* table_creator_clob(soci::session& s) const override
1328         {
1329             return new TableCreatorCLOB(s);
1330         }
1331 
1332         tests::table_creator_base* table_creator_xml(soci::session& s) const override
1333         {
1334             return new TableCreatorXML(s);
1335         }
1336 
1337         std::string to_date_time(std::string const &datdt_string) const override
1338         {
1339             return "'" + datdt_string + "'";
1340         }
1341 
1342         void on_after_ddl(soci::session& sql) const override
1343         {
1344             sql.commit();
1345         }
1346 
1347         std::string sql_length(std::string const& s) const override
1348         {
1349             return "char_length(" + s + ")";
1350         }
1351 };
1352 
1353 
1354 int main(int argc, char** argv)
1355 {
1356 
1357 #ifdef _MSC_VER
1358     // Redirect errors, unrecoverable problems, and assert() failures to STDERR,
1359     // instead of debug message window.
1360     // This hack is required to run assert()-driven tests by Buildbot.
1361     // NOTE: Comment this 2 lines for debugging with Visual C++ debugger to catch assertions inside.
1362     _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE);
1363     _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR);
1364 #endif //_MSC_VER
1365 
1366     if (argc >= 2)
1367     {
1368         connectString = argv[1];
1369 
1370         // Replace the connect string with the process name to ensure that
1371         // CATCH uses the correct name in its messages.
1372         argv[1] = argv[0];
1373 
1374         argc--;
1375         argv++;
1376     }
1377     else
1378     {
1379         std::cout << "usage: " << argv[0]
1380             << " connectstring [test-arguments...]\n"
1381             << "example: " << argv[0]
1382             << " \"service=/usr/local/firebird/db/test.fdb user=SYSDBA password=masterkey\"\n";
1383         return EXIT_FAILURE;
1384     }
1385 
1386     test_context tc(backEnd, connectString);
1387 
1388     return Catch::Session().run(argc, argv);
1389 }