From 9708a1260720d879d76a10f894925962f20611bc Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Wed, 18 Mar 2015 19:36:00 -0700 Subject: [PATCH] Squashed 'src/soci/' content from commit 6e9312c git-subtree-dir: src/soci git-subtree-split: 6e9312c4bb3748907bd28d62c40feca42878cfef --- .gitattributes | 22 + .gitignore | 24 + .travis.yml | 40 + README.md | 52 + bin/ci/before_install.sh | 13 + bin/ci/before_install_db2.sh | 39 + bin/ci/before_install_firebird.sh | 16 + bin/ci/before_install_odbc.sh | 13 + bin/ci/before_install_oracle.sh | 15 + bin/ci/before_script.sh | 9 + bin/ci/before_script_db2.sh | 10 + bin/ci/before_script_firebird.sh | 11 + bin/ci/before_script_mysql.sh | 9 + bin/ci/before_script_odbc.sh | 11 + bin/ci/before_script_postgresql.sh | 9 + bin/ci/common.sh | 36 + bin/ci/script.sh | 14 + bin/ci/script_db2.sh | 24 + bin/ci/script_empty.sh | 22 + bin/ci/script_firebird.sh | 23 + bin/ci/script_mysql.sh | 23 + bin/ci/script_odbc.sh | 24 + bin/ci/script_oracle.sh | 30 + bin/ci/script_postgresql.sh | 23 + bin/ci/script_sqlite3.sh | 23 + bin/vm/debian-oracle10g-install.sh | 89 + build/README | 2 + build/unix/build-core.tcl | 37 + build/unix/build-mysql.tcl | 142 + build/unix/build-oracle.tcl | 120 + build/unix/build-postgresql.tcl | 148 + build/unix/build-sqlite3.tcl | 142 + build/unix/build.tcl | 87 + build/unix/execute.tcl | 14 + build/unix/find-boost.tcl | 47 + build/unix/install.tcl | 74 + build/unix/parse-parameters.tcl | 60 + doc/backends.html | 499 +++ doc/backends/db2.html | 120 + doc/backends/firebird.html | 240 + doc/backends/index.html | 126 + doc/backends/mysql.html | 261 ++ doc/backends/odbc.html | 288 ++ doc/backends/oracle.html | 260 ++ doc/backends/postgresql.html | 230 + doc/backends/sqlite3.html | 207 + doc/beyond.html | 174 + doc/boost.html | 106 + doc/connections.html | 142 + doc/errors.html | 128 + doc/exchange.html | 758 ++++ doc/index.html | 102 + doc/installation.html | 466 ++ doc/interfaces.html | 103 + doc/languages/ada/concepts.html | 64 + doc/languages/ada/idioms.html | 306 ++ doc/languages/ada/index.html | 78 + doc/languages/ada/reference.html | 674 +++ doc/languages/ada/style.css | 67 + doc/multithreading.html | 64 + doc/queries.html | 146 + doc/rationale.html | 313 ++ doc/reference.html | 963 ++++ doc/statements.html | 389 ++ doc/structure.html | 81 + doc/structure.odg | Bin 0 -> 29603 bytes doc/structure.png | Bin 0 -> 26919 bytes doc/style.css | 118 + src/.gitignore | 20 + src/AUTHORS | 73 + src/CHANGES | 338 ++ src/CMakeLists.txt | 105 + src/CTestConfig.cmake | 13 + src/LICENSE_1_0.txt | 23 + src/README | 35 + src/TODO | 0 src/backends/.gitignore | 5 + src/backends/CMakeLists.txt | 48 + src/backends/db2/CMakeLists.txt | 18 + src/backends/db2/blob.cpp | 62 + src/backends/db2/common.h | 21 + src/backends/db2/factory.cpp | 40 + src/backends/db2/row-id.cpp | 28 + src/backends/db2/session.cpp | 217 + src/backends/db2/soci-db2.h | 282 ++ src/backends/db2/standard-into-type.cpp | 168 + src/backends/db2/standard-use-type.cpp | 201 + src/backends/db2/statement.cpp | 322 ++ src/backends/db2/test/CMakeLists.txt | 14 + src/backends/db2/test/test-db2.cpp | 454 ++ src/backends/db2/vector-into-type.cpp | 411 ++ src/backends/db2/vector-use-type.cpp | 395 ++ src/backends/empty/CMakeLists.txt | 17 + src/backends/empty/Makefile.basic | 88 + src/backends/empty/blob.cpp | 61 + src/backends/empty/factory.cpp | 42 + src/backends/empty/row-id.cpp | 27 + src/backends/empty/session.cpp | 63 + src/backends/empty/soci-empty.h | 193 + src/backends/empty/standard-into-type.cpp | 39 + src/backends/empty/standard-use-type.cpp | 47 + src/backends/empty/statement.cpp | 103 + src/backends/empty/test/.gitignore | 1 + src/backends/empty/test/CMakeLists.txt | 14 + src/backends/empty/test/Makefile.basic | 13 + src/backends/empty/test/test-empty.cpp | 170 + src/backends/empty/vector-into-type.cpp | 50 + src/backends/empty/vector-use-type.cpp | 46 + src/backends/firebird/CMakeLists.txt | 19 + src/backends/firebird/Makefile.basic | 101 + src/backends/firebird/blob.cpp | 301 ++ src/backends/firebird/common.cpp | 215 + src/backends/firebird/common.h | 236 + src/backends/firebird/error-firebird.cpp | 87 + src/backends/firebird/error-firebird.h | 35 + src/backends/firebird/factory.cpp | 36 + src/backends/firebird/row-id.cpp | 21 + src/backends/firebird/session.cpp | 375 ++ src/backends/firebird/soci-firebird.h | 349 ++ src/backends/firebird/standard-into-type.cpp | 147 + src/backends/firebird/standard-use-type.cpp | 182 + src/backends/firebird/statement.cpp | 724 +++ src/backends/firebird/test/CMakeLists.txt | 15 + src/backends/firebird/test/Makefile.basic | 22 + src/backends/firebird/test/test-firebird.cpp | 1361 ++++++ src/backends/firebird/vector-into-type.cpp | 208 + src/backends/firebird/vector-use-type.cpp | 207 + src/backends/mysql/CMakeLists.txt | 18 + src/backends/mysql/Makefile.basic | 97 + src/backends/mysql/blob.cpp | 62 + src/backends/mysql/common.cpp | 87 + src/backends/mysql/common.h | 76 + src/backends/mysql/factory.cpp | 45 + src/backends/mysql/row-id.cpp | 32 + src/backends/mysql/session.cpp | 389 ++ src/backends/mysql/soci-mysql.h | 273 ++ src/backends/mysql/standard-into-type.cpp | 141 + src/backends/mysql/standard-use-type.cpp | 172 + src/backends/mysql/statement.cpp | 480 ++ src/backends/mysql/test/.gitignore | 1 + src/backends/mysql/test/CMakeLists.txt | 14 + src/backends/mysql/test/Makefile.basic | 22 + src/backends/mysql/test/test-mysql.cpp | 1002 +++++ src/backends/mysql/vector-into-type.cpp | 231 + src/backends/mysql/vector-use-type.cpp | 220 + src/backends/odbc/CMakeLists.txt | 18 + src/backends/odbc/Makefile.basic | 89 + src/backends/odbc/blob.cpp | 57 + src/backends/odbc/factory.cpp | 39 + src/backends/odbc/makefile.msvc | 48 + src/backends/odbc/row-id.cpp | 23 + src/backends/odbc/session.cpp | 296 ++ src/backends/odbc/soci-odbc.h | 432 ++ src/backends/odbc/standard-into-type.cpp | 203 + src/backends/odbc/standard-use-type.cpp | 262 ++ src/backends/odbc/statement.cpp | 360 ++ src/backends/odbc/test/CMakeLists.txt | 48 + src/backends/odbc/test/Makefile.basic | 24 + src/backends/odbc/test/makefile.msvc | 19 + src/backends/odbc/test/test-access.dsn | 13 + src/backends/odbc/test/test-mssql.dsn | 8 + src/backends/odbc/test/test-mysql.dsn | 4 + src/backends/odbc/test/test-odbc-access.cpp | 155 + src/backends/odbc/test/test-odbc-db2.cpp | 306 ++ src/backends/odbc/test/test-odbc-mssql.cpp | 148 + src/backends/odbc/test/test-odbc-mysql.cpp | 144 + .../odbc/test/test-odbc-postgresql.cpp | 146 + src/backends/odbc/test/test-postgresql.dsn | 8 + src/backends/odbc/utility.h | 62 + src/backends/odbc/vector-into-type.cpp | 472 ++ src/backends/odbc/vector-use-type.cpp | 455 ++ src/backends/oracle/CMakeLists.txt | 18 + src/backends/oracle/Makefile.basic | 95 + src/backends/oracle/blob.cpp | 112 + src/backends/oracle/error.cpp | 60 + src/backends/oracle/error.h | 33 + src/backends/oracle/factory.cpp | 139 + src/backends/oracle/row-id.cpp | 32 + src/backends/oracle/session.cpp | 216 + src/backends/oracle/soci-oracle.h | 295 ++ src/backends/oracle/standard-into-type.cpp | 274 ++ src/backends/oracle/standard-use-type.cpp | 476 ++ src/backends/oracle/statement.cpp | 343 ++ src/backends/oracle/test/CMakeLists.txt | 14 + src/backends/oracle/test/Makefile.basic | 23 + src/backends/oracle/test/test-oracle.cpp | 1241 +++++ src/backends/oracle/vector-into-type.cpp | 465 ++ src/backends/oracle/vector-use-type.cpp | 397 ++ src/backends/postgresql/CMakeLists.txt | 51 + src/backends/postgresql/Makefile.basic | 114 + src/backends/postgresql/blob.cpp | 116 + src/backends/postgresql/common.cpp | 111 + src/backends/postgresql/common.h | 135 + src/backends/postgresql/error.cpp | 73 + src/backends/postgresql/factory.cpp | 49 + src/backends/postgresql/row-id.cpp | 40 + src/backends/postgresql/session.cpp | 130 + src/backends/postgresql/soci-postgresql.h | 357 ++ .../postgresql/standard-into-type.cpp | 185 + src/backends/postgresql/standard-use-type.cpp | 206 + src/backends/postgresql/statement.cpp | 591 +++ src/backends/postgresql/test/.gitignore | 1 + src/backends/postgresql/test/CMakeLists.txt | 15 + src/backends/postgresql/test/Makefile.basic | 22 + .../postgresql/test/test-postgresql.cpp | 847 ++++ src/backends/postgresql/vector-into-type.cpp | 247 + src/backends/postgresql/vector-use-type.cpp | 232 + src/backends/sqlite3/CMakeLists.txt | 19 + src/backends/sqlite3/Makefile.basic | 96 + src/backends/sqlite3/blob.cpp | 116 + src/backends/sqlite3/common.cpp | 67 + src/backends/sqlite3/common.h | 87 + src/backends/sqlite3/factory.cpp | 42 + src/backends/sqlite3/row-id.cpp | 26 + src/backends/sqlite3/session.cpp | 161 + src/backends/sqlite3/soci-sqlite3.h | 279 ++ src/backends/sqlite3/standard-into-type.cpp | 166 + src/backends/sqlite3/standard-use-type.cpp | 233 + src/backends/sqlite3/statement.cpp | 437 ++ src/backends/sqlite3/test/.gitignore | 2 + src/backends/sqlite3/test/CMakeLists.txt | 15 + src/backends/sqlite3/test/Makefile.basic | 23 + src/backends/sqlite3/test/test-sqlite3.cpp | 395 ++ src/backends/sqlite3/vector-into-type.cpp | 221 + src/backends/sqlite3/vector-use-type.cpp | 260 ++ src/cmake/.gitignore | 21 + src/cmake/CMakeLists.txt | 13 + src/cmake/SociBackend.cmake | 349 ++ src/cmake/SociConfig.cmake | 53 + src/cmake/SociDependencies.cmake | 82 + src/cmake/SociSystemInfo.cmake | 82 + src/cmake/SociUtilities.cmake | 416 ++ src/cmake/SociVersion.cmake | 57 + src/cmake/dependencies/Boost.cmake | 14 + src/cmake/dependencies/DB2.cmake | 5 + src/cmake/dependencies/Firebird.cmake | 6 + src/cmake/dependencies/MySQL.cmake | 10 + src/cmake/dependencies/ODBC.cmake | 5 + src/cmake/dependencies/Oracle.cmake | 5 + src/cmake/dependencies/PostgreSQL.cmake | 5 + src/cmake/dependencies/SQLite3.cmake | 5 + src/cmake/dependencies/Threads.cmake | 5 + src/cmake/modules/FindDB2.cmake | 103 + src/cmake/modules/FindDL.cmake | 21 + src/cmake/modules/FindFirebird.cmake | 33 + src/cmake/modules/FindMySQL.cmake | 139 + src/cmake/modules/FindODBC.cmake | 60 + src/cmake/modules/FindOracle.cmake | 75 + src/cmake/modules/FindPostgreSQL.cmake | 83 + src/cmake/modules/FindSQLite3.cmake | 62 + src/cmake/modules/FindSoci.cmake | 98 + .../vs2010-test-cmd-args.vcxproj.user.in | 7 + src/core/.gitignore | 5 + src/core/CMakeLists.txt | 154 + src/core/Makefile.basic | 79 + src/core/backend-loader.cpp | 347 ++ src/core/backend-loader.h | 37 + src/core/blob-exchange.h | 58 + src/core/blob.cpp | 50 + src/core/blob.h | 47 + src/core/boost-fusion.h | 99 + src/core/boost-gregorian-date.h | 47 + src/core/boost-optional.h | 55 + src/core/boost-tuple.h | 324 ++ src/core/connection-parameters.cpp | 62 + src/core/connection-parameters.h | 68 + src/core/connection-pool.cpp | 339 ++ src/core/connection-pool.h | 39 + src/core/error.cpp | 17 + src/core/error.h | 27 + src/core/exchange-traits.h | 138 + src/core/into-type.cpp | 105 + src/core/into-type.h | 164 + src/core/into.h | 55 + src/core/once-temp-type.cpp | 53 + src/core/once-temp-type.h | 108 + src/core/prepare-temp-type.cpp | 53 + src/core/prepare-temp-type.h | 51 + src/core/procedure.cpp | 33 + src/core/procedure.h | 89 + src/core/query_transformation.h | 59 + src/core/ref-counted-prepare-info.cpp | 46 + src/core/ref-counted-prepare-info.h | 59 + src/core/ref-counted-statement.cpp | 44 + src/core/ref-counted-statement.h | 91 + src/core/row-exchange.h | 77 + src/core/row.cpp | 113 + src/core/row.h | 147 + src/core/rowid-exchange.h | 59 + src/core/rowid.cpp | 23 + src/core/rowid.h | 41 + src/core/rowset.h | 242 + src/core/session.cpp | 392 ++ src/core/session.h | 150 + src/core/soci-backend.h | 277 ++ src/core/soci-config.h | 38 + src/core/soci-platform.h | 57 + src/core/soci-simple.cpp | 1842 ++++++++ src/core/soci-simple.h | 137 + src/core/soci.h | 61 + src/core/soci_backends_config.h.in | 12 + src/core/statement.cpp | 742 +++ src/core/statement.h | 271 ++ src/core/test/common-tests.h | 3980 +++++++++++++++++ src/core/transaction.cpp | 53 + src/core/transaction.h | 38 + src/core/type-conversion-traits.h | 41 + src/core/type-conversion.h | 365 ++ src/core/type-holder.h | 68 + src/core/type-ptr.h | 30 + src/core/unsigned-types.h | 114 + src/core/use-type.cpp | 105 + src/core/use-type.h | 239 + src/core/use.h | 87 + src/core/values-exchange.h | 117 + src/core/values.cpp | 69 + src/core/values.h | 349 ++ src/core/version.h | 32 + src/ideas.txt | 134 + src/languages/ada/postgresql_client.gpr | 8 + src/languages/ada/soci-mysql.ads | 16 + src/languages/ada/soci-oracle.ads | 16 + src/languages/ada/soci-postgresql.ads | 16 + src/languages/ada/soci.adb | 1465 ++++++ src/languages/ada/soci.ads | 486 ++ src/languages/ada/soci_ada.gpr | 14 + src/languages/ada/soci_core.gpr | 7 + src/languages/ada/soci_postgresql.gpr | 8 + src/languages/ada/std_cpp.gpr | 7 + src/languages/ada/test/postgresql_test.adb | 609 +++ src/languages/ada/test/postgresql_test.gpr | 12 + www/articles.html | 42 + www/doc.html | 45 + www/doc/README.md | 11 + www/doc/index.html | 45 + www/events.html | 97 + www/forkus_github.png | Bin 0 -> 5735 bytes www/index.html | 134 + www/links.html | 78 + www/people.html | 122 + www/style.css | 87 + 341 files changed, 54122 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 README.md create mode 100755 bin/ci/before_install.sh create mode 100755 bin/ci/before_install_db2.sh create mode 100755 bin/ci/before_install_firebird.sh create mode 100755 bin/ci/before_install_odbc.sh create mode 100755 bin/ci/before_install_oracle.sh create mode 100755 bin/ci/before_script.sh create mode 100755 bin/ci/before_script_db2.sh create mode 100755 bin/ci/before_script_firebird.sh create mode 100755 bin/ci/before_script_mysql.sh create mode 100755 bin/ci/before_script_odbc.sh create mode 100755 bin/ci/before_script_postgresql.sh create mode 100644 bin/ci/common.sh create mode 100755 bin/ci/script.sh create mode 100755 bin/ci/script_db2.sh create mode 100755 bin/ci/script_empty.sh create mode 100755 bin/ci/script_firebird.sh create mode 100755 bin/ci/script_mysql.sh create mode 100755 bin/ci/script_odbc.sh create mode 100755 bin/ci/script_oracle.sh create mode 100755 bin/ci/script_postgresql.sh create mode 100755 bin/ci/script_sqlite3.sh create mode 100644 bin/vm/debian-oracle10g-install.sh create mode 100644 build/README create mode 100644 build/unix/build-core.tcl create mode 100644 build/unix/build-mysql.tcl create mode 100644 build/unix/build-oracle.tcl create mode 100644 build/unix/build-postgresql.tcl create mode 100644 build/unix/build-sqlite3.tcl create mode 100755 build/unix/build.tcl create mode 100644 build/unix/execute.tcl create mode 100644 build/unix/find-boost.tcl create mode 100644 build/unix/install.tcl create mode 100644 build/unix/parse-parameters.tcl create mode 100644 doc/backends.html create mode 100644 doc/backends/db2.html create mode 100644 doc/backends/firebird.html create mode 100644 doc/backends/index.html create mode 100644 doc/backends/mysql.html create mode 100644 doc/backends/odbc.html create mode 100644 doc/backends/oracle.html create mode 100644 doc/backends/postgresql.html create mode 100644 doc/backends/sqlite3.html create mode 100644 doc/beyond.html create mode 100644 doc/boost.html create mode 100644 doc/connections.html create mode 100644 doc/errors.html create mode 100644 doc/exchange.html create mode 100644 doc/index.html create mode 100644 doc/installation.html create mode 100644 doc/interfaces.html create mode 100644 doc/languages/ada/concepts.html create mode 100644 doc/languages/ada/idioms.html create mode 100644 doc/languages/ada/index.html create mode 100644 doc/languages/ada/reference.html create mode 100644 doc/languages/ada/style.css create mode 100644 doc/multithreading.html create mode 100644 doc/queries.html create mode 100644 doc/rationale.html create mode 100644 doc/reference.html create mode 100644 doc/statements.html create mode 100644 doc/structure.html create mode 100644 doc/structure.odg create mode 100644 doc/structure.png create mode 100644 doc/style.css create mode 100644 src/.gitignore create mode 100644 src/AUTHORS create mode 100644 src/CHANGES create mode 100644 src/CMakeLists.txt create mode 100644 src/CTestConfig.cmake create mode 100644 src/LICENSE_1_0.txt create mode 100644 src/README create mode 100644 src/TODO create mode 100644 src/backends/.gitignore create mode 100644 src/backends/CMakeLists.txt create mode 100644 src/backends/db2/CMakeLists.txt create mode 100644 src/backends/db2/blob.cpp create mode 100644 src/backends/db2/common.h create mode 100644 src/backends/db2/factory.cpp create mode 100644 src/backends/db2/row-id.cpp create mode 100644 src/backends/db2/session.cpp create mode 100644 src/backends/db2/soci-db2.h create mode 100644 src/backends/db2/standard-into-type.cpp create mode 100644 src/backends/db2/standard-use-type.cpp create mode 100644 src/backends/db2/statement.cpp create mode 100644 src/backends/db2/test/CMakeLists.txt create mode 100644 src/backends/db2/test/test-db2.cpp create mode 100644 src/backends/db2/vector-into-type.cpp create mode 100644 src/backends/db2/vector-use-type.cpp create mode 100644 src/backends/empty/CMakeLists.txt create mode 100644 src/backends/empty/Makefile.basic create mode 100644 src/backends/empty/blob.cpp create mode 100644 src/backends/empty/factory.cpp create mode 100644 src/backends/empty/row-id.cpp create mode 100644 src/backends/empty/session.cpp create mode 100644 src/backends/empty/soci-empty.h create mode 100644 src/backends/empty/standard-into-type.cpp create mode 100644 src/backends/empty/standard-use-type.cpp create mode 100644 src/backends/empty/statement.cpp create mode 100644 src/backends/empty/test/.gitignore create mode 100644 src/backends/empty/test/CMakeLists.txt create mode 100644 src/backends/empty/test/Makefile.basic create mode 100644 src/backends/empty/test/test-empty.cpp create mode 100644 src/backends/empty/vector-into-type.cpp create mode 100644 src/backends/empty/vector-use-type.cpp create mode 100644 src/backends/firebird/CMakeLists.txt create mode 100644 src/backends/firebird/Makefile.basic create mode 100644 src/backends/firebird/blob.cpp create mode 100644 src/backends/firebird/common.cpp create mode 100644 src/backends/firebird/common.h create mode 100644 src/backends/firebird/error-firebird.cpp create mode 100644 src/backends/firebird/error-firebird.h create mode 100644 src/backends/firebird/factory.cpp create mode 100644 src/backends/firebird/row-id.cpp create mode 100644 src/backends/firebird/session.cpp create mode 100644 src/backends/firebird/soci-firebird.h create mode 100644 src/backends/firebird/standard-into-type.cpp create mode 100644 src/backends/firebird/standard-use-type.cpp create mode 100644 src/backends/firebird/statement.cpp create mode 100644 src/backends/firebird/test/CMakeLists.txt create mode 100644 src/backends/firebird/test/Makefile.basic create mode 100644 src/backends/firebird/test/test-firebird.cpp create mode 100644 src/backends/firebird/vector-into-type.cpp create mode 100644 src/backends/firebird/vector-use-type.cpp create mode 100644 src/backends/mysql/CMakeLists.txt create mode 100644 src/backends/mysql/Makefile.basic create mode 100644 src/backends/mysql/blob.cpp create mode 100644 src/backends/mysql/common.cpp create mode 100644 src/backends/mysql/common.h create mode 100644 src/backends/mysql/factory.cpp create mode 100644 src/backends/mysql/row-id.cpp create mode 100644 src/backends/mysql/session.cpp create mode 100644 src/backends/mysql/soci-mysql.h create mode 100644 src/backends/mysql/standard-into-type.cpp create mode 100644 src/backends/mysql/standard-use-type.cpp create mode 100644 src/backends/mysql/statement.cpp create mode 100644 src/backends/mysql/test/.gitignore create mode 100644 src/backends/mysql/test/CMakeLists.txt create mode 100644 src/backends/mysql/test/Makefile.basic create mode 100644 src/backends/mysql/test/test-mysql.cpp create mode 100644 src/backends/mysql/vector-into-type.cpp create mode 100644 src/backends/mysql/vector-use-type.cpp create mode 100644 src/backends/odbc/CMakeLists.txt create mode 100644 src/backends/odbc/Makefile.basic create mode 100644 src/backends/odbc/blob.cpp create mode 100644 src/backends/odbc/factory.cpp create mode 100644 src/backends/odbc/makefile.msvc create mode 100644 src/backends/odbc/row-id.cpp create mode 100644 src/backends/odbc/session.cpp create mode 100644 src/backends/odbc/soci-odbc.h create mode 100644 src/backends/odbc/standard-into-type.cpp create mode 100644 src/backends/odbc/standard-use-type.cpp create mode 100644 src/backends/odbc/statement.cpp create mode 100644 src/backends/odbc/test/CMakeLists.txt create mode 100644 src/backends/odbc/test/Makefile.basic create mode 100644 src/backends/odbc/test/makefile.msvc create mode 100644 src/backends/odbc/test/test-access.dsn create mode 100644 src/backends/odbc/test/test-mssql.dsn create mode 100644 src/backends/odbc/test/test-mysql.dsn create mode 100644 src/backends/odbc/test/test-odbc-access.cpp create mode 100644 src/backends/odbc/test/test-odbc-db2.cpp create mode 100644 src/backends/odbc/test/test-odbc-mssql.cpp create mode 100644 src/backends/odbc/test/test-odbc-mysql.cpp create mode 100644 src/backends/odbc/test/test-odbc-postgresql.cpp create mode 100644 src/backends/odbc/test/test-postgresql.dsn create mode 100644 src/backends/odbc/utility.h create mode 100644 src/backends/odbc/vector-into-type.cpp create mode 100644 src/backends/odbc/vector-use-type.cpp create mode 100644 src/backends/oracle/CMakeLists.txt create mode 100644 src/backends/oracle/Makefile.basic create mode 100644 src/backends/oracle/blob.cpp create mode 100644 src/backends/oracle/error.cpp create mode 100644 src/backends/oracle/error.h create mode 100644 src/backends/oracle/factory.cpp create mode 100644 src/backends/oracle/row-id.cpp create mode 100644 src/backends/oracle/session.cpp create mode 100644 src/backends/oracle/soci-oracle.h create mode 100644 src/backends/oracle/standard-into-type.cpp create mode 100644 src/backends/oracle/standard-use-type.cpp create mode 100644 src/backends/oracle/statement.cpp create mode 100644 src/backends/oracle/test/CMakeLists.txt create mode 100644 src/backends/oracle/test/Makefile.basic create mode 100644 src/backends/oracle/test/test-oracle.cpp create mode 100644 src/backends/oracle/vector-into-type.cpp create mode 100644 src/backends/oracle/vector-use-type.cpp create mode 100644 src/backends/postgresql/CMakeLists.txt create mode 100644 src/backends/postgresql/Makefile.basic create mode 100644 src/backends/postgresql/blob.cpp create mode 100644 src/backends/postgresql/common.cpp create mode 100644 src/backends/postgresql/common.h create mode 100644 src/backends/postgresql/error.cpp create mode 100644 src/backends/postgresql/factory.cpp create mode 100644 src/backends/postgresql/row-id.cpp create mode 100644 src/backends/postgresql/session.cpp create mode 100644 src/backends/postgresql/soci-postgresql.h create mode 100644 src/backends/postgresql/standard-into-type.cpp create mode 100644 src/backends/postgresql/standard-use-type.cpp create mode 100644 src/backends/postgresql/statement.cpp create mode 100644 src/backends/postgresql/test/.gitignore create mode 100644 src/backends/postgresql/test/CMakeLists.txt create mode 100644 src/backends/postgresql/test/Makefile.basic create mode 100644 src/backends/postgresql/test/test-postgresql.cpp create mode 100644 src/backends/postgresql/vector-into-type.cpp create mode 100644 src/backends/postgresql/vector-use-type.cpp create mode 100644 src/backends/sqlite3/CMakeLists.txt create mode 100644 src/backends/sqlite3/Makefile.basic create mode 100644 src/backends/sqlite3/blob.cpp create mode 100644 src/backends/sqlite3/common.cpp create mode 100644 src/backends/sqlite3/common.h create mode 100644 src/backends/sqlite3/factory.cpp create mode 100644 src/backends/sqlite3/row-id.cpp create mode 100644 src/backends/sqlite3/session.cpp create mode 100644 src/backends/sqlite3/soci-sqlite3.h create mode 100644 src/backends/sqlite3/standard-into-type.cpp create mode 100644 src/backends/sqlite3/standard-use-type.cpp create mode 100644 src/backends/sqlite3/statement.cpp create mode 100644 src/backends/sqlite3/test/.gitignore create mode 100644 src/backends/sqlite3/test/CMakeLists.txt create mode 100644 src/backends/sqlite3/test/Makefile.basic create mode 100644 src/backends/sqlite3/test/test-sqlite3.cpp create mode 100644 src/backends/sqlite3/vector-into-type.cpp create mode 100644 src/backends/sqlite3/vector-use-type.cpp create mode 100644 src/cmake/.gitignore create mode 100644 src/cmake/CMakeLists.txt create mode 100644 src/cmake/SociBackend.cmake create mode 100644 src/cmake/SociConfig.cmake create mode 100644 src/cmake/SociDependencies.cmake create mode 100644 src/cmake/SociSystemInfo.cmake create mode 100644 src/cmake/SociUtilities.cmake create mode 100644 src/cmake/SociVersion.cmake create mode 100644 src/cmake/dependencies/Boost.cmake create mode 100644 src/cmake/dependencies/DB2.cmake create mode 100644 src/cmake/dependencies/Firebird.cmake create mode 100644 src/cmake/dependencies/MySQL.cmake create mode 100644 src/cmake/dependencies/ODBC.cmake create mode 100644 src/cmake/dependencies/Oracle.cmake create mode 100644 src/cmake/dependencies/PostgreSQL.cmake create mode 100644 src/cmake/dependencies/SQLite3.cmake create mode 100644 src/cmake/dependencies/Threads.cmake create mode 100644 src/cmake/modules/FindDB2.cmake create mode 100644 src/cmake/modules/FindDL.cmake create mode 100644 src/cmake/modules/FindFirebird.cmake create mode 100644 src/cmake/modules/FindMySQL.cmake create mode 100644 src/cmake/modules/FindODBC.cmake create mode 100644 src/cmake/modules/FindOracle.cmake create mode 100644 src/cmake/modules/FindPostgreSQL.cmake create mode 100644 src/cmake/modules/FindSQLite3.cmake create mode 100644 src/cmake/modules/FindSoci.cmake create mode 100644 src/cmake/resources/vs2010-test-cmd-args.vcxproj.user.in create mode 100644 src/core/.gitignore create mode 100644 src/core/CMakeLists.txt create mode 100644 src/core/Makefile.basic create mode 100644 src/core/backend-loader.cpp create mode 100644 src/core/backend-loader.h create mode 100644 src/core/blob-exchange.h create mode 100644 src/core/blob.cpp create mode 100644 src/core/blob.h create mode 100644 src/core/boost-fusion.h create mode 100644 src/core/boost-gregorian-date.h create mode 100644 src/core/boost-optional.h create mode 100644 src/core/boost-tuple.h create mode 100644 src/core/connection-parameters.cpp create mode 100644 src/core/connection-parameters.h create mode 100644 src/core/connection-pool.cpp create mode 100644 src/core/connection-pool.h create mode 100644 src/core/error.cpp create mode 100644 src/core/error.h create mode 100644 src/core/exchange-traits.h create mode 100644 src/core/into-type.cpp create mode 100644 src/core/into-type.h create mode 100644 src/core/into.h create mode 100644 src/core/once-temp-type.cpp create mode 100644 src/core/once-temp-type.h create mode 100644 src/core/prepare-temp-type.cpp create mode 100644 src/core/prepare-temp-type.h create mode 100644 src/core/procedure.cpp create mode 100644 src/core/procedure.h create mode 100644 src/core/query_transformation.h create mode 100644 src/core/ref-counted-prepare-info.cpp create mode 100644 src/core/ref-counted-prepare-info.h create mode 100644 src/core/ref-counted-statement.cpp create mode 100644 src/core/ref-counted-statement.h create mode 100644 src/core/row-exchange.h create mode 100644 src/core/row.cpp create mode 100644 src/core/row.h create mode 100644 src/core/rowid-exchange.h create mode 100644 src/core/rowid.cpp create mode 100644 src/core/rowid.h create mode 100644 src/core/rowset.h create mode 100644 src/core/session.cpp create mode 100644 src/core/session.h create mode 100644 src/core/soci-backend.h create mode 100644 src/core/soci-config.h create mode 100644 src/core/soci-platform.h create mode 100644 src/core/soci-simple.cpp create mode 100644 src/core/soci-simple.h create mode 100644 src/core/soci.h create mode 100644 src/core/soci_backends_config.h.in create mode 100644 src/core/statement.cpp create mode 100644 src/core/statement.h create mode 100644 src/core/test/common-tests.h create mode 100644 src/core/transaction.cpp create mode 100644 src/core/transaction.h create mode 100644 src/core/type-conversion-traits.h create mode 100644 src/core/type-conversion.h create mode 100644 src/core/type-holder.h create mode 100644 src/core/type-ptr.h create mode 100644 src/core/unsigned-types.h create mode 100644 src/core/use-type.cpp create mode 100644 src/core/use-type.h create mode 100644 src/core/use.h create mode 100644 src/core/values-exchange.h create mode 100644 src/core/values.cpp create mode 100644 src/core/values.h create mode 100644 src/core/version.h create mode 100644 src/ideas.txt create mode 100644 src/languages/ada/postgresql_client.gpr create mode 100644 src/languages/ada/soci-mysql.ads create mode 100644 src/languages/ada/soci-oracle.ads create mode 100644 src/languages/ada/soci-postgresql.ads create mode 100644 src/languages/ada/soci.adb create mode 100644 src/languages/ada/soci.ads create mode 100644 src/languages/ada/soci_ada.gpr create mode 100644 src/languages/ada/soci_core.gpr create mode 100644 src/languages/ada/soci_postgresql.gpr create mode 100644 src/languages/ada/std_cpp.gpr create mode 100644 src/languages/ada/test/postgresql_test.adb create mode 100644 src/languages/ada/test/postgresql_test.gpr create mode 100644 www/articles.html create mode 100644 www/doc.html create mode 100644 www/doc/README.md create mode 100644 www/doc/index.html create mode 100644 www/events.html create mode 100644 www/forkus_github.png create mode 100644 www/index.html create mode 100644 www/links.html create mode 100644 www/people.html create mode 100644 www/style.css diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..412eeda78d --- /dev/null +++ b/.gitattributes @@ -0,0 +1,22 @@ +# Auto detect text files and perform LF normalization +* text=auto + +# Custom for Visual Studio +*.cs diff=csharp +*.sln merge=union +*.csproj merge=union +*.vbproj merge=union +*.fsproj merge=union +*.dbproj merge=union + +# Standard to msysgit +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..405ab0d53b --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# General +*.swp +tags +tmp + +# Build directories +_build* +src/_build* +src/build + +# Visual Studio +*.opensdf +*.sdf +*.suo + +# KDevelop +*.kate-swp +*.kdev4 + +# Qt Creator +*.config +*.creator* +*.files +*.includes diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..894f6a63f1 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,40 @@ +# .travis.yml +# Configure Travis CI service to build SOCI library, http://github.com/SOCI +# +# Copyright (c) 2013 Mateusz Loskot +# +language: cpp + +compiler: + - g++ + #- clang + +services: + - mysql + - postgresql + +env: + matrix: + - SOCI_TRAVIS_BACKEND=db2 + - SOCI_TRAVIS_BACKEND=empty + - SOCI_TRAVIS_BACKEND=firebird + - SOCI_TRAVIS_BACKEND=mysql + - SOCI_TRAVIS_BACKEND=odbc + - SOCI_TRAVIS_BACKEND=oracle + - SOCI_TRAVIS_BACKEND=postgresql + - SOCI_TRAVIS_BACKEND=sqlite3 + +before_install: ./bin/ci/before_install.sh +before_script: ./bin/ci/before_script.sh +script: ./bin/ci/script.sh + +notifications: + email: + recipients: + - soci-devel@lists.sourceforge.net + on_success: change # [always|never|change] # default: change + on_failure: always # [always|never|change] # default: always + + irc: + channels: + - "irc.freenode.org#soci" diff --git a/README.md b/README.md new file mode 100644 index 0000000000..0262964528 --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +SOCI - The C++ Database Access Library +====================================== + +Website: http://soci.sourceforge.net + +GitHub hosts SOCI source code repository, issues tracker and wiki: +https://github.com/SOCI + +Downloads and mailing lists at +http://sourceforge.net/projects/soci/ + +Travis CI service at https://travis-ci.org/SOCI/soci + +[![Build Status](https://api.travis-ci.org/SOCI/soci.png)](https://travis-ci.org/SOCI/soci) + +License +------- + +The SOCI library is distributed under the terms of the [Boost Software License](http://www.boost.org/LICENSE_1_0.txt). + +Requirements +------------ + +Core: +* C++ compiler +* Boost C++ Libraries (optional, headers only) + +Backend specific client libraries for: +* DB2 +* Firebird +* MySQL +* ODBC andwith specific database driver +* Oracle +* PostgreSQL +* SQLite 3 + +See documentation at http://soci.sourceforge.net for details + +Brief History +------------- +Originally, SOCI was developed by [Maciej Sobczak](http://www.msobczak.com/) +at [CERN](http://www.cern.ch/) as abstraction layer for Oracle, +a **Simple Oracle Call Interface**. +Later, several database backends have been developed for SOCI, +thus the long name has lost its practicality. +Currently, if you like, SOCI may stand for **Simple Open (Database) Call Interface** +or something similar. + +> "CERN is also a user of the SOCI library, which serves as a database access +> layer in some of the control system components." + +-- Maciej Sobczak at [Inspirel](http://www.inspirel.com/users.html) diff --git a/bin/ci/before_install.sh b/bin/ci/before_install.sh new file mode 100755 index 0000000000..4bc98abb19 --- /dev/null +++ b/bin/ci/before_install.sh @@ -0,0 +1,13 @@ +#!/bin/bash -e +# Run before_install actions for SOCI build at travis-ci.org +# +# Copyright (c) 2013 Mateusz Loskot +# +source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh + +sudo apt-key adv --recv-keys --keyserver keyserver.ubuntu.com 16126D3A3E5C1192 +sudo apt-get update -qq +sudo apt-get install -qq libboost-dev libboost-date-time-dev + +before_install="${TRAVIS_BUILD_DIR}/bin/ci/before_install_${SOCI_TRAVIS_BACKEND}.sh" +[ -x ${before_install} ] && ${before_install} || echo "nothing to run" diff --git a/bin/ci/before_install_db2.sh b/bin/ci/before_install_db2.sh new file mode 100755 index 0000000000..52953bb8ab --- /dev/null +++ b/bin/ci/before_install_db2.sh @@ -0,0 +1,39 @@ +#!/bin/bash -e +# Installs DB2 for SOCI build at travis-ci.org +# +# Copyright (c) 2013 Brian R. Toonen +# Copyright (c) 2013 Mateusz Loskot +# +source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh + +sudo bash -c 'echo "deb http://archive.canonical.com/ubuntu precise partner" >> /etc/apt/sources.list' +sudo apt-get update -qq +sudo apt-get install -y db2exc + +echo "Running db2profile and db2rmln" +sudo /bin/sh -c '. ~db2inst1/sqllib/db2profile ; $DB2DIR/cfg/db2rmln' + +echo "Setting up db2 users" +echo -e "db2inst1\ndb2inst1" | sudo passwd db2inst1 +echo -e "db2fenc1\ndb2fenc1" | sudo passwd db2fenc1 +echo -e "dasusr1\ndasusr1" | sudo passwd dasusr1 + +echo "Configuring DB2 ODBC driver" +if test `getconf LONG_BIT` = "64" ; then + if test -f /home/db2inst1/sqllib/lib64/libdb2o.so ; then + DB2_ODBC_DRIVER=/home/db2inst1/sqllib/lib64/libdb2o.so + else + echo "ERROR: can't find the 64-bit DB2 ODBC library" + exit 1 + fi +else + if test -f /home/db2inst1/sqllib/lib32/libdb2.so ; then + DB2_ODBC_DRIVER=/home/db2inst1/sqllib/lib32/libdb2.so + elif test -f /home/db2inst1/sqllib/lib/libdb2.so ; then + DB2_ODBC_DRIVER=/home/db2inst1/sqllib/lib/libdb2.so + else + echo "ERROR: can't find the 32-bit DB2 ODBC library" + exit 1 + fi +fi +echo "DB2 ODBC driver set to $DB2_ODBC_DRIVER" diff --git a/bin/ci/before_install_firebird.sh b/bin/ci/before_install_firebird.sh new file mode 100755 index 0000000000..9ec3214f4b --- /dev/null +++ b/bin/ci/before_install_firebird.sh @@ -0,0 +1,16 @@ +#!/bin/bash -e +# Install Firebird server for SOCI at travis-ci.org +# +# Copyright (c) 2013 Mateusz Loskot +# +source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh + +sudo apt-get install -qq firebird2.5-super firebird2.5-dev + +# Configure Firebird server +# See: Non-interactive setup for travis-ci.org +# http://tech.groups.yahoo.com/group/firebird-support/message/120883 +#sudo dpkg-reconfigure -f noninteractive firebird2.5-super +sudo sed /ENABLE_FIREBIRD_SERVER=/s/no/yes/ -i /etc/default/firebird2.5 +cat /etc/default/firebird2.5 | grep ENABLE_FIREBIRD_SERVER +sudo service firebird2.5-super start diff --git a/bin/ci/before_install_odbc.sh b/bin/ci/before_install_odbc.sh new file mode 100755 index 0000000000..e9f9346b4d --- /dev/null +++ b/bin/ci/before_install_odbc.sh @@ -0,0 +1,13 @@ +#!/bin/bash -e +# Install ODBC libraries for SOCI at travis-ci.org +# +# Copyright (c) 2013 Mateusz Loskot +# +source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh + +sudo apt-get install -qq \ + tar bzip2 \ + unixodbc-dev \ + libmyodbc odbc-postgresql + +sudo odbcinst -i -d -f /usr/share/libmyodbc/odbcinst.ini diff --git a/bin/ci/before_install_oracle.sh b/bin/ci/before_install_oracle.sh new file mode 100755 index 0000000000..fdd1067501 --- /dev/null +++ b/bin/ci/before_install_oracle.sh @@ -0,0 +1,15 @@ +#!/bin/bash -e +# Install Oracle client libraries for SOCI at travis-ci.org +# +# Copyright (c) 2013 Mateusz Loskot +# +source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh + +sudo apt-get install -qq tar bzip2 libaio1 + +wget http://brzuchol.loskot.net/software/oracle/instantclient_11_2-linux-x64-mloskot.tar.bz2 +tar -jxf instantclient_11_2-linux-x64-mloskot.tar.bz2 +sudo mkdir -p /opt +sudo mv instantclient_11_2 /opt +sudo ln -s ${ORACLE_HOME}/libclntsh.so.11.1 ${ORACLE_HOME}/libclntsh.so +sudo ln -s ${ORACLE_HOME}/libocci.so.11.1 ${ORACLE_HOME}/libocci.so diff --git a/bin/ci/before_script.sh b/bin/ci/before_script.sh new file mode 100755 index 0000000000..4fe32bc924 --- /dev/null +++ b/bin/ci/before_script.sh @@ -0,0 +1,9 @@ +#!/bin/bash -e +# Run before_script actions for SOCI build at travis-ci.org +# +# Copyright (c) 2013 Mateusz Loskot +# +source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh + +before_script="${TRAVIS_BUILD_DIR}/bin/ci/before_script_${SOCI_TRAVIS_BACKEND}.sh" +[ -x ${before_script} ] && ${before_script} || echo "nothing to run" diff --git a/bin/ci/before_script_db2.sh b/bin/ci/before_script_db2.sh new file mode 100755 index 0000000000..9480c4d50a --- /dev/null +++ b/bin/ci/before_script_db2.sh @@ -0,0 +1,10 @@ +#!/bin/bash -e +# Sets up environment for SOCI backend DB2 at travis-ci.org +# +# Copyright (c) 2013 Brian R. Toonen +# Copyright (c) 2013 Mateusz Loskot +# +source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh + +sudo -u db2inst1 -i db2 "CREATE DATABASE SOCITEST" +sudo -u db2inst1 -i db2 "ACTIVATE DATABASE SOCITEST" diff --git a/bin/ci/before_script_firebird.sh b/bin/ci/before_script_firebird.sh new file mode 100755 index 0000000000..35954571a4 --- /dev/null +++ b/bin/ci/before_script_firebird.sh @@ -0,0 +1,11 @@ +#!/bin/bash -e +# Configure Firebird database for SOCI build at travis-ci.org +# +# Mateusz Loskot , http://github.com/SOCI +# +source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh + +isql-fb -z -q -i /dev/null # --version +echo 'CREATE DATABASE "LOCALHOST:/tmp/soci_test.fdb" PAGE_SIZE = 16384;' > /tmp/create_soci_test.sql +isql-fb -u SYSDBA -p masterkey -i /tmp/create_soci_test.sql -q +cat /tmp/create_soci_test.sql diff --git a/bin/ci/before_script_mysql.sh b/bin/ci/before_script_mysql.sh new file mode 100755 index 0000000000..b32f31492a --- /dev/null +++ b/bin/ci/before_script_mysql.sh @@ -0,0 +1,9 @@ +#!/bin/bash -e +# Sets up environment for SOCI backend MySQL at travis-ci.org +# +# Mateusz Loskot , http://github.com/SOCI +# +source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh + +mysql --version +mysql -e 'create database soci_test;' diff --git a/bin/ci/before_script_odbc.sh b/bin/ci/before_script_odbc.sh new file mode 100755 index 0000000000..56495740d8 --- /dev/null +++ b/bin/ci/before_script_odbc.sh @@ -0,0 +1,11 @@ +#!/bin/bash -e +# Sets up environment for SOCI backend ODBC at travis-ci.org +# +# Mateusz Loskot , http://github.com/SOCI +# +source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh + +mysql --version +mysql -e 'create database soci_test;' +psql --version +psql -c 'create database soci_test;' -U postgres diff --git a/bin/ci/before_script_postgresql.sh b/bin/ci/before_script_postgresql.sh new file mode 100755 index 0000000000..20a2579a51 --- /dev/null +++ b/bin/ci/before_script_postgresql.sh @@ -0,0 +1,9 @@ +#!/bin/bash -e +# Sets up environment for SOCI backend PostgreSQL at travis-ci.org +# +# Mateusz Loskot , http://github.com/SOCI +# +source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh + +psql --version +psql -c 'create database soci_test;' -U postgres diff --git a/bin/ci/common.sh b/bin/ci/common.sh new file mode 100644 index 0000000000..0a643943da --- /dev/null +++ b/bin/ci/common.sh @@ -0,0 +1,36 @@ +#!/bin/bash -e +# Common definitions used by SOCI build scripts at travis-ci.org +# +# Copyright (c) 2013 Mateusz Loskot +# +if [[ "$TRAVIS" != "true" ]] ; then + echo "Running this script makes no sense outside of travis-ci.org" + exit 1 +fi +# +# Environment +# +TCI_NUMTHREADS=2 +if [[ -f /sys/devices/system/cpu/online ]]; then + # Calculates 1.5 times physical threads + TCI_NUMTHREADS=$(( ( $(cut -f 2 -d '-' /sys/devices/system/cpu/online) + 1 ) * 15 / 10 )) +fi +export ORACLE_HOME=/opt/instantclient_11_2 +export LD_LIBRARY_PATH=${ORACLE_HOME}:${LD_LIBRARY_PATH} +# +# Functions +# +tmstamp() +{ + echo -n "[$(date '+%H:%M:%S')]" ; +} + +run_make() +{ + [ $TCI_NUMTHREADS -gt 0 ] && make -j $TCI_NUMTHREADS ] || make +} + +run_test() +{ + ctest -V --output-on-failure . +} diff --git a/bin/ci/script.sh b/bin/ci/script.sh new file mode 100755 index 0000000000..d982aee6e9 --- /dev/null +++ b/bin/ci/script.sh @@ -0,0 +1,14 @@ +#!/bin/bash -e +# Run test script actions for SOCI build at travis-ci.org +# +# Copyright (c) 2013 Mateusz Loskot +# +source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh + +# prepare build directory +builddir="${TRAVIS_BUILD_DIR}/src/_build" +mkdir -p ${builddir} +cd ${builddir} + +# build and run tests +${TRAVIS_BUILD_DIR}/bin/ci/script_${SOCI_TRAVIS_BACKEND}.sh diff --git a/bin/ci/script_db2.sh b/bin/ci/script_db2.sh new file mode 100755 index 0000000000..ed174c154b --- /dev/null +++ b/bin/ci/script_db2.sh @@ -0,0 +1,24 @@ +#!/bin/bash -e +# Builds and tests SOCI backend DB2 at travis-ci.org +# +# Copyright (c) 2013 Brian R. Toonen +# Copyright (c) 2013 Mateusz Loskot +# +source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh + +cmake \ + -DSOCI_TESTS=ON \ + -DSOCI_STATIC=OFF \ + -DSOCI_DB2=ON \ + -DSOCI_EMPTY=OFF \ + -DSOCI_FIREBIRD=OFF \ + -DSOCI_MYSQL=OFF \ + -DSOCI_ODBC=OFF \ + -DSOCI_ORACLE=OFF \ + -DSOCI_POSTGRESQL=OFF \ + -DSOCI_SQLITE3=OFF \ + -DSOCI_DB2_TEST_CONNSTR:STRING="DSN=SOCITEST\;Uid=db2inst1\;Pwd=db2inst1" \ + .. + +run_make +run_test diff --git a/bin/ci/script_empty.sh b/bin/ci/script_empty.sh new file mode 100755 index 0000000000..a93e541eb9 --- /dev/null +++ b/bin/ci/script_empty.sh @@ -0,0 +1,22 @@ +#!/bin/bash -e +# Builds and tests SOCI backend empty at travis-ci.org +# +# Copyright (c) 2013 Mateusz Loskot +# +source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh + +cmake \ + -DSOCI_TESTS=ON \ + -DSOCI_STATIC=OFF \ + -DSOCI_DB2=OFF \ + -DSOCI_EMPTY=ON \ + -DSOCI_FIREBIRD=OFF \ + -DSOCI_MYSQL=OFF \ + -DSOCI_ODBC=OFF \ + -DSOCI_ORACLE=OFF \ + -DSOCI_POSTGRESQL=OFF \ + -DSOCI_SQLITE3=OFF \ + .. + +run_make +run_test diff --git a/bin/ci/script_firebird.sh b/bin/ci/script_firebird.sh new file mode 100755 index 0000000000..7c75fca7ad --- /dev/null +++ b/bin/ci/script_firebird.sh @@ -0,0 +1,23 @@ +#!/bin/bash -e +# Builds and tests SOCI backend Firebird at travis-ci.org +# +# Copyright (c) 2013 Mateusz Loskot +# +source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh + +cmake \ + -DSOCI_TESTS=ON \ + -DSOCI_STATIC=OFF \ + -DSOCI_DB2=OFF \ + -DSOCI_EMPTY=OFF \ + -DSOCI_FIREBIRD=ON \ + -DSOCI_MYSQL=OFF \ + -DSOCI_ODBC=OFF \ + -DSOCI_ORACLE=OFF \ + -DSOCI_POSTGRESQL=OFF \ + -DSOCI_SQLITE3=OFF \ + -DSOCI_FIREBIRD_TEST_CONNSTR:STRING="service=LOCALHOST:/tmp/soci_test.fdb user=SYSDBA password=masterkey" \ + .. + +run_make +run_test diff --git a/bin/ci/script_mysql.sh b/bin/ci/script_mysql.sh new file mode 100755 index 0000000000..58d57709b6 --- /dev/null +++ b/bin/ci/script_mysql.sh @@ -0,0 +1,23 @@ +#!/bin/bash -e +# Builds and tests SOCI backend SQLite3 at travis-ci.org +# +# Copyright (c) 2013 Mateusz Loskot +# +source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh + +cmake \ + -DSOCI_TESTS=ON \ + -DSOCI_STATIC=OFF \ + -DSOCI_DB2=OFF \ + -DSOCI_EMPTY=OFF \ + -DSOCI_FIREBIRD=OFF \ + -DSOCI_MYSQL=ON \ + -DSOCI_ODBC=OFF \ + -DSOCI_ORACLE=OFF \ + -DSOCI_POSTGRESQL=OFF \ + -DSOCI_SQLITE3=OFF \ + -DSOCI_MYSQL_TEST_CONNSTR:STRING="db=soci_test" \ + .. + +run_make +run_test diff --git a/bin/ci/script_odbc.sh b/bin/ci/script_odbc.sh new file mode 100755 index 0000000000..79a655e788 --- /dev/null +++ b/bin/ci/script_odbc.sh @@ -0,0 +1,24 @@ +#!/bin/bash -e +# Builds and tests SOCI backend ODBC at travis-ci.org +# +# Copyright (c) 2013 Mateusz Loskot +# +source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh + +cmake \ + -DSOCI_TESTS=ON \ + -DSOCI_STATIC=OFF \ + -DSOCI_DB2=OFF \ + -DSOCI_EMPTY=OFF \ + -DSOCI_FIREBIRD=OFF \ + -DSOCI_MYSQL=OFF \ + -DSOCI_ODBC=ON \ + -DSOCI_ORACLE=OFF \ + -DSOCI_POSTGRESQL=OFF \ + -DSOCI_SQLITE3=OFF \ + -DSOCI_ODBC_TEST_POSTGRESQL_CONNSTR="FILEDSN=${PWD}/../backends/odbc/test/test-postgresql.dsn;" \ + -DSOCI_ODBC_TEST_MYSQL_CONNSTR="FILEDSN=${PWD}/../backends/odbc/test/test-mysql.dsn;" \ + .. + +run_make +run_test diff --git a/bin/ci/script_oracle.sh b/bin/ci/script_oracle.sh new file mode 100755 index 0000000000..743476c893 --- /dev/null +++ b/bin/ci/script_oracle.sh @@ -0,0 +1,30 @@ +#!/bin/bash -e +# Builds and tests SOCI backend Oracle at travis-ci.org +# +# Copyright (c) 2013 Mateusz Loskot +# +source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh + +if [ "${CXX}" == "g++" ] +then + ORACLE_USER="soci_tester" +else + ORACLE_USER="soci_tester1" +fi + +cmake \ + -DSOCI_TESTS=ON \ + -DSOCI_STATIC=OFF \ + -DSOCI_DB2=OFF \ + -DSOCI_EMPTY=OFF \ + -DSOCI_FIREBIRD=OFF \ + -DSOCI_MYSQL=OFF \ + -DSOCI_ODBC=OFF \ + -DSOCI_ORACLE=ON \ + -DSOCI_POSTGRESQL=OFF \ + -DSOCI_SQLITE3=OFF \ + -DSOCI_ORACLE_TEST_CONNSTR:STRING="service=brzuchol.loskot.net user=${ORACLE_USER} password=soci_secret" \ + .. + +run_make +run_test diff --git a/bin/ci/script_postgresql.sh b/bin/ci/script_postgresql.sh new file mode 100755 index 0000000000..2a5194615e --- /dev/null +++ b/bin/ci/script_postgresql.sh @@ -0,0 +1,23 @@ +#!/bin/bash -e +# Builds and tests SOCI backend SQLite3 at travis-ci.org +# +# Copyright (c) 2013 Mateusz Loskot +# +source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh + +cmake \ + -DSOCI_TESTS=ON \ + -DSOCI_STATIC=OFF \ + -DSOCI_DB2=OFF \ + -DSOCI_EMPTY=OFF \ + -DSOCI_FIREBIRD=OFF \ + -DSOCI_MYSQL=OFF \ + -DSOCI_ODBC=OFF \ + -DSOCI_ORACLE=OFF \ + -DSOCI_POSTGRESQL=ON \ + -DSOCI_SQLITE3=OFF \ + -DSOCI_POSTGRESQL_TEST_CONNSTR:STRING="dbname=soci_test user=postgres" \ + .. + +run_make +run_test diff --git a/bin/ci/script_sqlite3.sh b/bin/ci/script_sqlite3.sh new file mode 100755 index 0000000000..1a3de1fe72 --- /dev/null +++ b/bin/ci/script_sqlite3.sh @@ -0,0 +1,23 @@ +#!/bin/bash -e +# Builds and tests SOCI backend SQLite3 at travis-ci.org +# +# Copyright (c) 2013 Mateusz Loskot +# +source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh + +cmake \ + -DSOCI_TESTS=ON \ + -DSOCI_STATIC=OFF \ + -DSOCI_DB2=OFF \ + -DSOCI_EMPTY=OFF \ + -DSOCI_FIREBIRD=OFF \ + -DSOCI_MYSQL=OFF \ + -DSOCI_ODBC=OFF \ + -DSOCI_ORACLE=OFF \ + -DSOCI_POSTGRESQL=OFF \ + -DSOCI_SQLITE3=ON \ + -DSOCI_SQLITE3_TEST_CONNSTR:STRING="soci_test.db" \ + .. + +run_make +run_test diff --git a/bin/vm/debian-oracle10g-install.sh b/bin/vm/debian-oracle10g-install.sh new file mode 100644 index 0000000000..39e8e4be22 --- /dev/null +++ b/bin/vm/debian-oracle10g-install.sh @@ -0,0 +1,89 @@ +#/bin/sh +# Script performs non-interactive instllation of Oracle XE 10g on Debian +# +# Based on oracle10g-update.sh from HTSQL project: +# https://bitbucket.org/prometheus/htsql +# +# Modified by Mateusz Loskot +# Changes: +# - Add fake swap support (backup /usr/bin/free manually anyway!) +# +set -ex + +# +# Utilities +# +function free_backup() +{ + # Multiple copies to be on safe side + cp /usr/bin/free /root + mv /usr/bin/free /usr/bin/free.original +} + +function free_restore() +{ + cp /usr/bin/free.original /usr/bin/free +} + +# Install fake free +# http://www.axelog.de/2010/02/7-oracle-ee-refused-to-install-into-openvz/ +free_backup +cat <> /usr/bin/free +#!/bin/sh +cat <<__eof + total used free shared buffers cached +Mem: 1048576 327264 721312 0 0 0 +-/+ buffers/cache: 327264 721312 +Swap: 2000000 0 2000000 +__eof +exit +EOF +chmod 755 /usr/bin/free + +# Enable HTTPS for APT repositories. +apt-get -q update +apt-get -qy install apt-transport-https + +# Register the Oracle repository. +echo "deb https://oss.oracle.com/debian/ unstable main non-free" >/etc/apt/sources.list.d/oracle.list +wget -q https://oss.oracle.com/el4/RPM-GPG-KEY-oracle -O- | apt-key add - +apt-get -q update + +# Install the Oracle 10g Express Edition. +apt-get -qy install oracle-xe + +# Clean APT cache. +apt-get clean + +# Fix the problem when the configuration script eats the last +# character of the password if it is 'n': replace IFS="\n" with IFS=$'\n'. +sed -i -e s/IFS=\"\\\\n\"/IFS=\$\'\\\\n\'/ /etc/init.d/oracle-xe + +# Configure the server; provide the answers for the following questions: +# The HTTP port for Oracle Application Express: 8080 +# A port for the database listener: 1521 +# The password for the SYS and SYSTEM database accounts: admin +# Start the server on boot: yes +/etc/init.d/oracle-xe configure <>/root/.bashrc + +. /usr/lib/oracle/xe/app/oracle/product/10.2.0/server/bin/oracle_env.sh +END + +free_restore + diff --git a/build/README b/build/README new file mode 100644 index 0000000000..db968b7b2b --- /dev/null +++ b/build/README @@ -0,0 +1,2 @@ +This directory is dedicated for: +- other building systems contributed by SOCI team and users diff --git a/build/unix/build-core.tcl b/build/unix/build-core.tcl new file mode 100644 index 0000000000..aba8d8ec87 --- /dev/null +++ b/build/unix/build-core.tcl @@ -0,0 +1,37 @@ +proc buildCore {} { + global CXXFLAGS + + puts "building static core" + + set cwd [pwd] + cd "../../src/core" + foreach cppFile [glob "*.cpp"] { + execute "g++ -c $cppFile $CXXFLAGS" + } + + execute "ar cr libsoci_core.a [glob *.o]" + cd $cwd + eval exec mkdir -p "lib" + execute "cp ../../src/core/libsoci_core.a lib" + eval exec mkdir -p "include" + execute "cp [glob ../../src/core/*.h] include" +} + +proc buildCoreSo {} { + global CXXFLAGS SHARED + + puts "building shared core" + + set cwd [pwd] + cd "../../src/core" + foreach cppFile [glob "*.cpp"] { + execute "g++ -c $cppFile $CXXFLAGS -fPIC" + } + + execute "g++ $SHARED -o libsoci_core.so [glob *.o]" + cd $cwd + eval exec mkdir -p "lib" + execute "cp ../../src/core/libsoci_core.so lib" + eval exec mkdir -p "include" + execute "cp [glob ../../src/core/*.h] include" +} diff --git a/build/unix/build-mysql.tcl b/build/unix/build-mysql.tcl new file mode 100644 index 0000000000..f1371ae519 --- /dev/null +++ b/build/unix/build-mysql.tcl @@ -0,0 +1,142 @@ +source "local/parameters.tcl" + +proc findMySQL {} { + global mysqlInclude mysqlLib + + # candidate directories for local MySQL: + set includeDirs { + "/usr/local/include/mysql" + "/usr/include/mysql" + "/usr/local/include" + "/usr/include" + "/opt/local/include" + } + set libDirs { + "/usr/local/lib/mysql" + "/usr/lib/mysql" + "/usr/local/lib" + "/usr/lib" + "/opt/local/lib" + } + + if [info exists mysqlInclude] { + set includeDirs [list $mysqlInclude] + } + if [info exists mysqlLib] { + set libDirs [list $mysqlLib] + } + + set includeDir "" + foreach I $includeDirs { + set header "${I}/mysql.h" + if {[file exists $header]} { + set includeDir $I + break + } + } + if {$includeDir == ""} { + return {} + } + + set libDir "" + foreach L $libDirs { + set libraryA "${L}/libmysqlclient.a" + set librarySo "${L}/libmysqlclient.so" + if {[file exists $libraryA] || [file exists $librarySo]} { + set libDir $L + break + } + } + if {$libDir == ""} { + return {} + } + + return [list $includeDir $libDir] +} + +proc buildMySQL {} { + global CXXFLAGS + + puts "building static MySQL" + + set dirs [findMySQL] + if {$dirs == {}} { + puts "cannot find MySQL library files, skipping this target" + return + } + + set includeDir [lindex $dirs 0] + set libDir [lindex $dirs 1] + + set cwd [pwd] + cd "../../src/backends/mysql" + foreach cppFile [glob "*.cpp"] { + execute "g++ -c $cppFile $CXXFLAGS -I../../core -I${includeDir}" + } + + execute "ar cr libsoci_mysql.a [glob *.o]" + cd $cwd + eval exec mkdir -p "lib" + execute "cp ../../src/backends/mysql/libsoci_mysql.a lib" + eval exec mkdir -p "include" + execute "cp ../../src/backends/mysql/soci-mysql.h include" +} + +proc buildMySQLSo {} { + global CXXFLAGS SHARED + + puts "building shared MySQL" + + set dirs [findMySQL] + if {$dirs == {}} { + puts "cannot find MySQL library files, skipping this target" + return + } + + set includeDir [lindex $dirs 0] + set libDir [lindex $dirs 1] + + set cwd [pwd] + cd "../../src/backends/mysql" + foreach cppFile [glob "*.cpp"] { + execute "g++ -c $cppFile $CXXFLAGS -fPIC -I../../core -I${includeDir}" + } + + execute "g++ $SHARED -o libsoci_mysql.so [glob *.o] -L${libDir} -lmysqlclient -lz" + cd $cwd + eval exec mkdir -p "lib" + execute "cp ../../src/backends/mysql/libsoci_mysql.so lib" + eval exec mkdir -p "include" + execute "cp ../../src/backends/mysql/soci-mysql.h include" +} + +proc buildMySQLTest {} { + global CXXTESTFLAGS LDL + + puts "building MySQL test" + + set dirs [findMySQL] + if {$dirs == {}} { + puts "cannot find MySQL library files, skipping this target" + return + } + + set includeDir [lindex $dirs 0] + set libDir [lindex $dirs 1] + + set dirs [findBoost] + if {$dirs == {}} { + puts "cannot find Boost library files, skipping this target" + return + } + + set boostIncludeDir [lindex $dirs 0] + set boostLibDir [lindex $dirs 1] + + set cwd [pwd] + cd "../../src/backends/mysql/test" + execute "g++ test-mysql.cpp -o test-mysql $CXXTESTFLAGS -I.. -I../../../core -I../../../core/test -I${includeDir} -I${boostIncludeDir} -L../../../../build/unix/lib -L${libDir} -L${boostLibDir} -lsoci_core -lsoci_mysql -lboost_date_time ${LDL} -lmysqlclient -lz" + cd $cwd + eval exec mkdir -p "tests" + execute "cp ../../src/backends/mysql/test/test-mysql tests" +} diff --git a/build/unix/build-oracle.tcl b/build/unix/build-oracle.tcl new file mode 100644 index 0000000000..ac59d0b287 --- /dev/null +++ b/build/unix/build-oracle.tcl @@ -0,0 +1,120 @@ +proc findOracle {} { + global env oracleInclude oracleLib + + if {[info exists oracleInclude] && + [info exists oracleLib]} { + set includeDir $oracleInclude + set libDir $oracleLib + } else { + if {[info exists env(ORACLE_HOME)] == 0} { + puts "The ORACLE_HOME variable is not set." + return {} + } + + set ORACLE_HOME $env(ORACLE_HOME) + + set includeDir [file join $ORACLE_HOME "rdbms/public"] + set header [file join $includeDir "oci.h"] + if {[file exists $header] == 0} { + puts "ORACLE_HOME is strange." + return {} + } + + set libDir [file join $ORACLE_HOME "lib"] + set libraryA [file join $libDir "libclntsh.a"] + set librarySo [file join $libDir "libclntsh.so"] + if {([file exists $libraryA] == 0) && ([file exists $librarySo] == 0)} { + puts "ORACLE_HOME is strange." + return {} + } + } + + return [list $includeDir $libDir] +} + +proc buildOracle {} { + global CXXFLAGS + + puts "building static Oracle" + + set dirs [findOracle] + if {$dirs == {}} { + puts "cannot find Oracle library files, skipping this target" + return + } + + set includeDir [lindex $dirs 0] + set libDir [lindex $dirs 1] + + set cwd [pwd] + cd "../../src/backends/oracle" + foreach cppFile [glob "*.cpp"] { + execute "g++ -c $cppFile $CXXFLAGS -I../../core -I${includeDir}" + } + + execute "ar cr libsoci_oracle.a [glob *.o]" + cd $cwd + eval exec mkdir -p "lib" + execute "cp ../../src/backends/oracle/libsoci_oracle.a lib" + eval exec mkdir -p "include" + execute "cp ../../src/backends/oracle/soci-oracle.h include" +} + +proc buildOracleSo {} { + global CXXFLAGS SHARED + + puts "building shared Oracle" + + set dirs [findOracle] + if {$dirs == {}} { + puts "cannot find Oracle library files, skipping this target" + return + } + + set includeDir [lindex $dirs 0] + set libDir [lindex $dirs 1] + + set cwd [pwd] + cd "../../src/backends/oracle" + foreach cppFile [glob "*.cpp"] { + execute "g++ -c $cppFile $CXXFLAGS -fPIC -I../../core -I${includeDir}" + } + + execute "g++ $SHARED -o libsoci_oracle.so [glob *.o] -L${libDir} -lclntsh -lnnz10" + cd $cwd + eval exec mkdir -p "lib" + execute "cp ../../src/backends/oracle/libsoci_oracle.so lib" + eval exec mkdir -p "include" + execute "cp ../../src/backends/oracle/soci-oracle.h include" +} + +proc buildOracleTest {} { + global CXXTESTFLAGS LDL + + puts "building Oracle test" + + set dirs [findOracle] + if {$dirs == {}} { + puts "cannot find Oracle library files, skipping this target" + return + } + + set includeDir [lindex $dirs 0] + set libDir [lindex $dirs 1] + + set dirs [findBoost] + if {$dirs == {}} { + puts "cannot find Boost library files, skipping this target" + return + } + + set boostIncludeDir [lindex $dirs 0] + set boostLibDir [lindex $dirs 1] + + set cwd [pwd] + cd "../../src/backends/oracle/test" + execute "g++ test-oracle.cpp -o test-oracle $CXXTESTFLAGS -I.. -I../../../core -I../../../core/test -I${includeDir} -I${boostIncludeDir} -L../../../../build/unix/lib -L${libDir} -L${boostLibDir} -lsoci_core -lsoci_oracle -lboost_date_time ${LDL} -lclntsh -lnnz10" + cd $cwd + eval exec mkdir -p "tests" + execute "cp ../../src/backends/oracle/test/test-oracle tests" +} diff --git a/build/unix/build-postgresql.tcl b/build/unix/build-postgresql.tcl new file mode 100644 index 0000000000..49a4f939ad --- /dev/null +++ b/build/unix/build-postgresql.tcl @@ -0,0 +1,148 @@ +proc findPostgreSQL {} { + global postgresqlInclude postgresqlLib + + # candidate directories for local PostgreSQL: + set includeDirs { + "/usr/local/pgsql/include" + "/usr/local/postgresql/include" + "/usr/local/include/pgsql" + "/usr/local/include/postgresql" + "/usr/include/pgsql" + "/usr/include/postgresql" + "/usr/local/include" + "/usr/include" + "/opt/local/include" + } + set libDirs { + "/usr/local/pgsql/lib" + "/usr/local/postgresql/lib" + "/usr/local/lib/pgsql" + "/usr/local/lib/postgresql" + "/usr/lib/pgsql" + "/usr/lib/postgresql" + "/usr/local/lib" + "/usr/lib" + "/opt/local/lib" + } + + if [info exists postgresqlInclude] { + set includeDirs [list $postgresqlInclude] + } + if [info exists postgresqlLib] { + set libDirs [list $postgresqlLib] + } + + set includeDir "" + foreach I $includeDirs { + set header "${I}/libpq/libpq-fs.h" + if {[file exists $header]} { + set includeDir $I + break + } + } + if {$includeDir == ""} { + return {} + } + + set libDir "" + foreach L $libDirs { + set libraryA "${L}/libpq.a" + set librarySo "${L}/libpq.so" + if {[file exists $libraryA] || [file exists $librarySo]} { + set libDir $L + break + } + } + if {$libDir == ""} { + return {} + } + + return [list $includeDir $libDir] +} + +proc buildPostgreSQL {} { + global CXXFLAGS + + puts "building static PostgreSQL" + + set dirs [findPostgreSQL] + if {$dirs == {}} { + puts "cannot find PostgreSQL library files, skipping this target" + return + } + + set includeDir [lindex $dirs 0] + set libDir [lindex $dirs 1] + + set cwd [pwd] + cd "../../src/backends/postgresql" + foreach cppFile [glob "*.cpp"] { + execute "g++ -c $cppFile $CXXFLAGS -I../../core -I${includeDir}" + } + + execute "ar cr libsoci_postgresql.a [glob *.o]" + cd $cwd + eval exec mkdir -p "lib" + execute "cp ../../src/backends/postgresql/libsoci_postgresql.a lib" + eval exec mkdir -p "include" + execute "cp ../../src/backends/postgresql/soci-postgresql.h include" +} + +proc buildPostgreSQLSo {} { + global CXXFLAGS SHARED + + puts "building shared PostgreSQL" + + set dirs [findPostgreSQL] + if {$dirs == {}} { + puts "cannot find PostgreSQL library files, skipping this target" + return + } + + set includeDir [lindex $dirs 0] + set libDir [lindex $dirs 1] + + set cwd [pwd] + cd "../../src/backends/postgresql" + foreach cppFile [glob "*.cpp"] { + execute "g++ -c $cppFile $CXXFLAGS -fPIC -I../../core -I${includeDir}" + } + + execute "g++ $SHARED -o libsoci_postgresql.so [glob *.o] -L${libDir} -lpq" + cd $cwd + eval exec mkdir -p "lib" + execute "cp ../../src/backends/postgresql/libsoci_postgresql.so lib" + eval exec mkdir -p "include" + execute "cp ../../src/backends/postgresql/soci-postgresql.h include" +} + +proc buildPostgreSQLTest {} { + global CXXTESTFLAGS LDL + + puts "building PostgreSQL test" + + set dirs [findPostgreSQL] + if {$dirs == {}} { + puts "cannot find PostgreSQL library files, skipping this target" + return + } + + set includeDir [lindex $dirs 0] + set libDir [lindex $dirs 1] + + set dirs [findBoost] + if {$dirs == {}} { + puts "cannot find Boost library files, skipping this target" + return + } + + set boostIncludeDir [lindex $dirs 0] + set boostLibDir [lindex $dirs 1] + + set cwd [pwd] + cd "../../src/backends/postgresql/test" + execute "g++ test-postgresql.cpp -o test-postgresql $CXXTESTFLAGS -I.. -I../../../core -I../../../core/test -I${includeDir} -I${boostIncludeDir} -L../../../../build/unix/lib -L${libDir} -L${boostLibDir} -lsoci_core -lsoci_postgresql -lboost_date_time ${LDL} -lpq" + cd $cwd + eval exec mkdir -p "tests" + execute "cp ../../src/backends/postgresql/test/test-postgresql tests" +} diff --git a/build/unix/build-sqlite3.tcl b/build/unix/build-sqlite3.tcl new file mode 100644 index 0000000000..a48d6516bd --- /dev/null +++ b/build/unix/build-sqlite3.tcl @@ -0,0 +1,142 @@ +proc findSqlite3 {} { + global Sqlite3Include Sqlite3Lib + + # candidate directories for local sqlite3: + set includeDirs { + "/usr/local/include" + "/usr/include" + "/opt/local/include" + } + set libDirs { + "/usr/local/lib" + "/usr/lib" + "/opt/local/lib" + } + + if [info exists Sqlite3Include] { + set includeDirs [list $Sqlite3Include] + } + if [info exists Sqlite3Lib] { + set libDirs [list $Sqlite3Lib] + } + + set includeDir "" + foreach I $includeDirs { + set header "${I}/sqlite3.h" + if {[file exists $header]} { + set includeDir $I + break + } + } + if {$includeDir == ""} { + return {} + } + + set libDir "" + foreach L $libDirs { + set libraryA "${L}/libsqlite3.a" + set librarySo "${L}/libsqlite3.so" + set libraryDl "${L}/libsqlite3.dylib" + if {[file exists $libraryA] || [file exists $librarySo] || [file exists $libraryDl]} { + set libDir $L + break + } + } + if {$libDir == ""} { + return {} + } + + return [list $includeDir $libDir] +} + +proc buildSqlite3 {} { + global CXXFLAGS tcl_platform + + puts "building static Sqlite3" + + set dirs [findSqlite3] + if {$dirs == {}} { + puts "cannot find Sqlite3 library files, skipping this target" + return + } + + set includeDir [lindex $dirs 0] + set libDir [lindex $dirs 1] + + set cwd [pwd] + cd "../../src/backends/sqlite3" + foreach cppFile [glob "*.cpp"] { + execute "g++ -c $cppFile $CXXFLAGS -I../../core -I${includeDir}" + } + + execute "ar cr libsoci_sqlite3.a [glob *.o]" + if {$tcl_platform(os) == "Darwin"} { + # special case for Mac OS X + execute "ranlib libsoci_sqlite3.a" + } + cd $cwd + eval exec mkdir -p "lib" + execute "cp ../../src/backends/sqlite3/libsoci_sqlite3.a lib" + eval exec mkdir -p "include" + execute "cp ../../src/backends/sqlite3/soci-sqlite3.h include" + +} + +proc buildSqlite3So {} { + global CXXFLAGS SHARED + + puts "building shared Sqlite3" + + set dirs [findSqlite3] + if {$dirs == {}} { + puts "cannot find Sqlite3 library files, skipping this target" + return + } + + set includeDir [lindex $dirs 0] + set libDir [lindex $dirs 1] + + set cwd [pwd] + cd "../../src/backends/sqlite3" + foreach cppFile [glob "*.cpp"] { + execute "g++ -c $cppFile $CXXFLAGS -fPIC -I../../core -I${includeDir}" + } + + execute "g++ $SHARED -o libsoci_sqlite3.so [glob *.o] -L${libDir} -lsqlite3" + cd $cwd + eval exec mkdir -p "lib" + execute "cp ../../src/backends/sqlite3/libsoci_sqlite3.so lib" + eval exec mkdir -p "include" + execute "cp ../../src/backends/sqlite3/soci-sqlite3.h include" +} + +proc buildSqlite3Test {} { + global CXXTESTFLAGS LDL + + puts "building Sqlite3 test" + + set dirs [findSqlite3] + if {$dirs == {}} { + puts "cannot find Sqlite3 library files, skipping this target" + return + } + + set includeDir [lindex $dirs 0] + set libDir [lindex $dirs 1] + + set dirs [findBoost] + if {$dirs == {}} { + puts "cannot find Boost library files, skipping this target" + return + } + + set boostIncludeDir [lindex $dirs 0] + set boostLibDir [lindex $dirs 1] + + set cwd [pwd] + cd "../../src/backends/sqlite3/test" + execute "g++ test-sqlite3.cpp -o test-sqlite3 $CXXTESTFLAGS -I.. -I../../../core -I../../../core/test -I${includeDir} -I${boostIncludeDir} -L../../../../build/unix/lib -L${libDir} -L${boostLibDir} -lsoci_core -lsoci_sqlite3 -lboost_date_time ${LDL} -lsqlite3" + cd $cwd + eval exec mkdir -p "tests" + execute "cp ../../src/backends/sqlite3/test/test-sqlite3 tests" +} diff --git a/build/unix/build.tcl b/build/unix/build.tcl new file mode 100755 index 0000000000..7ec3d32108 --- /dev/null +++ b/build/unix/build.tcl @@ -0,0 +1,87 @@ +#!/usr/bin/tclsh + +# some common compilation settings if you need to change them: + +if [info exists env(CXXFLAGS)] { + set CXXFLAGS $env(CXXFLAGS) +} else { + set CXXFLAGS "-Wall -pedantic -Wno-long-long -O2" +} + +set CXXTESTFLAGS "-O2" + +if {$tcl_platform(os) == "Darwin"} { + # special case for Mac OS X + set SHARED "-dynamiclib -flat_namespace -undefined suppress" +} else { + set SHARED "-shared" +} + +if {$tcl_platform(os) == "FreeBSD"} { + # FreeBSD does not have the libdl library, it is part of libc. + set LDL "" +} else { + set LDL "-ldl" +} + +source "execute.tcl" +source "find-boost.tcl" +source "build-core.tcl" +source "build-oracle.tcl" +source "build-postgresql.tcl" +source "build-mysql.tcl" + +proc printUsageAndExit {} { + puts "Usage:" + puts "$ ./build.tcl list-of-targets" + puts "" + puts "list of targets can contain any of:" + puts "core - the core part of the library (static version)" + puts "core-so - the core part of the library (shared version)" + puts "oracle - the static Oracle backend" + puts "oracle-so - the shared Oracle backend" + puts " Note: before building Oracle backend" + puts " set the ORACLE_HOME variable properly." + puts "postgresql - the static PostgreSQL backend" + puts "postgresql-so - the shared PostgreSQL backend" + puts "mysql - the static MySQL backend" + puts "mysql-so - the shared MySQL backend" + puts "" + puts "oracle-test - the test for Oracle" + puts "postgresql-test - the test for PostgreSQL" + puts "mysql-test - the test for MySQL" + puts " Note: build static core and backends first." + puts "" + puts "Examples:" + puts "" + puts "$ ./build.tcl core mysql" + puts "" + puts "$ ./build.tcl core postgresql postgresql-test" + puts "" + puts "After successful build the results are in include, lib and test directories." + puts "Move/copy the contents of these directories wherever you want." + exit +} + +if {$argc == 0 || $argv == "--help"} { + printUsageAndExit +} + +foreach target $argv { + switch -exact $target { + core buildCore + core-so buildCoreSo + oracle buildOracle + oracle-so buildOracleSo + oracle-test buildOracleTest + postgresql buildPostgreSQL + postgresql-so buildPostgreSQLSo + postgresql-test buildPostgreSQLTest + mysql buildMySQL + mysql-so buildMySQLSo + mysql-test buildMySQLTest + default { + puts "unknown target $target - skipping" + } + } +} diff --git a/build/unix/execute.tcl b/build/unix/execute.tcl new file mode 100644 index 0000000000..90d5748572 --- /dev/null +++ b/build/unix/execute.tcl @@ -0,0 +1,14 @@ +proc execute {command} { + puts $command + set result [catch {eval exec $command "2>@ stdout"} output] + if {$result != 0} { + puts "The last command did not execute properly:" + puts $output + puts "Please contact the SOCI team." + exit + } else { + if {$output != ""} { + puts $output + } + } +} diff --git a/build/unix/find-boost.tcl b/build/unix/find-boost.tcl new file mode 100644 index 0000000000..2904ac3d78 --- /dev/null +++ b/build/unix/find-boost.tcl @@ -0,0 +1,47 @@ +proc findBoost {} { + global rootBoost + + # candidate directories for local Boost: + set includeDirs { + "/usr/local/include" + "/usr/include" + "/opt/local/include" + } + set libDirs { + "/usr/local/lib" + "/usr/lib" + "/opt/local/lib" + } + + if [info exists rootBoost] { + set includeDirs [list $rootBoost] + set libDirs [list $rootBoost] + } + + set includeDir "" + foreach I $includeDirs { + set header "${I}/boost/version.hpp" + if {[file exists $header]} { + set includeDir $I + break + } + } + if {$includeDir == ""} { + return {} + } + + set libDir "" + foreach L $libDirs { + set libraryA "${L}/libboost_date_time.a" + set librarySo "${L}/libboost_date_time.so" + if {[file exists $libraryA] || [file exists $librarySo]} { + set libDir $L + break + } + } + if {$libDir == ""} { + return {} + } + + return [list $includeDir $libDir] +} diff --git a/build/unix/install.tcl b/build/unix/install.tcl new file mode 100644 index 0000000000..e11eda5bb8 --- /dev/null +++ b/build/unix/install.tcl @@ -0,0 +1,74 @@ +set headerInstallPrefix "/usr/local/include/soci" +set libInstallPrefix "/usr/local/lib" +set sociVersion "3.0.0" +set sociMajor "3" + +source "execute.tcl" +source "local/parameters.tcl" + +if [info exists env(DESTDIR)] { + set DESTDIR $env(DESTDIR) + set headerInstallPrefix [file normalize ${DESTDIR}/${headerInstallPrefix}] + set libInstallPrefix [file normalize ${DESTDIR}/${libInstallPrefix}] +} + +set uninstallFile [open "local/uninstall.sh" "w"] + +if {[file exists $headerInstallPrefix] == 0} { + execute "mkdir -p $headerInstallPrefix" + puts $uninstallFile "rm -rf $headerInstallPrefix" +} + +foreach header [glob "include/*"] { + set tail [file tail $header] + puts "copying $tail to ${headerInstallPrefix}" + execute "cp $header $headerInstallPrefix" + puts $uninstallFile "rm -f ${headerInstallPrefix}/${tail}" +} + +if {[file exists $libInstallPrefix] == 0} { + execute "mkdir -p $libInstallPrefix" + puts $uninstallFile "rm -rf $libInstallPrefix" +} + +foreach lib [glob "lib/*.a"] { + set tail [file tail $lib] + puts "copying $tail to ${libInstallPrefix}" + execute "cp $lib $libInstallPrefix" + puts $uninstallFile "rm -f ${libInstallPrefix}/${tail}" +} + +set buildDir [pwd] +cd $libInstallPrefix +foreach lib [glob "${buildDir}/lib/*.so"] { + set rootName [file rootname [file tail $lib]] + set targetName "${rootName}-${sociVersion}.so" + set majorLink "${rootName}-${sociMajor}.so" + set link "${rootName}.so" + + puts "copying [file tail $lib] to ${targetName}" + execute "cp $lib $targetName" + puts $uninstallFile "rm -f ${libInstallPrefix}/${targetName}" + + puts "creating link ${majorLink}" + execute "ln -s $targetName [file tail $majorLink]" + puts $uninstallFile "rm -f ${libInstallPrefix}/${majorLink}" + + puts "creating ${link}" + execute "ln -s $targetName [file tail $link]" + puts $uninstallFile "rm -f ${libInstallPrefix}/${link}" +} + +close $uninstallFile + +puts "ldconfig ${libInstallPrefix}" +catch { eval exec "ldconfig ${libInstallPrefix}" } + +puts "" +puts "" +puts "Hint: the shared libraries were installed in $libInstallPrefix" +puts "- If you use dynamically loaded backends, then you might need to set" +puts " the SOCI_BACKENDS_PATH variable accordingly." +puts "" +puts "Hint: to remove all installed files and links run make uninstall" +puts "" diff --git a/build/unix/parse-parameters.tcl b/build/unix/parse-parameters.tcl new file mode 100644 index 0000000000..574a6f60e6 --- /dev/null +++ b/build/unix/parse-parameters.tcl @@ -0,0 +1,60 @@ +set paramsFile [open "local/parameters"] +set tclParamsFile [open "local/parameters.tcl" "w"] + +set line [gets $paramsFile] +while {![eof $paramsFile]} { + set pair [split $line "="] + if {[llength $pair] == 2} { + set name [lindex $pair 0] + set value [lindex $pair 1] + + switch -exact -- $name { + --include-prefix { + puts $tclParamsFile "set headerInstallPrefix $value" + puts "setting prefix for SOCI headers to $value" + } + --lib-prefix { + puts $tclParamsFile "set libInstallPrefix $value" + puts "setting prefix for SOCI libraries to $value" + } + --mysql-include { + puts $tclParamsFile "set mysqlInclude $value" + puts "setting include directory for MySQL to $value" + } + --mysql-lib { + puts $tclParamsFile "set mysqlLib $value" + puts "setting lib directory for MySQL to $value" + } + --oracle-include { + puts $tclParamsFile "set oracleInclude $value" + puts "setting include directory for Oracle to $value" + } + --oracle-lib { + puts $tclParamsFile "set oracleLib $value" + puts "setting lib directory for Oracle to $value" + } + --postgresql-include { + puts $tclParamsFile "set postgresqlInclude $value" + puts "setting include directory for PostgreSQL to $value" + } + --postgresql-lib { + puts $tclParamsFile "set postgresqlLib $value" + puts "setting lib directory for PostgreSQL to $value" + } + --boost-include { + puts $tclParamsFile "set boostInclude $value" + puts "setting Boost include directory to $value" + } + --boost-lib { + puts $tclParamsFile "set boostLib $value" + puts "setting Boost lib directory to $value" + } + default { + puts "unknown option: $name : skipping it!" + } + } + } + set line [gets $paramsFile] +} +close $paramsFile +close $tclParamsFile diff --git a/doc/backends.html b/doc/backends.html new file mode 100644 index 0000000000..869fd5ca6d --- /dev/null +++ b/doc/backends.html @@ -0,0 +1,499 @@ + + + + + + SOCI - backends + + + + + +

Backends reference

+ +

This part of the documentation is provided for those who want to +write (and contribute!) their own backends. It is anyway recommended +that authors of new backend see the code of some existing backend for +hints on how things are really done.

+ +

The backend interface is a set of base classes that the actual backends +are supposed to specialize. The main SOCI interface uses only the +interface and respecting the protocol (for example, the order of +function calls) described here. Note that both the interface and the +protocol were initially designed with the Oracle database in mind, +which means +that whereas it is quite natural with respect to the way Oracle API +(OCI) works, it might impose some implementation burden on other +backends, where things are done differently and therefore have to be +adjusted, cached, converted, etc.

+ +

The interface to the common SOCI interface is defined in the core/soci-backend.h +header file. This file is dissected below.

+ +

All names are defined in either soci or soci::details +namespace.

+ +
+// data types, as seen by the user
+enum data_type
+{
+    dt_string, dt_date, dt_double, dt_integer, dt_long_long, dt_unsigned_long_long
+};
+
+// the enum type for indicator variables
+enum indicator { i_ok, i_null, i_truncated };
+
+// data types, as used to describe exchange format
+enum exchange_type
+{
+    x_char,
+    x_stdstring,
+    x_short,
+    x_integer,
+    x_long_long,
+    x_unsigned_long_long,
+    x_double,
+    x_stdtm,
+    x_statement,
+    x_rowid,
+    x_blob
+};
+
+struct cstring_descriptor
+{
+    cstring_descriptor(char * str, std::size_t bufSize)
+        : str_(str), bufSize_(bufSize) {}
+
+    char * str_;
+    std::size_t bufSize_;
+};
+
+// actually in error.h:
+class soci_error : public std::runtime_error
+{
+public:
+    soci_error(std::string const & msg);
+};
+
+ +

The data_type enumeration type defines all types that +form the core type support for SOCI. The enum itself can be used by +clients when dealing with dynamic rowset description.

+ +

The indicator enumeration type defines all recognized states of data. +The i_truncated +state is provided for the case where the string is retrieved from the +database into the char buffer that is not long enough to hold the whole +value.

+ +

The exchange_type enumeration type defines all possible +types that can be used with the into and use +elements.

+ +

The cstring_descriptor is a helper class that allows to +store the address of char buffer together with its size. +The objects of this class are passed to the backend when the x_cstring +type is involved.

+ +

The soci_error class is an exception type used for +database-related (and +also usage-related) errors. The backends should throw exceptions of +this or derived type only.

+ +
+class standard_into_type_backend
+{
+public:
+    standard_into_type_backend() {}
+    virtual ~standard_into_type_backend() {}
+
+    virtual void define_by_pos(int& position, void* data, exchange_type type) = 0;
+
+    virtual void pre_fetch() = 0;
+    virtual void post_fetch(bool gotData, bool calledFromFetch, indicator* ind) = 0;
+
+    virtual void clean_up() = 0;
+};
+
+ +

The standard_into_type_back_end class implements the dynamic +interactions with the simple (non-bulk) into elements. +The objects of this class (or, rather, of the derived class implemented +by the actual backend) are created by the statement +object when the into element is bound - in terms of +lifetime management, statement is the master of this +class.

+
    +
  • define_by_pos - Called when the into +element is bound, once and before the statement is executed. The data +pointer points to the variable used for into element (or +to the cstring_descriptor object, which is artificially +created when the plain char buffer is used for data +exchange). The position parameter is a "column number", +assigned by +the library. The backend should increase this parameter, according to +the number of fields actually taken (usually 1).
  • +
  • pre_fetch - Called before each row is fetched.
  • +
  • post_fetch - Called after each row is fetched. The gotData +parameter is true if the fetch operation really retrieved +some data and false otherwise; calledFromFetch +is true when the call is from the fetch operation and false +if it is from the execute operation (this is also the case for simple, +one-time queries). In particular, (calledFromFetch && +!gotData) indicates that there is an end-of-rowset condition. ind +points to the indicator provided by the user, or is NULL, +if there is no indicator.
  • +
  • clean_up - Called once when the statement is +destroyed.
  • +
+ +

The intended use of pre_fetch and post_fetch +functions is to manage any internal buffer and/or data conversion for +each value retrieved from the database. If the given server supports +binary data transmission and the data format for the given type agrees +with what is used on the client machine, then these two functions need +not do anything; otherwise buffer management and data conversions +should go there.

+ +
+class vector_into_type_backend
+{
+public:
+    vector_into_type_backend() {}
+    virtual ~vector_into_type_backend() {}
+
+    virtual void define_by_pos(int& position, void* data, exchange_type type) = 0;
+
+    virtual void pre_fetch() = 0;
+    virtual void post_fetch(bool gotData, indicator* ind) = 0;
+
+    virtual void resize(std::size_t sz) = 0;
+    virtual std::size_t size() = 0;
+
+    virtual void clean_up() = 0;
+};
+
+ +

The vector_into_type_back_end has similar structure and +purpose as the previous one, but is used for vectors (bulk data +retrieval).

+ +

The data pointer points to the variable of type std::vector<T> +(and not to its internal buffer), resize +is supposed to really resize the user-provided vector and size +is supposed to return the current size of this vector. +The important difference with regard to the previous class is that ind +points (if not NULL) to the beginning of the array of indicators. The backend +should fill this array according to the actual state of the retrieved +data.

+ +
+class standard_use_type_backend
+{
+public:
+    virtual ~standard_use_type_backend() {}
+
+    virtual void bind_by_pos(int& position,
+        void* data, exchange_type type, bool readOnly) = 0;
+    virtual void bind_by_name(std::string const& name,
+        void* data, exchange_type type, bool readOnly) = 0;
+
+    virtual void pre_use(indicator const* ind) = 0;
+    virtual void post_use(bool gotData, indicator* ind) = 0;
+
+    virtual void clean_up() = 0;
+};
+
+ +

The standard_use_type_backend implements the interactions +with the simple (non-bulk) use elements, created and +destroyed by the statement object.

+
    +
  • bind_by_pos - Called for each use +element, once and before the statement is executed - for those use +elements that do not provide explicit names for parameter binding. The +meaning of parameters is same as in previous classes.
  • +
  • bind_by_name - Called for those use +elements that provide the explicit name.
  • +
  • pre_use - Called before the data is transmitted to +the server (this means before the statement is executed, which can +happen many times for the prepared statement). ind points +to the indicator provided by the user (or is NULL).
  • +
  • post_use - Called after statement execution. gotData +and ind have the same meaning as in standard_into_type_back_end::post_fetch, +and this can be used by those backends whose respective servers support +two-way data exchange (like in/out parameters in stored procedures).
  • +
+ +

The intended use for pre_use and post_use +methods is to manage any internal buffers and/or data conversion. They +can be called many times with the same statement.

+ +
+class vector_use_type_backend
+{
+public:
+    virtual ~vector_use_type_backend() {}
+
+    virtual void bind_by_pos(int& position,
+        void* data, exchange_type type) = 0;
+    virtual void bind_by_name(std::string const& name,
+        void* data, exchange_type type) = 0;
+
+    virtual void pre_use(indicator const* ind) = 0;
+
+    virtual std::size_t size() = 0;
+
+    virtual void clean_up() = 0;
+};
+
+ +

Objects of this type (or rather of type derived from this one) are used +to implement interactions with user-provided vector (bulk) use +elements and are managed by the statement object. +The data pointer points to the whole vector object +provided by the user (and not to +its internal buffer); ind points to the beginning of the +array of indicators (or is NULL). The meaning of this +interface is analogous to those presented above.

+ +
+class statement_backend
+{
+public:
+    statement_backend() {}
+    virtual ~statement_backend() {}
+
+    virtual void alloc() = 0;
+    virtual void clean_up() = 0;
+
+    virtual void prepare(std::string const& query, statement_type eType) = 0;
+
+    enum exec_fetch_result
+    {
+        ef_success,
+        ef_no_data
+    };
+
+    virtual exec_fetch_result execute(int number) = 0;
+    virtual exec_fetch_result fetch(int number) = 0;
+
+    virtual long long get_affected_rows() = 0;
+    virtual int get_number_of_rows() = 0;
+
+    virtual std::string rewrite_for_procedure_call(std::string const& query) = 0;
+
+    virtual int prepare_for_describe() = 0;
+    virtual void describe_column(int colNum, data_type& dtype,
+        std::string& column_name) = 0;
+
+    virtual standard_into_type_backend* make_into_type_backend() = 0;
+    virtual standard_use_type_backend* make_use_type_backend() = 0;
+    virtual vector_into_type_backend* make_vector_into_type_backend() = 0;
+    virtual vector_use_type_backend* make_vector_use_type_backend() = 0;
+};
+
+ +

The statement_backend type implements the internals of the +statement objects. The objects of this class are created by the session +object.

+
    +
  • alloc - Called once to allocate everything that is +needed for the statement to work correctly.
  • +
  • clean_up - Supposed to clean up the resources, called +once.
  • +
  • prepare - Called once with the text of the SQL +query. For servers that support explicit query preparation, this is the +place to do it.
  • +
  • execute - Called to execute the query; if number is +zero, the intent is not to exchange data with the user-provided objects +(into and use elements); positive values +mean the number of rows to exchange (more than 1 is used only for bulk +operations).
  • +
  • fetch - Called to fetch next bunch of rows; number +is positive and determines the requested number of rows (more than 1 is +used only for bulk operations).
  • +
  • get_affected_rows - Called to determine the actual +number of rows affected by data modifying statement.
  • +
  • get_number_of_rows - Called to determine the actual +number of rows retrieved by the previous call to execute +or fetch.
  • +
  • rewrite_for_procedure_call - Used when the procedure +is used instead of statement, to call the stored +procedure. This function should rewrite the SQL query (if necessary) to +the form that will allow to execute the given procedure. +
  • +
  • prepare_for_describe - Called once when the into +element is used with the row type, which means that +dynamic rowset description should be performed. It is supposed to do +whatever is needed to later describe the column properties and should +return the number of columns.
  • +
  • describe_column - Called once for each column (column +numbers - colNum - start from 1), should fill its +parameters according to the column properties.
  • +
  • make_into_type_backend, make_use_type_backend, + make_vector_into_type_backend, make_vector_use_type_backend +- Called once for each into or use element, +to create the objects of appropriate classes (described above).
  • +
+ +

Notes:

+
    +
  1. Whether the query is executed using the simple one-time syntax or +is prepared, the alloc, prepare and execute +functions are always called, in this order.
  2. +
  3. All into and use elements are bound +(their define_by_pos or bind_by_pos/bind_by_name +functions are called) between +statement preparation and execution. +
  4. +
+ +
+class rowid_backend
+{
+public:
+    virtual ~rowid_backend() {}
+};
+
+ +

The rowid_backend class is a hook for the backends to +provide their own state for the row identifier. It has no functions, +since the only portable interaction with the row identifier object is +to use it with into and use elements.

+ +
+class blob_backend
+{
+public:
+    virtual ~blob_backend() {}
+
+    virtual std::size_t get_len() = 0;
+    virtual std::size_t read(std::size_t offset, char * buf,
+        std::size_t toRead) = 0;
+    virtual std::size_t write(std::size_t offset, char const * buf,
+        std::size_t toWrite) = 0;
+    virtual std::size_t append(char const * buf, std::size_t toWrite) = 0;
+    virtual void trim(std::size_t newLen) = 0;
+};
+
+ +

The blob_backend interface provides the entry points for +the blob methods.

+ +
+class session_backend
+{
+public:
+    virtual ~session_backend() {}
+
+    virtual void begin() = 0;
+    virtual void commit() = 0;
+    virtual void rollback() = 0;
+
+    virtual bool get_next_sequence_value(session&, std::string const&, long&);
+    virtual bool get_last_insert_id(session&, std::string const&, long&);
+
+    virtual std::string get_backend_name() const = 0;
+
+    virtual statement_backend * make_statement_backend() = 0;
+    virtual rowid_backend * make_rowid_backend() = 0;
+    virtual blob_backend * make_blob_backend() = 0;
+};
+
+ +

The object of the class derived from session_backend implements the +internals of the session object.

+
    +
  • begin, commit, rollback +- Forward-called when the same functions of session are +called by user.
  • +
  • get_next_sequence_value, get_last_insert_id +- Called to retrieve sequences or auto-generated values and every backend should +define at least one of them to allow the code using auto-generated values to work. +
  • +
  • make_statement_backend, make_rowid_backend, + make_blob_backend - Called to create respective +implementations for the statement, rowid +and blob classes. +
  • +
+ +
+struct backend_factory
+{
+    virtual ~backend_factory() {}
+
+    virtual details::session_backend * make_session(
+        std::string const& connectString) const = 0;
+};
+
+ +

The backend_factory is a base class for backend-provided +factory class that is able to create valid sessions. The connectString +parameter passed to make_session is provided here by the session +constructor and contains only the backend-related parameters, without the backend name +(if the dynamic backend loading is used).

+ +

The actual backend factory object is supposed to be provided by the +backend implementation and declared in its header file. In addition to this, +the factory_ABC function with the "C" calling convention +and returning the pointer to concrete factory object should be provided, +where ABC is the backend name.

+ +

The following example is taken from soci-postgresql.h, +which declares entities of the PostgreSQL backend:

+ +
+struct postgresql_backend_factory : backend_factory
+{
+    virtual postgresql_session_backend* make_session(
+        std::string const& connectString) const;
+};
+
+extern postgresql_backend_factory const postgresql;
+
+extern "C"
+{
+
+// for dynamic backend loading
+backend_factory const * factory_postgresql();
+
+} // extern "C"
+
+ +

With the above declarations, it is enough to pass the postgresql +factory name to the constructor of the session object, +which will use this factory to create concrete implementations for any +other objects that +are needed, with the help of appropriate make_XYZ +functions. Alternatively, the factory_postgresql function will +be called automatically by the backend loader if the backend name is provided +at run-time instead.

+ +

Note that the backend source code is placed in the backends/name directory (for example, +backends/oracle) and the test driver is in backends/name/test. There is also backends/empty +directory provided as a skeleton for development of new backends and +their tests. It is +recommended that all backends respect naming conventions by just +appending their name to the base-class names. The backend name used for +the global factory object should clearly identify the given +database engine, like oracle, postgresql, mysql, +and so on.

+ + + + + + + + + + + + + diff --git a/doc/backends/db2.html b/doc/backends/db2.html new file mode 100644 index 0000000000..d366518149 --- /dev/null +++ b/doc/backends/db2.html @@ -0,0 +1,120 @@ + + + + + + SOCI - DB2 Backend Reference + + + + + +

DB2 Backend Reference

+ + + +

Prerequisites

+

Supported Versions

+ +

The SOCI DB2 backend .

+ +

Tested Platforms

+ + + + + + + + + + + +
DB2 versionOperating SystemCompiler
-Linux PPC64GCC
9.1LinuxGCC
9.5LinuxGCC
9.7LinuxGCC
10.1LinuxGCC
10.1Windows 8Visual Studio 2012
+ +

Required Client Libraries

+ +

The SOCI DB2 backend requires IBM DB2 Call Level Interface (CLI) library.

+ +

Connecting to the Database

+ +

On Unix, before using the DB2 backend please make sure, that you have sourced +DB2 profile into your environment:

+ +
. ~/db2inst1/sqllib/db2profile
+ +

To establish a connection to the DB2 database, create a session object +using the DB2 backend factory together with the database file name:

+ +
+soci::session sql(soci::db2, "your DB2 connection string here");
+
+ +

SOCI Feature Support

+

Dynamic Binding

+ +

TODO

+ +

Binding by Name

+ +

TODO

+ +

Bulk Operations

+ +

Supported, but with caution as it hasn't been extensively tested.

+ +

Transactions

+ +

Currently, not supported.

+ +

BLOB Data Type

+ +

Currently, not supported.

+ +

Nested Statements

+ +

Nesting statements are not processed by SOCI in any special way and +they work as implemented by the DB2 database.

+ +

Stored Procedures

+ +

Stored procedures are supported, with CALL statement.

+ +

Acessing the native database API

+ +

TODO

+ +

Backend-specific extensions

+ +

None.

+ +

Configuration options

+ +

None

+ + + + diff --git a/doc/backends/firebird.html b/doc/backends/firebird.html new file mode 100644 index 0000000000..c1deafb20c --- /dev/null +++ b/doc/backends/firebird.html @@ -0,0 +1,240 @@ + + + + + + SOCI - Firebird Backend Reference + + + + + +

Firebird Backend Reference

+ + + +

Prerequisites

+

Supported Versions

+ +

The SOCI Firebird backend is currently supported for use with Firebird 1.5.
+Other versions of Firebird may work as well, but they have not been tested by the SOCI team.

+ +

Tested Platforms

+ + + + + + + + +
Firebird versionOperating SystemCompiler
1.5.2.4731SunOS 5.10g++ 3.4.3
1.5.2.4731Windows XPVisual C++ 8.0
1.5.3.4870Windows XPVisual C++ 8.0 Professional
+ +

Required Client Libraries

+ +

The Firebird backend requires Firebird's libfbclient client library.

+ +

Connecting to the Database

+ +

To establish a connection to a Firebird database, create a Session object +using the firebird backend factory together with a connection string:

+ +
+BackEndFactory const &backEnd = firebird;
+Session sql(backEnd, 
+        "service=/usr/local/firbird/db/test.fdb user=SYSDBA password=masterkey");
+
+ +

or simply:

+ +
+Session sql(firebird, 
+        "service=/usr/local/firbird/db/test.fdb user=SYSDBA password=masterkey");
+
+ +

The set of parameters used in the connection string for Firebird is:

+
    +
  • service
  • +
  • user
  • +
  • password
  • +
  • role
  • +
  • charset
  • +
+

The following parameters have to be provided as part of the connection string : +service, user, password. Role and charset parameters are optional.

+ +

Once you have created a Session object as shown above, you can use it to access the database, for example:

+
+int count;
+sql << "select count(*) from user_tables", into(count);
+
+ +

(See the SOCI basics and exchanging data documentation for general information on using the Session class.)

+ +

SOCI Feature Support

+

Dynamic Binding

+ +

The Firebird backend supports the use of the SOCI Row class, which facilitates retrieval of data whose type is not known at compile time.

+ +

When calling Row::get<T>(), the type you should pass as T depends upon the underlying database type. For the Firebird backend, this type mapping is:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Firebird Data TypeSOCI Data TypeRow::get<T> specializations
numeric, decimal
(where scale > 0)
eDoubledouble
numeric, decimal [1]
(where scale = 0)
eInteger, eDoubleint, double
double precision, floateDoubledouble
smallint, integereIntegerint
char, varchareStringstd::string
date, time, timestampeDatestd::tm
+

 [1]  There is also 64bit integer type for larger values which is +currently not supported.

+

(See the dynamic resultset binding documentation for general information on using the Row class.)

+ +

Binding by Name

+ +

In addition to binding by position, the Firebird backend supports binding by name, via an overload of the use() function:

+ +
+int id = 7;
+sql << "select name from person where id = :id", use(id, "id")
+
+ +

It should be noted that parameter binding by name is supported only by means of emulation, since the underlying API used by the backend doesn't provide this feature.

+ +

Bulk Operations

+ +

The Firebird backend has full support for SOCI's bulk operations interface. This feature is also supported by emulation.

+ +

Transactions

+ +

Transactions are also fully +supported by the Firebird backend. In fact, there is always a transaction which is automatically commited in Session's destructor. +
See the Configuration options section for more details.

+ +

BLOB Data Type

+ +

The Firebird backend supports working with data stored in columns of type Blob, via SOCI's BLOB class.

+

It should by noted, that entire Blob data is fetched from database to allow random read and write access. +This is because Firebird itself allows only writing to a new Blob or reading from existing one - +modifications of existing Blob means creating a new one. Firebird backend hides those details from user.

+ +

RowID Data Type

+ +

This feature is not supported by Firebird backend.

+ +

Nested Statements

+ +

This feature is not supported by Firebird backend.

+ +

Stored Procedures

+ +

Firebird stored procedures can be executed by using SOCI's Procedure class.

+ +

Acessing the native database API

+ +

SOCI provides access to underlying datbabase APIs via several getBackEnd() functions, as described in the beyond SOCI documentation.

+ +

The Firebird backend provides the following concrete classes for navite API access:

+ + + + + + + + + + + + + + + + + + + + + + + + +
Accessor FunctionConcrete Class
SessionBackEnd* Session::getBackEnd()FirebirdSessionBackEnd
StatementBackEnd* Statement::getBackEnd()FirebirdStatementBackEnd
BLOBBackEnd* BLOB::getBackEnd()FirebirdBLOBBackEnd
RowIDBackEnd* RowID::getBackEnd()FirebirdRowIDBackEnd
+ + +

Backend-specific extensions

+

FirebirdSOCIError

+ +

The Firebird backend can throw instances of class FirebirdSOCIError, +which is publicly derived from SOCIError and has an +additional public status_ member containing the Firebird status vector.

+ +

Configuration options

+ +

The Firebird backend recognize the following configuration macros :

+
    +
  • SOCI_FIREBIRD_NORESTARTTRANSACTION - + Transactions will not be restarted automatically after commit() or rollback(). + The default is to restart transactions.
  • +
+ + + + diff --git a/doc/backends/index.html b/doc/backends/index.html new file mode 100644 index 0000000000..fc1735cc4e --- /dev/null +++ b/doc/backends/index.html @@ -0,0 +1,126 @@ + + + + + + SOCI - Existing Backends + + + + + +

Existing backends and supported platforms

+ +

Supported Features

+ +

(Follow the links to learn more about each backend.)

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OraclePostgreSQLMySQLSQLite3FirebirdODBCDB2
Binding by NameYESYES (>=8.0)YESYESYESYESYES
Dynamic BindingYESYESYESYESYESYES
Bulk OperationsYESYESYESYESYESYESYES
TransactionsYESYESYES + (with servers that support them, usually >= 4.0)YESYESYESYES
BLOB Data TypeYESYESMySQL's BLOB type is mapped to std::stringYESYESNONO
RowID Data TypeYESYESNONONONONO
Nested StatementsYESNONONONONOYES
Stored ProceduresYESYESNO (but stored functions, YES)NOYESNOYES
+ + + + + + + + + + + + diff --git a/doc/backends/mysql.html b/doc/backends/mysql.html new file mode 100644 index 0000000000..11cf0af15e --- /dev/null +++ b/doc/backends/mysql.html @@ -0,0 +1,261 @@ + + + + + + SOCI - MySQL Backend Reference + + + + + +

MySQL Backend Reference

+ + + +

Prerequisites

+

Supported Versions

+ +

The SOCI MySQL backend should in principle work with every version of MySQL 5.x. +Some of the features (transactions, stored functions) are not available when +MySQL server doesn't support them.

+ +

Tested Platforms

+ + + + + + + + + +
MySQL versionOperating SystemCompiler
5.5.28OS X 10.8.2Apple LLVM version 4.2 +(clang-425.0.24)
5.0.96Ubuntu 8.04.4 LTS (Hardy Heron)g++ (GCC) 4.2.4 (Ubuntu 4.2.4-1ubuntu4)
+ +

Required Client Libraries

+ +

The SOCI MySQL backend requires MySQL's libmysqlclient +client library.

+

Note that the SOCI library itself depends also on libdl, so the minimum set of libraries needed to compile a basic client program is:

+
+-lsoci_core -lsoci_mysql -ldl -lmysqlclient
+
+ + +

Connecting to the Database

+ +

To establish a connection to a MySQL server, create a session object +using the mysql backend factory together with a connection +string:

+ +
+session sql(mysql, "db=test user=root password='Ala ma kota'");
+
+// or:
+session sql("mysql", "db=test user=root password='Ala ma kota'");
+
+// or:
+session sql("mysql://db=test user=root password='Ala ma kota'");
+
+ +

The set of parameters used in the connection string for MySQL is:

+
    +
  • dbname or db or service + (required)
  • +
  • user
  • +
  • password or pass
  • +
  • host
  • +
  • port
  • +
  • unix_socket
  • +
  • sslca
  • +
  • sslcert
  • +
  • local_infile - should be 0 or 1, + 1 means MYSQL_OPT_LOCAL_INFILE will be set.
  • +
  • charset
  • +
+ +

Once you have created a session object as shown above, you +can use it to access the database, for example:

+
+int count;
+sql << "select count(*) from invoices", into(count);
+
+ +

(See the SOCI basics and exchanging data documentation for general information on using the session class.)

+ +

SOCI Feature Support

+

Dynamic Binding

+ +

The MySQL backend supports the use of the SOCI row class, +which facilitates retrieval of data which type is not known at compile +time.

+ +

When calling row::get<T>(), the type you should pass +as T depends upon the underlying database type.
+For the MySQL backend, this type mapping is:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MySQL Data TypeSOCI Data Typerow::get<T> specializations
FLOAT, DOUBLE, DECIMAL and synonymsdt_doubledouble
TINYINT, TINYINT UNSIGNED, SMALLINT, SMALLINT UNSIGNED, INTdt_integerint
INT UNSIGNEDdt_long_longlong long or unsigned
BIGINTdt_long_longlong long
BIGINT UNSIGNEDdt_unsigned_long_longunsigned long long
CHAR, VARCHAR, BINARY, VARBINARY, TINYBLOB, MEDIUMBLOB, BLOB, + LONGBLOB, TINYTEXT, MEDIUMTEXT, TEXT, LONGTEXT, ENUMdt_stringstd::string
TIMESTAMP (works only with MySQL >= 5.0), DATE, + TIME, DATETIMEdt_datestd::tm
+ +

(See the dynamic resultset binding documentation for general information on using the Row class.)

+ +

Binding by Name

+ +

In addition to binding by +position, the MySQL backend supports +binding by name, via an overload +of the use() function:

+ +
+int id = 7;
+sql << "select name from person where id = :id", use(id, "id")
+
+ +

It should be noted that parameter binding of any kind is supported +only by means of emulation, since the underlying API used by the backend +doesn't provide this feature.

+ +

Bulk Operations

+ +

The MySQL backend has full support for SOCI's bulk operations interface. This feature is also supported +by emulation.

+ +

Transactions

+ +

Transactions are also +supported by the MySQL backend. Please note, however, that transactions +can only be used when the MySQL server supports them (it depends on +options used during the compilation of the server; typically, but not +always, servers >=4.0 support transactions and earlier versions do not) +and only with appropriate table types. +

+ +

BLOB Data Type

+ +

SOCI blob interface is not supported by the MySQL backend.

+

Note that this does not mean you cannot use MySQL's BLOB +types. They can be selected using the usual SQL syntax and read into +std::string on the C++ side, so no special interface is +required.

+ +

RowID Data Type

+ +

The rowid functionality is not supported by the MySQL backend.

+ +

Nested Statements

+ +

Nested statements are not supported by the MySQL backend.

+ +

Stored Procedures

+ +

MySQL version 5.0 and later supports two kinds of +stored routines: stored procedures and stored functions +(for details, please consult the +MySQL +documentation). Stored functions can be executed by using +SOCI's procedure class. +There is currently no support for stored procedures.

+ +

Accessing the native database API

+ +

SOCI provides access to underlying datbabase APIs via several get_backend() functions, as described in the Beyond SOCI documentation.

+ +

The MySQL backend provides the following concrete classes for native API access:

+ + + + + + + + + + + + + + + + +
Accessor FunctionConcrete Class
session_backend * session::get_backend()mysql_session_backend
statement_backend * statement::get_backend()mysql_statement_backend
+ + +

Backend-specific extensions

+ +

None.

+ +

Configuration options

+ +

None.

+ + + + diff --git a/doc/backends/odbc.html b/doc/backends/odbc.html new file mode 100644 index 0000000000..12dcf4e538 --- /dev/null +++ b/doc/backends/odbc.html @@ -0,0 +1,288 @@ + + + + + + SOCI - ODBC Backend Reference + + + + + +

ODBC Backend Reference

+ + + +

Prerequisites

+

Supported Versions

+ +

The SOCI ODBC backend is supported for use with ODBC 3.

+ +

Tested Platforms

+ + + + + + + + + + + + +
ODBC versionOperating SystemCompiler
3Linux (Ubuntu 12.04)g++ 4.6.3
3Linux (Ubuntu 12.04)clang 3.2
3.8Windows 8Visual Studio 2012
3Windows 7Visual Studio 2010
3Windows XPVisual Studio 2005 (express)
3Windows XPVisual C++ 8.0 Professional
3Windows XPg++ 3.3.4 (Cygwin)
+ +

Required Client Libraries

+ +

The SOCI ODBC backend requires the ODBC client library.

+ +

Connecting to the Database

+ +

To establish a connection to the ODBC database, create a Session object +using the ODBC backend factory together with a connection string:

+ +
+backend_factory const& backEnd = odbc;
+session sql(backEnd, "filedsn=c:\\my.dsn");
+
+ +

or simply:

+ +
+session sql(odbc, "filedsn=c:\\my.dsn");
+
+ +

The set of parameters used in the connection string for ODBC is the same as accepted by the SQLDriverConnect function from the ODBC library.

+ +

Once you have created a session object as shown above, you can use it to access the database, for example:

+
+int count;
+sql << "select count(*) from invoices", into(count);
+
+ +

(See the SOCI basics and exchanging data documentation for general information on using the session class.)

+ +

SOCI Feature Support

+

Dynamic Binding

+ +

The ODBC backend supports the use of the SOCI row class, which facilitates retrieval of data whose type is not known at compile time.

+ +

When calling row::get<T>(), the type you should pass as T depends upon the underlying database type.
For the ODBC backend, this type mapping is:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ODBC Data TypeSOCI Data Typerow::get<T> specializations
SQL_DOUBLE + , SQL_DECIMAL + , SQL_REAL + , SQL_FLOAT + , SQL_NUMERIC + dt_doubledouble
SQL_TINYINT + , SQL_SMALLINT + , SQL_INTEGER + , SQL_BIGINTdt_integerint
SQL_CHAR, SQL_VARCHARdt_stringstd::string
SQL_TYPE_DATE + , SQL_TYPE_TIME + , SQL_TYPE_TIMESTAMPdt_datestd::tm
+ +

Not all ODBC drivers support all datatypes

+ +

(See the dynamic resultset binding documentation for general information on using the row class.)

+ +

Binding by Name

+ +

In addition to binding by position, the ODBC backend supports binding by name, via an overload of the use() function:

+ +
+int id = 7;
+sql << "select name from person where id = :id", use(id, "id")
+
+ +

Apart from the portable "colon-name" syntax above, which is achieved by rewriting the query string, the backend also supports the ODBC ? syntax:

+ +
+int i = 7;
+int j = 8;
+sql << "insert into t(x, y) values(?, ?)", use(i), use(j);
+
+ +

Bulk Operations

+ +

The ODBC backend has support for SOCI's bulk operations interface. Not all ODBC drivers support bulk operations, the following is a list of some tested backends:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ODBC DriverBulk ReadBulk Insert
MS SQL Server 2005YESYES
MS Access 2003YESNO
PostgresQL 8.1YESYES
MySQL 4.1NONO
+ +

Transactions

+ +

Transactions are also fully supported by the ODBC backend, provided that they are supported by the underlying database.

+ +

BLOB Data Type

+ +

Not currently supported

+ +

RowID Data Type

+ +

Not currently supported

+ +

Nested Statements

+ +

Not currently supported

+ +

Stored Procedures

+ +

Not currently supported

+ +

Acessing the native database API

+ +

SOCI provides access to underlying datbabase APIs via several getBackEnd() functions, as described in the beyond SOCI documentation.

+ +

The ODBC backend provides the following concrete classes for navite API access:

+ + + + + + + + + + + + + + + + + + + + +
Accessor FunctionConcrete Class
session_backend* session::get_backend()odbc_statement_backend
statement_backend* statement::get_backend()odbc_statement_backend
rowid_backend* rowid::get_backend()odbc_rowid_backend
+ + +

Backend-specific extensions

+

odbc_soci_error

+ +

The ODBC backend can throw instances of class odbc_soci_error, +which is publicly derived from soci_error and has +additional public members containing the ODBC error code, the Native database +error code, and the message returned from ODBC:

+ +
+int main()
+{
+    try
+    {
+        // regular code
+    }
+    catch (soci::odbc_soci_error const& e)
+    {
+        cerr << "ODBC Error Code: " << e.odbc_error_code() << endl
+             << "Native Error Code: " << e.native_error_code() << endl
+             << "SOCI Message: " << e.what() << std::endl
+             << "ODBC Message: " << e.odbc_error_message() << endl;
+    }
+   catch (exception const &e)
+    {
+        cerr << "Some other error: " << e.what() << endl;
+    }
+}
+
+ +

get_connection_string()

+

The odbc_session_backend class provides std::string get_connection_string() const method +that returns fully expanded connection string as returned by the SQLDriverConnect function.

+ +

Configuration options

+ +

This backend supports odbc_option_driver_complete option which can be passed to it via connection_parameters class. The value of this option is passed to SQLDriverConnect() function as "driver completion" parameter and so must be one of SQL_DRIVER_XXX values, in the string form. The default value of this option is SQL_DRIVER_PROMPT meaning that the driver will query the user for the user name and/or the password if they are not stored together with the connection. If this is undesirable for some reason, you can use SQL_DRIVER_NOPROMPT value for this option to suppress showing the message box:

+ +
+connection_parameters parameters("odbc", "DSN=mydb");
+parameters.set_option(odbc_option_driver_complete, "0" /* SQL_DRIVER_NOPROMPT */);
+session sql(parameters);
+
+ + + + + diff --git a/doc/backends/oracle.html b/doc/backends/oracle.html new file mode 100644 index 0000000000..48bd41c77e --- /dev/null +++ b/doc/backends/oracle.html @@ -0,0 +1,260 @@ + + + + + + SOCI - Oracle Backend Reference + + + + + +

Oracle Backend Reference

+ + + +

Prerequisites

+

Supported Versions

+ +

The SOCI Oracle backend is currently supported for use with Oracle 10 or later.
+Older versions of Oracle may work as well, but they have not been tested by the SOCI team.

+ +

Tested Platforms

+ + + + + + +
Oracle versionOperating SystemCompiler
10.2.0 (XE)RedHat 5g++ 4.3
+ +

Required Client Libraries

+ +

The SOCI Oracle backend requires Oracle's libclntsh client library. +Depending on the particular system, the libnnz10 library might be needed as well.

+

Note that the SOCI library itself depends also on libdl, so the minimum set of libraries needed to compile a basic client program is:

+
+-lsoci_core -lsoci_oracle -ldl -lclntsh -lnnz10
+
+ +

Connecting to the Database

+ +

To establish a connection to an Oracle database, create a session object +using the oracle backend factory together with a connection string:

+ +
+session sql(oracle, "service=orcl user=scott password=tiger");
+
+// or:
+session sql("oracle", "service=orcl user=scott password=tiger");
+
+// or:
+session sql("oracle://service=orcl user=scott password=tiger");
+
+ +

The set of parameters used in the connection string for Oracle is:

+
    +
  • service
  • +
  • user
  • +
  • password
  • +
  • mode (optional)
  • +
+

The first 3 of these parameters have to be provided as part of the connection string.

+

The mode parameter allows to specify the connection mode and can be any of:

+
    +
  • default (which is assumed if omitted)
  • +
  • sysdba
  • +
  • sysoper
  • +
+ +

Once you have created a session object as shown above, you can use it to access the database, for example:

+
+int count;
+sql << "select count(*) from user_tables", into(count);
+
+ +

(See the SOCI basics and exchanging data documentation for general information on using the session class.)

+ +

SOCI Feature Support

+ +

Dynamic Binding

+ +

The Oracle backend supports the use of the SOCI row class, which facilitates retrieval of data which type is not known at compile time.

+ +

When calling row::get<T>(), the type you should pass as T depends upon the underlying database type.
For the Oracle backend, this type mapping is:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Oracle Data TypeSOCI Data Typerow::get<T> specializations
number (where scale > 0)dt_doubledouble
number
(where scale = 0 and precision ≤ std::numeric_limits<int>::digits10)
dt_integerint
numberdt_long_longlong long
char, varchar, varchar2dt_stringstd::string
datedt_datestd::tm
+ +

(See the dynamic resultset binding documentation for general information on using the row class.)

+ +

Binding by Name

+ +

In addition to binding by position, the Oracle backend supports binding by name, via an overload of the use() function:

+ +
+int id = 7;
+sql << "select name from person where id = :id", use(id, "id")
+
+ +

SOCI's use of ':' to indicate a value to be bound within a SQL string is consistant with the underlying Oracle client library syntax.

+ +

Bulk Operations

+ +

The Oracle backend has full support for SOCI's bulk operations interface.

+ +

Transactions

+ +

Transactions are also fully supported by the Oracle backend, +although transactions with non-default isolation levels have to be managed by explicit SQL statements.

+ +

blob Data Type

+ +

The Oracle backend supports working with data stored in columns of type Blob, via SOCI's blob class.

+ +

rowid Data Type

+ +

Oracle rowid's are accessible via SOCI's rowid class.

+ +

Nested Statements

+ +

The Oracle backend supports selecting into objects of type statement, so that you may work with nested sql statements and PL/SQL cursors:

+ +
+statement stInner(sql);
+statement stOuter = (sql.prepare <<
+    "select cursor(select name from person order by id)"
+    " from person where id = 1",
+    into(stInner));
+stInner.exchange(into(name));
+stOuter.execute();
+stOuter.fetch();
+
+while (stInner.fetch())
+{
+    std::cout << name << '\n';
+}
+
+ +

Stored Procedures

+ +

Oracle stored procedures can be executed by using SOCI's procedure class.

+ +

Acessing the native database API

+ +

SOCI provides access to underlying datbabase APIs via several get_backend() functions, as described in the Beyond SOCI documentation.

+ +

The Oracle backend provides the following concrete classes for navite API access:

+ + + + + + + + + + + + + + + + + + + + + + + + +
Accessor FunctionConcrete Class
session_backend * session::get_backend()oracle_session_backend
statement_backend * statement::get_backend()oracle_statement_backend
blob_backend * blob::get_backend()oracle_blob_backend
rowid_backend * rowid::get_backend()oracle_rowid_backend
+ + +

Backend-specific extensions

+

eracle=soci_error

+ +

The Oracle backend can throw instances of class oracle_soci_error, +which is publicly derived from soci_error and has an +additional public err_num_ member containing the Oracle error code:

+ +
+int main()
+{
+    try
+    {
+        // regular code
+    }
+    catch (oracle_soci_error const & e)
+    {
+        cerr << "Oracle error: " << e.err_num_
+            << " " << e.what() << endl;
+    }
+    catch (exception const &e)
+    {
+        cerr << "Some other error: " << e.what() << endl;
+    }
+}
+
+ + + + diff --git a/doc/backends/postgresql.html b/doc/backends/postgresql.html new file mode 100644 index 0000000000..cee1ef3f87 --- /dev/null +++ b/doc/backends/postgresql.html @@ -0,0 +1,230 @@ + + + + + + SOCI - PostgreSQL Backend Reference + + + + + +

PostgreSQL Backend Reference

+ + + +

Prerequisites

+

Supported Versions

+ +

The SOCI PostgreSQL backend is supported for use with PostgreSQL >= 7.3, although versions older than 8.0 will suffer from limited feature support. See below for details.

+ +

Tested Platforms

+ + + + + + + + + +
PostgreSQL versionOperating SystemCompiler
9.0Mac OS X 10.6.6g++ 4.2
8.4FreeBSD 8.2g++ 4.1
8.4Debian 6g++ 4.3
8.4RedHat 5g++ 4.3
+ +

Required Client Libraries

+ +

The SOCI PostgreSQL backend requires PostgreSQL's libpq client library.

+

Note that the SOCI library itself depends also on libdl, so the minimum set of libraries needed to compile a basic client program is:

+
+-lsoci_core -lsoci_postgresql -ldl -lpq
+
+ +

Connecting to the Database

+ +

To establish a connection to the PostgreSQL database, create a session object +using the postgresql backend factory together with a connection string:

+ +
+session sql(postgresql, "dbname=mydatabase");
+
+// or:
+session sql("postgresql", "dbname=mydatabase");
+
+// or:
+session sql("postgresql://dbname=mydatabase");
+
+ +

The set of parameters used in the connection string for PostgreSQL is the same as accepted by the PQconnectdb function from the libpq library.

+ +

Once you have created a session object as shown above, you can use it to access the database, for example:

+
+int count;
+sql << "select count(*) from invoices", into(count);
+
+ +

(See the SOCI basics and exchanging data documentation for general information on using the session class.)

+ +

SOCI Feature Support

+ +

Dynamic Binding

+ +

The PostgreSQL backend supports the use of the SOCI row class, which facilitates retrieval of data whose type is not known at compile time.

+ +

When calling row::get<T>(), the type you should pass as T depends upon the underlying database type.
For the PostgreSQL backend, this type mapping is:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PostgreSQL Data TypeSOCI Data Typerow::get<T> specializations
numeric, real, doubledt_doubledouble
boolean, smallint, integerdt_integerint
int8dt_long_longlong long
oiddt_integerunsigned long
char, varchar, text, cstring, bpchardt_stringstd::string
abstime, reltime, date, time, timestamp, timestamptz, timetzdt_datestd::tm
+ +

(See the dynamic resultset binding documentation for general information on using the row class.)

+ +

Binding by Name

+ +

In addition to binding by position, the PostgreSQL backend supports binding by name, via an overload of the use() function:

+ +
+int id = 7;
+sql << "select name from person where id = :id", use(id, "id")
+
+ +

Apart from the portable "colon-name" syntax above, which is achieved by rewriting the query string, the backend also supports the PostgreSQL native numbered syntax:

+ +
+int i = 7;
+int j = 8;
+sql << "insert into t(x, y) values($1, $2)", use(i), use(j);
+
+ +

The use of native syntax is not recommended, but can be nevertheless imposed by switching off the query rewriting. This can be achieved by defining the macro SOCI_POSTGRESQL_NOBINDBYNAME and it is actually necessary for PostgreSQL 7.3, in which case binding of use elements is not supported at all. See the Configuration options section for details.

+ +

Bulk Operations

+ +

The PostgreSQL backend has full support for SOCI's bulk operations interface.

+ +

Transactions

+ +

Transactions are also fully supported by the PostgreSQL backend.

+ +

blob Data Type

+ +

The PostgreSQL backend supports working with data stored in columns of type Blob, via SOCI's blob class with the exception that trimming is not supported.

+ +

rowid Data Type

+ +

The concept of row identifier (OID in PostgreSQL) is supported via SOCI's rowid class.

+ +

Nested Statements

+ +

Nested statements are not supported by PostgreSQL backend.

+ +

Stored Procedures

+ +

PostgreSQL stored procedures can be executed by using SOCI's procedure class.

+ +

Acessing the native database API

+ +

SOCI provides access to underlying datbabase APIs via several get_backend() functions, as described in the beyond SOCI documentation.

+ +

The PostgreSQL backend provides the following concrete classes for navite API access:

+ + + + + + + + + + + + + + + + + + + + + + + + +
Accessor FunctionConcrete Class
session_backend * session::get_backend()postgresql_session_backend
statement_backend * statement::get_backend()postgresql_statement_backend
blob_backend * blob::get_backend()postgresql_blob_backend
rowid_backend * rowid::get_backend()postgresql_rowid_backend
+ + +

Backend-specific extensions

+ +

None.

+ +

Configuration options

+ +

To support older PostgreSQL versions, the following configuration macros are recognized:

+
    +
  • SOCI_POSTGRESQL_NOBINDBYNAME - switches off the query rewriting.
  • +
  • SOCI_POSTGRESQL_NOPARAMS - disables support for parameterized queries (binding of use elements), automatically imposes also the SOCI_POSTGRESQL_NOBINDBYNAME macro. It is necessary for PostgreSQL 7.3.
  • +
  • SOCI_POSTGRESQL_NOPREPARE - disables support for separate query preparation, which in this backend is significant only in terms of optimization. It is necessary for PostgreSQL 7.3 and 7.4.
  • +
+ + + + + diff --git a/doc/backends/sqlite3.html b/doc/backends/sqlite3.html new file mode 100644 index 0000000000..51221123a9 --- /dev/null +++ b/doc/backends/sqlite3.html @@ -0,0 +1,207 @@ + + + + + + SOCI - SQLite3 Backend Reference + + + + + +

SQLite3 Backend Reference

+ + + +

Prerequisites

+

Supported Versions

+ +

The SOCI SQLite3 backend is supported for use with SQLite3 >= 3.1

+ +

Tested Platforms

+ + + + + + + + + + + + + + +
SQLite3 versionOperating SystemCompiler
3.5.2Mac OS X 10.5g++ 4.0.1
3.1.3Mac OS X 10.4g++ 4.0.1
3.2.1Linux i686 2.6.10-gentoo-r6g++ 3.4.5
3.3.4Ubuntu 5.1g++ 4.0.2
3.3.4Windows XP(cygwin) g++ 3.3.4
3.3.4Windows XPVisual C++ 2005 Express Edition
3.3.8Windows XPVisual C++ 2005 Professional
3.4.0Windows XP(cygwin) g++ 3.4.4
3.4.0Windows XPVisual C++ 2005 Express Edition
+ +

Required Client Libraries

+ +

The SOCI SQLite3 backend requires SQLite3's libsqlite3 client library.

+ +

Connecting to the Database

+ +

To establish a connection to the SQLite3 database, create a Session object +using the SQLite3 backend factory together with the database file name:

+ +
+session sql(sqlite3, "database_filename");
+
+ +

The only option for the connection string is the name of the file to use as a database.

+ +

Once you have created a session object as shown above, you can use it to access the database, for example:

+
+int count;
+sql << "select count(*) from invoices", into(count);
+
+ +

(See the SOCI basics and exchanging data documentation for general information on using the session class.)

+ +

SOCI Feature Support

+

Dynamic Binding

+ +

The SQLite3 backend supports the use of the SOCI row class, which facilitates retrieval of data whose type is not known at compile time.

+ +

When calling row::get<T>(), the type you should pass as T depends upon the underlying database type.

+ +

For the SQLite3 backend, this type mapping is complicated by the fact the SQLite3 does not enforce types *, and makes no attempt to validate the type names used in table creation or alteration statements. SQLite3 will return the type as a string, SOCI will recognize the following strings and match them the corresponding SOCI types:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SQLite3 Data TypeSOCI Data Typerow::get<T> specializations
*float*dt_oubledouble
*int*dt_integerint
*char*dt_stringstd::string
*date*, *time*dt_datestd::tm
+ +

* There is one case where SQLite3 enforces type. If a column is declared as "integer primary key", then SQLite3 uses that as an alias to the internal ROWID column that exists for every table. Only integers are allowed in this column.

+ +

(See the dynamic resultset binding documentation for general information on using the row class.)

+ +

Binding by Name

+ +

In addition to binding by position, the SQLite3 backend supports binding by name, via an overload of the use() function:

+ +
+int id = 7;
+sql << "select name from person where id = :id", use(id, "id")
+
+ +

The backend also supports the SQLite3 native numbered syntax, "one or more literals can be replace by a parameter "?" or ":AAA" or "@AAA" or "$VVV" where AAA is an alphanumeric identifier and VVV is a variable name according to the syntax rules of the TCL programming language." [1]:

+ +
+int i = 7;
+int j = 8;
+sql << "insert into t(x, y) values(?, ?)", use(i), use(j);
+
+ +

Bulk Operations

+ +

The SQLite3 backend has full support for SOCI's bulk operations interface. However, this support is emulated and is not native.

+ +

Transactions

+ +

Transactions are also fully supported by the SQLite3 backend.

+ +

BLOB Data Type

+ +

The SQLite3 backend supports working with data stored in columns of type Blob, via SOCI's blob class. Because of SQLite3 general typelessness the column does not have to be declared any particular type.

+

RowID Data Type

+ +

In SQLite3 RowID is an integer. "Each entry in an SQLite table has a unique integer key called the "rowid". The rowid is always available as an undeclared column named ROWID, OID, or _ROWID_. If the table has a column of type INTEGER PRIMARY KEY then that column is another an alias for the rowid."[2]

+ +

Nested Statements

+ +

Nested statements are not supported by SQLite3 backend.

+ +

Stored Procedures

+ +

Stored procedures are not supported by SQLite3 backend

+ +

Acessing the native database API

+ +

SOCI provides access to underlying datbabase APIs via several +get_backend() functions, as described in the +beyond SOCI documentation.

+ +

The SQLite3 backend provides the following concrete classes for navite API access:

+ + + + + + + + + + + + + + + + + + + + +
Accessor FunctionConcrete Class
session_backend* session::get_backend()sqlie3_session_backend
statement_backend* statement::get_backend()sqlite3_statement_backend
rowid_backend* rowid::get_backend()sqlite3_rowid_backend
+ + +

Backend-specific extensions

+ +

None.

+ +

Configuration options

+ +

None

+ + + + diff --git a/doc/beyond.html b/doc/beyond.html new file mode 100644 index 0000000000..14a661e0d0 --- /dev/null +++ b/doc/beyond.html @@ -0,0 +1,174 @@ + + + + + + SOCI - beyond standard SQL + + + + + +

Beyond standard SQL

+ +

Sometimes the standard SQL is not enough and database-specific syntax needs +to be used. When possible and practical, SOCI provides wrappers hiding the +differences between the backends and this section describes these wrappers. +And if this is still not enough, you can use the backend-specific methods +directly as described below.

+ +

Getting the number of rows affected by an operation

+ +

It can be useful to know how many rows were affected by the last SQL +statement, most often when using INSERT, UPDATE or +DELETE. SOCI provides statement::get_affected_rows() +method allowing to do this:

+ +
+statement st = (sql.prepare << "update some_table ...");
+st.execute(true);
+
+if ( !st.get_affected_rows() )
+{
+    ... investigate why no rows were modified ...
+}
+
+ +
+

Portability note:

+This method is currently not supported by the Oracle backend. It is however +supported when using Oracle database via ODBC backend.

+
+ + +

Working with sequences

+ +

It is common to have auto-incrementing database fields or fields whose +value come from a sequence. In the latter case you need to retrieve the value +of the field for a new row before inserting it into the database. In the +former case, this is unnecessary but you may still want to know the value +generated by the database, e.g. to use it as a foreign key in another table. +So it would be useful to have a way to obtain the value of such a field. +But, of course, to make life of database programmers more interesting, +different products usually support either autoincrement fields or sequences +but not both -- and they use different syntaxes for them, too. SOCI tries to +help to deal with this unfortunate situation by providing two functions: +session::get_next_sequence_value() and +session::get_last_insert_id.

+ +

If you know which kind of database you use, you may use only one of them: +when working with sequences, the first one allows to generate the next value +in a sequence and when working with autoincrement fields, the second one +retrieves the last value generated for such a field for the given table.

+ +

However if you use multiple SOCI backends or even just a single ODBC +backend but support connecting to databases of different types, you actually +must use both of them in the following way to insert a row:

+ +
+long id;
+statement st;
+if ( sql.get_next_sequence_value("table_sequence", id) )
+{
+    st << "insert into table(id, f1, f2) values(:id, :f1, :f2)",
+        use(id), use(f1), use(f2);
+}
+else
+{
+    // We're not using sequences, so don't specify the value,
+    // it will be automatically generated by the database on insert.
+    st << "insert into table(f1, f2) value(:f1, :f2)",
+        use(f1), use(f2);
+
+    // If the ID used for the above row is needed later, get it:
+    if ( !sql.get_last_insert_id("table", id) )
+        ... unexpected error, handle appropriately ...
+}
+
+ +
+

Portability note:

+These methods are currently only implemented in Firebird and ODBC backends.

+
+ + +

Beyond SOCI API

+ +

As the original name of the library (Simple Oracle Call Interface) +clearly stated, SOCI is intended to be a simple library, targeting the +majority of needs in regular C++ application. We do not claim that +everything can be done with SOCI and it was never the intent of the +library. What is important, though, is that the simplicity of the +library does not prevent the +client applications from reaching into the low-level specifics of each +database backend in order to achieve special configuration or +performance goals.

+ +

Most of the SOCI classes have the getBackEnd method, +which +returns the pointer to the actual backend object that implements the +given functionality. The knowledge of the actual backend allows the +client application to get access to all low-level details that are +involved.

+ +
+blob b(sql);
+
+oracle_session_back_end * sessionBackEnd = static_cast<oracle_session_back_end *>(sql.get_back_end());
+oracle_blob_back_end * blobBackEnd = static_cast<oracle_blob_back_end *>(b.get_back_end());
+
+OCILobDisableBuffering(sessionBackEnd->svchp_, sessionBackEnd->errhp_, blobBackEnd->lobp_);
+
+ +

The above code creates the blob object and uses two calls +to the get_back_end function (on both the session +and the blob objects) to get access to the actual backend +objects. Assuming that it is the "oracle" backend which +is in use, the downcasts allow to access all relevant low-level handles +and use them in the call +to the OCILobDisableBuffering function. This way, the +BLOB handle was configured in a way that the SOCI library alone would +not allow.

+ +
+rowid rid(sql); // sql is a session object
+sql << "select oid from mytable where id = 7", into(rid);
+
+postgresql_rowid_back_end * rbe = static_cast<postgresql_rowid_back_end *>(rid.get_back_end());
+
+unsigned long oid = rbe->value_;
+
+ +

The above example retrieves the rowid ("something" that +identifies the +row in the table) from the table and uses the get_back_end +function to +extract the actual object that implements this functionality. Assuming +that it is the "postgresql" backend which is in use, the +downcast is +performed to use the postgresql_rowid_back_end interface to +get the actual +OID value that is a physical, low-level implementation of row +identifier on PostgreSQL databases.

+ +

In order for any of the above to compile, you have to explicitly #include +the appropriate backend's header file.

+ +

Please see the header file related to the given backend to learn what +low-level handles and descriptors are available.

+ + + + + + + + + + + + diff --git a/doc/boost.html b/doc/boost.html new file mode 100644 index 0000000000..dc441e8b73 --- /dev/null +++ b/doc/boost.html @@ -0,0 +1,106 @@ + + + + + + SOCI - integration with Boost + + + + + +

Integration with Boost

+ +

The SOCI user code can be easily integrated with the Boost library thanks to the very flexible type conversion facility. There are three important Boost types that are supported out of the box.

+ +

boost::optional<T>

+ +

boost::optional<T> provides an alternative way to support the null data condition and as such relieves the user from necessity to handle separate indicator values.

+

The boost::optional<T> objects can be used everywhere where the regular user provided +values are expected.

+

Example:

+ +
+boost::optional<string> name;
+sql << "select name from person where id = 7", into(name);
+
+if (name.is_initialized())
+{
+    // OK, the name was retrieved and is not-null
+    cout << "The name is " << name.get();
+}
+else
+{
+    // the name is null
+}
+
+ +

The boost::optional<T> objects are fully supported for both into and use elements, in both single and vector forms. They can be also used for user-defined data types.

+ +

boost::tuple<T1, ...>

+ +

boost::tuple<T1, ...> allows to work with whole rows of information and in some cases can be more convenient to use than the more dynamically-oriented row type.

+

Example:

+ +
+boost::tuple<string, string, int> person;
+
+sql << "select name, phone, salary from persons where ...",
+    into(person);
+
+ +

Tuples are supported for both into and use elements. They can be used with rowset as well.

+ +

Tuples can be also composed with boost::optional<T>:

+ +
+boost::tuple<string, boost::optional<string>, int> person;
+
+sql << "select name, phone, salary from persons where ...",
+    into(person);
+
+if (person.get<1>().is_initialized())
+{
+    // the given person has a phone number
+}
+else
+{
+    // this person does not have a phone number
+}
+
+ +

boost::fusion::vector<T1, ...>

+ +

The boost::fusion::vector types are supported in the same way as tuples.

+ +

boost::gregorian::date

+ +

The boost::gregorian::date is provided as a conversion for base type std::tm and can be used as a replacement for it.

+ +
+

Optional integration:

+

The integration with Boost types is optional and not enabled by default, which means that SOCI can be compiled and used without any dependency on Boost.

+

In order to enable the support for any of the above types, the user needs to either include one of these headers:

+
+#include <boost-optional.h>
+#include <boost-tuple.h>
+#include <boost-fusion.h>
+#include <boost-gregorian-date.h>
+
+

or to define the SOCI_USE_BOOST macro before including the soci.h main header file. Note that in this case the support for boost::fusion::vector is enabled only if the detected Boost version is at least 1.35.

+
+ + + + + + + + + + + diff --git a/doc/connections.html b/doc/connections.html new file mode 100644 index 0000000000..d12167296a --- /dev/null +++ b/doc/connections.html @@ -0,0 +1,142 @@ + + + + + + SOCI - connections + + + + + +

Connections and simple queries

+ +

Connecting to the database

+ +

The session class encapsulates the database connection +and other backend-related details, which are common to all the +statements +that will be later executed. It has a couple of overloaded constructors.

+ +

The most basic one expects two parameters: +the requested backend factory object and the generic connection string, +which meaning is backend-dependent.

+

Example:

+ +
+session sql(oracle, "service=orcl user=scott password=tiger");
+
+ +

Another example might be:

+ +
+session sql(postgresql, "dbname=mydb");
+
+ +

Above, the sql object is a local (automatic) object +that encapsulates the connection.

+ +

This session constructor either connects successfully, or +throws an exception.

+ +

Another constructor allows to name backends at run-time and supports +the dynamically loadable backends, which have to be compiled as shared libraries. The usage is similar to the above, but instead of providing the factory object, the backend name is expected:

+ +
+session sql("postgresql", "dbname=mydb");
+
+ +

For convenience, the URL-like form that combines both the backend name with connection parameters is supported as well:

+ +
+session sql("postgresql://dbname=mydb");
+
+ +

The last two constructors described above try to locate the shared library with the name libsoci_ABC.so (or libsoci_ABC.dll on Windows), where ABC is the backend name. In the above examples, the expected library name will be libsoci_postgresql.so for Unix-like systems.

+ +

The most general form of the constructor takes a single object of connection_parameters type which contains a pointer to the backend to use, the connection string and also any connection options. Using this constructor is the only way to pass any non-default options to the backend. For example, to suppress any interactive prompts when using ODBC backend you could do:

+
+connection_parameters parameters("odbc", "DSN=mydb");
+parameters.set_option(odbc_option_driver_complete, "0" /* SQL_DRIVER_NOPROMPT */);
+session sql(parameters);
+
+Notice that you need to #include <soci-odbc.h> to obtain the option name declaration. The existing options are described in the backend-specific part of the documentation. + +
+

Environment configuration:

+

The SOCI_BACKENDS_PATH environment variable defines the set of paths where the shared libraries will be searched for. There can be many paths, separated by colons, and they are used from left to right until the library with the appropriate name is found. If this variable is not set or is empty, the current directory is used as a default path for dynamically loaded backends.

+
+ +

The run-time selection of backends is also supported with libraries +linked statically. Each backend provides a separate function of the +form register_factory_name, where +name is a backend name. Thus:

+ +
+extern "C" void register_factory_postgresql();
+// ...
+register_factory_postgresql();
+session sql("postgresql://dbname=mydb");
+
+ +

The above example registers the backend for PostgreSQL and later +creates the session object for that backend. This form is provided for +those projects that prefer static linking but still wish to benefit +from run-time backend selection.

+ +

An alternative way to set up the session is to create it in the disconnected state and connect later:

+ +
+session sql;
+
+// some time later:
+sql.open(postgresql, "dbname=mydb");
+
+// or:
+sql.open("postgresql://dbname=mydb");
+
+// or also:
+connection_parameters parameters("postgresql", "dbname=mydb");
+sql.open(parameters);
+
+ +

The rules for backend naming are the same as with the constructors described above.

+

The session can be also explicitly closed and reconnected, which can help with basic session error recovery. The reconnect function has no parameters and attempts to use the same values as those provided with earlier constructor or open calls.

+ +

See also the page devoted to multithreading for a detailed description of connection pools.

+ +

It is possible to have many active sessions at the same +time, even using different backends.

+ +
+

Portability note:

+

The following backend factories are currently (as of 3.1.0 release) available:

+
    +
  • mysql (requires #include "soci-mysql.h")
  • +
  • oracle (requires #include "soci-oracle.h")
  • +
  • postgresql (requires #include "soci-postgresql.h")
  • +
+

The following backends are also available, with various levels of completeness:

+
    +
  • sqlite3 (requires #include "soci-sqlite3.h")
  • +
  • odbc (requires #include "soci-odbc.h")
  • +
  • firebird (requires #include "soci-firebird.h")
  • +
+
+ + + + + + + + + + + + + diff --git a/doc/errors.html b/doc/errors.html new file mode 100644 index 0000000000..5c147af53f --- /dev/null +++ b/doc/errors.html @@ -0,0 +1,128 @@ + + + + + + SOCI - errors + + + + + +

Errors

+ +

All DB-related errors manifest themselves as exceptions of type soci_error, +which is derived from std::runtime_error.
+This allows to +handle database errors within the standard exception framework:

+ +
+int main()
+{
+    try
+    {
+        // regular code
+    }
+    catch (std::exception const & e)
+    {
+        cerr << "Bang! " << e.what() << endl;
+    }
+}
+
+ +
+

Portability note:

+

The Oracle backend can also throw the instances of the oracle_soci_error, +which is publicly derived from soci_error and has an +additional public err_num_ +member containing the Oracle error code:

+ +
+int main()
+{
+    try
+    {
+        // regular code
+    }
+    catch (soci::oracle_soci_error const & e)
+    {
+        cerr << "Oracle error: " << e.err_num_
+            << " " << e.what() << endl;
+    }
+    catch (soci::exception const & e)
+    {
+        cerr << "Some other error: " << e.what() << endl;
+    }
+}
+
+
+ +
+

Portability note:

+

The MySQL backend can throw instances of the mysql_soci_error, +which is publicly derived from soci_error and has an +additional public err_num_ +member containing the MySQL error code (as returned by +mysql_errno()):

+ +
+int main()
+{
+    try
+    {
+        // regular code
+    }
+    catch (soci::mysql_soci_error const & e)
+    {
+        cerr << "MySQL error: " << e.err_num_
+            << " " << e.what() << endl;
+    }
+    catch (soci::exception const & e)
+    {
+        cerr << "Some other error: " << e.what() << endl;
+    }
+}
+
+
+ +
+

Portability note:

+

The PostgreSQL backend can also throw the instances of the postgresql_soci_error, +which is publicly derived from soci_error and has an +additional public sqlstate() +member function returning the five-character "SQLSTATE" error code:

+ +
+int main()
+{
+    try
+    {
+        // regular code
+    }
+    catch (soci::postgresql_soci_error const & e)
+    {
+        cerr << "PostgreSQL error: " << e.sqlstate()
+            << " " << e.what() << endl;
+    }
+    catch (soci::exception const & e)
+    {
+        cerr << "Some other error: " << e.what() << endl;
+    }
+}
+
+
+ + + + + + + + + + + diff --git a/doc/exchange.html b/doc/exchange.html new file mode 100644 index 0000000000..de9628e727 --- /dev/null +++ b/doc/exchange.html @@ -0,0 +1,758 @@ + + + + + + SOCI - exchanging data + + + + + +

Exchanging data

+ + + +

Binding local data

+ +
+

Note: +The Oracle documentation uses two terms: defining (for +instructing the library where the output data should go) and binding +(for the input data and input/output PL/SQL +parameters). For the sake of simplicity, SOCI uses the term binding +for both of these.

+
+ +

Binding output data

+ +

The into expression is used to add binding information to +the statement:

+ +
+int count;
+sql << "select count(*) from person", into(count);
+
+string name;
+sql << "select name from person where id = 7", into(name);
+
+ +

In the above examples, some data is retrieved from the database and +transmitted into the given local variable.

+ +

There should be as many into elements as there are +expected columns in the result (see dynamic +resultset binding for the exception to this rule).

+ +

Binding input data

+ +

The use expression associates the SQL placeholder (written with colon) with the local data:

+ +
+int val = 7;
+sql << "insert into numbers(val) values(:val)", use(val);
+
+ +

In the above statement, the first "val" is a column name (assuming +that +there is appropriate table numbers with this column), the +second "val" (with colon) is a +placeholder and its name is ignored here, and the third "val" is a name +of local variable.

+ +

To better understand the meaning of each "val" above, consider also:

+ +
+int number = 7;
+sql << "insert into numbers(val) values(:blabla)", use(number);
+
+ +

Both examples above will insert the value of some local variable into +the table numbers - we say that the local variable is used in the SQL statement.

+ +

There should be as many use elements as there are +parameters used in the SQL query.

+ +
+

Portability note:

+

Older versions of the PostgreSQL client API do not allow to use input +parameters at all. In order to compile SOCI with those old client +libraries, define the SOCI_POSTGRESQL_NOPARAMS preprocessor +name passing -DSOCI_POSTGRESQL_NOPARAMS=ON variable to CMake.

+
+ +

Binding by position

+ +

If there is more output or input "holes" in the single statement, it +is possible to use many into and use +expressions, separated by commas, where each expression will be +responsible for the consecutive "hole" in the statement:

+ +
+string firstName = "John", lastName = "Smith";
+int personId = 7;
+
+sql << "insert into person(id, firstname, lastname) values(:id, :fn, :ln)",
+    use(personId), use(firstName), use(lastName);
+
+sql << "select firstname, lastname from person where id = :id",
+    into(firstName), into(lastName), use(personId);
+
+ +

In the code above, the order of "holes" in the SQL statement and the +order of into and use expression should +match.

+ +

Binding by name

+ +

The SQL placeholders that have their names (with colon) can be bound +by name to clearly associate the local variable with the given placeholder.

+ +

This explicit naming allows to use different order of elements:

+ +
+string firstName = "John", lastName = "Smith";
+int personId = 7;
+sql << "insert into person(id, firstname, lastname) values(:id, :fn, :ln)",
+    use(firstName, "fn"), use(lastName, "ln"), use(personId, "id");
+
+ +

or bind the same local data to many "holes" at the same time:

+ +
+string addr = "...";
+sql << "update person"
+       " set mainaddress = :addr, contactaddress = :addr"
+       " where id = 7",
+       use(addr, "addr");
+
+ +
+

Object lifetime and immutability:

+

SOCI assumes that local variables provided as use elements +live at least as long at it takes to execute the whole statement. +In short statement forms like above, the statement is executed sometime +at the end of the full expression and the whole process is driven by the invisible +temporary object handled by the library. If the data provided by user comes +from another temporary variable, it might be possible for the compiler to arrange +them in a way that the user data will be destroyed before the statement will +have its chance to execute, referencing objects that no longer exist:

+
+// dangerous code:
+
+string getNameFromSomewhere();
+
+sql << "insert into person(name) values(:n)",
+    use(getNameFromSomewhere());
+
+

In the above example, the data passed to the database comes from the temporary +variable that is a result of call to getNameFromSomewhere - this +should be avoided and named variables should be used to ensure safe lifetime relations:

+
+// safe code:
+
+string getNameFromSomewhere();
+
+string name = getNameFromSomewhere();
+sql << "insert into person(name) values(:n)",
+    use(name);
+
+ +

It is still possible to provide const data for use elements. +Note that some database servers, like Oracle, allow PL/SQL procedures to modify their +in/out parameters - this is detected by the SOCI library and an error is reported +if the database attempts to modify the use element that holds const +data.

+

The above example can be ultimately written in the following way:

+
+// safe and efficient code:
+
+string getNameFromSomewhere();
+
+const string & name = getNameFromSomewhere();
+sql << "insert into person(name) values(:n)",
+    use(name);
+
+
+ +
+

Portability notes:

+

The PostgreSQL backend allows to use the "native" PostgreSQL way of +naming parameters in the query, which is by numbers like $1, +$2, $3, etc. In fact, the backend rewrites +the given query to the native form - and this is also one of the very few places +where SOCI intrudes into the SQL query. For portability reasons, it is +recommended to use named parameters, as shown in the examples above.
+The query rewriting can be switched off by compiling the backend with +the SOCI_POSTGRESQL_NOBINDBYNAME name defined (pass +-DSOCI_POSTGRESQL_NOBINDBYNAME=ON variable to CMake). +Note that in this case it is also necessary to define SOCI_POSTGRESQL_NOPREPARE +(controlled by CMake variable -DSOCI_POSTGRESQL_NOPREPARE=ON), +because statement preparation relies on successful query rewriting. +In practice, both macros will be needed for PostgreSQL server older than 8.0.

+
+ +

Handling nulls and other conditions

+ +

Indicators

+ +

In order to support null values and other conditions which are not +real errors, the concept of indicator is provided.

+ +

For example, when the following SQL query is executed:

+
+select name from person where id = 7
+
+ +

there are three possible outcomes:

+
    +
  1. there is a person with id = 7 and his name is returned
  2. +
  3. there is a person with id = 7, but he has no name (his name is +null in the database table)
  4. +
  5. there is no such person
  6. +
+ +

Whereas the first alternative is easy to handle, the other two are more +complex. Moreover, they are not necessarily errors from the +application's point of view and what's more interesting, they are different +and the application may wish to detect which is the case.
+The following example does this:

+ +
+string name;
+indicator ind;
+
+sql << "select name from person where id = 7", into(name, ind);
+
+if (sql.got_data())
+{
+    switch (ind)
+    {
+    case i_ok:
+        // the data was returned without problems
+        break;
+    case i_null:
+        // there is a person, but he has no name (his name is null)
+        break;
+    case i_truncated:
+        // the name was returned only in part,
+        // because the provided buffer was too short
+        // (not possible with std::string, but possible with char* and char[])
+        break;
+    }
+}
+else
+{
+    // no such person in the database
+}
+
+ +

The use of indicator variable is optional, but if it is not used and +the result would be i_null, +then the exception is thrown. This means that you should use indicator +variables everywhere where the application logic (and database schema) +allow the "attribute not set" condition.

+ +

Indicator variables can be also used when binding input data, to +control whether the data is to be used as provided, or explicitly +overrided to be null:

+ +
+int id = 7;
+string name;
+indicator ind = i_null;
+sql << "insert into person(id, name) values(:id, :name)",
+    use(id), use(name, ind);
+
+ +

In the above example, the row is inserted with name +attribute set to null.

+ +

Indicator variables can also be used in conjunction with vector +based insert, update, and select statements:

+ +
+vector<string> names(100);
+vector<indicator> inds;
+sql << "select name from person where id = 7", into(names, inds);
+
+ +

The above example retrieves first 100 rows of data (or less). The +initial size of names vector provides the (maximum) +number of rows that should be read. Both vectors will be +automatically resized according to the number of rows that were +actually read.

+ +

The following example inserts null for each value of name:

+ +
+vector<int> ids;
+vector<string> names;
+vector<indicator> nameIndicators;
+
+for (int i = 0; i != 10; ++i)
+{
+    ids.push_back(i);
+    names.push_back("");
+    nameIndicators.push_back(i_null);
+}
+
+sql << "insert into person(id, name) values(:id, :name)",
+    use(ids), use(name, nameIndicators);
+
+ +

See also Integration with Boost to learn +how the Boost.Optional library can be used to handle null data conditions +in a more natural way.

+ +

Types

+ +

Static type binding

+ +

The static binding for types is most useful when the types used in +the database are known at compile time - this was already presented +above with the help of into and use +functions.

+ +

The following types are currently supported for use with into +and use expressions:

+
    +
  • char (for character values)
  • +
  • short, int, unsigned +long, long long, double (for numeric values)
  • +
  • char*, char[], std::string +(for string values)
  • +
  • std::tm (for datetime +values)
  • +
  • soci::statement (for nested statements and PL/SQL +cursors)
  • +
  • soci::blob (for Binary Large OBjects)
  • +
  • soci::row_id (for row identifiers)
  • +
+ +

See the test code that accompanies the library to see how each of +these types is used.

+ +

Static type binding for bulk operations

+ +

Bulk inserts, updates, and selects are supported through the +following std::vector based into and use types: +

+
    +
  • std::vector<char>
  • +
  • std::vector<short>
  • +
  • std::vector<int>
  • +
  • std::vector<unsigned long>
  • +
  • std::vector<long long>
  • +
  • std::vector<double>
  • +
  • std::vector<std::string>
  • +
  • std::vector<std::tm>
  • +
+ +

Use of the vector based types mirrors that of the standard types, with +the size of the vector used to specify the number of records to process +at a time. See below for examples.

+ +

Note that bulk operations are supported only for std::vectors of the types listed above.

+ +

Dynamic resultset binding

+ +

For certain applications it is desirable to be able to select data from +arbitrarily structured tables (e.g. via "select * from ...") and format the +resulting data based upon its type. +SOCI supports this through the soci::row and soci::column_properties +classes.

+ +

Data is selected into a row object, which holds column_properties +objects describing +the attributes of data contained in each column. Once the data type for each +column is known, the data can be formatted appropriately.

+ +

For example, the code below creates an XML document from a selected row +of data from an arbitrary table:

+ +
+row r;
+sql << "select * from some_table", into(r);
+
+std::ostringstream doc;
+doc << "<row>" << std::endl;
+for(std::size_t i = 0; i != r.size(); ++i)
+{
+    const column_properties & props = r.get_properties(i);
+
+    doc << '<' << props.get_name() << '>';
+
+    switch(props.get_data_type())
+    {
+    case dt_string:
+        doc << r.get<std::string>(i);
+        break;
+    case dt_double:
+        doc << r.get<double>(i);
+        break;
+    case dt_integer:
+        doc << r.get<int>(i);
+        break;
+    case dt_long_long:
+        doc << r.get<long long>(i);
+        break;
+    case dt_unsigned_long_long:
+        doc << r.get<unsigned long long>(i);
+        break;
+    case dt_date:
+        std::tm when = r.get<std::tm>(i);
+        doc << asctime(&when);
+        break;
+    }
+
+    doc << "</" << props.get_name() << '>' << std::endl;
+}
+doc << "</row>";
+
+ +

The type T parameter that should be passed to +row::get<T>() depends on the SOCI data type that +is returned from +column_properties::get_data_type().

+

row::get<T>() +throws an exception of type +std::bad_cast if an incorrect type T is +requested.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SOCI Data Typerow::get<T> specialization
dt_doubledouble
dt_integerint
dt_long_longlong long
dt_unsigned_long_longunsigned long long
dt_stringstd::string
dt_datestd::tm
+ +

The mapping of underlying database column types to SOCI datatypes is database specific. +See the backend documentation for details.

+ +

The row also provides access to indicators for each column:

+ +
+row r;
+sql << "select name from some_table where id = 1", into(r);
+if (r.get_indicator(0) != soci::i_null)
+{
+   std::cout << r.get<std::string>(0);
+}
+
+ +

It is also possible to extract data from the row object using its stream-like +interface, where each extracted variable should have matching type respective to its position in the chain:

+ +
+row r;
+sql << "select name, address, age from persons where id = 123", into(r);
+
+string name, address;
+int age;
+
+r >> name >> address >> age;
+
+ +

Note, however, that this interface is not compatible with the standard +std::istream class and that it is only possible to extract a single row at a time +- for "safety" reasons the row boundary is preserved and it is necessary to perform the +fetch operation explicitly for each consecutive row +(see next page).

+ +

Extending SOCI to support custom (user-defined) C++ types

+ +

SOCI can be easily extended with support for user-defined datatypes.

+ +

The extension mechanism relies on appropriate specialization of the type_conversion +struct that converts to and from one of the following SOCI base types:

+ +
    +
  • double
  • +
  • int
  • +
  • long long
  • +
  • unsigned long long
  • +
  • std::string
  • +
  • char
  • +
  • std::tm
  • +
+ +

There are three required class members for a valid type_conversion +specialization:

+
    +
  • the base_type type definition, aliasing either one of the base types or another ser-defined type
  • +
  • the from_base() static member function, converting from +the base type
  • +
  • the to_base() static member function, converting to the +base type
  • +
+ +

Note that no database-specific code is required to define user conversion.

+ +

The following example shows how the user can extend SOCI to +support his own type MyInt, which here is some wrapper +for the fundamental int type:

+ +
+class MyInt
+{
+public:
+    MyInt() {}
+    MyInt(int i) : i_(i) {}
+
+    void set(int i) { i_ = i; }
+    int get() const { return i_; }
+
+private:
+    int i_;
+};
+
+namespace soci
+{
+    template <>
+    struct type_conversion<MyInt>
+    {
+        typedef int base_type;
+
+        static void from_base(int i, indicator ind, MyInt & mi)
+        {
+            if (ind == i_null)
+            {
+                throw soci_error("Null value not allowed for this type");
+            }
+
+            mi.set(i);
+        }
+
+        static void to_base(const MyInt & mi, int & i, indicator & ind)
+        {
+            i = mi.get();
+            ind = i_ok;
+        }
+    };
+}
+
+ +

The above specialization for soci::type_conversion<MyInt> is enough +to enable the following:

+ +
+MyInt i;
+
+sql << "select count(*) from person", into(i);
+
+cout << "We have " << i.get() << " persons in the database.\n";
+
+ +

Note that there is a number of types from the Boost library integrated with SOCI out of the box, see Integration with Boost for complete description. Use these as examples of conversions for more complext data types.

+ +

Note also that user-defined datatypes are not supported with bulk data transfer.

+ +

Another possibility to extend SOCI with custom data types is to use +the into_type<T> and use_type<T> +class templates, which specializations can be user-provided. These +specializations need to implement the interface defined by, +respectively, the into_type_base and use_type_base +classes.

+ +

Note that when specializing these template classes the only convention +is that when the indicator +variable is used (see below), it should appear in the second position. +Please refer to the library source code to see how this is done for the +standard types.

+ +

Object-relational mapping

+ +

SOCI provides a class called values specifically to +enable object-relational mapping via type_conversion +specializations.

+ +

For example, the following code maps a Person object to +and from a +database table containing columns "ID", "FIRST_NAME", "LAST_NAME", and +"GENDER".

+ +

Note that the mapping is non-invasive - the Person object +itself does not contain any SOCI-specific code:

+ +
+struct Person
+{
+    int id;
+    std::string firstName;
+    std::string lastName;
+    std::string gender;
+};
+
+namespace soci
+{
+    template<>
+    struct type_conversion<Person>
+    {
+        typedef values base_type;
+
+        static void from_base(values const & v, indicator /* ind */, Person & p)
+        {
+            p.id = v.get<int>("ID");
+            p.firstName = v.get<std::string>("FIRST_NAME");
+            p.lastName = v.get<std::string>("LAST_NAME");
+
+            // p.gender will be set to the default value "unknown"
+            // when the column is null:
+            p.gender = v.get<std::string>("GENDER", "unknown");
+
+            // alternatively, the indicator can be tested directly:
+            // if (v.indicator("GENDER") == i_null)
+            // {
+            //     p.gender = "unknown";
+            // }
+            // else
+            // {
+            //     p.gender = v.get<std::string>("GENDER");
+            // }
+        }
+    
+        static void to_base(const Person & p, values & v, indicator & ind)
+        {
+            v.set("ID", p.id);
+            v.set("FIRST_NAME", p.firstName);
+            v.set("LAST_NAME", p.lastName);
+            v.set("GENDER", p.gender, p.gender.empty() ? i_null : i_ok);
+            ind = i_ok;
+        }
+    };
+}
+
+ +

With the above type_conversion specialization in place, it +is possible to use Person directly with SOCI:

+ +
+session sql(oracle, "service=db1 user=scott password=tiger");
+
+Person p;
+p.id = 1;
+p.lastName = "Smith";
+p.firstName = "Pat";
+sql << "insert into person(id, first_name, last_name) "
+       "values(:ID, :FIRST_NAME, :LAST_NAME)", use(p);
+
+Person p1;
+sql << "select * from person", into(p1);
+assert(p1.id == 1);
+assert(p1.firstName + p.lastName == "PatSmith");
+assert(p1.gender == "unknown");
+
+p.firstName = "Patricia";
+sql << "update person set first_name = :FIRST_NAME "
+       "where id = :ID", use(p);
+
+ +
+

Note: The values +class is currently not suited for use outside of type_conversion +specializations. It is specially designed to facilitate +object-relational mapping when used as shown above.

+
+ +

Large objects (BLOBs)

+ +

The SOCI library provides also an interface for basic operations on +large objects (BLOBs - Binary Large OBjects).

+ +
+blob b(sql); // sql is a session object
+sql << "select mp3 from mymusic where id = 123", into(b);
+
+ +

The following functions are provided in the blob +interface, mimicking the file-like operations:

+
    +
  • std::size_t get_len();
  • +
  • std::size_t read(std::size_t offset, char *buf, std::size_t +toRead);
  • +
  • std::size_t write(std::size_t offset, char const *buf, +std::size_t toWrite);
  • +
  • std::size_t append(char const *buf, std::size_t toWrite);
  • +
  • void trim(std::size_t newLen);
  • +
+ +

The offset parameter is always counted from the beginning +of the BLOB's data.

+ +
+

Portability notes:

+
    +
  1. The way to define BLOB table columns and create or destroy BLOB +objects in the database varies between different database engines. +Please see the SQL documentation relevant for the given server to learn +how this is actually done. The test programs provided with the SOCI +library can be also a simple source of full working examples.
  2. +
  3. The trim function is not currently available for +the PostgreSQL backend.
  4. +
+
+ + + + + + + + + + + + + diff --git a/doc/index.html b/doc/index.html new file mode 100644 index 0000000000..77f66368cf --- /dev/null +++ b/doc/index.html @@ -0,0 +1,102 @@ + + + + + + SOCI + + + + + +

Documentation and tutorial

+ + + +

The following (complete!) example is purposedly provided without any explanation.

+ +
+#include "soci.h"
+#include "soci-oracle.h"
+#include <iostream>
+#include <istream>
+#include <ostream>
+#include <string>
+#include <exception>
+
+using namespace soci;
+using namespace std;
+
+bool get_name(string &name)
+{
+    cout << "Enter name: ";
+    return cin >> name;
+}
+
+int main()
+{
+    try
+    {
+        session sql(oracle, "service=mydb user=john password=secret");
+
+        int count;
+        sql << "select count(*) from phonebook", into(count);
+
+        cout << "We have " << count << " entries in the phonebook.\n";
+
+        string name;
+        while (get_name(name))
+        {
+            string phone;
+            indicator ind;
+            sql << "select phone from phonebook where name = :name",
+                into(phone, ind), use(name);
+
+            if (ind == i_ok)
+            {
+                cout << "The phone number is " << phone << '\n';
+            }
+            else
+            {
+                cout << "There is no phone for " << name << '\n';
+            }
+        }
+    }
+    catch (exception const &e)
+    {
+        cerr << "Error: " << e.what() << '\n';
+    }
+}
+
+ + + + + + + + + + + + + diff --git a/doc/installation.html b/doc/installation.html new file mode 100644 index 0000000000..3855a8ea9d --- /dev/null +++ b/doc/installation.html @@ -0,0 +1,466 @@ + + + + + + SOCI - structure + + + + + +

Installation

+ + + +

Requirements

+ +

Below is an overall list of SOCI core:

+ + +

and backend-specific dependencies:

+ + +

Downloading

+ +

Download package with latest release of the SOCI source code: soci-X.Y.Z, where X.Y.Z is the version number. Unpack the archive.

+ +

You can always clone SOCI from the Git repository:

+ +
git clone git://github.com/SOCI/soci.git
+ +

Building using CMake

+ +

SOCI is configured to build using CMake system in version 2.8+.

+ +

The build configuration allows to control various aspects of compilation and installation by setting common CMake variables that change behaviour, describe system or control build (see CMake help) as well as SOCI-specific variables described below. All these variables are available regardless of platform or compilation toolset used.

+ +

Running CMake from the command line allows to set variables in the CMake cache with the following syntax: -DVARIABLE:TYPE=VALUE. If you are new to CMake, you may find the tutorial Running CMake helpful.

+ +

The following tables provide summary of variables accepted by CMake scripts configuring SOCI build. The lists consist of common variables for SOCI core and all backends as well as variables specific to SOCI backends and their direct dependencies.

+ + + + + + + + + + + + + + + + + + +
List of a few essential CMake variables
CMAKE_BUILD_TYPEstringSpecifies the build type for make based generators (see CMake help).
CMAKE_INSTALL_PREFIXpathInstall directory used by install command (see CMake help).
CMAKE_VERBOSE_MAKEFILEbooleanIf ON, create verbose makefile (see CMake help).
+ + + + + + + + + + + + + + + + + + +
List of variables to control common SOCI features and dependencies
SOCI_STATICbooleanRequest to build static libraries, along with shared, of SOCI core and all successfully configured backends.
SOCI_TESTSbooleanRequest to build regression tests for SOCI core and all successfully configured backends.
WITH_BOOSTbooleanShould CMake try to detect Boost C++ Libraries. If ON, CMake will try to find Boost headers and binaries of Boost.Date_Time library.
+ +

IBM DB2

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SOCI DB2 backend configuration
WITH_DB2booleanShould CMake try to detect IBM DB2 Call Level Interface (CLI) library.
DB2_INCLUDE_DIRstringPath to DB2 CLI include directories where CMake should look for sqlcli1.h header.
DB2_LIBRARIESstringFull paths to db2 or db2api libraries to link SOCI against to enable the backend support.
SOCI_DB2booleanRequests to build DB2 backend. Automatically switched on, if WITH_DB2 is set to ON.
SOCI_DB2_TEST_CONNSTRstringSee DB2 backend refernece for details. Example: -DSOCI_DB2_TEST_CONNSTR:STRING="DSN=SAMPLE;Uid=db2inst1;Pwd=db2inst1;autocommit=off"
+ +

Firebird

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SOCI Firebird backend configuration
WITH_FIREBIRDbooleanShould CMake try to detect Firebird client library.
FIREBIRD_INCLUDE_DIRstringPath to Firebird include directories where CMake should look for ibase.h header.
FIREBIRD_LIBRARIESstringFull paths to Firebird fbclient or fbclient_ms libraries to link SOCI against to enable the backend support.
SOCI_FIREBIRDbooleanRequests to build Firebird backend. Automatically switched on, if WITH_FIREBIRD is set to ON.
SOCI_FIREBIRD_TEST_CONNSTRstringSee Firebird backend refernece for details. Example: -DSOCI_FIREBIRD_TEST_CONNSTR:STRING="service=LOCALHOST:/tmp/soci_test.fdb user=SYSDBA password=masterkey"
+ +

MySQL

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SOCI MySQL backend configuration
WITH_MYSQLbooleanShould CMake try to detect mysqlclient libraries providing MySQL C API. Note, currently the mysql_config program is not being used.
MYSQL_DIRstringPath to MySQL installation root directory. CMake will scan subdirectories MYSQL_DIR/include and MYSQL_DIR/lib respectively for MySQL headers and libraries.
MYSQL_INCLUDE_DIRstringPath to MySQL include directory where CMake should look for mysql.h header.
MYSQL_LIBRARIESstringFull paths to libraries to link SOCI against to enable the backend support.
SOCI_MYSQLbooleanRequests to build MySQL backend. Automatically switched on, if WITH_MYSQL is set to ON.
SOCI_MYSQL_TEST_CONNSTRstringConnection string to MySQL test database. Format of the string is explained MySQL backend refernece. Example: -DSOCI_MYSQL_TEST_CONNSTR:STRING="db=mydb user=mloskot password=secret"
+ +

ODBC

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SOCI ODBC backend configuration
WITH_ODBCbooleanShould CMake try to detect ODBC libraries. On Unix systems, CMake tries to find unixODBC or iODBC implementations.
ODBC_INCLUDE_DIRstringPath to ODBC implementation include directories where CMake should look for sql.h header.
ODBC_LIBRARIESstringFull paths to libraries to link SOCI against to enable the backend support.
SOCI_ODBCbooleanRequests to build ODBC backend. Automatically switched on, if WITH_ODBC is set to ON.
SOCI_ODBC_TEST_{database}_CONNSTRstringODBC Data Source Name (DSN) or ODBC File Data Source Name (FILEDSN) to test database: Microsoft Access (.mdb), Microsoft SQL Server, MySQL, PostgreSQL or any other ODBC SQL data source. {database} is placeholder for name of database driver ACCESS, MYSQL, POSTGRESQL, etc. See ODBC backend refernece for details. Example: -DSOCI_ODBC_TEST_POSTGRESQL_CONNSTR="FILEDSN=/home/mloskot/dev/soci/_git/build/test-postgresql.dsn"
+ +

Oracle

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SOCI Oracle backend configuration
WITH_ORACLEbooleanShould CMake try to detect Oracle Call Interface (OCI) libraries.
ORACLE_INCLUDE_DIRstringPath to Oracle include directory where CMake should look for oci.h header.
ORACLE_LIBRARIESstringFull paths to libraries to link SOCI against to enable the backend support.
SOCI_ORACLEbooleanRequests to build Oracle backend. Automatically switched on, if WITH_ORACLE is set to ON.
SOCI_ORACLE_TEST_CONNSTRstringConnection string to Oracle test database. Format of the string is explained Oracle backend refernece. Example: -DSOCI_ORACLE_TEST_CONNSTR:STRING="service=orcl user=scott password=tiger"
+ +

PostgreSQL

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SOCI PostgreSQL backend configuration
WITH_POSTGRESQLbooleanShould CMake try to detect PostgreSQL client interface libraries. SOCI relies on libpq C library.
POSTGRESQL_INCLUDE_DIRstringPath to PostgreSQL include directory where CMake should look for libpq-fe.h header.
POSTGRESQL_LIBRARIESstringFull paths to libraries to link SOCI against to enable the backend support.
SOCI_POSTGRESQLbooleanRequests to build PostgreSQL backend. Automatically switched on, if WITH_POSTGRESQL is set to ON.
SOCI_POSTGRESQL_TEST_CONNSTRbooleanConnection string to PostgreSQL test database. Format of the string is explained PostgreSQL backend refernece. Example: -DSOCI_POSTGRESQL_TEST_CONNSTR:STRING="dbname=mydb user=mloskot"
+ +

SQLite 3

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SOCI SQLite 3 backend configuration
WITH_SQLITE3booleanShould CMak try to detect SQLite C/C++ library. As bonus, the configuration tries OSGeo4W distribution if OSGEO4W_ROOT environment variable is set.
SQLITE_INCLUDE_DIRstringPath to SQLite 3 include directory where CMake should look for sqlite3.h header.
SQLITE_LIBRARIESstringFull paths to libraries to link SOCI against to enable the backend support.
SOCI_SQLITE3booleanRequests to build SQLite3 backend. Automatically switched on, if WITH_SQLITE3 is set to ON.
SOCI_SQLITE3_TEST_CONNSTRstringConnection string is simply a file path where SQLite3 test database will be created (e.g. /home/john/soci_test.db). Check SQLite3 backend refernece for details. Example: -DSOCI_SQLITE3_TEST_CONNSTR="my.db"
+ +

Empty (sample backend)

+ + + + + + + + + + + + + +
SOCI empty sample backend configuration
SOCI_EMPTYbooleanBuilds the sample backend called Empty. Always ON by default.
SOCI_EMPTY_TEST_CONNSTRstringConnection string used to run regression tests of the Empty backend. It is a dummy value. Example: -DSOCI_EMPTY_TEST_CONNSTR="dummy connection"
+ +

By default, CMake will try to determine availability of all depdendencies automatically. If you are lucky, you will not need to specify any of the CMake variables explained above. However, if CMake reports some of the core or backend-specific dependencies as missing, you will need specify relevant variables to tell CMake where to look for the required components.

+ +

CMake configures SOCI build performing sequence of steps. Each +subsequent step is dependant on result of previous steps corresponding +with particular feature. First, CMake checks system platform and +compilation toolset. Next, CMake tries to find all external +dependencies. Then, depending on the results of the dependency check, +CMake determines SOCI backends which are possible to build. The +SOCI-specific variables described above provide users with basic +control of this behaviour.

+ +

Building using CMake on Unix

+ +

Short version using GNU Make makefiles:

+
+$ mkdir build
+$ cd build
+$ cmake -G "Unix Makefiles" -DWITH_BOOST=OFF -DWITH_ORACLE=OFF (...) ../soci-X.Y.Z
+$ make
+$ make install
+
+ +

Building using CMake on Windows

+ +

Short version using Visual Studio 2010 and MSBuild:

+ +
+C:\>MKDIR build
+C:\>cd build
+C:\build>cmake -G "Visual Studio 10" -DWITH_BOOST=OFF -DWITH_ORACLE=OFF (...) ..\soci-X.Y.Z
+C:\build>msbuild.exe SOCI.sln
+
+ +

Building using classic Makefiles on Unix (deprecated)

+ +

NOTE: These Makefiles have not been maintained for long time. +The officially maintained build configuration is CMake. If you still want to +use these Makefiles, you've been warned that you may need to patch them.

+ +

The classic set of Makefiles for Unix/Linux systems is provided for those users who need complete control over the whole process +and who can benefit from the basic scaffolding that they can extend on their own. +In this sense, the basic Makefiles are supposed to provide a minimal starting point for custom experimentation and are not intended to be a complete build/installation solution.
+At the same time, they are complete in the sense that they can compile the library with all test programs and for some users this level of support will be just fine.

+ +

The core directory of the library distribution contains +the Makefile.basic that can be used to compile the core part of +the library. Run make -f Makefile.basic or make -f Makefile.basic shared to get the static and shared versions, respectively. +Similarly, the backends/name directory contains the +backend part for each supported backend with the appropriate Makefile.basic +and the backends/name/test +directory contains the test program for the given backend.

+ +

For example, the simplest way to compile the static version of the +library and the test program for PostgreSQL is:

+ +
+$ cd src/core
+$ make -f Makefile.basic
+$ cd ../backends/postgresql
+$ make -f Makefile.basic
+$ cd test
+$ make -f Makefile.basic
+
+ +
+

Note: +For each backend and its test program, the Makefile.basics +contain the variables that can have values specific to the given +environment - they usually name the include and library paths. +These variables are placed at the beginning of the Makefile.basics. +Please review their values in case of any compilation problems.

+
+ +

The Makefiles for test programs can be a good starting point to find out correct compiler and linker options.

+ +

Running regression tests

+ +

The process of running regression tests highly depends on user's environment and build configuration, so it may be quite involving process. The CMake configuration provides variables to allow users willing to run the tests to configure build and specify database connection parameters (see the tables above for variable names).

+ +

In order to run regression tests, configure and build desired SOCI backends and prepare working database instances for them.

+ +

While configuring build with CMake, specify SOCI_TESTS=ON to enable building regression tests. Also, specify SOCI_{backend name}_TEST_CONNSTR variables to tell the tests runner how to connect with your test databases.

+ +

Dedicated make test target can be used to execute regression tests on build completion:

+
+$ mkdir build
+$ cd build
+$ cmake -G "Unix Makefiles" -DWITH_BOOST=OFF \
+   -DSOCI_TESTS=ON \
+   -DSOCI_EMPTY_TEST_CONNSTR="dummy connection" \
+   -DSOCI_SQLITE3_TEST_CONNSTR="test.db" \
+   (...)
+   ../soci-X.Y.Z
+$ make
+$ make test
+$ make install
+
+ +

In the example above, regression tests for the sample Empty backend and SQLite 3 backend are configured for execution by make test target.

+ +

Libraries usage

+ +

CMake build produces set of shared and static libraries for SOCI core and backends separately. On Unix, for example, build/lib directory +will consist of the static libraries named like libsoci_core.a, libsoci_sqlite3.a and shared libraries with names like +libsoci_core.so.3.2.2, libsoci_sqlite3.so.3.2.2, and so on.

+ +

In order to use SOCI in your program, you need to specify your project build configuration with paths to SOCI headers and libraries, +and specify linker to link against the libraries you want to use in your program.

+ +

+ + + + + + + + + + + diff --git a/doc/interfaces.html b/doc/interfaces.html new file mode 100644 index 0000000000..72e168ace9 --- /dev/null +++ b/doc/interfaces.html @@ -0,0 +1,103 @@ + + + + + + SOCI - integration with Boost + + + + + +

Interfaces

+ +

One of the major features of SOCI, although not immediately visible, is the variety of interfaces (APIs) that are available for the user. These can be divided into sugar, core and simple.

+ +

Sugar

+ +

The most exposed and promoted interface supports the syntax sugar that makes SOCI similar in look and feel to embedded SQL. The example of application code using this interface is:

+ +
+session sql("postgresql://dbname=mydb");
+
+int id = 123;
+string name;
+
+sql << "select name from persons where id = :id", into(name), use(id);
+
+ +

Core

+ +

The above example is equivalent to the following, more explicit sequence of calls:

+
+session sql("postgresql://dbname=mydb");
+
+int id = 123;
+string name;
+
+statement st(sql);
+st.exchange(into(name));
+st.exchange(use(id));
+st.alloc();
+st.prepare("select name from persons where id = :id");
+st.define_and_bind();
+st.execute(true);
+
+ +

The value of the core interface is that it is the basis for all other interfaces, and can be also used by developers to easily prepare their own convenience interfaces. Users who cannot or for some reason do not want to use the natural sugar interface should try the core one as the foundation and access point to all SOCI functionality.

+ +

Note that the sugar interface wraps only those parts of the core that are related to data binding and query streaming.

+ +

Simple

+ +

The simple interface is provided specifically to allow easy integration of the SOCI library with other languages that have the ability to link with binaries using the "C" calling convention. To facilitate this integration, the simple interface does not use any pointers to data except C-style strings and opaque handles, but the consequence of this is that user data is managed by SOCI and not by user code. To avoid exceptions passing the module boundaries, all errors are reported as state variables of relevant objects.

+ +

The above examples can be rewritten as (without error-handling):

+ +
+#include <soci-simple.h>
+
+// ...
+session_handle sql = soci_create_session("postgresql://dbname=mydb");
+
+statement_handle st = soci_create_statement(sql);
+
+soci_use_int(st, "id");
+soci_set_use_int(st, "id", 123);
+
+int namePosition = soci_into_string(st);
+
+soci_prepare(st, "select name from persons where id = :id");
+
+soci_execute(st, true);
+
+char const * name = soci_get_into_string(st, namePosition);
+
+printf("name is %s\n", name);
+
+soci_destroy_statement(st);
+soci_destroy_session(sql);
+
+ +

The simple interface supports single and bulk data exchange for static binding. Dynamic row description is not supported in this release.

+

See Simple client interface reference documentation for more details.

+ +

Low-level backend interface

+ +

The low-level backend interface allows to interact with backends directly and in principle allows to access the database without involving any other component. There is no particular reason to use this interface in the user code.

+ + + + + + + + + + + + diff --git a/doc/languages/ada/concepts.html b/doc/languages/ada/concepts.html new file mode 100644 index 0000000000..4880c5597a --- /dev/null +++ b/doc/languages/ada/concepts.html @@ -0,0 +1,64 @@ + + + + + SOCI-Ada - documentation + + + +

SOCI-Ada - manual

+ +

Concepts

+

+The SOCI-Ada library borrows its concepts and naming from the main SOCI project. They are shortly explained here in the bottom-up fashion. +

+

+One of the main properties of the library is that the data objects which are bound for transfer to and from the database server are managed by the library itself and are not directly visible from the user code. This ensures that no aliasing of objects occurs between Ada and underlying C++ code, which makes the inter-language interface easier and more resilient to the differences in how compilers handle the linkage. As a direct result of this design choice, users of SOCI-Ada need to instruct the library to internally create all objects that will be subject to data transfer. +

+

+There are two kinds of objects that can be managed by the SOCI-Ada library: +

+
    +
  • Into elements, which are data objects that are transferred from the database to the user program as a result of executing a query. There are single into elements for binding single rows of results and vector into elements for binding whole bunches of data corresponding to whole result sets or their subranges. The into elements are identified by their position.
  • +
  • Use elements, which are data objects that are transferred from the user program to the database as parameters of the query (and, if supported by the target database, that can be modified by the database server and transferred back to the user program). There are single use elements for binding parameters of single-row queries and vector use elements for binding whole bunches of data for transfer. The use elements are identified by their name.
  • +
+

+The user program can read the current value of into and use elements and assign new values to use elements. Elements are strongly typed and the following types are currently supported: +

+
    +
  • String
  • +
  • SOCI.DB_Integer, which is defined by the library in terms of Interfaces.C.int
  • +
  • SOCI.DB_Long_Long_Integer, which is defined in terms of Interfaces.Integer_64
  • +
  • SOCI.DB_Long_Float, which is defined in terms of Interfaces.C.double
  • +
  • Ada.Calendar.Time
  • +
+

+Both into and use elements are managed for a single statement, which can be prepared once and executed once or many times, with data transfer handled during execution or fetch phase. +

+

+Statements can be managed explicitly, which is required if they are to be used repeteadly or when data transfer is needed or implicitly, which is a shorthand notation that is particularly useful with simple queries or DDL commands. +

+

+All statements are handled within the context of some session, which also supports transactions. +

+

+Sessions can be managed in isolation or as a group called connection pool, which helps to decouple tasking design choices from the concurrency policies at the database connection level. Sessions are leased from the pool for some time during which no other task can access them and returned back when no longer needed, where they can be acquired again by other tasks. +

+

+All potential problems are signalled via exceptions that have some descriptive message attached to them. +

+ + + + + + + + + + + diff --git a/doc/languages/ada/idioms.html b/doc/languages/ada/idioms.html new file mode 100644 index 0000000000..07452d3812 --- /dev/null +++ b/doc/languages/ada/idioms.html @@ -0,0 +1,306 @@ + + + + + SOCI-Ada - documentation + + + +

SOCI-Ada - manual

+ +

Common Idioms

+

+As any other library, SOCI-Ada has its set of idioms that ensure optimal work in terms of performance and resource usage. Still, the optimal use will depend on the concrete usage scenario - the places where programmer choices are needed will be described explicitly. +

+

+The idioms below are provided as complete programs with the intent to make them more understandable and to give complete context of use for each idiom. The programs assume that the target database is PostgreSQL, but this can be changed by a different connection string in each place where the sessions are established. The programs use the Ada 2005 interface and some minor changes will be required to adapt them for Ada 95 compilers. +

+ +

Single query without data transfer

+

+This type of query is useful for DDL commands and can be executed directly on the given session, without explicit statement management. +

+
+with SOCI;
+
+procedure My_Program is
+
+   SQL : SOCI.Session := SOCI.Make_Session ("postgresql://dbname=my_database");
+
+begin
+
+   SQL.Execute ("drop table some_table");
+
+end My_Program;
+
+ +
+

Note:

+

+The session object is initialized by a constructor function call. An alternative would be to declare it without initialization and later use the Open operation to establish a physical connection with the database. +

+
+ +

Simple query without parameters resulting in one row of data

+

+This type of query requires only single into elements, which together with the statement have to be manipulated explicitly. +

+
+with SOCI;
+with Ada.Text_IO;
+
+procedure My_Program is
+
+   SQL : SOCI.Session := SOCI.Make_Session ("postgresql://dbname=my_database");
+   St : SOCI.Statement := SOCI.Make_Statement (SQL);
+   Pos : SOCI.Into_Position;
+
+   Num_Of_Persons : SOCI.DB_Integer;
+
+begin
+
+   Pos := St.Into_Integer;
+   St.Prepare ("select count(*) from persons");
+   St.Execute (True);
+
+   Num_Of_Persons := St.Get_Into_Integer (Pos);
+
+   Ada.Text_IO.Put_Line ("Number of persons: " & SOCI.DB_Integer'Image (Num_Of_Persons));
+
+end My_Program;
+
+ +
+

Note:

+

+The into element is inspected by providing the position value that was obtained at the time if was created. No operations are defined for the position type. There can be many into elements with a single query. +

+
+ +

Simple query with parameters and without results

+

+This type of query requires only use elements. +

+
+with SOCI;
+
+procedure My_Program is
+
+   SQL : SOCI.Session := SOCI.Make_Session ("postgresql://dbname=my_database");
+   St : SOCI.Statement := SOCI.Make_Statement (SQL);
+
+begin
+
+   St.Use_Integer ("increase");
+   St.Set_Use_Integer ("increase", 1000);
+
+   St.Prepare ("update persons set salary = salary + :increase");
+   St.Execute (True);
+
+end My_Program;
+
+ +
+

Note:

+

+The ":increase" in the query is a placeholder variable. There can be many such variables and each of them needs to be filled in by respective use element. +

+
+ +

Repeated query with parameters and without results

+

+This type of query requires only use elements, but they can be set differently for each statement execution. +

+
+with SOCI;
+
+procedure My_Program is
+
+   SQL : SOCI.Session := SOCI.Make_Session ("postgresql://dbname=my_database");
+   St : SOCI.Statement := SOCI.Make_Statement (SQL);
+
+begin
+
+   St.Use_String ("name");
+
+   St.Prepare ("insert into countries(country_name) values(:name)");
+
+   St.Set_Use_String ("name", "Poland");
+   St.Execute (True);
+
+   St.Set_Use_String ("name", "Switzerland");
+   St.Execute (True);
+
+   St.Set_Use_String ("name", "France");
+   St.Execute (True);
+
+end My_Program;
+
+ +
+

Note:

+

+Each time the query is executed, the current values of use elements are transferred to the database. +

+
+ +

Batch query with parameters and without results

+

+This type of query requires vector use elements. Compare with the previous example. +

+
+with SOCI;
+
+procedure My_Program is
+
+   SQL : SOCI.Session := SOCI.Make_Session ("postgresql://dbname=my_database");
+   St : SOCI.Statement := SOCI.Make_Statement (SQL);
+   First : SOCI.Vector_Index;
+
+   use type SOCI.Vector_Index;
+
+begin
+
+   St.Use_Vector_String ("name");
+
+   St.Use_Vectors_Resize (3);
+
+   First := St.Use_Vectors_First_Index;
+
+   St.Set_Use_Vector_String ("name", First + 0, "Poland");
+   St.Set_Use_Vector_String ("name", First + 1, "Switzerland");
+   St.Set_Use_Vector_String ("name", First + 2, "France");
+
+   St.Prepare ("insert into countries(country_name) values(:name)");
+   St.Execute (True);
+
+end My_Program;
+
+ +
+

Note:

+

+The whole bunch of data is transferred to the database if the target database server supports it and the statement is automatically repeated otherwise. This is the preferred way to transfer many rows of data to the server when the data for all rows are known before executing the query. +

+
+ +
+

Note:

+

+The query can be executed many times and each time a new batch of data can be transferred to the server. The size of the batch (set by calling Use_Vectors_Resize) can be different each time the query is executed, but cannot be larger than the size that was used the first time. The size of the batch defines a tradeoff between the amount of data being transmitted in a single step (this influences the memory used by the user program and the time of a single call) and the number of executions required to handle big data sets. The optimal size of the batch will therefore differ depending on the application, but in general tens of thousands is a reasonable limit for a batch size - the performance of the whole operation is usually not affected above this value so there is no need to imply higher memory usage at the client side. +

+
+ +

Simple query with many rows of results

+

+This type of query requires simple into elements. +

+
+with SOCI;
+with Ada.Text_IO;
+
+procedure My_Program is
+
+   SQL : SOCI.Session := SOCI.Make_Session ("postgresql://dbname=my_database");
+   St : SOCI.Statement := SOCI.Make_Statement (SQL);
+   Pos : SOCI.Into_Position;
+
+begin
+
+   Pos := St.Into_String;
+
+   St.Prepare ("select country_name from countries");
+   St.Execute;
+
+   while St.Fetch loop
+
+      Ada.Text_IO.Put_Line (St.Get_Into_String (Pos));
+
+   end loop;
+
+end My_Program;
+
+ +
+

Note:

+

+The loop above executes as many times as there are rows in the result. After each row is read, the into elements contain the respective values from that row. The Execute operation is called without parameter, which is False by default, meaning that no data transfer is intended. The data is being transferred only during the Fetch operation, which returns False when no data has been retrieved and the result is exhausted. +

+

+This type of query can have simple parameters which are fixed at the execution time. +

+
+ +

Batch query with many rows of results

+

+This type of query requires vector into elements. Compare with previous example. +

+
+with SOCI;
+with Ada.Text_IO;
+
+procedure My_Program is
+
+   SQL : SOCI.Session := SOCI.Make_Session ("postgresql://dbname=my_database");
+   St : SOCI.Statement := SOCI.Make_Statement (SQL);
+   Pos : SOCI.Into_Position;
+
+   Batch_Size : constant := 10;
+
+begin
+
+   Pos := St.Into_Vector_String;
+   St.Into_Vectors_Resize (Batch_Size);
+
+   St.Prepare ("select country_name from countries");
+   St.Execute;
+
+   while St.Fetch loop
+
+      for I in St.Into_Vectors_First_Index .. St.Into_Vectors_Last_Index loop
+
+         Ada.Text_IO.Put_Line (St.Get_Into_Vector_String (Pos, I));
+
+      end loop;
+
+      St.Into_Vectors_Resize (Batch_Size);
+
+   end loop;
+
+end My_Program;
+
+ +
+

Note:

+

+The loop above is nested. The outer while loop fetches consecutive batches of rows from the database with requested batch size; the returned batch can be smaller than requested (the into vector elements are downsized automatically if needed) and the intended batch size is requested again before repeating the Fetch operation. For each returned batch, the into vector elements are inspected in the inner for loop. This scheme ensures correct operation independently on the size of returned batch and is therefore a recommended idiom for efficiently returning many rows of data. +

+

+There is a tradeoff between efficiency and memory usage and this tradeoff is controlled by the requested batch size. Similarly to one of the examples above, there is no benefit from using batches bigger than tens of thousands of rows. +

+

+This type of query can have simple (not vectors) parameters that are fixed at execution time. +

+
+ +
+

Final note:

+

+Follow good database usage principles and avoid constructing queries by concatenating strings computed at run-time. Thanks to a good type system Ada is much better in preventing various SQL-injection attacks than weaker languages like PHP, but there is still a potential for vulnerability or at least performance loss. As a rule of thumb, rely on use elements to parameterize your queries and to provide clean separation between data and code. This will prevent many security vulnerabilities and will allow some servers to optimize their work by reusing already cached execution plans. +

+
+ + + + + + + + + + + diff --git a/doc/languages/ada/index.html b/doc/languages/ada/index.html new file mode 100644 index 0000000000..dfa859b7c0 --- /dev/null +++ b/doc/languages/ada/index.html @@ -0,0 +1,78 @@ + + + + + SOCI-Ada Language Binding - documentation + + + +

SOCI-Ada Language Binding - documentation

+ +

+Introduction
+Compilation
+Concepts
+Common Idioms
+API Reference
+

+ +
+ +

Introduction

+

+SOCI-Ada is a database access library for Ada. +

+

+The library itself is a wrapper for the selected functionality of the SOCI library, which is a C++ database access library recognized for its high quality and innovative interface. +

+

+The SOCI-Ada library offers the following features to the Ada community: +

+
+ +

+Currently the following database servers are directly supported via their native interfaces: +

+
    +
  • Oracle
  • +
  • PostgreSQL
  • +
  • MySQL
  • +
+

+Other backends exist in the SOCI Git repository and can be provided with future version of the library. +

+ +

Compilation

+

+In order to use SOCI-Ada, compile the C++ parts first (core and required backends). +

+

+Note: SOCI header files are not needed to use SOCI-Ada, only compiled SOCI libraries (core and relevant backend) need to exist to build and use SOCI-Ada programs. +

+

+The SOCI-Ada library itself is a single package named +SOCI. This package can be just imported in the target project as is or pre-built to the binary form if required. +

+

+In order to link the user programs the -lsoci_core -lstdc++ linker options need to be provided on the Unix/Linux platforms. +

+ + + + + + + + + + + diff --git a/doc/languages/ada/reference.html b/doc/languages/ada/reference.html new file mode 100644 index 0000000000..42dc415bac --- /dev/null +++ b/doc/languages/ada/reference.html @@ -0,0 +1,674 @@ + + + + + SOCI-Ada - documentation + + + +

SOCI-Ada - manual

+ +

API Reference

+

+The SOCI-Ada library is entirely implemented as a single package named +SOCI. Additional child packages contain single procedures +for static registration of backends - these child packages are not +necessary for typical use, but can be useful to force static +linking of backend code. +

+

+The following describes all publicly visible elements of this package: +

+ +
+
+   --
+   --  General exception related to database and library usage.
+   --
+
+   Database_Error : exception;
+
+

+Each problem related to the interaction with the database or to the incorrect usage of the library itself is signalled by raising this exception. Each occurrence of this exception has some human-readable error message that can be obtained by a call to Ada.Exceptions.Exception_Message. +

+ +
+
+   --
+   --  Session.
+   --
+
+   type Session is tagged limited private;
+
+   not overriding
+   function Make_Session (Connection_String : in String) return Session;
+
+   not overriding
+   procedure Open (This : in out Session; Connection_String : in String);
+
+   not overriding
+   procedure Close (This : in out Session);
+
+   not overriding
+   function Is_Open (This : in Session) return Boolean;
+
+

+The Session object can exist in two states: "connected" (or "open") and "disconnected". It can be created as connected at initialization time with a call to the constructor function Make_Session or left default-initialized in the disconnected state and later changed to connected with Open (the latter option is the only that is available in the Ada 95 version of the library). Session objects can be also associated with the connection pool, see below. +

+

+The Connection_String should have the form "backendname://parameters", where backendname is used to construct the name of the dynamically loadable library that will be used to provide specific database services. Backends included in the current distribution of the main SOCI library are: +

+
    +
  • oracle (implemented as libsoci_oracle.so or libsoci_oracle.dll)
  • +
  • postgresql (implemented as libsoci_postgresql.so or libsoci_postgresql.dll)
  • +
  • mysql (implemented as libsoci_mysql.so or libsoci_mysql.dll)
  • +
+

+Other backends can be added to the library in the future or by the user himself, please see the documentation of the main SOCI library for details. +

+

+The parameters component of the Connection_String depends on the given backend, please see the documentation of the main SOCI project for the meaning and recognized options. The web pages related to the backends above are: +

+
+ +

+The Open operation can be called only in the disconnected state (which changes the state of Session object to connected). The Close operation can be called in any state (provided that the session is not associated with the connection pool, see below) and after that the Session is in the disconnected state. +

+

+Session objects are closed automatically as part of their finalization. If the Session object is associated with the connection pool, the finalizer detaches from the pool without closing the connection. +

+ +
+
+   --  Transaction management.
+
+   not overriding
+   procedure Start (This : in Session);
+
+   not overriding
+   procedure Commit (This : in Session);
+
+   not overriding
+   procedure Rollback (This : in Session);
+
+

+These operations handle transactions. The exact meaning of transactions and whether transactions are automatic for some kinds of statements (and which ones) depend on the target database. +

+ +
+
+   --  Immediate query execution.
+   not overriding
+   procedure Execute (This : in Session; Query : in String);
+
+

+This operation allows to create implicit statement, prepare it for the given Query and execute it. +

+ +
+
+   --
+   --  Connection pool management.
+   --
+
+   type Connection_Pool (Size : Positive) is tagged limited private;
+
+   not overriding
+   procedure Open
+     (This : in out Connection_Pool;
+      Position : in Positive;
+      Connection_String : in String);
+
+   not overriding
+   procedure Close (This : in out Connection_Pool; Position : in Positive);
+
+   not overriding
+   procedure Lease (This : in out Connection_Pool; S : in out Session'Class);
+
+

+The Connection_Pool encapsulates a fixed-size collection of sessions. Individual sessions are indexed from 1 to Size (provided as discriminant) and can be Opened and Closed explicitly. Each connection in the pool can be created with different Connection_String, if needed. +

+

+The Lease operation allows to associate a given Session object (that has to be in the disconnected state itself) with one connection from the pool. The pool guarantees that at most one task can lease a given connection from the pool. If there are no free connections in the pool, the Lease operation will block waiting until some connection is freed. +

+

+The Session object that is associated with a connection from the pool automatically gives it back to pool as part of the Session's finalizer. There is no other way to "detach" from the pool. +

+ +
+

Note:

+

+It is assumed that the lifetime of Connection_Pool encloses the lifetimes of all Session objects that are leased from it. There is no particular protection against it and it is possible to construct a code example with allocators that create partially overlapping Connection_Pool and Session, but this is considered obscure and not representative to the actual use scenarios. To avoid any potential problems, create Connection_Pool in the scope that encloses the scopes of leased Sessions. +

+
+ +
+
+   --
+   --  Statement.
+   --
+
+   type Statement (<>) is tagged limited private;
+
+   type Data_State is (Data_Null, Data_Not_Null);
+
+   type Into_Position is private;
+
+   type Vector_Index is new Natural;
+
+

+The Statement type and supporting types. Data_State is used to indicate null values in the database sense - each value of into or use elements has a state from this type. +

+

+Into_Position is used to identify into elements. Vector_Index is used to name individual entries in vector into and use elements. +

+ +
+
+   not overriding
+   function Make_Statement (Sess : in Session'Class) return Statement;
+
+   --  Ada 95 version:
+   --  procedure Make_Statement (This : in out Statement; Sess : in Session'Class);
+
+

+Construction function for creating Statement objects. The Statement is associated with one Session for its whole lifetime. +

+ +
+
+   --  Statement preparation and execution.
+
+   not overriding
+   procedure Prepare (This : in Statement; Query : in String);
+
+   not overriding
+   procedure Execute
+     (This : in Statement;
+      With_Data_Exchange : in Boolean := False);
+
+   not overriding
+   function Execute
+     (This : in Statement;
+      With_Data_Exchange : in Boolean := False) return Boolean;
+
+   not overriding
+   function Fetch (This : in Statement) return Boolean;
+
+   not overriding
+   function Got_Data (This : in Statement) return Boolean;
+
+

+The Prepare operation needs to be called before any other operation in the above group and it prepares the execution for the given Query. No into and use elements can be created after this operation is called. +

+

+The Execute operations cause the statement to execute, which might be combined with data exchange if requested. The function version of this operation returns True if some data has been returned back from the database server. +

+

+The Fetch function is used to transfer next portion of data (a single row or a whole bunch) from the database server and returns True if some data has been fetched. If this function returns False it means that no new data will be ever fetched for this statement and indicates the end-of-row condition. +

+

+The Got_Data function returns True if the last execution or fetch resulted in some data being transmitted from the database server. +

+ +
+
+   --
+   --  Data items handling.
+   --
+
+   --  Database-specific types.
+   --  These types are most likely identical to standard Integer,
+   --  Long_Long_Integer and Long_Float, but are defined distinctly
+   --  to avoid interfacing problems with other compilers.
+
+   type DB_Integer is new Interfaces.C.int;
+   type DB_Long_Long_Integer is new Interfaces.Integer_64;
+   type DB_Long_Float is new Interfaces.C.double;
+
+

+The data types used for interaction with the database are: +

+
    +
  • String
  • +
  • DB_Integer, defined above
  • +
  • DB_Long_Long_Integer, defined above
  • +
  • DB_Long_Float, defined above
  • +
  • Ada.Calendar.Time
  • +
+ +
+
+   --  Creation of single into elements.
+
+   not overriding
+   function Into_String (This : in Statement) return Into_Position;
+
+   not overriding
+   function Into_Integer (This : in Statement) return Into_Position;
+
+   not overriding
+   function Into_Long_Long_Integer (This : in Statement) return Into_Position;
+
+   not overriding
+   function Into_Long_Float (This : in Statement) return Into_Position;
+
+   not overriding
+   function Into_Time (This : in Statement) return Into_Position;
+
+

+These functions instruct the library to create internal simple into elements of the relevant type. They return the position of the into element, which can be later used to identify it. +

+ +
+

Note:

+

+Simple into elements cannot be created together with vector into elements for the same statement. +

+
+ +
+
+   --  Creation of vector into elements.
+
+   not overriding
+   function Into_Vector_String (This : in Statement) return Into_Position;
+
+   not overriding
+   function Into_Vector_Integer (This : in Statement) return Into_Position;
+
+   not overriding
+   function Into_Vector_Long_Long_Integer (This : in Statement) return Into_Position;
+
+   not overriding
+   function Into_Vector_Long_Float (This : in Statement) return Into_Position;
+
+   not overriding
+   function Into_Vector_Time (This : in Statement) return Into_Position;
+
+

+These functions instruct the library to create internal vector into elements of the relevant type. They return the position of the into element, which can be later used to identify it. +

+ +
+

Note:

+

+Vector into elements are empty (they have size 0) after they are created and have to be resized before any data is written to them. +

+
+ +
+

Note:

+

+Simple into elements cannot be created together with vector into elements for the same statement. +

+
+ +
+
+   --  Inspection of single into elements.
+
+   not overriding
+   function Get_Into_State
+     (This : in Statement;
+      Position : in Into_Position)
+     return Data_State;
+
+   not overriding
+   function Get_Into_String
+     (This : in Statement;
+      Position : in Into_Position)
+     return String;
+
+   not overriding
+   function Get_Into_Integer
+     (This : in Statement;
+      Position : in Into_Position)
+     return DB_Integer;
+
+   not overriding
+   function Get_Into_Long_Long_Integer
+     (This : in Statement;
+      Position : in Into_Position)
+     return DB_Long_Long_Integer;
+
+   not overriding
+   function Get_Into_Long_Float
+     (This : in Statement;
+      Position : in Into_Position)
+     return DB_Long_Float;
+
+   not overriding
+   function Get_Into_Time
+     (This : in Statement;
+      Position : in Into_Position)
+     return Ada.Calendar.Time;
+
+

+These functions allow to inspect the state and value of the simple into element identified by its position. If the state of the given element is Data_Null, the data-reading functions raise exceptions for that element. +

+ +
+
+   --  Inspection of vector into elements.
+
+   not overriding
+   function Get_Into_Vectors_Size (This : in Statement) return Natural;
+
+   not overriding
+   function Into_Vectors_First_Index (This : in Statement) return Vector_Index;
+
+   not overriding
+   function Into_Vectors_Last_Index (This : in Statement) return Vector_Index;
+
+   not overriding
+   procedure Into_Vectors_Resize (This : in Statement; New_Size : in Natural);
+
+

+The Get_Into_Vectors_Size returns the number of entries in any of the vector into elements for the given statement. +

+

+The Into_Vectors_First_Index returns the lowest index value for vector into elements (which is always 0, even if the vectors are empty). The Into_Vectors_Last_Index returns the last index of into vectors, and raises the CONSTRAINT_ERROR exception if the vectors are empty. +

+

+The Into_Vectors_Resize procedure allows to change the size of all use vectors for the given statement. +

+ +
+
+   not overriding
+   function Get_Into_Vector_State
+     (This : in Statement;
+      Position : in Into_Position;
+      Index : in Vector_Index)
+     return Data_State;
+
+   not overriding
+   function Get_Into_Vector_String
+     (This : in Statement;
+      Position : in Into_Position;
+      Index : in Vector_Index)
+     return String;
+
+   not overriding
+   function Get_Into_Vector_Integer
+     (This : in Statement;
+      Position : in Into_Position;
+      Index : in Vector_Index)
+     return DB_Integer;
+
+   not overriding
+   function Get_Into_Vector_Long_Long_Integer
+     (This : in Statement;
+      Position : in Into_Position;
+      Index : in Vector_Index)
+     return DB_Long_Long_Integer;
+
+   not overriding
+   function Get_Into_Vector_Long_Float
+     (This : in Statement;
+      Position : in Into_Position;
+      Index : in Vector_Index)
+     return DB_Long_Float;
+
+   not overriding
+   function Get_Into_Vector_Time
+     (This : in Statement;
+      Position : in Into_Position;
+      Index : in Vector_Index)
+     return Ada.Calendar.Time;
+
+

+These functions allow to inspect the state and value of the vector use element identified by its position and index. If the state of the given element is Data_Null, the data-reading functions raise exceptions for that element. +

+ +
+
+   --  Creation of single use elements.
+
+   not overriding
+   procedure Use_String (This : in Statement; Name : in String);
+
+   not overriding
+   procedure Use_Integer (This : in Statement; Name : in String);
+
+   not overriding
+   procedure Use_Long_Long_Integer (This : in Statement; Name : in String);
+
+   not overriding
+   procedure Use_Long_Float (This : in Statement; Name : in String);
+
+   not overriding
+   procedure Use_Time (This : in Statement; Name : in String);
+
+

+These functions instruct the library to create internal simple use elements of the relevant type, identified by the given Name. +

+ +
+

Note:

+

+Simple use elements cannot be created together with vector use elements for the same statement. +

+

+Vector use elements cannot be created together with any into elements for the same statement. +

+
+ +
+
+   --  Creation of vector use elements.
+
+   not overriding
+   procedure Use_Vector_String (This : in Statement; Name : in String);
+
+   not overriding
+   procedure Use_Vector_Integer (This : in Statement; Name : in String);
+
+   not overriding
+   procedure Use_Vector_Long_Long_Integer (This : in Statement; Name : in String);
+
+   not overriding
+   procedure Use_Vector_Long_Float (This : in Statement; Name : in String);
+
+   not overriding
+   procedure Use_Vector_Time (This : in Statement; Name : in String);
+
+

+These functions instruct the library to create internal vector use elements of the relevant type, identified by the given Name. +

+ +
+

Note:

+

+Simple use elements cannot be created together with vector use elements for the same statement. +

+

+Vector use elements cannot be created together with any into elements for the same statement. +

+
+ +
+
+   --  Modifiers for single use elements.
+
+   not overriding
+   procedure Set_Use_State
+     (This : in Statement;
+      Name : in String;
+      State : in Data_State);
+
+   not overriding
+   procedure Set_Use_String
+     (This : in Statement;
+      Name : in String;
+      Value : in String);
+
+   not overriding
+   procedure Set_Use_Integer
+     (This : in Statement;
+      Name : in String;
+      Value : in DB_Integer);
+
+   not overriding
+   procedure Set_Use_Long_Long_Integer
+     (This : in Statement;
+      Name : in String;
+      Value : in DB_Long_Long_Integer);
+
+   not overriding
+   procedure Set_Use_Long_Float
+     (This : in Statement;
+      Name : in String;
+      Value : in DB_Long_Float);
+
+   not overriding
+   procedure Set_Use_Time
+     (This : in Statement;
+      Name : in String;
+      Value : in Ada.Calendar.Time);
+
+

+These operations allow to modify the state and value of simple use elements. Setting the value of use element automatically sets its state to Data_Not_Null. +

+ +
+
+   --  Modifiers for vector use elements.
+
+   not overriding
+   function Get_Use_Vectors_Size (This : in Statement) return Natural;
+
+   not overriding
+   function Use_Vectors_First_Index (This : in Statement) return Vector_Index;
+
+   not overriding
+   function Use_Vectors_Last_Index (This : in Statement) return Vector_Index;
+
+   not overriding
+   procedure Use_Vectors_Resize (This : in Statement; New_Size : in Natural);
+
+

+The Get_Use_Vectors_Size returns the number of entries in any of the vector use elements for the given statement. +

+

+The Use_Vectors_First_Index returns the lowest index value for vector use elements (which is always 0, even if the vectors are empty). The Use_Vectors_Last_Index returns the last index of use vectors, and raises the CONSTRAINT_ERROR exception if the vectors are empty. +

+

+The Use_Vectors_Resize procedure allows to change the size of all use vectors for the given statement. +

+ +
+
+   not overriding
+   procedure Set_Use_Vector_State
+     (This : in Statement;
+      Name : in String;
+      Index : in Vector_Index;
+      State : in Data_State);
+
+   not overriding
+   procedure Set_Use_Vector_String
+     (This : in Statement;
+      Name : in String;
+      Index : in Vector_Index;
+      Value : in String);
+
+   not overriding
+   procedure Set_Use_Vector_Integer
+     (This : in Statement;
+      Name : in String;
+      Index : in Vector_Index;
+      Value : in DB_Integer);
+
+   not overriding
+   procedure Set_Use_Vector_Long_Long_Integer
+     (This : in Statement;
+      Name : in String;
+      Index : in Vector_Index;
+      Value : in DB_Long_Long_Integer);
+
+   not overriding
+   procedure Set_Use_Vector_Long_Float
+     (This : in Statement;
+      Name : in String;
+      Index : in Vector_Index;
+      Value : in DB_Long_Float);
+
+   not overriding
+   procedure Set_Use_Vector_Time
+     (This : in Statement;
+      Name : in String;
+      Index : in Vector_Index;
+      Value : in Ada.Calendar.Time);
+
+

+These operations allow to modify the state and value of vector use elements. Setting the value of use element automatically sets its state to Data_Not_Null. +

+ +
+
+   --  Inspection of single use elements.
+   --
+   --  Note: Use elements can be modified by the database if they
+   --        are bound to out and inout parameters of stored procedures
+   --        (although this is not supported by all database backends).
+   --        This feature is available only for single use elements.
+
+   not overriding
+   function Get_Use_State
+     (This : in Statement;
+      Name : in String)
+     return Data_State;
+
+   not overriding
+   function Get_Use_String
+     (This : in Statement;
+      Name : in String)
+     return String;
+
+   not overriding
+   function Get_Use_Integer
+     (This : in Statement;
+      Name : in String)
+     return DB_Integer;
+
+   not overriding
+   function Get_Use_Long_Long_Integer
+     (This : in Statement;
+      Name : in String)
+     return DB_Long_Long_Integer;
+
+   not overriding
+   function Get_Use_Long_Float
+     (This : in Statement;
+      Name : in String)
+     return DB_Long_Float;
+
+   not overriding
+   function Get_Use_Time
+     (This : in Statement;
+      Name : in String)
+     return Ada.Calendar.Time;
+
+

+These functions allow to inspect the state and value of the simple use element identified by its name. If the state of the given element is Data_Null, the data-reading functions raise exceptions for that element. +

+ + + + + + + + + + + diff --git a/doc/languages/ada/style.css b/doc/languages/ada/style.css new file mode 100644 index 0000000000..7f6dc8ca6f --- /dev/null +++ b/doc/languages/ada/style.css @@ -0,0 +1,67 @@ +body +{ + background-color: white; + color: black; + margin-left: 100px; + margin-right: 100px; + font-family: arial, sans-serif; + font-size: small; +} + +div.note +{ + border-color: black; + border-style: dotted; + border-width: 1px; + margin-top: 20px; + padding-left: 20px; + padding-right: 20px; +} + +span.note +{ + font-size: large; + font-weight: bold; +} + +code +{ +/* font-weight: bold; */ + background-color: white; + color: #8B0000; +/* font-size: large; */ +} + +pre.example +{ + background-color: #F0F0F0; + color: black; + border-width: 1px; + border-style: dashed; + border-color: blue; + padding: 10px 10px 10px 10px; +} + +table.foot-links +{ + width: 100%; + padding-top: 20px; +} + +td.foot-link-left +{ + text-align: left; +} + +td.foot-link-right +{ + text-align: right; +} + +p.copyright +{ + border-top-color: black; + border-top-style: solid; + border-top-width: 1px; + padding-top: 5px; +} diff --git a/doc/multithreading.html b/doc/multithreading.html new file mode 100644 index 0000000000..a22a742ecc --- /dev/null +++ b/doc/multithreading.html @@ -0,0 +1,64 @@ + + + + + + SOCI - multithreading + + + + + +

Multithreading

+ +

The general rule for multithreading is that SOCI classes are not thread-safe, meaning that their instances should not be used concurrently by multiple threads.

+ +

The simplest solution for multithreaded code is to set up a separate session object for each thread that needs to inteact with the database. Depending on the design of the client application this might be also the most straightforward approach.

+ +

For some applications, however, it might be preferable to decouple the set of threads from the set of sessions, so that they can be optimized separately with different resources in mind. The connection_pool class is provided for this purpose:

+ +
+// phase 1: preparation
+
+const size_t poolSize = 10;
+connection_pool pool(poolSize);
+
+for (size_t i = 0; i != poolSize; ++i)
+{
+    session & sql = pool.at(i);
+
+    sql.open("postgresql://dbname=mydb");
+}
+
+// phase 2: usage from working threads
+
+{
+    session sql(pool);
+
+    sql << "select something from somewhere...";
+
+} // session is returned to the pool automatically
+
+ +

The connection_pool's constructor expects the size of the pool and internally creates an array of sessions in the disconnected state. Later, the at function provides non-synchronized access to each element of the array. Note that this function is not thread-safe and exists only to make it easier to set up the pool in the initialization phase.

+ +

Note that it is not obligatory to use the same connection parameters for all sessions in the pool, although this will be most likely the usual case.

+ +

The working threads that need to lease a single session from the pool use the dedicated constructor of the session class - this constructor blocks until some session object becomes available in the pool and attaches to it, so that all further uses will be forwarded to the session object managed by the pool. As long as the local session object exists, the associated session in the pool is locked and no other thread will gain access to it. When the local session variable goes out of scope, the related entry in the pool's internal array is released, so that it can be used by other threads. This way, the connection pool guarantees that its session objects are never used by more than one thread at a time.

+ +

Note that the above scheme is the simplest way to use the connection pool, but it is also constraining in the fact that the session's constructor can block waiting for the availability of some entry in the pool. For more demanding users there are also low-level functions that allow to lease sessions from the pool with timeout on wait. Please consult the reference for details.

+ + + + + + + + + + + diff --git a/doc/queries.html b/doc/queries.html new file mode 100644 index 0000000000..b8afd4725d --- /dev/null +++ b/doc/queries.html @@ -0,0 +1,146 @@ + + + + + + SOCI - queries + + + + + +

Queries

+ +

Simple SQL statements

+ +

In many cases, the SQL query is intended to be executed only once, +which means that statement parsing and execution can go together. +The session class provides a special once +member, which triggers parsing and execution of such one-time +statements:

+ +
+sql.once << "drop table persons";
+
+ +

For shorter syntax, the following form is also allowed:

+ +
+sql << "drop table persons";
+
+ +

The IOStream-like interface is exactly what it looks like, so that +the +statement text can be composed of many parts, involving anything that +is streamable (including custom classes, if they have +appropriate operator<<):

+ +
+string tableName = "persons";
+sql << "drop table " << tableName;
+
+int id = 123;
+sql << "delete from companies where id = " << id;
+
+ +

Query transformation

+ +

In SOCI 3.2.0, query transformation mechanism was introduced.

+ +

Query transformation is specified as user-defined unary function or callable +function object with input parameter of type std::string which +returns object of type std::string as well.

+ +

The query transformation function is registered for current database session +using dedicated session::set_query_transformation method. +Then, the transformation function is called with query string as argument +just before the query is sent to database backend for execution or for +preparation.

+ +

For one-time statements, query transformation is performed before +each execution of statement. For prepared statements, query is transformed +only once, before preparation, regardless how many times it is executed.

+ +

A few short examples how to use query transformation:

+ +
  • defined as free function:
+ +
+std::string less_than_ten(std::string query)
+{
+    return query + " WHERE price < 10";
+}
+
+session sql(postgresql, "dbname=mydb");
+sql.set_query_transformation(less_than_ten);
+sql << "DELETE FROM item";
+
+ +
  • defined as function object:
+ +
+struct order : std::unary_function<std::string, std::string>
+{
+    order(std::string const& by) : by_(by) {}
+
+    result_type operator()(argument_type query) const
+    {
+        return query + " ORDER BY " + by_;
+    }
+
+    std::string by_;
+};
+
+char const* query = "SELECT * FROM product";
+sql.set_query_transformation(order("price");
+sql << query;
+sql.set_query_transformation(order("id");
+sql << query;
+
+ +
  • defined as lambda function (since C++11):
+ +
+std::string dep = "sales";
+sql.set_query_transformation(
+    [&dep](std::string const& query) {
+        return query + " WHERE department = '" + dep + "'";
+});
+sql << "SELECT * FROM employee";
+
+ +

Query transformations enable users with simple mechanism to apply extra +requirements to or interact with SQL statement being executed and that is +without changing the SQL statement itself which may be passed from different +parts of application.

+ +

For example, the query transformation may be used to:

+
    +
  • modify or add clauses of SQL statements (i.e. WHERE + clause with new condition)
  • +
  • prefix table names with new schema to allow namespaces switch
  • +
  • validate SQL statements
  • +
  • perform sanitization checking for any unverified input
  • +
  • apply database-specific features like add optimization hints to SQL + statements (i.e. SELECT /*+RULE*/ A FROM C in Oracle 9)
  • +
+ +

Query transformation mechanism can also be considered for similar uses as +prefix_with function from +SQLAlchemy Expressions API.

+ + + + + + + + + + + + diff --git a/doc/rationale.html b/doc/rationale.html new file mode 100644 index 0000000000..deb5faab0e --- /dev/null +++ b/doc/rationale.html @@ -0,0 +1,313 @@ + + + + + + SOCI - rationale + + + + + +

Rationale FAQ

+ +

This part of the documentation is supposed to gather in a single place +the usual questions (and answers) about SOCI with regard to the design +decisions that have shaped it.

+ +

Q: Why "SOCI"?

+ +

SOCI was initially developed in the environment where Oracle was the +main database technology in use. As a wrapper for the native OCI API +(Oracle Call Interface), the name "Simple Oracle Call Interface" was +quite obvious - until the 2.0 release, when the internal architecture +was largely redesigned to allow the use of backends that support other +database servers. We have kept the same name to indicate that Oracle is +the main supported technology in the sense +that the library includes only those features that were naturally +implemented in +Oracle. With the 2.1 release of the library, two new backends were +added (MySQL and SQLite3) and we decided to drop the original full name +so that new users looking for a library supporting any of these simpler +libraries are not discouraged by seeing "Oracle" somewhere in the name. +The other possible interpretation was "Syntax Oriented Call Interface", +which stresses the fact that SOCI was built to support the most natural +and easy interface for the user that is similar to the Embedded SQL +concept (see below). But on the other hand, SOCI also provides other +features (like object-relational mapping) and as a whole it is not just +"emulator" of the Embedded SQL. With all these considerations in mind, +SOCI is just "SOCI - The C++ Database Access Library".

+ +

Still, Oracle is considered to be the main driving server technology in +terms of the set of features that are supported by the library. This +also means that backends for other servers might need to +work around some of the imposed idioms and protocols, but already +available and well-working PostgreSQL, MySQL and SQLite3 backends show +that it's actually not that +bad and the abstractions provided by the library are actually very +universal. Of +course, some of the features that were provided for Oracle might not be +supported for all other servers, but we think that it's better to have +one leading technology (where at least one group is fully happy) +instead of some "common denominator" for all databases (where nobody is happy).

+ +

Q: Where the basic SOCI syntax comes from?

+ +

The basic SOCI syntax was inspired by the Embedded SQL, which is part +of the SQL standard, supported by the major DB technologies and even +available as built-in part of the languages used in some DB-oriented +integrated +development environments. The term "Embedded SQL" is enough for Google +to spit millions of references - one of the typical examples is:

+ +
+{
+    int a;
+    /* ... */
+    EXEC SQL SELECT salary INTO :a
+             FROM Employee
+             WHERE SSN=876543210;
+    /* ... */
+    printf("The salary is %d\n", a);
+    /* ... */
+}
+
+ +

The above is not a regular C (nor C++) code, of course. It's the mix of +C and SQL and there is a separate, specialized preprocessor needed to +convert it to something that the actual C (or C++) compiler will be +able to understand. This means that the compilation of the program +using embedded SQL is two-phase: preprocess the embedded SQL part and +compile the result. This two-phase development is quite troublesome, +especially when it comes to debugging. Yet, the advantage of it is that +the code expresses the programmer's intents in a very straightforward +way: read something from the database and put it into the local +variable. Just like that.

+ +

The SOCI library was born as an anwer to the following question: is it +possible to have the same expressive power without the disadvantages of +two-phase builds?

+ +

The following was chosen to be the basic SOCI syntax that can mirror +the above Embedded SQL example:

+ +
+int a;
+sql << "SELECT salary FROM Employee WHERE SSN=876543210", into(a);
+
+ +

(as you see, SOCI changes the order of elements a little bit, so that +the SQL query is separate and not mixed with other elements)

+ +

Apart from mimicking the Embedded SQL techniques in the regular, fully +standard C++ code, the above syntax has the following benefit: it is +minimal with respect to what has to +be said. Every single piece above is needed and expresses something +important, like:

+
    +
  • which session should be used (the client can be connected to many +databases at the same time) - here, the sql object +encapsulates the session,
  • +
  • what SQL query should be executed - here, it's the string +literal, but it could be also a std::string variable,
  • +
  • where to put the result - here, the local variable a +will receive the result.
  • +
+ +

Everything else is just a couple of operators that allow to treat the +whole as a single expression. It's rather difficult to remove anything +from this example.

+ +

The fact that the basic SOCI syntax is minimal (but without being +obscure at the same time, see below) means that the programmer does not +need to bother with unnecessary noise that some other database +libraries impose. We hope that after having written one line of code +like above by themselves, most programmers will react with something +like "how obvious!" instead of "how advanced!".

+ +

Q: Why should I use SQL queries as strings in my program? I prefer the query to be generated or composed piece-by-piece by separate functions.

+ +

First, you don't need to use SQL queries as string literals. In bigger +projects it is a common practice to store SQL queries externally (in a +file, or in a... database) and load them before use. This means that +they are not necessarily expected to appear in the program code, as +they do in our simple code examples and the advantage of separating +them from the source code of the main program is, among others, the +possibility to optimize and tune the SQL queries without recompilation +and relinking of the whole program.

+ +

What is the most important, though, is that SOCI does not try to mess +with the +text of the query (apart from very few cases), which means that the +database server will get exactly the same text of the query as is used +in the program. The advantage of this is that there is no new SQL-like +(or even SQL-unlike) +syntax that you would need to learn, and also that it's much easier to +convince a typical +DBA to help with SQL tuning or other specialized activities, if he is +given the material in the form that is not polluted with any foreign +abstractions.

+ +

Q: Why not some stream-like interface, which is well-known to all C++ programmers?

+ +

An example of the stream-like interface might be something like this +(this is imaginary syntax, not supported by SOCI):

+ +
+sql.exec("select a, b, c from some_table");
+
+while (!sql.eof())
+{
+    int a, b, c;
+    sql >> a >> b >> c;
+    // ...
+}
+
+ +

We think that the data stored in the relational database should be +treated as a set of relations - which is exactly what it is. This means +that what is read from the database as a result of some SQL query is a +set of rows. This set might be +ordered, but it is still a set of rows, not a uniformly flat list of values. +This distinction might seem to be unnecessarily low-level and that the +uniform stream-like presentation of data is more preferable, but it's +actually the other way round - the set of rows is something more +structured - and that structure was designed +into the database - than the flat stream and +is therefore less prone to programming errors like miscounting the +number of values that is expected in each row.

+ +

Consider the following programming error:

+ +
+sql.exec("select a, b from some_table"); // only TWO columns
+
+while (!sql.eof())
+{
+    int a, b, c;
+    sql >> a >> b >> c; // this causes "row-tearing"
+    // ...
+}
+
+ +

"How to detect the end of each line in a file" is a common beginner's question that +relates to the use of IOStreams - and this +common question clearly shows that for the record-oriented data the +stream is not an optimal abstraction. Of course, we don't claim that +IOStreams is bad - but we do insist that the record-oriented data is +better manipulated in a way that is also record-aware.

+ +

Having said that, we have provided some form of the stream-like +interface, but with the important limitation that the stream is always bound to the +single row, so that the row-tearing effect is not possible. In other words, +data returned from the database is still structured into rows, but each row can be +separately traversed like a stream. We hope that it provides a good balance between +convenience and code safety.

+ +

Q: Why use indicators instead of some special value to discover that something is null?

+ +

Some programmers are used to indicating the null value by using some +special (which means: "unlikely" to be ever used) value - for example, +to use the smallest integer value to indicate null integer. Or to use +empty string to indicate null string. And so on.

+ +

We think that it's completely wrong. Null (in the database sense) is an +information about the data. It describes the state of the data and if it's null, +then there's no data at all. +Nothing. Null. It does not make any sense to talk about some special +value if in fact there is no +value at all - especially if we take into account that, for example, the +smallest integer value (or whatever else you choose as the "special" +value) might not be that +special in the given application or domain.

+ +

Thus, SOCI uses a separate indicators to describe the state of +exchanged data. It also has an additional benefit of allowing the +library to convey more than two states (null and not null). Indeed, the +SOCI library uses indicators also to report that the data was read, but +truncated (this applies to strings when reading to fixed-length +character arrays). Truncation is also an information about the data and +as such it's better to have it in addition to the data, not as part of +it.

+ +

Having said that, it is important to point at the Integration with Boost +that allows to use boost::optional<T> to conveniently pack together +the data and the information about its state.

+ +

Q: Overloaded comma operator is just obfuscation, I don't like it.

+ +

Well, consider the following:

+ +

"Send the query X to the server Y and put result into variable Z."

+ +

Above, the "and" plays a role of the comma. Even if overloading the +comma operator is not a very popular practice in C++, some libraries do +this, achieving terse and easy to learn syntax. We are pretty sure that +in SOCI the comma operator was overloaded with a good effect.

+ +

Q: The operator<< provides a bad abstraction for the "input" statements.

+ +

Indeed, the operator<< in the basic SOCI syntax +shows that something (the query) is sent somewhere (to the server). +Some people don't like this, especially when the "select" statements +are involved. If the high-level idea is to read + data from somewhere, then operator<< +seems unintuitive to the die-hard IOStreams users. The fact is, +however, that the code containing SQL statement already indicates +that there is a client-server relationship with some other software +component (very likely remote). In such code it does not make any sense +to pretend that the communication is one-way only, because it's clear +that even the "select" statements need to be sent somewhere. This approach is +also more uniform and allows to cover other statements like "drop +table" or alike, where no data is expected to be exchanged at all (and +therefore the IOStreams analogies for data exchange have no sense at +all). No matter what is the kind of the SQL statement, it is sent + to the server and this +"sending" justifies the choice of operator<<.

+ +

Using different operators (operator>> and operator<<) +as a way of distinguishing between different high-level ideas +(reading and writing from the data store, +respectively) does make sense on much higher level of abstraction, +where the SQL statement itself is already hidden - and we do encourage +programmers to use SOCI for implementing such high-level abstractions. +For this, the object-relational mapping facilities available in SOCI +might prove to be a valuable tool as well, as an effective bridge +between the low-level world of SQL statements and the high-level world +of user-defined abstract data types.

+ +

Q: Why the Boost license?

+ +

We decided to use the Boost license, because +it's well recognized in the C++ community, allows us to keep our +minimum copyrights, and at the same time allows SOCI to be safely used +in commercial projects, without imposing concerns (or just plain +uncertainty) typical to other open source licenses, like GPL. We also +hope that by choosing the Boost license we have made the life easier +for both us and our users. It saves us from answering law-related +questions that were already answered on the Boost license info +page and it should also give more confidence to our users - +especially to those of them, who already accepted the conditions of the +Boost license - the just have one license less to analyze.

+ +

Still, if for any reason the conditions of this license are not +acceptable, we encourage the users to contact us directly (see links +on the relevant SOCI page) to discuss any remaining concerns.

+ + + + + + + + + + + diff --git a/doc/reference.html b/doc/reference.html new file mode 100644 index 0000000000..d265f75e26 --- /dev/null +++ b/doc/reference.html @@ -0,0 +1,963 @@ + + + + + + SOCI - reference + + + + + +

Client interface reference

+ + + +

The core client interface is a set of classes and free functions declared in +the soci.h header file. All names are dbeclared in the soci +namespace.

+ +

There are also additional names declared in the soci::details +namespace, but they are not supposed to be directly used by the users +of the library and are therefore not documented here. When such types +are used in the declarations that are part of the "public" interface, +they are replaced by "IT", which means "internal type". Types related +to the backend interface are named here, but documented on the next page.

+ +

commonly used types

+ +

The following types are commonly used in the rest of the interface:

+ +
+// data types, as seen by the user
+enum data_type { dt_string, dt_date, dt_double, dt_integer, dt_long_long, dt_unsigned_long_long };
+
+// the enum type for indicator variables
+enum indicator { i_ok, i_null, i_truncated };
+
+// the type used for reporting exceptions
+class soci_error : public std::runtime_error { /* ... */ };
+
+ +

The data_type type defines the basic SOCI data types. +User provided data types need to be associated with one of these basic +types.

+ +

The indicator type defines the possible states of data.

+ +

The soci_error type is used for error reporting.

+ +

class session

+ +

The session class encapsulates the connection to the +database.

+ +
+class session
+{
+public:
+    session();
+    explicit session(connection_parameters const & parameters);
+    session(backend_factory const & factory, std::string const & connectString);
+    session(std::string const & backendName, std::string const & connectString);
+    explicit session(std::string const & connectString);
+    explicit session(connection_pool & pool);
+
+    ~session();
+
+    void open(backend_factory const & factory, std::string const & connectString);
+    void open(std::string const & backendName, std::string const & connectString);
+    void open(std::string const & connectString);
+    void close();
+    void reconnect();
+
+    void begin();
+    void commit();
+    void rollback();
+
+    IT once;
+    IT prepare;
+
+    template <typename T> IT operator<<(T const & t);
+
+    bool got_data() const;
+
+    bool get_next_sequence_value(std::string const & sequence, long & value);
+    bool get_last_insert_id(std::string const & table, long & value);
+
+    std::ostringstream & get_query_stream();
+
+    void set_log_stream(std::ostream * s);
+    std::ostream * get_log_stream() const;
+
+    std::string get_last_query() const;
+
+    void uppercase_column_names(bool forceToUpper);
+
+    details::session_backend * get_backend();
+
+    std::string get_backend_name() const;
+};
+
+ +

This class contains the following members:

+
    +
  • Various constructors. The default one creates the session in the disconnected state. + The others expect the backend factory object, or the backend name, or the URL-like + composed connection string or the special parameters object containing both the backend + and the connection string as well as possibly other connection options. + The last constructor creates a session proxy associated + with the session that is available in the given pool and releases it back to the pool + when its lifetime ends. Example: +
    +session sql(postgresql, "dbname=mydb");
    +session sql("postgresql", "dbname=mydb");
    +session sql("postgresql://dbname=mydb");
    +
    + The constructors that take backend name as string load the shared library (if not yet loaded) + with name computed as libsoci_ABC.so (or libsoci_ABC.dll on Windows) + where ABC is the given backend name. +
  • +
  • open, close and reconnect functions for + reusing the same session object many times; the reconnect function attempts + to establish the connection with the same parameters as most recently used with constructor + or open. The arguments for open are treated in the same way as + for constructors. +
  • +
  • begin, commit and rollback +functions for transaction control. +
  • +
  • once member, which is used for performing instant +queries that do not need to be separately prepared. Example: +
    +sql.once << "drop table persons";
    +
    +
  • +
  • prepare member, which is used for statement +preparation - the result of the statement preparation must be provided +to the constructor of the statement class. Example: +
    +int i;
    +statement st = (sql.prepare <<
    +                "insert into numbers(value) values(:val)", use(i));
    +
    +
  • +
  • operator<< that is a shortcut forwarder to the +equivalent operator of the once member. Example: +
    +sql << "drop table persons";
    +
    +
  • +
  • got_data returns true if the last executed query had non-empty result.
  • +
  • get_next_sequence_value returns true if the next value of + the sequence with the specified name was generated and returned in its + second argument. Unless you can be sure that your program will use only + databases that support sequences, consider using this method in conjunction + with get_last_insert_id() as explained in + "Working with sequences" section.
  • +
  • get_last_insert_id returns true if it could retrieve the + last value automatically generated by the database for an auto-incremented + field. Notice that although this method takes the table name, for some + databases, such as Microsoft SQL Server and SQLite, this value is actually + global, so you should attempt to retrieve it immediately after performing an + insertion.
  • +
  • get_query_stream provides direct access to the stream object that is used + to accumulate the query text and exists in particular to allow the user to imbue specific locale + to this stream.
  • +
  • set_log_stream and get_log_stream functions for setting and getting + the current stream object used for basic query logging. By default, it is NULL, which means no logging. + The string value that is actually logged into the stream is one-line verbatim copy of the query string provided by the user, + without including any data from the use elements. The query is logged exactly once, before the preparation step.
  • +
  • get_last_query retrieves the text of the last used query.
  • +
  • uppercase_column_names allows to force all column names to uppercase in dynamic row description; + this function is particularly useful for portability, since various database servers + report column names differently (some preserve case, some change it).
  • +
  • get_backend returns the internal +pointer to the concrete backend implementation of the session. This is +provided for advanced users that need access to the functionality that +is not otherwise available.
  • +
  • get_backend_name is a convenience forwarder to the same function +of the backend object.
  • +
+ +

See Connections and simple queries for more +examples.

+ +

class connection_parameters

+ +

The connection_parameters class is a simple container for the backend pointer, connection string and any other connection options. It is used together with session constructor and open() method.

+ +
+class connection_parameters
+{
+public:
+    connection_parameters();
+    connection_parameters(backend_factory const & factory, std::string const & connectString);
+    connection_parameters(std::string const & backendName, std::string const & connectString);
+    explicit connection_parameters(std::string const & fullConnectString);
+
+    void set_option(const char * name, std::string const & value);
+    bool get_option(const char * name, std::string & value) const
+};
+
+ +

The methods of this class are:

+
    +
  • Default constructor is rarely used as it creates an uninitialized + object and the only way to initialize it later is to assign another, valid, + connection_parameters object to this one.
  • +
  • The other constructors correspond to the similar constructors of the + session class and specify both the backend, either as a + pointer to it or by name, and the connection string.
  • +
  • set_option can be used to set the value of an option with + the given name. Currently all option values are strings, so if you need to + set a numeric option you need to convert it to a string first. If an option + with the given name had been already set before, its old value is + overwritten.
  • +
+ +

class connection_pool

+ +

The connection_pool class encapsulates the thread-safe pool of connections +and ensures that only one thread at a time has access to any connection that it manages.

+ +
+class connection_pool
+{
+public:
+    explicit connection_pool(std::size_t size);
+    ~connection_pool();
+
+    session & at(std::size_t pos);
+
+    std::size_t lease();
+    bool try_lease(std::size_t & pos, int timeout);
+    void give_back(std::size_t pos);
+};
+
+ +

The operations of the pool are:

+ +
    +
  • Constructor that takes the intended size of the pool. After construction, + the pool contains regular session objects in disconnected state.
  • +
  • at function that provides direct access to any given entry + in the pool. This function is non-synchronized.
  • +
  • lease function waits until some entry is available (which means + that it is not used) and returns the position of that entry in the pool, marking + it as locked.
  • +
  • try_lease acts like lease, but allows to set up a + time-out (relative, in milliseconds) on waiting. Negative time-out value means no time-out. + Returns true if the entry was obtained, in which case its position + is written to the pos parametr, and false if no entry + was available before the time-out.
  • +
  • give_back should be called when the entry on the given position + is no longer in use and can be passed to other requesting thread.
  • +
+

Note: calls to lease and give_back are automated by the +dedicated constructor of the session class, see above.

+ +

class transaction

+ +

The class transaction can be used for associating the transaction +with some code scope. It is a RAII wrapper for regular transaction operations that +automatically rolls back in its destructor if the transaction was not explicitly +committed before.

+
+class transaction
+{
+public:
+    explicit transaction(session & sql);
+
+    ~transaction();
+
+    void commit();
+    void rollback();
+
+private:
+    // ...
+};
+
+ +

Note that objects of this class are not notified of other transaction related operations +that might be executed by user code explicitly or hidden inside SQL queries. +It is not recommended to mix different ways of managing transactions.

+ +

function into

+ +

The function into is used for binding local output data +(in other words, it defines where the results of the query are stored).

+ +
+template <typename T>
+IT into(T & t);
+
+template <typename T, typename T1>
+IT into(T & t, T1 p1);
+
+template <typename T>
+IT into(T & t, indicator & ind);
+
+template <typename T, typename T1>
+IT into(T & t, indicator & ind, T1 p1);
+
+template <typename T>
+IT into(T & t, std::vector<indicator> & ind);
+
+ +

Example:

+ +
+int count;
+sql << "select count(*) from person", into(count);
+
+ +

See Binding local data +for more examples.

+ +

function use

+ +

The function use is used for binding local input data (in +other words, it defines where the parameters of the query come from).

+ +
+template <typename T>
+IT use(T & t);
+
+template <typename T, typename T1>
+IT use(T & t, T1 p1);
+
+template <typename T>
+IT use(T & t, indicator & ind);
+
+template <typename T, typename T1>
+IT use(T & t, indicator & ind, T1 p1);
+
+template <typename T>
+IT use(T & t, std::vector<indicator> const & ind);
+
+template <typename T, typename T1>
+IT use(T & t, std::vector<indicator> const & ind, T1 p1);
+
+ +

Example:

+ +
+int val = 7;
+sql << "insert into numbers(val) values(:val)", use(val);
+
+ +

See Binding local data +for more examples.

+ +

class statement

+ +

The statement class encapsulates the prepared statement.

+ +
+class statement
+{
+public:
+    statement(session & s);
+    statement(IT const & prep);
+    ~statement();
+
+    statement(statement const & other);
+    void operator=(statement const & other);
+
+    void alloc();
+    void bind(values & v);
+    void exchange(IT const & i);
+    void exchange(IT const & u);
+    void clean_up();
+
+    void prepare(std::string const & query);
+    void define_and_bind();
+
+    bool execute(bool withDataExchange = false);
+    long long get_affected_rows();
+    bool fetch();
+
+    bool got_data() const;
+
+    void describe();
+    void set_row(row * r);
+    void exchange_for_rowset(IT const & i);
+
+    details::statement_backend * get_backend();
+};
+
+ +

This class contains the following members:

+
    +
  • Constructor accepting the session object. This can +be used for later query preparation. Example: +
    +statement stmt(sql);
    +
    +
  • +
  • Constructor accepting the result of using prepare +on the session object, see example provided above for the + session class.
  • +
  • Copy operations.
  • +
  • alloc function, which allocates necessary internal resources.
  • +
  • bind function, which is used to bind the values +object - this is used in the object-relational mapping and normally +called automatically.
  • +
  • exchange functions for registering the binding of local data - +they expect the result of calling the into or use +functions and are normally invoked automatically.
  • +
  • clean_up function for cleaning up resources, normally +called automatically.
  • +
  • prepare function for preparing the statement for +repeated execution.
  • +
  • define_and_bind function for actually executing the +registered bindings, normally called automatically.
  • +
  • execute function for executing the statement. If its +parameter is false then there is no data exchange with +locally bound variables (this form should be used if later fetch +of multiple rows is foreseen). Returns true if there was at least +one row of data returned.
  • +
  • get_affected_rows function returns the number of rows +affected by the last statement. Returns -1 if it's not +implemented by the backend being used.
  • +
  • fetch function for retrieving the next portion of +the result. Returns true if there was new data.
  • +
  • got_data return true if the most recent +execution returned any rows.
  • +
  • describe function for extracting the type +information for the result (note: no data is exchanged). This is normally +called automatically and only when dynamic resultset binding is used.
  • +
  • set_row function for associating the statement +and row objects, normally called automatically.
  • +
  • exchange_for_rowset as a special case for binding rowset +objects.
  • +
  • get_backend function that returns the internal +pointer to +the concrete backend implementation of the statement object. This is +provided +for advanced users that need access to the functionality that is not +otherwise available.
  • +
+ +

See Statement preparation and +repeated execution for example uses.

+ +

Most of the functions from the statement class +interface are called automatically, but can be also used explicitly. +See Interfaces for the description of various way to use +this interface.

+ +

class procedure

+ +

The procedure class encapsulates the call to the stored +procedure and is aimed for higher portability of the client code.

+ +
+class procedure
+{
+public:
+    procedure(IT const & prep);
+
+    bool execute(bool withDataExchange = false);
+    bool fetch();
+    bool got_data() const;
+};
+
+ +

The constructor expects the result of using prepare +on the session object.

+ +

See Stored procedures for +examples.

+ +

class type_conversion

+ +

The type_conversion class is a traits class that is +supposed to be provided (specialized) by the user for defining +conversions to and from one of the basic SOCI types.

+ +
+template <typename T>
+struct type_conversion
+{
+    typedef T base_type;
+
+    static void from_base(base_type const & in, indicator ind, T & out);
+
+    static void to_base(T const & in, base_type & out, indicator & ind);
+};
+
+ +

Users are supposed to properly implement the from_base and to_base +functions in their specializations of this template class.

+ +

See Extending +SOCI to support custom (user-defined) C++ types.

+ +

class row

+ +

The row class encapsulates the data and type information +retrieved for the single row when the dynamic rowset binding is used.

+ +
+class row
+{
+public:
+    row();
+    ~row();
+
+    void uppercase_column_names(bool forceToUpper);
+
+    std::size_t size() const;
+
+    indicator get_indicator(std::size_t pos) const;
+    indicator get_indicator(std::string const & name) const;
+
+    column_properties const & get_properties (std::size_t pos) const;
+    column_properties const & get_properties (std::string const & name) const;
+
+    template <typename T>
+    T get(std::size_t pos) const;
+
+    template <typename T>
+    T get(std::size_t pos, T const & nullValue) const;
+
+    template <typename T>
+    T get(std::string const & name) const;
+
+    template <typename T>
+    T get(std::string const & name, T const & nullValue) const;
+
+    template <typename T>
+    row const & operator>>(T & value) const;
+
+    void skip(std::size_t num = 1) const;
+
+    void reset_get_counter() const
+};
+
+ +

This class contains the following members:

+
    +
  • Default constructor that allows to declare a row +variable.
  • +
  • uppercase_column_names - see the same function in the session class.
  • +
  • size function that returns the number of columns in +the row.
  • +
  • get_indicator function that returns the indicator value +for the given column (column is specified by position - starting from 0 +- or by name).
  • +
  • get_properties function that returns the properties +of the column given by position (starting from 0) or by name.
  • +
  • get functions that return the value of the column +given by position or name. If the column contains null, then these +functions either return the provided "default" nullValue +or throw an exception.
  • +
  • operator>> for convenience stream-like +extraction interface. Subsequent calls to this function are equivalent +to calling get with increasing position parameter, +starting from the beginning.
  • +
  • skip and reset_get_counter allow to change the +order of data extraction for the above operator.
  • +
+ +

See Dynamic resultset binding for +examples.

+ +

class column_properties

+ +

The column_properties class provides the type and name +information about the particular column in a rowset.

+ +
+class column_properties
+{
+public:
+    std::string get_name() const;
+    data_type get_data_type() const;
+};
+
+ +

This class contains the following members:

+
    +
  • get_name function that returns the name of the column.
  • +
  • get_data_type that returns the type of the column.
  • +
+ +

See Dynamic resultset binding for +examples.

+ +

class values

+ +

The values class encapsulates the data and type +information and is used for object-relational mapping.

+ +
+class values
+{
+public:
+    values();
+
+    void uppercase_column_names(bool forceToUpper);
+
+    indicator get_indicator(std::size_t pos) const;
+    indicator get_indicator(std::string const & name) const;
+
+    template <typename T>
+    T get(std::size_t pos) const;
+
+    template <typename T>
+    T get(std::size_t pos, T const & nullValue) const;
+
+    template <typename T>
+    T get(std::string const & name) const;
+    
+    template <typename T>
+    T get(std::string const & name, T const & nullValue) const;
+
+    template <typename T>
+    values const & operator>>(T & value) const;
+
+    void skip(std::size_t num = 1) const;
+    void reset_get_counter() const;
+    
+    template <typename T>
+    void set(std::string const & name, T const & value, indicator indic = i_ok);
+
+    template <typename T>
+    void set(const T & value, indicator indic = i_ok);
+
+    template <typename T>
+    values & operator<<(T const & value);
+};
+
+ +

This class contains the same members as the row class (with the same meaning) +plus:

+
    +
  • set function for storing values in named columns or in subsequent positions.
  • +
  • operator<< for convenience.
  • +
+ +

See Object-relational mapping +for examples.

+ +

class blob

+ +

The blob class encapsulates the "large object" +functionality.

+ +
+class blob
+{
+public:
+    explicit blob(session & s);
+    ~blob();
+
+    std::size_t getLen();
+    std::size_t read(std::size_t offset, char * buf, std::size_t toRead);
+    std::size_t write(std::size_t offset, char const * buf, std::size_t toWrite);
+    std::size_t append(char const * buf, std::size_t toWrite);
+    void trim(std::size_t newLen);
+
+    details::blob_backend * get_backend();
+};
+
+ +

This class contains the following members:

+
    +
  • Constructor associating the blob object with the session object.
  • +
  • get_len function that returns the size of the BLOB +object.
  • +
  • read function that reads the BLOB data into provided +buffer.
  • +
  • write function that writes the BLOB data from +provided buffer.
  • +
  • append function that appends to the existing BLOB +data.
  • +
  • trim function that truncates the existing data to +the new length.
  • +
  • get_backend function that returns the internal +pointer to +the concrete backend implementation of the BLOB object. This is +provided +for advanced users that need access to the functionality that is not +otherwise available.
  • +
+ +

See Large objects (BLOBs) for more +discussion.

+ +

class rowid

+ +

The rowid class encapsulates the "row identifier" object.

+ +
+class rowid
+{
+public:
+    explicit rowid(Session & s);
+    ~rowid();
+
+    details::rowid_backend * get_backend();
+};
+
+ +

This class contains the following members:

+
    +
  • Constructor associating the rowid object with the session +object.
  • +
  • get_backend function that returns the internal +pointer to +the concrete backend implementation of the rowid object.
  • +
+ +

class backend_factory

+ +

The backend_factory class provides the abstract interface +for concrete backend factories.

+ +
+struct backend_factory
+{
+    virtual details::session_backend * make_session(
+        std::string const & connectString) const = 0;
+};
+
+ +

The only member of this class is the make_session function +that is supposed to create concrete backend implementation of the +session object.

+ +

Objects of this type are declared by each backend and should be +provided to the constructor of the session class. +In simple programs users do not need to use this class directly, but +the example use is:

+ +
+backend_factory & factory = postgresql;
+std::string connectionParameters = "dbname=mydb";
+
+session sql(factory, parameters);
+
+ +

Simple client interface

+ +

The simple client interface is provided with other languages in mind, +to allow easy integration of the SOCI library with script interpreters and those +languages that have the ability to link directly with object files using +the "C" calling convention.

+

The functionality of this interface is limited and in particular the +dynamic rowset description and type conversions are not supported in this release. +On the other hand, the important feature of this interface is that it does not +require passing pointers to data managed by the user, because all data is handled +at the SOCI side. This should make it easier to integrate SOCI with languages that +have constrained ability to understand the C type system.

+

Users of this interface need to explicitly #include <soci-simple.h>.

+ +
+typedef void * session_handle;
+session_handle soci_create_session(char const * connectionString);
+void soci_destroy_session(session_handle s);
+
+void soci_begin(session_handle s);
+void soci_commit(session_handle s);
+void soci_rollback(session_handle s);
+
+int soci_session_state(session_handle s);
+char const * soci_session_error_message(session_handle s);
+
+ +

The functions above provide the session abstraction with the help of opaque handle. +The soci_session_state function returns 1 if there was no error +during the most recently executed function and 0 otherwise, in which +case the soci_session_error_message can be used to obtain a human-readable +error description.

+

Note that the only function that cannot report all errors this way is soci_create_session, +which returns NULL if it was not possible to create an internal object +representing the session. However, if the proxy object was created, but the connection +could not be established for whatever reason, the error message can be obtained in +the regular way.

+ +
+typedef void * statement_handle;
+statement_handle soci_create_statement(session_handle s);
+void soci_destroy_statement(statement_handle st);
+
+int soci_statement_state(statement_handle s);
+char const * soci_statement_error_message(statement_handle s);
+
+ +

The functions above create and destroy the statement object. If the statement cannot +be created by the soci_create_statement function, the error condition is set up in the related session object; +for all other functions the error condition is set in the statement object itself.

+ +
+int soci_into_string   (statement_handle st);
+int soci_into_int      (statement_handle st);
+int soci_into_long_long(statement_handle st);
+int soci_into_double   (statement_handle st);
+int soci_into_date     (statement_handle st);
+
+int soci_into_string_v   (statement_handle st);
+int soci_into_int_v      (statement_handle st);
+int soci_into_long_long_v(statement_handle st);
+int soci_into_double_v   (statement_handle st);
+int soci_into_date_v     (statement_handle st);
+
+ +

These functions create new data items for storing query results (into elements). +These elements can be later identified by their position, which is counted from 0. For convenience, +these function return the position of the currently added element. In case of error, +-1 is returned and the error condition is set in the statement object.

+

The _v versions create a vector into elements, which can be used +to retrieve whole arrays of results.

+ +
+int soci_get_into_state(statement_handle st, int position);
+int soci_get_into_state_v(statement_handle st, int position, int index);
+
+ +

This function returns 1 if the into element at the given position has non-null value and 0 otherwise. +The _v version works with vector elements and expects an array index.

+ +
+int  soci_into_get_size_v(statement_handle st);
+void soci_into_resize_v  (statement_handle st, int new_size);
+
+ +

The functions above allow to get and set the size of vector into element.

+ +

Note: the soci_into_resize_v always sets all into vectors in the given statement +to the same size, which guarantees that all vector into elements have equal size.

+ +
+char const * soci_get_into_string   (statement_handle st, int position);
+int          soci_get_into_int      (statement_handle st, int position);
+long long    soci_get_into_long_long(statement_handle st, int position);
+double       soci_get_into_double   (statement_handle st, int position);
+char const * soci_get_into_date     (statement_handle st, int position);
+
+char const * soci_get_into_string_v   (statement_handle st, int position, int index);
+int          soci_get_into_int_v      (statement_handle st, int position, int index);
+long long    soci_get_into_long_long_v(statement_handle st, int position, int index);
+double       soci_get_into_double_v   (statement_handle st, int position, int index);
+char const * soci_get_into_date_v     (statement_handle st, int position, int index);
+
+ +

The functions above allow to retrieve the current value of the given into element.

+ +

Note: the date function returns the date value in the "YYYY MM DD HH mm ss" string format.

+ +
+void soci_use_string   (statement_handle st, char const * name);
+void soci_use_int      (statement_handle st, char const * name);
+void soci_use_long_long(statement_handle st, char const * name);
+void soci_use_double   (statement_handle st, char const * name);
+void soci_use_date     (statement_handle st, char const * name);
+
+void soci_use_string_v   (statement_handle st, char const * name);
+void soci_use_int_v      (statement_handle st, char const * name);
+void soci_use_long_long_v(statement_handle st, char const * name);
+void soci_use_double_v   (statement_handle st, char const * name);
+void soci_use_date_v     (statement_handle st, char const * name);
+
+ +

The functions above allow to create new data elements that will be used to provide +data to the query (use elements). The new elements can be later identified by given name, which +must be unique for the given statement.

+ +
+void soci_set_use_state(statement_handle st, char const * name, int state);
+
+ +

The soci_set_use_state function allows to set the state of the given use element. +If the state parameter is set to non-zero the use element is considered non-null +(which is also the default state after creating the use element).

+ +
+int  soci_use_get_size_v(statement_handle st);
+void soci_use_resize_v  (statement_handle st, int new_size);
+
+ +

These functions get and set the size of vector use elements (see comments for vector into elements above).

+ +
+void soci_set_use_string   (statement_handle st, char const * name, char const * val);
+void soci_set_use_int      (statement_handle st, char const * name, int val);
+void soci_set_use_long_long(statement_handle st, char const * name, long long val);
+void soci_set_use_double   (statement_handle st, char const * name, double val);
+void soci_set_use_date     (statement_handle st, char const * name, char const * val);
+
+void soci_set_use_state_v    (statement_handle st, char const * name, int index, int state);
+void soci_set_use_string_v   (statement_handle st, char const * name, int index, char const * val);
+void soci_set_use_int_v      (statement_handle st, char const * name, int index, int val);
+void soci_set_use_long_long_v(statement_handle st, char const * name, int index, long long val);
+void soci_set_use_double_v   (statement_handle st, char const * name, int index, double val);
+void soci_set_use_date_v     (statement_handle st, char const * name, int index, char const * val);
+
+ +

The functions above set the value of the given use element, for both single and vector elements.

+ +

Note: the expected format for the data values is "YYYY MM DD HH mm ss".

+ +
+int          soci_get_use_state    (statement_handle st, char const * name);
+char const * soci_get_use_string   (statement_handle st, char const * name);
+int          soci_get_use_int      (statement_handle st, char const * name);
+long long    soci_get_use_long_long(statement_handle st, char const * name);
+double       soci_get_use_double   (statement_handle st, char const * name);
+char const * soci_get_use_date     (statement_handle st, char const * name);
+
+ +

These functions allow to inspect the state and value of named use elements.

+

Note: these functions are provide only for single use elements, not for vectors; +the rationale for this is that modifiable use elements are not supported for bulk operations.

+ +
+void soci_prepare(statement_handle st, char const * query);
+int  soci_execute(statement_handle st, int withDataExchange);
+int  soci_fetch(statement_handle st);
+int  soci_got_data(statement_handle st);
+
+ +

The functions above provide the core execution functionality for the statement object +and their meaning is equivalent to the respective functions in the core C++ interface +described above.

+ + + + + + + + + + + + + diff --git a/doc/statements.html b/doc/statements.html new file mode 100644 index 0000000000..81a3f3a8b9 --- /dev/null +++ b/doc/statements.html @@ -0,0 +1,389 @@ + + + + + + SOCI - statements, procedures and transactions + + + + + +

Statements, procedures and transactions

+ + + +

Statement preparation and repeated execution

+ +

Consider the following examples:

+ +
+// Example 1.
+for (int i = 0; i != 100; ++i)
+{
+    sql << "insert into numbers(value) values(" << i << ")";
+}
+
+// Example 2.
+for (int i = 0; i != 100; ++i)
+{
+    sql << "insert into numbers(value) values(:val)", use(i);
+}
+
+ +

Both examples will populate the table numbers with the +values from 0 to 99.

+ +

The problem is that in both examples, not only the statement execution is +repeated 100 times, but also the statement parsing and preparation. +This means unnecessary overhead, even if some of the database servers +are likely to optimize the second case. In fact, more complicated queries are +likely to suffer in terms of lower performance, because finding the optimal +execution plan is quite expensive and here it would be needlessly repeated.

+ +

The following example uses the class statement +explicitly, by preparing the statement only once and repeating its +execution with changing data (note the use of prepare +member of session class):

+ + +
+int i;
+statement st = (sql.prepare <<
+                "insert into numbers(value) values(:val)",
+                use(i));
+for (i = 0; i != 100; ++i)
+{
+    st.execute(true);
+}
+
+ +

The true parameter given to the execute +method indicates that the actual data exchange is wanted, so that the +meaning of the whole example is "prepare the statement and exchange the +data for each value of variable i".

+ +
+

Portability note:

+

The above syntax is supported for all backends, even if some database server +does not actually provide this functionality - in which case the library will internally +execute the query in a single phase, without really separating +the statement preparation from execution.

+

For PostgreSQL servers older than 8.0 it is necessary to define the +SOCI_POSTGRESQL_NOPREPARE macro while compiling the library +to fall back to this one-phase behaviour. Simply, pass +-DSOCI_POSTGRESQL_NOPREPARE=ON variable to CMake.

+
+ +

Rowset and iterator-based access

+ +

The rowset class provides an alternative means of executing queries and accessing results using STL-like iterator interface.

+ +

The rowset_iterator type is compatible with requirements defined for input iterator category and is available via iterator and const_iterator definitions in the rowset class.

+

The rowset itself can be used only with select queries.

+ +

The following example creates an instance of the rowset class and binds query results into elements of int type - in this query only one result column is expected. After executing the query the code iterates through the query result using rowset_iterator:

+ +
+rowset<int> rs = (sql.prepare << "select values from numbers");
+
+for (rowset<int>::const_iterator it = rs.begin(); it != rs.end(); ++it)
+{
+     cout << *it << '\n';
+}
+
+ +

Another example shows how to retrieve more complex results, where rowset elements are of type row and therefore use dynamic bindings:

+ +
+// person table has 4 columns
+
+rowset<row> rs = (sql.prepare << "select id, firstname, lastname, gender from person");
+
+// iteration through the resultset:
+for (rowset<row>::const_iterator it = rs.begin(); it != rs.end(); ++it)
+{
+    row const& row = *it;
+
+    // dynamic data extraction from each row:
+    cout << "Id: " << row.get<int>(0) << '\n'
+         << "Name: " << row.get<string>(1) << " " << row.get<string>(2) << '\n'
+         << "Gender: " << row.get<string>(3) << endl;
+}
+
+ +

rowset_iterator can be used with standard algorithms as well:

+ +
+rowset<string> rs = (sql.prepare << "select firstname from person");
+
+std::copy(rs.begin(), rs.end(), std::ostream_iterator<std::string>(std::cout, "\n"));
+
+ +

Above, the query result contains a single column which is bound to rowset element of type of std::string. All records are sent to standard output using the std::copy algorithm.

+ +

Bulk operations

+ +

When using some databases, further performance improvements may be possible by having the underlying database API group operations together to reduce network roundtrips. SOCI makes such bulk operations possible by supporting std::vector +based types:

+ +
+// Example 3.
+const int BATCH_SIZE = 25;
+std::vector<int> ids;
+for (int i = 0; i != BATCH_SIZE; ++i)
+{
+    ids.push_back(i);
+}
+
+statement st = (sql.prepare <<
+                "insert into numbers(value) values(:val)",
+                use(ids));
+for (int i = 0; i != 4; ++i)
+{
+    st.execute(true);
+}
+
+ +

(Of course, the size of the vector that will achieve optimum +performance will vary, depending on many environmental factors, such as +network speed.)

+ +

It is also possible to read all the numbers written in the above +examples:

+ +
+int i;
+statement st = (sql.prepare <<
+                "select value from numbers order by value",
+                into(i));
+st.execute();
+while (st.fetch())
+{
+    cout << i << '\n';
+}
+
+ +

In the above example, the execute method is called +with the default parameter false. This means that the +statement should be executed, but the actual data exchange will be +performed later.

+ +

Further fetch +calls perform the actual data retrieval and cursor traversal. The +end-of-cursor condition is indicated by the fetch +function returning false.

+ +

The above code example should be treated as an idiomatic way +of reading many rows of data, one at a time.

+ +

It is further possible to select records in batches into std::vector +based types, with the size of the vector specifying the number of +records to retrieve in each round trip:

+ +
+std::vector<int> valsOut(100);
+sql << "select val from numbers", into(valsOut);
+
+ +

Above, the value 100 indicates that no more values +should be retrieved, even if it would be otherwise possible. If there +are less rows than asked for, the vector will be appropriately +down-sized.

+ +

The statement::execute() and statement::fetch() +functions can also be used to repeatedly select all rows returned by a +query into a vector based type:

+ +
+const int BATCH_SIZE = 30;
+std::vector<int> valsOut(BATCH_SIZE);
+statement st = (sql.prepare <<
+                "select value from numbers",
+                into(valsOut));
+st.execute();
+while (st.fetch())
+{
+    std::vector<int>::iterator pos;
+    for(pos = valsOut.begin(); pos != valsOut.end(); ++pos)
+    {
+        cout << *pos << '\n';
+    }
+
+    valsOut.resize(BATCH_SIZE);
+}
+
+ +

Assuming there are 100 rows returned by the query, the above code +will retrieve and print all of them. Since the output vector was +created with size 30, it will take (at least) 4 calls to fetch() +to retrieve all 100 values. Each call to fetch() +can potentially resize the vector to a size less than its initial size +- how often this happens depends on the underlying database +implementation. +This explains why the resize(BATCH_SIZE) operation is +needed - it is there to ensure that each time the fetch() +is called, the vector is ready to accept the next bunch of values. +Without this operation, the vector might +be getting smaller with subsequent iterations of the loop, forcing more +iterations to be performed (because all +rows will be read anyway), than really needed.

+ +

Note the following details about the above examples:

+
    +
  • After performing fetch(), the vector's size might +be less than requested, but fetch() +returning true means that there was at least one row retrieved.
  • +
  • It is forbidden to manually resize the vector to the size higher than it was initially (this +can cause the vector to reallocate its internal buffer and the library +can lose track of it).
  • +
+ +

Taking these points under consideration, the above code example should +be treated as an idiomatic way of reading many rows by bunches of +requested size.

+ +
+

Portability note:

+

Actually, all supported backends guarantee that the requested +number of rows will be read with each fetch and that the vector will +never be down-sized, unless for the last fetch, when the end of rowset condition is met. +This means that the manual vector +resizing is in practice not needed - the vector will keep its size until the end of +rowset. The above idiom, however, is provided with future backends in +mind, where the constant size of the vector might be too expensive to +guarantee and where allowing fetch to down-size the +vector even before reaching the end of rowset might buy some +performance gains.

+
+ +

Stored procedures

+ +

The procedure class provides a convenient mechanism for +calling stored procedures:

+ +
+sql << "create or replace procedure echo(output out varchar2,"
+       "input in varchar2) as "
+       "begin output := input; end;";
+
+std::string in("my message");
+std::string out;
+procedure proc = (sql.prepare << "echo(:output, :input)",
+                                 use(out, "output"),
+                                 use(in, "input"));
+proc.execute(true);
+assert(out == "my message");
+
+ +
+

Portability note:

+

The above way of calling stored procedures is provided for portability +of the code that might need it. It is of course still possible to call +procedures or functions using the syntax supported by the given +database server.

+
+ +

Transactions

+ +

The SOCI library provides the following members of the session +class for transaction management:

+
    +
  • void begin();
  • +
  • void commit();
  • +
  • void rollback();
  • +
+ +

In addition to the above there is a RAII wrapper that allows to associate the transaction with the +given scope of code:

+
+class transaction
+{
+public:
+    explicit transaction(session & sql);
+
+    ~transaction();
+
+    void commit();
+    void rollback();
+
+private:
+    // ...
+};
+
+ +

The object of class transaction will roll back automatically when the object is destroyed +(usually as a result of leaving the scope) and when the transaction was not explicitly committed before that.

+

A typical usage pattern for this class might be:

+
+{
+    transaction tr(sql);
+
+    sql << "insert into ...";
+    sql << "more sql queries ...";
+    // ...
+
+    tr.commit();
+}
+
+

With the above pattern the transaction is committed only when the code successfully +reaches the end of block. If some exception is thrown before that, the scope will be left +without reaching the final statement and the transaction object +will automatically roll back in its destructor.

+ +
+

Portability note:

+

Different database servers have different policies with regard to the +implicit transaction management. Some of them start the implicit +transaction with the first DML statement and keep it open until +explicitly commited or rolled back (or closing the whole session). +Others will treat each statement as if it was a separate, auto-commited +transaction. For better compatibility, it is recommended to use the +above functions for explicit transaction management.

+
+ +

Basic logging support

+ +

The following members of the session class support the basic logging functionality:

+
    +
  • void set_log_stream(std::ostream * s);
  • +
  • std::ostream * get_log_stream() const;
  • +
  • std::string get_last_query() const;
  • +
+ +

The first two functions allow to set the user-provided output stream object for logging. The NULL value, which is the default, means that there is no logging. An example use might be:

+ +
+session sql(oracle, "...");
+
+ofstream file("my_log.txt");
+sql.set_log_stream(&file);
+
+// ...
+
+ +

Each statement logs its query string before the preparation step (whether explicit or implicit) and therefore logging is effective whether the query succeeds or not. Note that each prepared query is logged only once, independent on how many times it is executed.

+

The get_last_query function allows to retrieve the last used query.

+ + + + + + + + + + + + diff --git a/doc/structure.html b/doc/structure.html new file mode 100644 index 0000000000..533ce2f2ed --- /dev/null +++ b/doc/structure.html @@ -0,0 +1,81 @@ + + + + + + SOCI - structure + + + + + +

Structure

+ +
+Library structure diagram +
+ +

The picture above presents the structure of the library, together with its +clients and servers. The boxes filled with cyan represent components that +are part of the library distribution.

+ +

The SOCI library is extensible in the following ways:

+
    +
  • More backends can be added to target various database servers.
  • +
  • More interfaces can be defined on top of common backend interface.
  • +
  • Other languages can use the simple interface, which was designed specifically + for the "C" calling convention to ensure easy binding.
  • +
+ +

The core part of the library and the backend interface definition are +placed in the core directory of the library distribution. +The soci-backend.h file is an internal abstract +interface to the actual backends, which are needed to perform +operations on the given database server. Normally, the C++ client +program needs to interface with the soci.h header and the +header(s) relevant to the given backend(s) (for example, soci-oracle.h), +although with dynamic backend loading this can be avoided. +It is possible for the same program to use many backends at the same +time.

+ +

Everything in SOCI is +declared in the namespace soci. +All code examples presented in this documentation assume that your code +begins with something +like:

+
+#include "soci.h"
+// other includes if necessary
+
+using namespace soci;
+
+// ...
+
+ +
+

Note: +In simple programs, #include for the relevant +backend is needed only in the file where the session +object is created with explicit name of the backend factory. +The example program on the previous +page shows the appropriate #include directive for the +Oracle backend. It is also possible to name backends at run-time +as part of the connection string, in which case no backend-specific +#include directive is necessary.

+
+ + + + + + + + + + + + diff --git a/doc/structure.odg b/doc/structure.odg new file mode 100644 index 0000000000000000000000000000000000000000..64aa0761c5b71a18ad12e8164348c80433522078 GIT binary patch literal 29603 zcmbrl1C%Av^CnofZQHhO+g6utcU5)SMi;u+WxJ}&wr$(q*FFE4ot^WWIeT_*oXotD z_eR7QU*yY^H{W|ovY=pSK!4kk^Eg)?C__Ijp#OJ+0&I5HcIK{Lj^@UWj<(jO#;(>5 z_KfcKW(*F-F4is#4vyybW)7xqcINi343^Huj#k#DE=vCohX5b{qu~I0q7L>J)|PJ0 z|6IASFuJ%JySllU7&|lmH#;am*uOyjhY_Gd`5)|zO-;>h%>iy4oEc5soSpw7@U*i9 z1yYiQ{0kfi`EzMmRMXX)zj2&J6 zmlUT81Fv*Jfq?#Q^Z;WeYg1P@Ku(OzO#gFZ`7h<~k(G&Cf?1To(cbchl7b{W3=Ryy z4qjSHOa*Wx`ny2^oB@dyMGgW10SYRoCJs=6fC2#m6d3q-_wNAc_Vn}s0et}k=)&kF z3?PdPpo|Ql@eH7^f5xoiKo;Xb8RJ0X<3L~ke6fB3S$qLyd;yLBjrZ3zz_$euz!WeS zF!(nTpjoi8X0Wo3v$B5u3k=XLELbxvSjR0`zy9F>3|nNdW@NCAXRv<#g9VsvG0vJX z&N@EM`t=Vbz}Vu8HRFqQ{EPMLA80@l0Mr1O09*hdfXIMQ0N(&-0BgW>z&PLppk-KC zj9XZI{mT##7@%ilSd3>_eEmxpfCDg`F>WzFZt?Xme*i4N?2Ipq@h^+7e{=xY0?-5C z13&;EV?c_4oB&AxPy=8BZ~=e-A_H6ld;^>TtO3&jmRd#gaJ4J!{c8WU;l^(lm$=+0Pz5(0n`Gx1ds<{3qTKm4*&sxi~%VEKm&XMPy=8B zZ~=e-A_H6ld;^>TtO3&j)2*Z;a>UgYRH?6sf=ZoSchdBC1e3SpjFtfZ3(>7J|3=9uUh zt#axV*m5M6$~fCzi#H&EDV3!@-uOg6J?3fYLS!HI3XUR@m%cTp)FDI=G4Jh zN#03Jor5bW%GTIPJ|O${yIB9CtW-8Nd4U4kmLa))5nWqTG^W*l3S%m4i4LGE2mRF< zz#6|1)aMVLvCNo4C@Z??&EPXGLQmvgNko;8mK3+@R7R(m)|l8AjF_3gSf4pApB{{n zAnPbwC5B#)%AjHo+q$Z%?BstsZB)swu;e6|I9fS=fAVH$l!B(2*2_)rv!8D~IanI= zb#(6>j|=72+ct5`g+A~v=bE`zb1JB^i>w$(_=F#)M2ZYA8L~4#KbdZ8v1e2>7QXA9 zNM6=bPp z`Z$BK_+9=C=OUFF-?E#?FHBrfMjpa@b4T#_bFSt%xhZNj(VkLx0qKl;0w>KT;PlU6 zPFu2MCA1n8cQ55;ak>R^ved?o|J996%NAtzN`>a{LsXr`m9>gDVfB_JC4U%R35-FXD^3 zhO=E|J{vRh3_x~fz#ukT=ENp`3Py&pXA#*PATh(el$RLp)(Xweik^CQzG+offW(Z7 z_bJTI)ngKA9aQlCg)zu)a?XL8}SQ%VK2?@dE@DbN~ ziOH&C&+E6_vaiYyP&SHgmCq^j%2E8tyb7RkOh>w7rLFK1?s|Wi@m;-^>|<(3M0>xk zyd3PXry9X{&t{rE3jI|?!%^@DQ${1eY&-0ic4mxkh5zrh)IA&EN(L!(24@N4xJ6$h zFiJEIKk!!k5PEjkanW-t#9G*Hg7?>$L5gq*bX(~5*Wgg}$-@f;T<_>XG#pUXg~f^{ z>Q=68|KO~c-jC|*Hzdf_LeE^QFQkJy<+y7ccKUZ6L6=`ck8VV8NQnJ{jos0iH6K3I zlN{~Xc$~4a75fCrR7Oqbf&bW4|sKcLJ`Dz&pL}6zbBLi`D<$Qc#^#8gnNY- z%lcotip4IeFi0}ykt4ViBD;=oBNab#2A1uCl~|qP2JLl0m$2dqB+;qxssEEs>|7lQ z$J~;tpjSS(m=U1dFgp4CLK|0`WWv6o8d4QF^?%Yy62{|TRB8%#y#2X(MTC~GAI1~L z9+*$gCU3eBj5d*>E^&{obhgr=DWv{SIwgK~nJb^-#`-~(>iQGtB!MVy+({M(ygn<_ za%z8H!T}S9)fQF=RJVIV*ZwOG-+vFKmh;!WhHX*O(&3 zo)G*h_Dju7-60N01MGD-IFCU4ZazNqVhd_FfJddPoZBv45 zdNtn2?m*43Q!%w3mvI#Fio7n(Y&~NIojQS5fO*2W4M;&ueh}F3@0-s?9j;iDD^$`} zoBz1)@RFzw-*~~>su!}Pdz_}g6A;XNEY(=csc?w1A(1<}c&r)Zo-ARj$7in=%5qJe z3acj5y|}2Dfw*|8fC5ac!wJ65}5MUqM*s8 z{wJNVxi3v?J1fgIuY4}>kkr}#O*#d$Zxpww{%g_($kb!(lx?3nm+C+9!Q{FNPT~Ru zSKN{NC+DwD5K-0AUr9rs4}0Uy&*5)$Q{o2Ox~A~0dp$LHW~6KI>my?SZEAA_>sF0V z1?w7A6`>^o4sIaZ6h5yo6M~W3A#T-~T>Lk1sMfAO830+Sj$+jw?8bF^YmDxY^4%#| zdE=3|8~*BY{eu0wN~=Z%wQ>in)w-hCb_{A;v>?@w1~KHUdSI?u9_9=Hh=#-BKb4FYu#z=TX?PfU;)XleA*6ovYyM$z1@zBMIjo)45yq$#;4p+WStaW!FXTirU z(_kp4)aoUuT&QaBFmW+zhIt)nuoyz?+&ZVsMW@iBSBmxCT1=0o8l05DiEr7SI?~(5 zX`Bno<44zZ*1GchSpf7{@{PZGU>UzJX)%I7m1-R_8H%h+`InUO;>quI{G}u#Q6m;O);=9b1?uRK=@V58@O}Y18Q)~RJyd)tTTNzB;uIn! z*klf=KwpMd)TY_j%DiZb0}G%YmY z9^Bpg=|LR&ty;0M2d#Y|WUb&&WPi*Dsr%cxcFAbJJ35<7m3 z<`1(h-X}|fG8mFn%;!f%s}I4aG(oVhZ1k5V?DEI&O*FLAG@`Vq>*8=C_UrLf>zOq1 zXfKMvgO%XeDTEmfjb9ovwJ!J(J{3Nb?pr?~YnE?vracb3@Z*WDjkW5t8(A^)Uzx(4yzNLrg*SCPI+ zNJvlRA9^RKnwfN*me}Vp-l8XuQX{2P!|qwEVU!-drHA;)l_jA)mklFL3>SchNJr(c zz}5NX_IZf48YUtXvH|V%OUiSfqYgYpZBB}q*k5{pc#oBCyb!7~nF=;??m8WsRA!DO z-=1DWt<_y@S<(#he9{G;&=gTyR)RPH*p@rkgbVB0_$*PbAFGf|`7EpvnL8{i-B@Zs zPi+eveTMriteE_pU0^o6ExI@~l;@sBEXZh9GDDAHkwR?Jiek`{`cpswwZNfhY_x zzpSbIXJLX-`UCY)NtV@Rf*D$eSEh>`Ic7SPA9P#%PTw%8E%!XcH*mIx5u4ty{IEmaDf89L;v^{AkVWs!D~bP;u6P* z1k_E3s9*6DB@*esE7;6OndOSkNL3ASFnUkVCqK3VzV>1VKKg!s66jrh27E z1$jFaCh1%yw^?GL3RpzDp^lO9OAs!ZP5eBc8|kIhgP9odR8VOPW4>teG^a!epa?z0 z6s1c4&WMsf0FK<|mjJb)2bb^I7cFJ)SfN!8BDD*s6eGufj4Df)}~x6(k3guP0-RCEzO@x%Yw#4C z<45pXRr_qvJbQ@k99@MX6rWqSRX<4uEt|z$M8qR_DmpZ~g4;ZC65YXbLZ5K=NrFI< z*4HFS;~tDix%D_=Q&y${W6DJc@P4V9US-Z@s8oUA~_xi4%C&l6(xp{k!FEEpK?gxak&h)zc+?au6vOJbVh@3S)HzW zc7(H8jJDByk_#*lFqL-%si%dkf@h?wp)*){%-*jkU&z029HFazMsBX!fu#s_X<`SB}oRL%|y@CB^sJ`uWppP5O$vO5I!zF z5xkWfaE|Kq6bFGoil(@z6GE$ElHjc#Z}vuEp31(DvUW8PlfV}OyNqo&tVPK@r0yPh zbnz0o{OOjM3(XQMd(6y5J5cO%PM50LxTw=Xixn6Hjkvc+!R8SbUt_X|RXFf34h-f?`q`($wLyKp z7mdWGC9Wb0bYx!C{n-;Y%6nZEMIckp2mFihmX9l-$VuUs` zE+dO})`K$i<5E~2rLIVtj%{;Yr_6uh1o6l_c>2M6qPkr#3u0&QCc?y31U#?RpUJKW zOmbSJ)ChT!Idgg2v7)Spf&KyFcN`05RvrD2O8xk+#$0*GmE}OIPP#~boGDa0-WB^w ztmWvlswZ~CHFd>|#EnI^ApMpuM5{&=*USJbv`Uk993`XlxiEiVzer=X!-F+Ua)*(T zH0}w+k4@YU)D}^6@A%lVmLMx4)N$YkeXr3-(XcUAf62(;?LPhHLz>9Cs9rI~`!nnS->+7>F8)#rqj`7? zIf`u(^a!%e(2V&LO1Bwkg&BtPp7#&6hJ-8Q(3OFSczTHRq~FX*_*cTtW|APuWv z_uXG0AO9Y)r<4iKN`ZlZ)WQBw)&2WwQO(ND&cxo>+SY~9^}l!jzM?eXbo=`<)qUkSX2a zXx(M%1ND;s=<{CS$Zygsz$f>}&p=ljOH>P78vv_+zJL32YXu0135bE|OFVoRryCe3 z)>}11_leZ`$DQuedEB(057iY{UlfRdb@OtLstR0y6h;x?lO1XMtvoX;mEbk`V8hFD zCiD$irHN@z*o8OKOKxWwiU5Cw0Gpaz$G41}EeyjM77P9q zGib1u<-O{zY&q$1h@DZC&3N!4Kf!vYhR5-pUZGmDlvF}Nw71|}K8Owa)~LLA^f)Ja z=M@FXW{-;Vp#EUg%9o&Wno@mSH`9-^Sb{p1Akr43*|KX%7+W7Q){L*;L%<8VJ{^_A0r_Q`YDZ(ZPAD7Rf5nxqR)9Qc?r10#S}x{bo*fj?Ug# zJ2B>)hwwU1yM$(rlAaMT8*IDScUJOx*FWV#Ri5aehsv)JN?UT+`Pe>{_Wsl1)JW3!vw~?w$1QO>igVq$o(0<3 z8))5*^W-jH?!0v?1Wf8yhr&lgH!x~=09jaGE--?vsb=%nV{^^eb+oMGSeIu=!LM;W zEC2pk%l+rGUbFARNy+9JBBAk#ANj0xwidCGj zABbep6Qo!T38SynFn6hZiPl+x+oCyq3kiftG zAoF;h!nZ{K*hZ!V5>F;h1nv&|Q4v4$Ix6%q2U9aJs3znQ1&lx1|9};khlpza(;y23 zt{Q^NdJv{7uLc>8D;|Lhtm)h=Zxi}kb82-w9A3cucz1%jIyyPm#qcQvWDaV-jV*3% zTUXA@VsszSaiHeKm{pstfO6-cY~QFt;@)>zf84P!TZk4#Q~G2Iq|CP`3H{>B9jU>1 z-CI3)-N2Kz=_yXUriFq1^y-pl$tB2!d*vmM_PEhw9d}y`U2A?Q@V&JioiY4uzNJqZ zg+I<6oZ4)4I25GDTnZSJ461@6IsHnu|gZuSZM9}zC|%G zDLbHyf&I}S63#K>N+f`SgG|w`oB#05k`s?zD;R}~s!83Y7djqLnj>zWEp>cFi;fzZIFU>_n* zj+ff>l4+<+2ge&xxhXu=8*w951-)jLj7KI|#-eMFgMu{4J87UIf@+z-O z68|O;+IX$suW=gx1=1wpCl;T*i*$XXVTwN#$ug!jR#1~RBuT$Qa1LBn!^2YBJy)F@43ac0tO^!?(s; z>hw?(Qqs%k^o~akQvYJq;mVP0Xk`~a&AFf=&PqOullm0IURX#o`AJNimA%z7W)F_o7(&AaU~Y(6tY)4Mf&Mq+DJL45BNzAVn4Gr%SReaj+Z^A5W78uyq5}PYl?P2rb2k zMX0Xw_n~taJ%@O|MZ?-GxQLN7_4G5;7tGm11BFUsqkl%16j8IGZLge$*y*CbL7QH` zz@t=|mAj8FxZB2u$bErk@M5GF_g=rj5jY06AriTPP_Z=DG2p&Qg9>#AR!coOPpLv@ z+PH<^of?VKlIO+p^2p+yU)O@^?=j|mKZYtV_vbs zkO5xPVgK`w zG^YJ0e|A8qT#nkPo^S{AYhHYH$GVc`hMsj0tFAEtiHf&0l+TTK z7gV;U%J9}0)t=<&dx@X@zMOBx^Q?g)C2Z~f5PB=TlD%{~`m6BM11EJkJ4q{s%!5jE zJRMfHMn>paztnY_)kF?2fp&1jC3R!v?vKQws)r$w%T>*r?rHAAY5^X@xgW{?dr+RL zbu_$>pXgT*nqhLNlL|<*)_XLzi z;7?B6&iY3!1h4qUO39QuYr1_oT0>W!H?zC?`~q%k9kag$KK}5Gl?#@+^Bx8^l3q!N zPJ0QnvZfx4=I$1+!ov?~%RjY_Tpg&g6I+l}a^i*G=aGHA0PWVHzm4wFal+C|=r#Q& z(Rr77II8=>xS+7#)-2a12|7eb9w`Ocs;QErU1q5tL^hyO;I|4{0RMvV#lTz7ym#R5 za(n9)6VVC7K<`izc9J~Zc=SuZq@&XZ#e>p?;jIhp+GY-%Z_ zjU|_aC0^~J6!S`5e4&{Zj_}^tzsd7Q9m^Q1f~}E&C0@3mKh|2NH>93}$wy6LQ}nD7 zB_4*lt=U*Nl!0+eal)=j+aE7Z$t(Bq;P+AVOO!r@2_v^pEbrT&tnjVk{CCv`xdfd} zM^dk1{Jt@l1;qTn>#1*z{~Sb;9i?aP&GlQrbJaNu3Ygf5Re4g%>B-=p^Uy^i>TpI% zsay$sJ`{CyC-_FyqlE?wOCXpb#KS$xr&d+&4Ks|1?-i3c26J5oA6C>(H;v(wE=FWy zGIBFfsIa%MK|Ddh}q5F4cis5iGP#+M7s8=h_~}bi~s$u^`<2< zxZ)wX(Yzn@nJ7GfvF;+K!Dt9}=*?tjt1}a3*tkGgxO_Led!*jYy^a6i9H6bwAa1NIfPfCqOmF$4XMW_Uu)3AJ}D8qF;grt*G zZ$?Yd+bQzpfPKHwo{irpr7idVB*?XDUvF&+!oCF5lDIIsus-RxdV^k<3-Mc&qidHZ zAA=M*3XgSmk`wfV+ILM*s+rq1bJn-v&%vB|8iY#TjyFrk@l_Oqi=U2{z9%vl!~ zqpO^d4gzbcnM#7Mzu1Vs8EdSqg}qd3E+I71fs(2Xw|M4CV7C#Tpo+Fxm+hhac0H0Kz;L;ii006QQccGR}Ta(q#bN&8)toEgSe%g+|w~_TY;#hy}kX7bgTL6^0WOa$HTJC z!AF~6vf`n=3-z{a(_by4cwRf6KjsZz-mHqLc^+6e?jE^O=soIh#=X%o4Db^ zbED0!rqOFpurpxR4>s_Mh{tQ7hI@NSn@@TqN+Bq|d4pCG8vWuxG58I>77H03e1fkv zjXkpj=VV`hTL_p~b>lEp01a6QA|5n)WYB=^)|m@;%-1XqG7ixQU8QkwjeP+FUFL}l zUi@*NBveI&6zhd4M7zjB>n)>>JTGJ zuwQZxv3u!pFGeCHj(&A9s&~M$`*}k=TDrfn+%Ktgs{A!af(bFNfT~lE4`+iZ;(PW3 z5;%_61S!sDi7k%+F3xvIn3qYe*K76IiWh`T6n2(3tRk}p=A1A)0aK!kk71&1GkvDa zJrEl9ieK;Ut5-iAEsYI!3!h|sr5Nn|L{+j@3-mYD5TUEN(s`}n`wij22+IYA=uB*B{CiCkO^FPOC z>&fkImMk5r6MlLXdYFM`cPT6F3FBy9MTzH*|kqS1*-l~gzM{4tvzga zlizWf$NBx3?7z&Tg*|$oCH_HmfhBVy`A!9_SjSpmoDdz4xe!#hrSR=$d;gS&j?%oN z?0yc3e^_QM5&v-K6yA3sff627<&?nvJg+miT9PSA6N=k4_Iq*`f4TM0a__EP4;jRb z`ulmXxzw9_?g|1*A0fqjK=2TbY1@27yU={{NCQ$)`twIQbFpQ=NMDoBZ`G-O0-DqC zkN9rFDmC#O1oJ@=bH)QH>9z;udvjD$ay6j>MP`?0DStv6EC-GJ4)|Pm-`8?7KIuTktIKvtVsipvVZm2U>B4d| z9D=4_CC4^BdMhBN<@$7sjwwNQL2D4dwTD-!)+19PF zN|-ZRFUML&Hu&g&qpb%&6D=cHQ83|3sESuG4&?l=-o{x$1aFs+*|DOJ7 zIF)=Qw6KMTTji-u#?aZf%8;5c*~AKY=c-#d*Xx8{u=*fJhB7fin3d(R%k~B}g2-lw zSfI*E)ezob--W}w!2o)YslJ2QsJ=6CgfIXPe;S~N^&2VWHCkE}_Plbt06j65&#Kbj z5Woc1O=ZW+u)S|ni>ZC3pIWl`htuzB1Gn^CqhFl}gJz%`v5#bY0}1kbuJPlM){Elpdb)}a3TM&S zeimfAuOubRBhw%cY%BAQ1fzf_8|N(z-x$HxFkj-3{(cffrL9GK1lq3bvdNa{bWvNp+mvCa1dd zBS|ExHtn+AnDn@`Pz;QW>fp;+g!;x1_eo(_M|3ZJ3YGChtnx(Uhx(D{IpIQHeKEgEuazVg$9am$| zT<1RZ2@6x)GIoA_9w*htPS)!otDpS>mdDAvP)eGExXg&lf+3ad5>s}HGd8um)$@oI z?|zYjAiRWF)di~~1XoLYAV2C?saUUMZR_taViR`v#O7}ae`diI{V{OGFopMVxc(TE z=U4{^KA|}fqiI&(VduX762byM6P)jWn!x0&Uqki5+gGO71Aa1MoS%JdSQ>oxTyEsI z=r!gE9FWyUD2q09%FasGty+v~C*`?R)P;){oUEjziz+>~UX-4*a*e{c>X0w4{4OKE5 zoLeC7J8$0I28L`(73Udd=3quaoIffLwjcRKkhoD&4%b4+%e!1UpQ)IvZk%GJ75p{GK7$XY;j|0OXac)Q*MZC_Q| z&_#WvDk-4*md-w5UFegoGr5%1SB&bL5YC|KuZi5M$J5H?L$@OgRvJC%mbUPZ4_~16 zu?Bet++r0o2=??n8s_Ul@hn-N+ z%&1d^7S}SvLRwhZQ?8v6;S1)zbqr3T#NVgHB`UPI9D+}-R4QsLp=+S5zJiUaRpAbl zpxB^gOQlRdMKB9zG>mP4LCIE;q9LoY(;*XTBn2L{>X|;x0_s})$lS5`e5c}%F`|Ee zu4yE+Zs33KUrz01IN~RsI#or^;!21d^d5)&9GfQJrRDbo{4Uz0|E~1hE-!k^_oSwj z&tB&i6Gg%tVcg`T&L>&2aJHfW18m12+G&qI1t8bzVWx^&=0Ib zfrlYn&zWax4%*hSsdF#fS%r1!?QHC?t$dI}+OX}R5Dor4zZ!}M^mo+{xDhJE7h z_-BbSbJ}Y6)5v|}F7E@T?=Wq;W4LWIP`LUrqrh;+Ii&)xd^)OpPdRKpzS7XGgagF4 z;Q8Ma-Jw5aVRl~?;21}jg}r#SAUW_w50vdW3kEPFk>x<`;?LW9?a5gZ6Br#rT=0Hv zEi?Tx^%KM9S{WFadJ+f3IhHfO(6X$H7%e=<$K1(&saz7T&D zTbJ-`g8Qe|o>8++tFl%ZhQu$g^yR+Bva0had>MRePDCxT5?ptd%< zaH(ly*R0G${o_0nv|rRpKxa1zdjNG_Wsv?|aSe6c+Ig9>v|?Y^6J}ZWS$_|8e5YC5 zX7N$Eo{s;G`ZK$zX-eHbRoh!0$N|bQm*A7Z;s<4^6V)E^+~WR7SSo zO`*EfewWAufrXxbgy;`nKXwm4n)T4-U@Nj;YGqN-8B>sEkwoxXeVBFKfR*DHmO*di zYHSC*c?m|J#{i{xU^TWkDdT8|a#`dcOG{BFjAH3b8~C@V+9b3IwgqP#9xL^M&*BYw%KZ*fwnYLMi+2B;+&sF%a3+z&I8crYu~Fef&7 z%>>is+lITnmy})uGd{oS2<(}%o;O;Y85dNsk3_LGN9)0FU^AO)q7Kmmz2hEBnE1B5 z8HTyK)D0xbgax9y9<3atr5>2E?{te(M3EY=8GaV$p)w3`uU-my^+|52QM~>^!zmiJ zmDEIGVI`L4{o1HnnVx4u2egauN3*F&jtwljRU?hLO^2y-e3WR!Fjv1?j|N^zMK$Uz zyUBr35@V5rF-TIDRVVfi5s_x`UOscV7`xC4$rLR-BT!0SK_v`AjKSOa4qkR@jIRD= z0^bv~Sx(wye47ek!td>TduNP7=ERwrm|rD>9uPjtMv$~Vm9uMWg0;SW?7u^zfSDX^ zzY@|73662~e9b!rJfnSCf`J4sr)#GJf0dpAeHknRe^%CkBHo5Bspe*IJG9q#=`_}L zn#;;2G4~ohe#v|-7>YptMC54-?FmWKxKgMJChVuG1%c$mya?6MFd!%j6cI`LG|8uT za4)ioRi`kVz;LD2voN|nEtR#Xxqocntz$X#YdYl`^>~_alE+G8GvuOCv#OCxKDho9 zbUqisoqdsRF;`c8M%T)_E`gifxp;Y>-jrlb7{$On;D=zW5w$VVoYy6tK@7GfW^GWV zi+N*1t7^OWXMah=*_U_yz{Pj2BLQ+Mw6#Oi#Y0AsH8L+AVhe;JDWphV;?N^)=;Od` z7*!N&u0oat@%EkHV|6=bi{plbF7R^1)IV)76TOXHL$A@Fw)ggeDJ1sF=hXJsnmi)R z1fG|`F{-j-e}!tPr(}<-TXJ?itoi<6us+qcx+7b zz(j-UB9Yv2=YjUIEsK`DeiVq;%uYs4vt5yxsgMasjqj3(<8moRCd^|_+bz;ZHO@04 zo`zG5`ItrvL*E_4okY9SKW{LY2#%e1lD1~HAOlee+?)`7m!k5Q*oLKB7c8zPN|{;E zgIV7?a!XzFhAH8aO`%W{AtE3&AVQzWb3oR$F@7#w+_9anvgCSQiZs>l+dVP%V2DaI zYj6m^am4>h;;fR2ygtmq{@8BIqWo1#s%fq{T#zL#PydmPerBeT)7xS;45ew zB^vuM=-8p=hq}1_Y%u>?!8(4D#^pjJNEOOstloA?8zR0TVjsZ+hV+|iACNS|WT^Jn zzJl%(%Tsgg)!ex-Vi}Q9RWX!+$PtYS5)STjLLNe1#8wZpPa4q}9Lk>A{Ip+Jvi#>I zwTE#zcWjp}ibe}h&b=d1e2b=D_PgXYICYO>_kXmMBWVb{af8SQ^%RG|n!+ax+Z`H* zyzh?0VJuXZ@hrlBYyPQem?)taM0uR_L+MSwG;Xy`_3Im);R6;$8?$kg08Vxr@yQxo zi7C0O^r0Y353~O@BOgS+{k>;koj*E$L%f;^4BQ!9Nc1$h05mYO$K)wTg>w=Ekt1J` z>_Ju7rdHV55}8+ajK?ES)9?-?3VraU`7;R~tv$ZDQ=+kWR-^Te4aI~rxglMU0SIKaU9dKm$gg5=jNu~2CcnQ0G^Z0EJj@~a{rWV{vF-C`Arm`^B#Jn6I#~QhWc+us{oqpB;8XGwQ8pfiko<%cI1X7e ztWy8w-q1=8vglt-7cS*MV|uGAVQTP$2|M$As6qZx5Az_4PMge=%#)Z+yl6``$v!id z<&UM!@i$W~XbD<=*F+y8?Tq$qGVx=G&+ENBN>r7I{9I+jEPAn0l!4)O5gIq@QhDQ6 zsHuEhygXIIVaurAc=FN5hKon8$N_mp*JPyw?a{pUQFj&P zIRBmkyI1ga0RG9|02y3PH4Tyc@a;rzEFwTUv_LQ~H>r!!dkTuK@4M#^t4dDY8M)&RzYP7Cee>al3#P;nj_yj44hDFADWe;YN|-dkC^?8Lo*p9V zKZ%-z#bss!+4liM|EGEFC zOrxmf*@>g*x<@h;1MCVx@EM(3t5Qc~Lc+w_6H*9fBOJ%3Jtp;-N)rD0DcYa!Y`dEA zjm;2JbqUM)1ycSm>hs61^&uXD#iFopZN`P3QL*!ynuRv;`*JTd4zC#rl`Q?R zKoa*Itf%(xquNdhvxF))b_&{()~VrveYcR-nAV-U84GNk+}769Y3BDVYh6~v56Emc zkb+4-8gXhN<|MCi%=Ex}S{(d_kn-V$rV;aOuI@YeGJJAxj3(@Dv*&_HD9#bog|(5M zW4@%lJsFqXTW3p_oMXq^HY2Q0;}L8b=uYMEzuDC_&Sd-Qqgri?zGIn2Y;)Uf22_RZ zZ41#GJl4^{GncsFcdF_zQek;@Uezhuv~&fnQqoXsQxYh?;P8z_;*r_I(*7cG*<^ia zuQrQ!L1#Ghrm@rO^ZK2ruRl4vd%Z%?KQf}Ntp`k6YmS=hp$s!(G`B5uh_K0aPZIUQ zJiSF9t&>CV;5j0r<}Mr4pRs6pS)UqCJ){&Q&&VrGM!5O3q>T+Bq#JcEb;DDEbDS~? zCRSubc?-_R<3~xRfGuHoIiTnDBh+pm34K%%lUD1<`K!T?b_O~W?w?IkBNqPGiX}%*A#lj_ zzJ~S1-#KVn9LC7W1q5-+I;w0@6%!dZh&hi8d9{x8aOTan)ks;{5CfaYA#tO2j6oVE z;bjboHR{L8oN(eJV?6T_CJHtj&uUe^@gbPcSS2HJR&E>ULY90>`x$@XZw1i><-i@C zoyQ?VEaWwg&^>|1+CsTOLlNJwJ{*4sN})Zd`0!?Ic$bIDfiSDu*9U}v((N5(ZcZu4 z_&UKB<#M1BA{k}sAL>iqX!%`}J;AROxDNmrYqK&@VqIkyd;TpqeLG9hnVQm_4)()O0 z-R~hLYTRgaoG}j});-seecJC6U$lXDsdVvwy6M>z+J=xJH<3g1ar9f`zc>x_$YW_W z`8KszbKHS1 z-RdkQ#>|rn2`zNM4+RqQ4j5RqBl&|6;cl>;aCc8Iy*CQ`V-^gPyIC@y_YT%wddRl>8u|2}?Z3OF#6Szxfn094Qs%TNz^; z&z_u}>o`dtMj24{{`G0zua;h3nl3XXn9|~Mhvq9(sm`UiQ1>ma)N|B+%-O;_%H!-u z8+nJm8xXYi*SA)QfnSS1qKV~!Ph`sge;Q!+^3V?O{Xy*Dge@aVbi=cfv?BM{ErG75d50bd_aC+N2x0c|aDtOlp*f+ukoWRQhYanHb;AoT)i)+q zi{FETv9AG5K2~;%vH^%u#D@q_)_z5wgC14yIyBquK5kT}?T#B+-~rpMb5AoDyU|Xf z&R~`gJ!_ZtwC8*JlDmEr!d>%7d0S|H=wzBE2Leg{nJkenteqpXS7naGLS!STo@m36 zNI$?y%Wj~+$QNYa7N+5IS}6G?b-Qw2PXpbWdi5jNEyT-b@bW|GM!oOu7Z6quHA;kA zISlQ(2Jzovx(uLFS$Z9m&#`{bmS*?F_hq-5{vaDjlpfqbL(iMq!#OnP z;$$`&aNA~C-Wku~YO#KWPz~wcZ932>e)@rKXgs?+NU9_SCuq-;VpQazjqpAD%C+n* zXriq)@m$F4r*pt!-RqGDHgkF%(fP>st7HA%)7dT zC~{KT*+K;~tOlYUMAuMT)l9$-GWL|@21w?MXgKDUHmXPZ=FQD$&j8a0MHPv>Bw85y za}I9H+@c2XVFQ?`{-pKXN;jg?2c9^}K3&zJTS2u82T>ZRb|4vB&`Pvo z(}i1k6Z>FHO-d0$S$52s&;;So_+x*{8G(XqZ>W;@aS67ll+TO~D3_pHp*vz;D>b9FeeAc18%p7rt*SF{ ziuwi@|6i581CV6R+P2&7p6Tvs+qP}nwr$&*wrx$@wlQtnw(UOs?i1fX?|%Qizc}Zq zh>EPJ_1vp+<*KZVtjzneA(X9!1Rth^I;$sIMOu*#N{(5eDtHc|u;WA6Mdi`q+0lm^YSE1A@u7t-{C?1H zoo95~nvVSs$*c+sZ$+QYy)h63k$VozqOPn$o_t0@8mJjl9 zM>>5IgFc~o#m?vs3Lrjjph`bg1h=Jr;eV^U`m!!kiaN!YoOmUL+nurK@m%Eggo^TRQO1Vd%74IF3MsU&)2ot5-8P~ z3t^H?>2GcumPrv__Ab9NOuxs@wzw!M;LR83rW@s0U#}@3F38*gozqoiXOv(A+^~A$ zLX2#X=nC`9a6Ai*Wql;$tC0Qi80&Af}gYO7NsM3Zi6k^4I9kWhEz5O>)LL8Y8o z0+q@Lh%ZHuk`U|p?P!e4o)hm;_vb^DPR+DD{VY`kf60^Ll=5A$w7bk8<u{`8{Db{T`(vh6YAxi?U8^=3*FDY4uPZjb!sJ$q>d>{PUEu+(xr%0wGmygpH#Vm{__;#RAJqh4yAp}Vg)ia6O%eT-X45zU?d z$L6f@=HfW5#*f!{?u;>7z_WN%zwc$rqa~zv*@3T=mmc z8{?5HxmW4csFY4=3b}a%iueW-d}>(Knn5P2+1$11anSk>hDd#s#0f{{(VSUTr%l@y zri#ou2v49*RA23v#DeI+7X| zDf4wGW`BH?NB?+;u zJ=vdl&_^kWF`+Cf4(Pbv}~@8OmTle8Wt8ovc|0lFtC^cNwwshZfVU^9X z-6?}ZdY|=l6Le!qr%^J03AP%aUL2Y@-z15fi@u1pfOW&BSd>n$Z_(ac>oKMU=dHSl z1cyfT$u>!s^se=;v_i>ua<$heEsSVYIGaj*0%?5ZkQIZBM~KJHr;odp7MylFoWO<5 zwKm7S6M}j}MFv^xh|@A{V$OGZR+lq%f=2`yE(F+ke+47QARQKWtuis2P$R5+1-&8s z0ohQWsk>Pf5-ukuRS*7k9iw70Z;N3qq}i7|3X!0f0~AInk*5{hy*`x`3u4f~JmDoj zTu+GHYs-#mY2g`8E-BQZXjxNHkf|D77w++i1C_qLA^CK*?s6J`2t^94WSBW~Bkj~A zlW1&@l1(eW86Cooi*02rDq^43N;|Im1_1n*VqeNlj!Vl*iWH;5`#Md5NC9Zz2;iF+ z#HKZVH;?E;(H*(;jO*235<^up%~t;8jtgO)1)4^#XrYBaqUt0%_HBbuG8<_dIftNg z#aS35gizU%UmULYyIKxFE(90}0(HY<5zLiS6-!s#OcN%9gt0*C@`U5e5Hyb*!kk*X zG9@11PWL9^06k~k*PImnJ7|CStZ6Rl^q)@lI}c>$IouWW1GU$+cT=L1C6DX_IRo*B zm4q!ojRi_?7{fATbmqibn830MFIQeSGlbu^j@OltZE-4M*sFK$P4-muVH7JmjY|l@ zj<=P`Icawtc=)guwueP;4Mwh*mmhieBPrjbu2L6kR0qpq*Na;ujJA%f55&{ATU<*R zAa647jlCk;hqUsutGPcIIEo+`&{a*Z1E-GXU?h#}PLaetg)s=g^95p*ztI5AuB~(C z9UG|?V0Co9*pRkTb{)Rjp5EE6cT1M!{Paon^67?-I}R_H)SuNEl4*zu%}cA$_&6XK z5c_~8!RBvG6Oe=vAJ4PMaz*w5(*}E~F;yF&h$`n~N+G23zgCS=^yn-t6aplscQrOP z+Y29C-C4`9QMxz|FB&*!-X1RC4epWVA4;7V_w;k^OIsyQ5p&mp7mhBoUFy4+H!6ES zzL#ST7cUBix-*A4SZ9UO(&g7~cE^&OM zsIW{qz?$PGq_40IZN)GGVvopFu`H!hB*Yxv9u5+A4@6CBxIbP>!#=g7<2D>cEm&`s z^QRco*XGqf1r)%AJ>wsejHq_EnVYl@`Y~qO2|+FgCKejMMy+|y`b{iqnu#CdHqk#t zB5Ej%a>siUBsgiQ`AK6RT#+T|;1~7|DI)_?6&M0y*VJdCW^zvcu*5(sTEH8}Xy@5L4}o>~1p^1(WMPY=)wcD`hrKW85| z5YW563%FLnC;{Ocx@QBW<7RP|*kRqTyc08#TrYL&`Hw1&n)lk~IN^T7Kbn2^euTfO zNFGF0n|zIRF&Ebvc6Q=%0gqv7x-&hnXrL|Et6M;@KUuDtXGfh!d8yo3tWGKHq=eJ; z;eD~*BJi&o-g$C7WYwDN`4;B=vTchhWTs1C!#&_WmkiGH(0G&7QVqF=d z-?WeDJPA!@2eg+ZXVNrZdbPkve*=1i-nx8GDEi*Y{D!Ki92gE>kjL5U(b@qoP8)!V zenisTf4UYFPocuBwir(h;#i-ZoVBNHlOQ81s{=!kP0ys^JPD^Y+R}vyy9;G}H9uQV zu=}*IWtr&;@2JQ(R9ChO0Lg$k&c))0j=UIzS)L+gs zL#k_w66a_oh}$oN`n4b~B4RGKtTtyZNw(9oHxE{en5nwz)jKsMxq_npjDf(#705&X zl8vnubuh{2c^$yM<1WJm!&w#l>^5T=J|6cb0&2OQ3YF6rnLnIS3`v&w$lJ2_n_2k7 zH(`^iQQ(&EIQ%5N;(|KGq|{=`)popNL*V}n>EG7GCz)6ALBM;fcGGmOpbZ~(A~%vvGF@d-7O#})Db>@5QZ4u zJS;BAaYm?}4Y4)ceanP3rr|a|JUpO^q|XfG>`YrDP;5(5zpYhLAL?yCa*BHlh+H@} z+6Dtjr4O;qUa#Z=E8%8&%l!BEDE$xH1Dso_y^-y(YOvKk5A)+!8X=a{I5?UWZ&9qF zR1h{H9?!nvwfblwA;Lr{PGI88$IIl2*#-(^y|MWOW;Yvn&g}7gZb8l>M+Z)pEfdpz z2e8w=XuJdDXK+L*&mi@JfZd>Wp}Yqi_(kuhnz zxqFpD4^@yLJts(szYyiXNIpP5hJQ`EivN4sFP)W-oxSU0$lQbeT3wdJbXRwt^QE^9p6yC5x&tA7No7h6mVxeXEY@G{5Be( zF(x|%eSZ9dw%O>l^^80#Hoo8zly7$AN-Ny*$6H45o5B1~-vd5aA5glgQbR{sE+%(B zl0`z9>o^?10q7?cY(9gVpY+Qbr5)DkXQSU+*Y@I6zF)w~j92PM!93zL>WS&Dg$i1B zeM8s$T0B;kr)?eUM$QSd3KQYxV!o;O^Dfr&NqX%%y+i`d)Wb+Fd)$Y}Fqzzp{H(CC z3gsmcw!p96`lzg&sc4jTV@De0+D2VCc9d71#_~#>KXwB)?MLCJSU)mX!8GlwKRFCU zw6sH>v)4y8Qj+w=0%WT{A7~cq9jBO?_ru5zKiv%iI@C)+{;C$S6-hN)_J!tgLWTGp zOr3*|={%y*STe(CL6Z`uag696?!LQhTWS1?F$KZbhr^Ft3Rj)vIM^g#bI2 zm1ZyzBF?*z3W`}n2RDytVltz5J2%KX;2YcIG6yi0{s&e=P6sW}^C`AU$3^95!iHC; zN!lv!G5dv@{1@#)i=jeqegB2D6 ze*4{N^oB(hSNJp*L~^nxDy?hALD?`Us%o?CXQjhw4NIb}P30imhwv9PLKXKb0$q^a zLZU6knf84iB|VH7z2)IKD<_Qy4?n;yW$Ib;#4Y|RLDr*pKnBuX-obL20xT&Jd2n&@S6ZIpa^M`2<7A3qvmdz{~jk$ z)X#}%2v^Qh#WxXbmqsA2fs@YY%_4SfYgbE;8`feMiyjVws}p-wFofoG^mh8rm<9Px zX5)39CN?Lc8}nnRN)q>WI_H!pw3qXiyKKXm$ZGS&Ln?A4b3+pVzaMvp(vO{uG#xN{ zGA;wG`>f*KuOK*+OU0W<>c2Ss`vb}%vg3UZY=*CE?iFK{cveR?LMNIAzP>yjL!}ZMIClHaz&Y)npp6Zpe()>DB4M=9tv9iNw1c59Y57bkenq z0NO0q9n6?7d1}kqcYt2cbBwWigPlxxOJG@O7&oYOu4@+zZatw1v0h*fIeED|9yoEg z3hu~sK^)o%ZGrb&BL9|PI@6DWs0TXqn~9aV)MIqY9uhGb)ne*y{+2mKaj2XFAz`6* zS%6t{-W$c@=E?EaTsG+M{J zEHy@1HvBtKMEslw0jLD+Lo?y&^&3z@G>dz)j-F<;!d#UGR6fYYp;lg?7VK-TymHle zThym!ZO*=cpcP5IG$=C}PM*lAx8i|l(;%4*gGuzp%vj^Cv!@-Lm&$!hzljaI1@AI>BJn1552 zJLHD9pTC&bW8vwbd5*39eHRVPE?#BEVI=!G#d+=PFQ)@ygbh;0q~|hTkZiy!p%hVZ zp3)$*0K*&D)B3JcM}m1Mw7Z!nhm-kGJG{>+o86JiSVVb!$*h33=L8j^ir^0q{Hxhy zuX?$_1-q6$)iDkU)ZHV6!uyIo&$mULq}gCL4*;Wxxl!thNwIBs#MNcWA=WS|gb~>S zMPsHg#3M*gChNBK-&rP}*uP5>uFaxvw=Ql-G=%G@@9yYFp{+zb+?Xh&BTRD=pz)QS z8X$pZV1pdh*+F-fBQ)vDpiDjAsILsxQ)D3Sgg|vss`KcFaB7YC>oOUqg7mpt+JWsg zharP>VSath<+IV_y2}M&D)aAwlw$WQCOVRNBuH*+jjo5xTwgxgGo>}iSBQtlVt%Xb zD}8|jjnkGTQ8gh;-JzT5=FGs22mWa>3fwEwBLZSGh}6^KN#XkHIu?XOj}zB-%V+CU zYM(UO^`>U zbY8%Z8)FIZUBJtJQBB6tYk;Wd5x%~RLOvOJjZeSU^pT4_J3Wq*U%SaLh3-wqQ0ane zh#R-k1MlYB!gCKN^u6<`Il(gySEByU_gZsKb*s3-(wr&s*5|L4@WcZ*U`ZdZ-iRb4 z{o;c)uGJHIXs2xh_LIkgB*i7{SEiYVWfq2&{0&dyzf9MA!hp%_9>TKA&@dLME0p5i zqV1J8rZ~tZ-E}!%#Rv6)?YE_yX9A5Ph#45e?vfy>PKh3u;%MicGA<#)EX~ZBoDF zJ20FAnBY!eQh{xWGlm$3?82K#QKoJR}Epvp@Yk1fMqqP8bpl!x*Owb5K!SElq*|*x&$Mg&IDA z7%JM-uRuJqxP#a?$~uuX8Yo}&8Z4nA(l99`ExoGDS9>xx%y8aA#Oc(Ff?xl%wu=V) z!SVX`mb5l7nXvBtAkJH1E0HQF{$Y7go(xR16!=X06KQ|N&O(q={5LsIv;)V8V0;>` zFh0EsH07a_D;mBOC2y3|vG4?4iWV!;Vfj@t?2!AI{setoQoEhRValCBj8~*fq}#bk zt94DK(`}4Mn3MJ^@4EP@__$;px(gbPRdB3OU)hMkqk&i3-Kph^6Q6s+k_*KF)F+A6 zJcM}u9=%&(pD4LiXf*nL8Zg+Ih;&*i%6M!SkGr_=CQ)HCl^B%ojUXS6$#NWt-6?to6k`o#J0exOT8-_mTJ=ZM2)W_lXm8nIEtz}}j7#&Jw4a^z0II~J{M^9ke1 zkP@3NIXL*a_&GJgRAOei^!O&$_gP>%uPI6p{dp{jhJvx!!d&1Lrztjeu~j-bYDZvP zonS(GbpjS!4tn7dgJo>ft_tz=RnQ!;vm$y`(RpMAUXGC1<(6FSMYQK(PcFY=TroV* zC=HByw$m2PrJ$)F=?20_?Xvxs!mbsCWo1PsjVY`UXc@JvDU9ha0sg{KC>T)gGw~6v zd93;|}ybte}rJakU{EytT0!T+ZwCSoq6_i`4^?jJTeA zp&k+6ic#*zWA+r1tKzMNT1;%14BU7cnG)czH5pyJfLzLc4u$w+>~FVuRxK14G)6=fw0H@20#a%+s~+=&3Hf`NyqDpvHsIM-jJ$jNf66DRaXyl2y4 zg>1$Wm>gt@>xSFvOtyA9z}1Ee$-n_wa|^6hy4TPivAwQPh*-excdfTfa7x+^n9KZ2 zPbET|-nC~!m=LIwGjI2X(VBai!>)v6WvR*tc4Y#KvgkhpRW$omO`F*F z5dRs(Rq8}baS}t^7h-HYYl#@s7bFniByepJC^%A*JoOAA!|xjF;?xKUTW>+IP*Ix zepc~%AU&r!Xb@7YJUL2D5Ii$pKUy2=?_6iZU>Uh;suWNY63nT%fr?Ld@XrOA-1mKO(iC0KzJHHnYjYzJ%j&k6E4x_n&S2N#jD=&`9(ZS$xGIT1mIb!r@UQ^>T5P%R zNE<48-TJ%_2F;(dKyO)W%}1}Fk#+^AZ~2|3yhS>Yq7GqnvZ`T&Qx36 zF+-SFW?kaaN<-i^gkcE(adwd5l3|{SirV50pPDm0YpIna(Qa&h(K$m&3GYWwmdY8^ zg=25q4MqaR&s+Gt7bHWKmu}a9Q_{IfmLWVDv5Ug!&_QPdjdN5J!Si%xp={(wbU)S} zQ}BFZE%&So20U7E1`vBkWG>Xk_(=P+lb!k!*#6$Yb07F3jki{>%fc9$LDXk0C%hZ{v`SKPzaDfKrruT& z`8scYS1E=)@n!RADvDpYkz3{Ud+}?txIfKOJSeGL*6o5J(t~QwfCN~59hML7H8gj; zj9ERuCg0?T3pp(OCGEtHR+}Ewa3DVJiKVbmLYjEV6A4cgb6?9dsT{bI@@nJaH1{S; zF=_~HVotq(0@zBcwMr08oVR!;XT{g)3gYST9P>X-(9#F$bI614Lp!BL-X-9Wky!@y z%1V*~rGc*BMLjn1U2w`u{>s($D$6Y^eJSH%LsYl~WI1E!?Rt}u1z2+i5z1Y7zVXGk z*HFR(hv~^KlZ7OD(*y69gXrChl&e}VmW&S3jY^TTF$blVG?w0}E+rs}z{|#?1adx^ zresc56v29hki^juHkTN~)tD6}pJ}EHHIgcN6$d_vq+x7L@u~tjU1VRHD|CKuGnY(g zcrLR@V+=>5ybg;wk`pl#Z)vG04>&+yUs&^35&mFvL?~{T2((bKQ&NEx!7n^6 z%!8EJX_X=0^e*jKiW}NU%%Xt2ZN~p#>#-}Ct%?cRo8#m*xdzrFT#iBSNcwEbEXzG; z%5_pr#A9*;F7kF9=g`uj6-uCN69lU`y3d4rxke!C0h6#S&a+>c@in&fGrN7sJd;;x z(D>xLf~k37hK(NwOPkyF^tJ&W3NBfGQi_7Lye69lOHk##&W%%K3D?1gZE;T@@sFvn zv6v>!RP<)MQj8Sc*|mvT)B<$-^b*W?-<mc}29`uXF)S-SBDKu%14 zZcQYDZ+Iv)0OCSZJS{T|lLL?~Z5@EG&(g;fY6{<&PbLJ56UdvB@S9eDkxZjC02P?A z@d1w^F+fOY=(lcA{ppW%$|9Th%G3C%?9=?1fR@h0a(V#L<6kyL)})38=0O6(GCEt`BuzeSDT!)iynnT#$TNKXf!d-?w#*KWx-2&j6&8c#1CBHukIQ9NwHv zULaq5BwiR!s|N3Ao}#fn13zc(f?vR22tKnJmX<#CUUlZW9N+yuEh-~rRUpU&IU!4iOPMIVPjeCqQ8_+iRPSL@GMt$&e zFl;B=Hx8i4CHUqRIA{~vb8!pe2|;kchavd>Q$LEJ*Js)HiiY3|!!Y1s=r?m99Hs8t z)%*b!WR>~(w6m!)_J+eAc)yv=s4?5+^xk0#vSq?yfPp7K;-G<1(PacU&%_AGJfW=Y z<0ARF8cz8cNA}iP&`ttWl~SC7ihw{tfq($cfPnD&$}ri}^QpobkpUg~DTd-z)Imb@ ziu2S_Q}>x~*TIt1Wp+86s=C2pJk9gyg631=$|5DWIh-OK_?p@OTr5QwBJ&|s1NasX(|@1Mip)&B z@Y&sQ`o8P?8ELTlhEV$1UE}-VaM8x8{O~#0VWa!W_Sp5zYpI#>SrG6(DgBhqn#9BR zDTBG#HSAzN<+DkS_0cagyw>-$uEZ;(vHIAr_rZZhXeDV0=p*zz$qSMLx@Cj_cK{1k z^Sv&BRdp#)HE_d)N%;qz9vv}VOmzxGJSk7_XEx}S3Njn}F$e2A62X**>=G;~nGjDn z-W$$eJNEU~ioq;-004CP|F<3cP=7fv8#y|fS)2UJgLz$j(_w`b*1M|I+POdo^FPC41ss?enqcsgHC2apR?6KGaBbeQAgiNA1Ebe14cm+um0RnRXFBgb?Y*7#u7kNFJOLQjLcAX}%VE7?gWGG9 zzRK;c+ZjDsbZ@uIzRW(F6t(p(;v+87LT|zOOOdN0u9C0kYX;M}z@Z|)>k5eDwb+9c zRjlXfIm6j5uIWsP;hP5ydTt zw}y+YPfmG~vk9KUPbT6d#4Ym)sPoX=YMO31G>2e#*!+_aZZQN5Bv1s|^f~QT8D9dX zY*da_9tOQeFN4CLKr%KEk6`p}3GBrGj!{c*ilbyvs$uz{RxfW18@OIk%?1JIo2$>7 z9RF3pth0-EX2X~vS}6e8^rui7;4KcWs}|y;Q?bT@7}9$YGnDHnHJaWkzOu_hTOdGF zinU|$$FJHe__`wyXPnR;(AAxs+ZpGS7Q;{&O>tVTDdz7Lm*xGo)iRJp1)~RyEENok%vow z^03o+(mU+gmr(GXnzJIkoLa#Hip1H4V`}A4Qk{pXkuwI%U~YZ@b9F9H=hMvCtIul6 zX+bABhbzg4@Hfde_?S?>NJ5%NYZ+2EH*-6Zwq=gfg`q&iw84}sqrhh!oHt2UwzCqC zh5~E|!Jl_k9_f}KVp-^G_Opo?w3wWOsRkIHnHkR*%;f4eu*FFQU7cjTPaJM;6+e7<7m1Am5LW}kD4LLpgC z6jP+GXE>0Y_E1GnLgRqfERs`nP(`yZ!bE?62m*9*q8d!`U3Wk?ptw1OCH&}v zQsko&lNP42(z7-*HgfnEwX7jY&ZZX^)_3!f0)0bcuER2N-jJC4vgaM(*P=MTpKb88 z5081Cv&NTy>0Lq0)FsEoQPBR&-0>L%b|Mo}ItoLQUu>gtVa3vK>-4Vb`xCR}Z~5rF zSf6{=T-^*~5T!j8T~!!zu(4T-tH0{e^iMmFkPMlQDbjC*#uJSHw^bK-lB z&a!md8Orjq5%D-uTAG>zvU2s>%>i*k7lFj4nH+q>CNjK`|7!;iOY@NB({8a~*fw>A8I?R4_0o&RECr(gwYFld`*>`Ge z{#367oagG)Ja0AZvxcdh{a0h9TJa^Af4Pr3e!1GpN&o^O0w8`h%zx(J{p*eY^YlM7 zynkl>p98)MfAQh|gxpv0pNzS`Bk%s-v;UoY@ITzTzo2)2BJ`{HPo4jQ!291m++R+w ze`4#a_)pP40K9*{Fn_ZCSw-ZpOZiWgzXS39yUt%oygwoRpLG5WkoVt}{=Hry|3T>w zFz^4XO6Px2`2*1V?+eH6Gi0%>oB;lNA#Cx^N00cul^tEFHkc8 literal 0 HcmV?d00001 diff --git a/doc/structure.png b/doc/structure.png new file mode 100644 index 0000000000000000000000000000000000000000..1694cb68715464bd7e21004cb38f1e3dca9f47af GIT binary patch literal 26919 zcmZsBV|-=LwrzJh9jB9YY}-!9ww>(Qwr$(CjUB6F+qP{dFaLASyZ7DqyC2s2uu-+D zYOFcN9CL=sN{hfkVL*NR_6=4{R8aogx9>Y&R|*8k*WHL&xc}=5NE=Z#`)}WVjelKZ z-`(ElSHFETSr!xIQ*_BV>wwijTI~4zT+R;n8;;`@L=^K2Qy-$12sFPU!RKRYv3W%_ zVQ%m{2rg=6@`g?;kd0hIp+-pu-7adWFzv?tyD$LEVeMaqsF+SS1Bj}w9Nm+r1ToEn zDm20h4w--08yPtp^34=$aiYmK zI22&R$V6CBD5Hp+QS-99W>~)l+JJL5Bow9cg0fgmv6 zb|d>$9P@6*RgN8SjWPz`q_$dfWP+Ub|t>QgQ^goAYm01}-|8&>qy*6nc%;>pkjqpN-m7O-)Vn6IVHCLEG)ntdTl( z!n`gbLRYgH^hu6Z?2RRbJx3%1YHJ#Jvf zkt_Be?l8mL6;L|M!=;;eIL>UeM>5ztceCM=QLoTza>rkk&c#^|fH_@g zs4n~OB8ne4_xEULeSw?xUarOsdxSkaDG`pMA>GQz3k`bj40HGRrXJN8yR^<#8==OM zAkCl$qT+|Rbnp^rX#oL5-SzPIb$^q<6$9y1 zzabj<-9mXt$Z|PXWh#z)zz<#Qr*T(E%UHQZn2&;+``?vo0Fc}}_(CW;iWvuUsB%;+f2Dr!I>{eB*sT|4ca^iI}m|&0o61(bKY{@~2mP@0*x&w-!w@o3w zvR&2(j}~-}MKg$rDStvrvie!~^-^OOUE|~G!#grg+OYC_f#pYerUuc`s4sybUQG}e z^tEO=sjn=c2pFd9c|8xWR5x|a-=2*GDv$t7TsiKknMw6Oe5mD3aIUarLc5JRYA&HV ziYD`!6%MFhsLVI!DqNwl{iUQiADDCNAV}|3+tCbV^kq0Gr1O_-(@xpgczjeoPUjfS zamp&!T=E~E2wmIOx%xxQjRzIC$yGOkZp4U5>gZrRLWLgZj`qQN?FQ-cqi$O>W&^Sb z38iQ-xcGR!;IRQ}MG0KRT|vxHW{N5v20dtNjX4m7d?|H__=T*Vh6l=$E}9|U*EM7U zSn|lNq+mfwEAd01vz2wOt5v8~VE#zPXhK=Bb%OsSAf(x?$L&B4awB!yrI6-igL4ii z70#K!t4rnH%jI0-rWd3BM1z8>)U{Vp(;Qovtr4KUyJJ~$mc_dttAZt8v; zOZ|H#rxO-zZQaM4LByup&Q3f-U%h2&GGskq@2`qobdqMWuT)l;@ieTCCsJr;XU-vA z=knpxP`S$=oH=LU0)Haw&V!$3YJ-OA7520Bss<~{{5R&y%l#<=0>(%C(p1l6s5El@ zxLbSwt-u^%Gqu+Pb8WzEKv};EopdA3bd@3L#*}m&j`sB%d^I;F_AOMEalUmuTvpIP zip2EbDvDP8EN?shn{o>|JZ{r{U)NORXMV@zB}kn4<#w>8CI19BQvyFB#6`c+*ksT( zPt8O64Bb^B;g~V?l_aAZO5PqM+Um6Yk6mn&F(chKN4Zwq#s)B{!n5int<3qj-Z~s& zmeG|2Ti&2&MpZD`F*U*$T(6~tMj|Oa9weUL#dq^+%cT?Wu>MCot|~D2K@{rn#)0Yn z2S+mxoHST)hW~Z*wM%W-LcVcQ1Nf0_Q^GAZp z%8DI$h!i1u5B$rt9%0ybyWN;my7!R!a|Qrd;^|Rx%LLgkF zS_reUOgbImwt{D+I@GS(!4bFL#jFLS>|wEJYSgW_fD`2HrgHEYt^R|d@oSxXC=f{f z_l?vXx9e)~LgX^M#eJY6i7@zsoY;IN;Y00&vdjFBd_Irka$rzU^y&%&cWO-k2Yv8W zyf-FNQ$a$=ja|#0YSZ^D=jta`M)QU%R+tpW%QW{(&0RBjL<{o?^GROk?K9nr=I**f zH2t=_X+@cqwB`>1~^aB%z-? zU?dtnf7d%{;I9`OleAC~b2Gy=S8ep!CpvvneWa~#Nrq9dQI=pvYWkls@bgeyU7rpq zRh9qpe_&hW7+2PO^Uv%Uf6f2%v9d68s+T+_VeKx?roJGq4rbVwn0*tX=aarqn_XA0 zg*E;)fieN#EzPbuNrPAK=F8t3RM^k6TuPDPr-iXvO0we0_iVXw@)&t1!aJH>DbCG> z!(Rk@Mj2{D15HaDqU@5EXAOSN;Sx(cXu=L5cWynm!mypM1bNoIjDRI1hJbH_vE=R1 zqW;D@2=ihtUX)i#SgR2`+U$^o!7?qiKU{~YeAs>scHLx{qZXCC`*hqS806){4aee~ z8@hK^?%FNWqs%-dv4s#Ti)py>04#6adel9r*>uhK*yux5k3IWlLiGA}#O-_kKog2! z=*M5e=JRPJ(FsWn4N+DzeiRD~C~Xf%p1YwlcO<%i-+n-K(ul-td9(L1zdh{RAc0g@ z_ACEt4id&|bi`|9=!2C3xi*54<)m5kKl~Liuz#z<1ZcYpMlScpi}0Uw4#nO3{mOAu z7ca3=aA_Y(tt3IP@a8f+7E|~)PKD;e)q#7=ul@AzFkq)kHHE-($M9sWji#j_6t<&& z{%W4I8o5aLPtY`x+TmSFQ<(%-bMxtFdKf_4ecx@e?VbzTd-F+fW9O$wC%IgfC2VvP z^X62{`qTZwcchK@$Y@|{F`Mnd_U{FP)5c3R6aY=zw)m~WIC&i^dM849K!}F`z)EV6 z?j|low@*VIu*_5*(j3759EJ(ETl|t%jys4m;hXp4hqZs%x4@23mvN*6QPg?pmREny z=M5+07z?B=390k(kynk9_q*5<&XU}YfVrtvC5qZd^~@5Avz@+tmyubHxE8-`)Jf!m zpp75Yo1WM}F6WD_)@BVB(dlPrB6CGtH1iA5p>tGe?6n3OYvW!_&ozJ3c@33qN4$bI z;rxzn+rYeocaB%ra|=z8x0eqL%QmZw?5j4oJ)%9<)Ykj70F6WntX--sT)kT0s^fbr zIRQVRc$mvxxh_T!8Lg+M=gx=zPPx66siu{w=Of;eG7gJo^pqeSWd^_g102ltpzN&- zjW@Zl>5m@7$#f8zZU$IVOeJ1Ev!)Oaf1!A<{xx)rAL{GOp ziSgY-_)?Tj4m-=LVtDIw)>Xt0CrFFnFNRxOd6cK94>MC~6y#26V*9V!V__ zd5KfKzMl-t!bm2Cj!edZKN-+%JF9}YJ{G6e;H5u72fPfd^?6=bYA*1AJh3P%&Rvn8 zrXq)qXy65t+j%$d-dK#wV;xXR=g}o1;Zk+Z$e@_y@b22rZ~M55U-5nszL=6d%Ko(PWo!Vuo{I{${Y)ce zWaad}+rQcLa5)4pC$^c3yxJU927MfJ+Y7*n!H&ar7YGkI<)Ff=FE9C9Ap*B6r0$Q= z7zheNGAT-9es2R@Z#^q)+wPxCvPA%?#mBOi6DJDpx1Hr(?MHJD$u8LfV7-?c#~qJZ zCR)faYi!YvljtzirlvexK%Toz+UVG{CXAIkNsV3cPg0+R<7-+h7-}c$5 zD&ge8eP<#t8r(mxU6Z$4oj5+%A zE8Xv|j))I&jpnG;dAd8jeyY2N?JBe_W;B3{(oU~Di6M8Cuk(0iy9vUE8#JX)ey#Hw z3)9;(@OV~Jtgi_y919r0hL%DJQ#r5?l@#u=+{dYV3o0S5`^c8!?KX0jaw5(?7ya-X z(b7aa8YU98*wFJRH~8%xc@28=u)Vk0fMW}`!u+uhEK?sk^^2Cg2FEVqjZ z_?Zr#WdQkJn2Q|U#dJqxElHchG{2o)38P*53r>3B@iG=;092gKwjSDx+~Fhq)8P^c z?&}rH33~YLwLFy>d_7EHkx8o-u-CE_@w_}XeGRqo2_pVjSKGG(W1%^y+$=gkHv5G- z5S_01gaVg#7c|QB!dNltg8B{3=bdEOOb?V*E#vXnlH{z`bvHEg6oJufH>80=3#!@_ z0W~hWUpjy5e28I#SOkx358@)X(#(XGei{L#`s|_*jl}qO z3b%6vcwy z!pxPdzf0btHfF0DtVC|XmjU+lPEO9py;I*MEQW8k7i6=<21<(TUGS#jLkW-V+n1D)z2*G}hBBZUSwK zom`dmu8(jV__v2l!UT?M&vYX@RvS*|K@nL>Q{JH zCi|iVer%g^IS{1Fd zU2V-V<>}eMVz;GdKb;!*db`8*4z_n4#O=|Z&&2hEA2A(lS}k+Jwz^DFdwsnho5P-5 zKz09b_70{{VoBGEencAcoMz^R(r27_13g1$NAd0fL2b^q^EVGNNoYv_DZjP9i#!mO z8RJ#6P7&|}J@*oq2_()`R(h#cp0$x(OIV9ZleXCor}r&tZI`TWt=OC!Q=0qeC`}|t zVm&rNCRyCVjWYqv8Ls`}Z^>P;`DxaVjg?Wue(UMZCQzFrU#iRA0$8}_ipGdgOI|LX zPL*nR(47DXoTrzghqNlY2zknnQXV{Dv2D>0Mub3SoxF8_tIb7*{Mu|;@U`G|-T-TZ zhD|FJX7mIVLR83=uu-|98`-_c6DU4_?Vs+|TiKHa6@$V7p{o(5T*Zu`yY7>79#gl& zeigVup=OHf)h6XcPmWSj21l=w_3tX(7)%}yO7f{8L-dW_=~i#;nx^@dJPI= z;##R0W@w-!PHxHbh=1Iw9b{&~s}wqlxJQH}^iNGiwm7fhI1Xm>d~lRE6h`UcO*Y}s zvpg_l#%OO6u~D(p;uDU#DIffb9IrHnA}9PggbY>|H&)x1ud^+xkP)Dlet+#o|M#k4 zf>23}K3DXcUYS;CWC-1$V_ZW2w9)IVhe5t#peBw7Yn<_PA6Pd-fRI#e5__1-KdZi4 z=E!5G7;fkP)!OqH4V(xBzw}}km9K$)^X={{=IfrwrzMo1_2gQ_@Ao86ab~}R< z8FHK{ziEtQo1`=B0m{}v><DnerKz&QuSDkz1QtO(e9ReB_hBV;dVY!)4epfpL!`rLp-oyL zdl`>L^`s>lMu`>c@rnVJh|j{m+`i`8C+u#Nu{p_fk39}5uki> z(i7?~f(aaIKloMRP4&lJ-ydiAYvhs_hA`<~3mrQ@$%BFX^aW%q4X&|gdU~Voiy~*{xI9NAG-)+HGgOfTL^fe& z+@RY&5h$VEsdPdo~+zodFjn zL^1e@*H_Q`D%EQ(b{mQumxnVmSvL!mrZpo#GxFDm{>K_brMSHP*Jan&RR2qMRPUgl z9!;;-XkmRexcqsk-Xgehr-j6%H-90!goeGCobXsp3)YY%)mM^|R_=HCu8d7hqigJMr{qT;>z%L49zF4|u9LlM+8!J?u5Y4(^OJZGNeW|(AkUIm2Xqom zyQ$#wB^pgm%$3lT3Fl@=#CpuiA@1(+`Bjg{q3`Zb$Htx2D|~Njuhj%i&gvn4!325I zJrXR`@T4KzTU9WjNs`11=lizM%8`>D??-zcepGvtTAU!Eyxn5{NWowUyceMIUFZK= zsKQ+C#3~^{PbdI~P4(vrxk7PW)F1={JMq{IWh%pbUgA30)K#4%rITr>_Vx;^o$W#B zO^{|gS5ycuKwXc%d;8R{8T7FEX)W+7t15GGWvb4y!ukcrmzjPXn#95*B$#5~A*|4O zCFW(8Ft0tddZX_c1osk%|9@iw9|IQwkkg3)RMVSXwv%WHA*cTVSvq~x-ty?wk*44Z z!kI7(<{qpi8un*m5|0SVZ?O8aA?S4kZmBa$8zwWpgyWWLKcZFnNw*F+s}Mr zbW4+WoaBWkAGu*VR{gAu(wx_iA!#lh1D8>{v%k=4G(8aqjhkg&H$oQhHD*^0iCz-~ z1JN7_>aN5;QMmYsvzuzczuit>Dat*LT?iL3h06UFj>gTKCmp{u;$=3z;&8X#inTbvn zLiAM?Y4naeJsL(yzCeXjXy zl1@M3(JVk_wb%Tr90OmfqX!Lo z(7E!p==Ko=9_#rI-yX#bMuvE0yL{EegMCb4J|8MdHn6s7#y%Pwk!&UWVU_%fc>?y| zm0V-r7B?78Qm6|N86pkwWx|Y`@IuVX0|>B__-TGBcXea!On%Q#*p(%^@J6Tq@pQ+7 zKtN+ne>!1XaO-X;LqzVr}W`5NAqD zxTw1G8&0YE3i3@( zBPowDd1?(EO@y6Qf}e^kh#YzJfKNGX^f;hygG0P@!vl`=fiTJ9ARlV`q$^-TVebb z2!_8+q6sSO@f)^vMH4#w$%f5-oAA?zzzLkV!~Yw-;xWg9i4+}qMdbx}yOA3^bIbVw zilsm-;{;cjDvmXCH-}E6b9Du40ec5*mF!ER6`jYbDh>&3*!-cX!xHK7N?w2%zoE75 zi^T_Ib7k2xpj;92+}3B z)6U=(+EatVJ2%`Bd`8J6o|`tq;#8yE#Y)`8KtRf=xr!VN*P*@vr^)fu-i`4@zUP$y zdNP4|Z{B{5JtsHUSEj6jxS^i?y|Jee4W=9vKyB>)+S5B%&)jV2hURT%r3(O^C{*!Y z(Mq6lSf6>V!f`XbAM0U}USzUSdc4mRR@4od%*)S~n8|6EZBi=i;T0)q0k$dXdPE)n zJsYu^v)`r;_Z4XNvO7&(>W`e(F2~hom&V+i;alhPFtk^Hdld<%Vm_y3X$U^8D{4CL zXg^A_jBRBx-)2>uZy(MM)1_6F_xxe+8XoAAe4J8ov(3_BruIGOJeh_O&^BE|^~_gO zNCDYOV$0?hk_mh0U8Pj&qPcF)(AnZ1^2j>eC29Z+^?fclS$JpSX0I!2D5iU#Ah1Nk zB?;zfD6|;v;@+S!u&pH~wL2Iq1Ym?OLt*w#yj##O5r*sU%%rzQE0))VFV!LB{3a8R zqT12arg9CWR^(%=Dbs3wR3x4b5uLw3cB2TI+m$R9E~lWg>zOX7WVc3I0Ppi^a4}wb z%iPKq)s&YCZjK2Dzlbm}Ct=VAJRX;4v|k0{vtA%ATq;)1r=3bLoSW;-I${;qzwHGy zV7f)k0zBF3{X->^pARW?w=LuAbrG_9UaETBFl8i$?C`^vBKRlTbp#R9@jpEhVhp){ z23NaC)ycSH4c>%|&JRZNU~A=2i{u-2OPEv;rxX^2jH1er?nx-cp>hxBc7BiiZIFhy z7bA%%E+F-TCZH$gjwzDlw{H|#uZ4vM_vAp6p&jaO#~=U39r1qlU@>~?ofk$CU1U;0 zB(~t;{>b}86yWk^^>0wt(r|32tt6KA28m;8J; zuFh5ys{vMuaC=Qjy(j<1tf9LVOQropA?Y6)t=w3c+cV?4;_^Jyuh*i+o>A6-X!u(5 z3QAo@^FLLVPb0B?nxt*w3g2fpCUkFCpjuW(wgwMhduK%w>bEZzH?(DsR+Mas$-9tE zvYT-5^=4IzCJv%L#1Amz=aok0CK9_oo@tY9kCS|tj^N8Vk}9Lc6-$5JYeZx-ZW_G7 z)yRlS1iMM#xNi4edSAa;U#_U%Y>g@JE*q{qZOl4)yS1T0qI;xt+rQ)?LbI%02O~OL zQHe!yuFtj^NIP`Zl6d@Ac8u|pp8Ud0?B99z{rhcKR`b!HB(t6&f7d`=P!LBCOcoUvqrueOZOzNrT}v-)Zd4k)zoLubTFi z?B@5M37+!=jv@aDn9#1};dbMZov)4kO#RLHYN7c+`WG|EPRpB^RN2}xg(8Ua(QUVM z#0~c5+1P%Afjo@36uQ{kw!JPi`O`4;#S(Ulm|RT%Mc}aJFik8;2wmH}bBW}DdcM}a znxX}C&1nS8SDZE32ILxB*xHjcMnIOl4lym(9(f3`Zc zm1&Bl=BQ3(x$Qr=(Jj@NrzchSmvgPVhd`~pEV)kWb$D-W>>Tyr&tzKtEqq~wU#;Cm z!|fEmB%{@zW4fASs=A(7CQJ{2)+9sgaxJBki-?n58X&DAx89|l{%Ai#YlzWo>dX~q z1t&A66osY|o9Cf7;TI0WNFvjdzwaMgMw&EE$TgPJ3oH~#(`y;WZ|d1RNumlfY(QCb z4|fCtLl4!XZ4gLlanm4Fq_~CIf)sFYF1H$Z$EM$YE|Wb z?bnj*QxRTn6)Z4tuDAtIKAi=stB`TY%*%<UeV`j--ZK$7kf25^?@pa1#Fm;)6` zmG-s@yS{7-TPl7@K(jk|oN2T^>jA$@nohFmLbp;xiCncXUWs8^M>V+^L`gjjd^DYqSNIX{cq=ILS1p1Js(s^+()sZOKBe2~X88d|h-J?C$Qx8haiymRud4_=!F&wDjpE-!-BwmJANRCktSP zkT6y_CW}8d;$rubPZ$c)=A;)6XKN?E+4=#i1?{PuN%L z8ZDG34kg(PHhiag1+u;he*dj?bD-GPMI&0|^QW%4Eej+Go$rB=O(s-}-$UePaJfpY z8wo=|_7B`zLfR=0G%c<0pE9$~HU)6oo~q99{Um2r0-CH7G(N{rL2PJn`M2zGpTRrLh8Gbn*|XM-NAY(r;0CKsq|HL=t?doN4$1PDTBY-Z zRJYRA?W2h#rrZ7kfxg^kpj&r0vjn&8^8%p(mnQu;6_ZHHpzX9JIi}YWgL1H9jez`f`^8A<3`|o2`HwAt6`v%$|NojYEf-uHFZH1dpSF5aO zNFx5_t5WHmCr0o#(zXl9jxx0qfCE2)$&gN|4WG5US2|oAL{XKbJeNkpB(P z|JXPGOtnqRP|0~p`sKFeY|%2D->q8m0(kJo%n8K>)c@k}FI$AA zvYb!udzD>CnjpLX`cW$)IRav+`AJ`3i)fncnvR}``ihl5KDFgnd&_C)9Wwsx;jf%R zRP=heC$?W1f?gD~=|@=naY30kUlYIobxKN%CJmV+w|Rz=fqX@`I<}X8kS6B>}oCWJWl~*(-wltAirmIEe4m3 z(z(ihMEe4PW3F%(CF}1?a4ZEKUamN_X{>IWZ<7m~EDJcEAH&Ep+};V7ODP-f_Wk;+ zT@8=>iH{r9!i?Y_*W_D)yO-aSXJ=+`7S275#yf5tyd1U?rxGeA<-J3ypv9OH4#s=mkLMDJM9D3W=hG?D6C_3kD;>TsR!T^$cYuyi0*6eQDX2>ZHG z^a<}**IhWK)7kq-vffwo>pf;eknxr4N{|Y`Dd26ki)%j3X|mKb8ra#smHD0XdWljb zJ?-Q4^$+U>v*b?$-*lXBEVNy(S5^zf-sIHhH&Sq%?T04YcOO7pENivf;~8tk4OVk` zV#NT-Vgo&GO!3)N1V|;=W_g>28CRv7t zXeMzzpZ5YzSS+lGZwbUnA?$+6B)4EQ&GCP%5V-;)OENFzu;~H@jk!_Y0}*MxopmUZ z=%UGYw0aq>dLln^Ir56up2C+DYtsPMQlAI63S$VxsXsmiZdCg|vl~LJZ@l})W=wLH zUy(QR^@Z6;LJ17m36P!7{mjr~sPExjw`*6QMcyhj=)y(xr} z#Akk9u!XuaG@tV9<1^&06+J8{yy=mxZo`9hz7Dsx2PGy59)Y8VO(KptxuLDZ)RER^ z3rbrYaP2bT-E{k0%H8BWn$$R$j~m_Dnm{v=eSQ@4?GbSaonCt>Os@f+X{+Cedoq?= z4a_#p^K^yIis65%av@R=hG`8yHM;AvnKmc6*9Wm?NTx8vhR!a0tK>0dUS+xn`$ou^ ztno5ItCp~uh94Al7?@XXu%}-5t}ey`od(@iFurWJYbsyH?6UE_&x+-7+&YR><4R5T zcmZ+-uJ5fNbsQ2AVzMK5;G_ttX8O(NFLVT0X**+l7O#Y_M|=@~4{_0XU>1dC8eZ0( z3YryD?0eADVM zgyCfdEb<0|323RLv�YEY+W2cDxptY^SYrEfdD+)!quwQ0oBZ$+{R7$rMDIXKhQq z=iugi$Z|M+>5#qUc1Zgs0S;2OLhr4c^2?3do6s@fp}Dq~U9Y}J^fS`c#y%@0;Iq-| z;?+Ct*<@ne+Q7=C`iP_cqo;pEw(IlKdNcLUu6LW!+vXx`<l((g5ATH)Z>*D_ zoOCF8gHMYU7qFFM}WWW!Ixud{_kDO?eK@%=PmkX zck3d!07s;!g^y2n^vzax6!%4s%P0G!zvjSHD{=*!5g@gI02_@5^~I?`!IdWZ`$9cO zaef#0w*8XI)ro$}^`_{uK5T_{XcSJyiZ35%i{U;yH z#dx^eEL<5}lg8)#15dj%_S2&b7|!sp#&vqK^AJc|a8AN;07$*W+wWw*9zhlZm-p7W zz^#>l{G$*0c$MjQ(901W%Yk8#^eV#A4gBaFJPWM_{)gDAIOpmL=Bq(*f8XTob?tWg ztLFrDYTD69(d^=99%xE%$)cLbM7ZxV)T}9zB-f`K7n~A0kMIc$=b|M!`h*$MJn{hK z8ug+uYF}n1(_t`@bcLdzKZw(Gkpnvms%wOxS#f1@SsweBJASvqA+iZ zse+L617HK4D+Tzlxjz~VRj`CD3Jk7nDs-_=M_qUp)DtA#pd?c$gqmdzilNH-L9wclL8i*? zVJLtmj~Yjcilc6L@LgS}5j|CUs3GNCD+oEWu{F|E#R`mhGX2NZC#DH3-Wr=^Oz?Tc zh32-^l)~A1Xr0y_B9V6mwwd57?h1~O7maMDdH`OkQ{-eHc4_a_`e+ngI_KUi7_N7a z3A0{yIM(+|m zxVeX`MlU!Xi&`@$_it7YFT*)Ql9!Eg?ff48*a32XzRCCLgP~>6`Xz;c^D|V;rsWy} znX;7FOtF512nM@xmhX3ZO>F%vR(|lT>uOO@sWAfoOG@zn;C4tDAr}wyJ0Ye}fc(|4 z7DP=5Gd&6vBi`Om57YdRhhq#x?em&ym?P|I+}feq*>|MSY5dd&!i+Lwk#w=XqZr9o z1n#Dbe3<_sOCzXf^V1J=HnK2ti=3eKM}d>$_MmWtsZ)hTIH*+*Tp8*Y*OIXi&$RZ`8MK8JtW!TrZT6q)kbM$L)@*QT|tN((w*mO@|LaE|eNo`JACr5hEgkrlw;;LFBV`eUi_et zoA!+>zV1b}Is3shA;QJl=^(tbdS6l@F!3FU#%Tru<&>^V!;sp$cGwKtXnC^$^J!~V z9vYO&mwfg!E)*AAz!l8CDO}6XgqE_GH~vq|vLqy^ITeZov4|6%#3Uy*-16-XZug1! z2v{G9{vrj^S;9W5-Z+0&0)=FZ9i6=(7eLnecc87w>l7Bbq@Ajr$1zi5wSX&^_!Fpn zY$}AsE-vz}^dNbXSKzF0jcRad9H!-;3!S&03n)u7OitSUw2+diQR4k}(->(BRB3sT ziADjXE<8c~l8~LzJx1k)aqu5<=9K7m0BZl_94Ou4q>279Ix$B}^4>0P8be*kF;d4` zA-hNWvzy7%pc6hL9U(!=p}a;YVf(0D@}>HKVWzz0$pKFwqTL3Ac7YqY%|0ij*6H1Ky@Q$8)tN>(;z1} ze*UM#k}Fcfhd8xJm=pw~cN%?j5*1PtSVZ32c9I@mM7KR% z9la5dy|>WRl^0xpOv3)uVSDZ+;kFT~uVpij5=X8Yr#UrSnSrt3an0jtuZZa zJSo)FL&;X|V?}GgTKqY?Z%K{nh+d0M-vk5Fju$30;)z$(I!i=bnWF!&QkWcs5H10r zXbkw9Q2%&dv@@r999lU3Iv6_v0c%&Go~#D^yqnJr#Qj{6pUvpIGtz%uNsDsRoKaTN zBVKHvkt0UY;_SD5KX?H<(>gH%(JI0}4~d>?uEzNo=K$n=+#FprdWW~`VeGs_2A^_N zqY{6@(V~k8rjzg*ymfY#t7xlp+0*Ts{bY>3v68bhh>Zam+%M1LP}9jQJH1RF!2qx@ z){~@J<66t3DkwXH{I;hnSY`^ycL z8v8v_yDBpm-sW%O5QM5<_SBNM$7%Mi(BS&VcK zm&LNSMyC64aF$@3>KJ6SLo2|a`m3CWjLO*Pc!Jgtn6;qm~P%i4zYzrX0@5$ zdXK{@ThGd08hKPM8<4o`eKX@0S&8Y!n^hrPQV-fvJiL>;o(vtW6G%+JCh7A{uPL2O z@(-!jft8*wapBZWSL+Lwk=W>C8;~@OWBcAT>oXyqud9Nyhyu#cT*m3e*T##=z&Vjg zSx6(PW_iv=vGnFg$!uF3j-LSmHB@m>t1bZ~#pOv20&R_D;@6$T*2*Uc#FXt0;>w%6q+5n* zA|D{A7|D}WIV74NokOXXX*CMyfisgM8uM{$O^*72C!@=e2=UUsSm$)h!-Ob+H8I^Ig$&eyn+6=3HM*_?w3Zd zTqpR??L3f|?EC01ikjKk8^nNYo#oCI#Kpj>W^ew*z!?46d%W5!fxQE;H5cEbuvbQ$ z)+0fmo;jvae%>wNd`*Y`A3p~pZA0+_z%=}RweEZe*Q~<}K5&B%&?a;$@zjLsNsdV) zf$+OB7nMQ;<$Gc-D1!9Ed&7{Q1?7YH8b>p8yISk6dE(@4t2_xs(;z2X`9Wpbv66(d ze_;34Q7<0)(BmL1%e$+&P0(49II!ZzN}9nEfi0fq4aJdfUdWYxNU8Ggdj` zc-g(@!&(fk6*5=S9?$4^H+FPbQfTMYpqoODTK)FZ{jPu^c4RsFGC$`w$L`-1M^2Dl5(KIhAGpV2&2i78) z9Tu5CoSScPH8DFS?32K9+!{#jRwzaVI*$>96!{;>YkGQR*Eg?(9R%%AHCC6_iyMNj zO-vHb>**;e`)UkpiwaR_#j{OmIVNP#9jt&FoVQ}Q-E(*1{N$d35DoiMmMxzH zF_flycCHqa&_5Sf2$~^fz_PA#`0S5oF*pT#<@@nwr zua#q>OGynD7|`@*6+bpKBo?K=SDi~`sOeF)jU);ahprx_Ka}YUrzv-BxZ38r z3V-`FgqWSCe*Y1gBTWmiIlllnJOzV`SM${aWkHR4(UoKQYK_2ZVi$MuadL^)1P%-D zkdnPL_x<)iMPF^h7Lb!iCQ%+q)20lZ%)>FO#(C2r%mI8S6L-Gp9Lz{0fhT{Cy;ba6 zD-5PK(kdTgZ(a{r+t$1D^QTBfMcjn&l(dD$<^yRkuuClkB-}wr7Vn} z3|KX>4#9OB<>zh!rSy1KrpyX+?DzK2WK9B@o#m@!W2`4jafWw4%dOge6YmcpBa@XD z{bIl)5JR2jfkuX_^G)j?wweg2?{5!DWaZt|KDow#wgM0S7aaUhZWZ(qxjSs08!LW! za?Mdt(vpwMp~wV&`w;r5u-F6^D*`G$^q}v-&~GgBpV$zTcH&6?5Vsawt_KqppfN9? z+C=`^(i#zt%8JcG^Rx>gI3U7%Vxy2#>1 za6JVIv8!vHi39O>l4)}x`HEH2`U91aR|@>rUF8tAp&p3=f*&P#On|F!sS#EQIf`?^ z;EarA^>CJQ{##sy%o(nb_+1crb38nU6_m)5s87So9?+XyLMZf-nGyZzg*tIx_NU(o zWGtgVI@0}=mg|V0(M?B42UKH`KbGLSku7@@8ippmjT+bQ=Kfu#E{=JD7Qh;(K5(x_ zf6|pjF)LdO&o@0tD>EA&WITHPJtA)g{E?VqU5C>V?v4fECNv6c&|vr)2CIu+%Ll7f zh6}*s%l~>Q%x_MK0%(BlAYHgWt%9hm9L71y@T=rr%Jw}GHT;=H#h^^(qk~$0~CMP60YgZ5~0S;kr@1*AF z%eLIJpPAG-1BEr&2A*(9S)?{Z4M@dji%{h;ffCl4>7>7SkKWC>p_Qq9#i(bRKn9b9tn~&BIsD7t9~r**#~%HmcTIk8{i|{_1a?3{}}Hu`I6L^I*5Tz z#f`g$NH0H19Y&|SG)>Tw??||$j2t2BMKUI{$#3J;r5&-R(97i!Er~r)M9HJv3a30E z4Os6Y)cP5NWk?nY8jU#)%`A*#W8|UAP8qYJN@Amth(?2YuZJ{kKKtlW=tty$@Of?t zs4RhcC=^Pd)`ud=_R@bkKN4Bunca%HsOrb8cM=%?yffTxJ>r6yA9=`{Kzw2vi+var zX<>Czt*oROcy#cem63Q9PRh$CDkgU>-6H5D#0hd8`HD&+dWH&;S-f>?evB?N<-dda z3QR5O*mjW2Z0+f#NF}s_+u| zCB+}Y*xRbH2RbA7(EDmtClENJDogV?cKl4)357FJIgFtBX%nBncr_)(XM*L5GVqwG z_i&!p)Eci{?8vPmW`JwZ(m#ix9bNdfU4R8{yBT=~m~E0+AD-s2!u9lz+msLEG)8`d z6;_DHmXa{7S{g~Q=*-f})t3K)>xcNvJE+pywGfkD zsJlTB?)p$qTzjC%kW7+yYn0{e(_}sdJ*2S-@Za9#t&Yw;Ipu7NuGM;Hy8m9&&|~+LZLNB9r{!?H)KqUiH*+&q<3r zahNvl%*$NyGN~|`O!6WU6TYc;H<{9LahUfdgL81n%mqe$&R@!cs}A=i1)1l0;G3>k zEryZQ7qPxqL~6%JLui-eHoJ5<|5bF3T@o0JtbwiPYxpq*;1%1_M%DSf7abaReYDE? zU~o+9N+`zPY5cnIkbg%Y=?(54SFHO0RG#XuSW#l6lI|#iAxQmylR&~O?q)=r!;v$wjU-GdfxF^ z!S*h0)(3!ITV@~J)ss3eMl|oevtAON1mgYkQ@Q97<4s@tzjycJHrr+WvOL9RLFhIFqy9Vhl z{SFfAS|@od{)~Sz9~Q$V+)b_M_t^c2+oiRdh_i9Vt?S~Yog(Zte9IX^?FAEWGtfQuQYtEO zaKw+4Uh?v9a9P}7aL~q{7PPsQ9NYK&UocC#SSnNSgO#9Eyi(>55iYY-pxqNz`PNec zIl3rP*IW*!wTt`f`{W#F>~Sk2dViSYeEz7mEW~(1bK|rnlE6=z2V5RbIFW&H0?d&| zg_Q$M9%{~LM1qvAygqE(Q7)D)E1EBDHp13g zZ34WBmMEr2Q?NIS zsU3?CWBLI`zEL;dLMbr7Z<7b1qA!w?)+-L5r}NKo%_~&3!{yQdMlLT-TIO@4Va%L}3UmlDGHQPdezL{a!tPdlA%f*$1wzW&XV08=@+a=S zp&c=WWu_o9Mu=Tf_(|y3yr4p}k=5{}JGiVm500*kpgV%PE}~jt3-t9k%6z{>@*O;` zvCMh_yZ{ISIkUJC#8QiHZE~Ml=;!HM3-B!;=}_rycjkoNqZ>l|@*+OiC>G@-NZ)l) zZERP@-@Nm@xwmle-ETf%%Oz{`Cz}#{tY}?6MBiAR(Eq1M?WLD6DRn^Z<`sekW56rl z>wH`6?|Z6?W}g^hVxB)R7G{^CMI|p05bryT{#JX~t`KN7^lGRoHT5`Urp6bpU%Z!{ z^8dksY<_=-Y;EUua;m3k<$E8(;Y+!OXMT)B>SdDpL_5&Xsa)|CLW4r33p+mSAJjE+ zRT-O;(#PPn$O(agG4?D>|$s5x{@Y{3|mJZIr$ zSlj2l;xWvqgLB)bL%gH8DQn)uhl zado`AmVRWSoY4utV)E_>36>9pU>>M(cP^{@#PwU&55$ZMk^iZ?94w4m-yOq^`r@CO zTA24!@_v5+NrxHZht&*F(pu0GIV&*4tkoC()Tp=Oc)2alOS|a@fAD0-2E)YBSL}$P z#?OLr3xE2MbMaZiH|7`fUBod zhe^2Q(Q!Gpa9^`Y&5rwa>XgB$ZrX3Rbu}H1lYSi~Z+nmkinq&lNvfz^-`C@+BFhak z?+fMOvc z4WfG4bn<&B_a|_ap!*C9V-w^t@Z>{_+XH+WZwytKz6&`4~jdxcKN2538=cXx`r<& zxNJX+$C~vKr+R6KQ9PW|pR_h;_cxK0oCch)wOBnnB*w7{>eO#Pu3`|OP1Q|^c{lE# zjpwb!@^!Uud(ksKYZ+(HyJ9xXSE2CKF;fwdCz9sb35cHf4ECT5WfoNS)>^5V#Hc@TD#+GM!;sslM7MC^aZ zPQ{pgUrVl699Vw2ELJVI+i$-tSW_lA-c>>=-X|RSVth za^Njt(W-YOAh*a{ zu;cLGQK$;G)2-&PU>3OwJc7o>Z+60vK~)dbq#WA52I~*zKk07DS0+lO??sw- zqHVHpzhRaB_8IDsJ545%|3ZZJMHkKYpWdh60wIGor z3a+#ZC{k;#;F}_y4GNX`)%Q`o+4;brYvsjA5VqCh_FMyI$T+g(xV_pGa&YtJazsrb~r#U%+{a71h5)tB!AE^##FzM`kP^$dX)Jg2D%ABVtaZAztu`xE6Yf`Zbw8 z92$2xZI#s;8oP>Po>MLFu`|qbpCujo4|MwMp$A=xMkY&zdmw~XTFFZ!#nK^Dscspg zn-o^1RF#zDK$8JKs5BO#<|-;g2~tIbet@QC$G-oc(GpJYFcSS$;m^8K%=|8p{qQBy z@*^zyX4uylJN%qw4Q>Gb5~~DRF!Iv*CKpouA^nkYka1dj=|qSJ^x5GlD7u6F>>ku8 z!@~2)-&lGI`0^*nti99UHseHm>P}X9U;mXsaIUhSnD67?%FOHvq4i>Rgta;n51r@U z)~+GzpHQ+b?@!ev^?pw~c|_FL!-knh??vtdgYY~(8+-(u#(%f!=>EZoaqzeKHyx8D z=Xuc>zZt@^VT2b#7;khzd0uM7b-(}%`^j|v@RJ|=9km2df7+bJVK{$y=22r0+J>z) z@k+acsyY{mSO3sY`7I54{O*zCq)sNwFIfY$p}B(@5~Y#e3I>9H53~r{8odwBEq)x* zT60WvMcIl0P7*(s`YH;y5!*CY6GF z12R61_nuB=D}mwtjV7K-Nwk5Rl%O%k+jpfov|0IFa#i23a30=0vvY%0RasO}7>84K zhlG}&xLm6w|1d&TZ|~QIW5l^@(}amhCN?9v8$$oVSf52;T(a<8H(iK_b|Q}CXjEf* zyVbi0Q}n?sIv1g$cm~V6K}OK~!QO=H$suuCo0?vg%fTg9$!NrWCWGq2xo;XbWMlH4 zaO!g*`tqaj*Q%YEry7n;XkO2bPVYtqdc47g(GDd)UZ9zXA*LyqVLRX**QC&jPn*=c z!_i;R;T%V5FFe`M8Hu)jSZpu~8?i{S!K}^*VIguf(MTpa zfvIGvfZL#trj=LCq|u?C?QYNK;t>NemkXIgc;!M4KI8uI-oJbExwAIl6{r0QD|!`6 zh;dLuOtvfDA-3$D3X>xWN2S_IggYE31wkTOi9L9-f4DT>I3f{ki~2FxA=%pg54@D% zMg`7E1H1jP3iw5|%Ig!vY5&RP)h?MWH5Cb%d0y@c*MaD1;K*O!Nfc&oWku9s_8R(y zw`X;071yXRfUR2XpatAVL^6Ue>dm9?#-{~j8Tvds#GO7b z-Q@h(rE3sb%jTjarC@z|Y7=3JfP!L~$L6K_xIB)x$+`L0r(GDv7{n zBpmBCK>u*Wuz4HKzzO|j(@A9u-QMPGfCdeFNkm^JUTZ_+!E`8^WhDF%H&#Nye_vO- z1&7EfkNuw|b@R?}#x-;4_q}S$3XW3%?_3A!oI{~(P_jwxMH4EzXRLK(2uS#t!^4p) z$4aKXKOS)|gUVJ`=~;H86Y_w%DQ6wB@lkJvzld@6K$s-|J#(69HYf>}s$id1SUZ?- z3|UxJBaq^woOBU);Fo*cM7_aiIx2O}E{FfL$xu4<0{4GiX6IgstP_~u8|gbgE-o+) zhOs{ClVe|`R6o0pl;7~Ae+vdMZu{UP002?c7I~+P-m=I10OFI)SCD}0AjTac3a(5q zp!$&V+76sMTjG>1sh}0^+6A!T*~uKE)5uS{#1y>pTN5WxEv+2$)=2sNxjnjz?zM$k zEZo3!ACW5)#Sv2FxggKwkCS>saaqXYPr+iL9N`Ka;;SnqKaL{FYqE?!E_Q_@(eBrd zyyBf#5MdjT)6Q-k*obZK!nZe)RU|N~?3&}PQS@vY>*EIpns#A#oM*UVYxIsUIHaq?x^N5;>%SCe1in|&+tNQKPB_=I-m*pXkHhD3 z?_$P@>f$xxHCgAqn$kKho4l#zOe?IXm@XXpxH&trU;&wi{k4BUZ;5s8TDf&4?xwTO zlsb(tR{&h0hBWru4z;8a=wPnHMIY0;Zrd&2I<|Z~zTI?S##)fM9g6%%Oh+ zHoN)9K(xzJ0=+T{MoPacx_kiLM%O;(RJ$XY;1CA@ZWi-Hbhfx`MECLrpX#5WTpI?3 zUbdL&07QL~^EXl(mwcfhXo^5mR5*$!%kd_kHKRq4+oLYg){Q*-0=dO@))}awU1Q{s8SH0XwdzAno$|UX(Vy zYPO!rUCS@mZZJc(3~`cj<(?FGCa~<>?P+U22R83fM2=D~R(0)^UeKRT^11rkkaQPX zcfy`q0~Do5{K;a-=%;gIeHpJqgc=xq22L7P1%0P!R34%E1g zS~Sx61$fW5pqAYM32yhEdALH}x&%XLlc}_4BW_Hb$y)5P{4w$A@Cd;U>8DachTd&A zhN)g5y5y#<^#GLQw^m1#zWJC=uZIWRYbLcsO5rYPym43gEUQH+9I3-q*<$kNC14t&*o355 zotrsR-@@39{1+gbFd*$cl@3L| zWHp_y4PWG9SU1^N7y&bX2iajossIqsHfQjc<|!=xt?Z6Q+~dC0Md0FD}*ofK$ZNN8-t^YbKL|q=7C? zUpN|B(!+a<==tHYb;}Xp!-b}9wb8lbpgd9m76~j6=C_sWxQ(t7vJE zb1ZR^OVpm+93>_>Nrnzt)A7Rajw|%Qm1$5$o|BCtz;El4!9H<&_(l<(@rn@Pl~55E zZ?VvuL~wqWt)TKB3~oLWib{ln%*#~-Xm}ATlmc+I$T6bVfOytYzNJQ-=7U_1Eg*Xo6u@x^48?9Oe_k+u@p zKQ*pNIjj{i)e};C54*xoN3$+3JQG%;l+mncOrhI2^=udu(!LBvCz-MtP?u&ev2tn( z-I;oysE#}yS;6kN|KI8KztFtqYi+~fy)70NVtyM_{e6`i_hMtbfWwF1?CJlRjuiKd;2oUcm5+!hql z(hBUSGx?udLB~qcMex08TE2Xm5L;p@DuiplwZh9RHcIS&6FC32f)rEh+kyJsQg0+M zXjN6U1DHlhPwURH??qP?6CUP~Rk>8rZ_oAA==a85U)Z_B#$2+y1cZ{hj!1mXDC0&V zPInLWFZ<}&O7+=Y0=@!azA7?1`gU)z#$k~L(_IgbGy*G}QVb3b-;)2J_jTQ0)Lin` zxi+_sFIRtgVnP`k^+{HS>eJnZo*I%(5JZ4)<}C(8Po00miX59`RsAZQ@n__2Q~J}5 z-1*S$zZzOFiSTVKYBA-YPc7T%I<>XTO8EF3teZEV9+)>?!Jk>HEO95+3 zKY38iYmu^;$4I~|gi~;W1YXR3q*&+b05;dCGJNCUIBQiUOv_s3vOlg!c_m+^X#Vwe z^BBxoCIWme)R-(IOh>D7jR8kRj7mhKLI0N2P3Kdp7T`RAbj*Lf8CG}>212bO#4p&rZ1zJ9X9mFpK4A; z{U!}@878=9vQi8nF;6Xg_xNl4Hlu7D6#O2Yylhu}`Pcq)0+sABu9Pas@%rf7J>(d| zu~#!#B=PP6P4U9?08Ry0^--Ko2iqFk@{2a}j`iee@i?2sB@6t$Ppna z7?B)u2|mhHu5`$a7-yKFIP!mKYr0`6-e=+UJ2mm?9W-GAkjq=IK)`FCDBag<)RPJ5 zv)qbHM6w#YbN)80uF}Lzt8v_g7&R!}C+f|Cn+#)SUM@@9SE02}c9tZe2hDfcE%=A} zk!P}YRlBp>kfF7}K*P2+F{~C+fqJgO0gfXu_E?@^06S%O2kFLL=Kl8OZ-w{Y2^%>-vroJxb-yF41mDY~hDUrAyAYeR*6;c;s&}=xj z2OiN+NNt!qZC~~<_T2irWL)wa18rYj7Z##_Z9L^uKm$RUTE)M378T>MFaW-z_uspJ z|CaxviIInOS0Sd|F0;(k`3M^Z$h2qy?1iY6D0rF8Xxvrl40oQ%cd@1uH9X?Hw&4k33PzXeM>K&{K7Q|w<_T`MYK!RmZ&HC7?&JeadDdahr| z(d70fC}=Z`;^XsF0{OMrJK87RN~N3ksQ8Doy?=O0@0$>NQ&M1LcL#Cf9YvST8%$!< zq#du1imbX1HDvoMP*Ek%BP>84wGrJzSo<#s<( z5($>IGhu4?+^*z+m|My3QXdYT{i#oSvu6!hTzfO7HrCw3g`;I7HM*bL7kGP#W&BxG zd=u%SXficFF&tRvt>JOHAv`r)x_F>duvfcL>0_|@Xyr)95TclA?)S_5Zf>R=su`{? zC?-Vje19LlI2?_&GpTg@PhN}otxx{$wV4tozTcjyn4~S~4k%&LnD8+0?RNM!sX_FQ zet2zrIo0>DdJ0I_TE}=4n+lk<{l}GTg8iUW?y(jni8uQSB zsK07vD5)u-yc!onbkQBJzr=>8JGaYYx%G=i_+#sX#OC8BEoA%7&gpT9yn%uR-L7!! z&Ax3Hl|+aMpbnfss1EKEo}*mPkk6=C0TA)Y&<+nu1bnOeqyc{K27kKS(2^O}-Q51m z(r)A|Tnb->BzJB*;C{ya54vlJYs003{&VwF*?enZ8NXk#5($Yp`!`=Qicm1E3;z`) zEFd&E^&QarvDm@pPGL?b95}Wszq~PRZ)u596SF|vX;gU8(lT~Zea&A&dVaKLr>yOe zC8{Dz5(WWpsP(Ws^#8aBn-0A`r}hnAgrNrSA7+T)NrC?ll}!2w2ZD_b+i|AOy zzty9uf}zpI9Y^lWORdB)_5n3>ZLmFC5+6Fm=X!)GqrA*xEC&4>59jjX{jbCF?g zyMc_qQ6MOp&cP<^CkM@;*_7l!a}mDQIBm_u&~{#j_IZT{VPVeV_i!!ZAsC%NH|pTKCzu z{NVjHR8>-vWRq*@!e{oo`gC^5f;HpQ+GF%)udsWEMwHbkN6GPq;ynJM_2F;S6vZ6% zF8*GZXEq3wx?ClGCw7cQJl(|t j0$Cn=(dH|@Qm|-- +- Added test for get_affected_rows after bulk operations + +- DB2 +-- Enable build and testing on Travis CI +-- Fixed use type size calculation during bind/pre_use + +- Firebird +-- Enable memory cleanup to avoid leaks after binding +-- Added missing C++ library headers +-- Added get_affected_rows() support for bulk operations +-- Fixed compilation with VS2008 +-- Fixed building tests with the backend built as DLL. + +- MySQL +-- Added get_affected_rows() support for bulk operations + +- ODBC +-- Always call ASCII version of ODBC function SQLGetDiagRec +-- Fixed invalid condition test in assertions +-- Added get_affected_rows() support for bulk operations +-- Added support for (unsigned) long long vectors +-- Changed mapping of SQL_BIGINT to dt_long_long in ODBC backend +-- Added some trivial optimizations in statement preparation code + +- Oracle +-- Fixed missing noData_ reported in particular case when OCIStmtExecute returns success +-- Improved Oracle testing setup on Travis CI + +- PostgreSQL +-- Added session::get_next_sequence_value() for PostgreSQL backend +-- Added get_affected_rows() support for bulk operations +-- Applied RAII and simplified error handling logic +-- Fixed issue with exceptions escaping postgresql_statement_backend destructor + +- SQLite3 +-- Replaced sqlite3_prepare with sqlite3_prepare_v2 +-- Added missing header +-- Fixed wrong size used in memcpy() during bulk processing +-- Added get_affected_rows() support for bulk operations +-- Added shared_cache=true propery to control SQLITE_OPEN_SHAREDCACHE flag + +- Documentation +-- Corrected usage examples + +- Building +-- Updated CMake for Oracle 12g +-- Restructured and improved Travis CI setup to do single build per backend +-- Fixed wrong GCC_VERSION used in CMake config for commandline-overriden GCC +-- Allows CMake to find 32-bit DB2 on Windows x64 +-- Removed redundant Oracle libclntsh library lookup +-- Fixed SQLITE3_LIBRARIES handling by find_package_handle_standard_args + +--- +Version 3.2.1 differs from 3.2.0 in the following ways: + +- Fixed binding of Oracle type NUMBER(9,0) to C++ int, by Tomasz Olszewski +- Fixed Oracle client library detection on Windows, by Tomasz Olszewski +- Fixed PostgreSQL issue with deallocate_prepared_statement called for non-existing statement +- Fixed prepared insert statements with custom data types support, by Tomasz Olszewski +- Fixed recent improvements in x:=y name binding, by Poul Bondo +- Fixed query transformation assignment in pooled sessions, by Stefan Chrobot +- Cleaned up SOCI_POSTGRESQL_NOPARAMS and related options in code and documentation +- Dropped patch (micro) version number from documentation URLs on the website + +--- +Version 3.2.0 differs from 3.1.0 in the following ways: + +- SOCI is now organization at GitHub +-- Git repository moved to https://github.com/SOCI/soci +-- Opened new bug tracker (SF.net tracker is read-only) +-- Opened Wiki for FAQ and development articles +-- Website, mailing lists and downloads remain on SourceForge.net +-- Applied GitFlow branching model +-- Added package maintenance repository https://github.com/SOCI/soci-pkg + +- Core +-- Added connection_parameters to enable support for session options used by core, backends or low-level API. +-- Added user-defined query transformation support based on function, functor and lambda +-- Fixed missing connection check and backend setup ensured for session +-- Fixed backend initialization when used with connection pool (Core) +-- Fixed memory leaks on exception thrown during statement preparation stage + +- DB2 +-- Added new backend for IBM DB2 + +- Firebird +-- Fixed bug in repeated statement execution +-- Fixed issues with input parameter binding +-- Fixed connection string parsing to handle possible white-spaces +-- Fixed issues with C++ type unsigned long +-- Fixed reading negative values into C++ type double +-- Added option to fetch numeric/decimal data into string of characters +-- Added CMake configuration +-- Updated tests +-- Continued regular testing on Windows and Unix platforms + +- MySQL +-- Replaced use of type=InnoDB with engine=InnoDB +-- Improved CMake configuration + +- ODBC +-- Added connection_parameters option ODBC_OPTION_DRIVER_COMPLETE +-- Fixed issues in handling C++ types int and long on 64-bit architectures +-- Added missing CMake configuration for tests +-- Continued regular testing on Windows and Unix platforms + +- Oracle +-- Implemented statement::get_affected_rows() operation +-- Use OCI_THREADED and OCI_ENV_NO_MUTEX with OCIEnvCreate +-- Added numerous fixes and improvements in tests +-- Added option to fetch numeric/decimal data into string of characters +-- Fixed issues in binding procedure IN/OUT parameters + +- PostgreSQL +-- Add reading of BYTEA data into std::string (not fully-featured binary data support yet) +-- Add JSON data type support available in PostgreSQL 9.2+ +-- Fixed incorrect assertion in postgresql::get_error_details +-- Fixed premature deallocation of prepared statements +-- Fixed servers-side memory leak in prepared statements +-- Fixed memory leak of PGresult on exception thrown +-- Fixed isues in parsing complex PL/PSQL functions + +- SQLite3 +-- Implemented statement::get_affected_rows() operation +-- Fixed handling of database file path with spaces + +- Building, testing and installation +-- Marked Makefile.basic as deprecated (maintainer wanted) +-- Cleaned numerous compilation warnings reported by various compilers +-- Improved compilation using latest version of clang +-- Added numerous improvements in CMake configuration +-- Added SOCI_STATIC option to enable/disable static libraries build +-- Fixed issues with ignored -DWITH_ options +-- Fixed FindMySQL.cmake to allow use of mysqlclient from custom location +-- Added support of SQLITE_ROOT_DIR variable to FindSQLite3.cmake module +-- Fixed and tested CMake configuration on OSX +-- Fixed and tested CMake configuration on FreeBSD +-- Fixed and tested CMake configuration to use with Ninja +-- Fixed building using Visual Studio 2010 +-- Added support for building using Visual Studio 2012 +-- Set up SOCI continuous integration at travis-ci.org +-- Added Debian packaging support to soci-pkg repository +-- Added Fedora/CentOS/RedHat packaging support to soci-pkg repository + +- Documentation +-- Review and update to catch up with current status quo +-- Updated code examples +-- Start maintenance and hosting of SOCI documentation per released version + +--- +Version 3.1.0 differs from 3.0.0 in the following ways: + +- Added Ada language binding + +- Migraded build system from GNU Autotools and Visual Studio projects to CMake + +- CMake build tested with Visual Studio, GCC and clang + +- Incorporated a compromise for naming versioned shared libraries + +- Enhanced and improved integration with Boost libraries: +-- Boost.DateTime +-- Boost.Fusion +-- Boost.Optional +-- Boost.Tuple + +- Bug fixes and improvements in core and backends: +-- Added soci::values::get_properties accessor useful for composing soci::type_conversion +-- Export advanced API of backend loader from DLL. +-- Added static factory registration functions for backends +-- Added get_affected_rows operation +-- Fixed thread-safety of connection pool under Windows +-- Fixed bug with droping const qualifiers when binding to std::vector +-- Fixed bug in default initialization of an object of const backend_factory wihch requires user-provided default constructor (see C++03/C++0x) +-- Fixes for 64-bit builds +-- Removed redundant exchange_traits breaking ODR on LP64 +-- Better ODBC support +-- Type conversion support for unsigned integer types +-- Bug ID:1971436 - incorrect rowset copy operations +-- Bug ID:2010367 - memory leak (ODBC) +-- Bug ID:2010409 - invalid memory allocaton in define by position (ODBC) +-- Bug ID:2021243 - long long type support in Visual C++ +-- Patch ID:2483066 - 64bit Linux and 64bit integer submitted +-- Patch ID:2809809 - Fix build with GCC 4.4 +-- Patch ID:2809810 - Fix SQLite3 build with GCC 4.3 +-- Patch ID:2581206 - Windows unicode application +-- Patch ID:3058275 - install target for cmake build submitted +-- Patch ID:3069375 - use $(prefix)/lib64 on AMD64 platforms. +-- Improved performance while accessing query results (MySQL) +-- Bug fixes for PROCEDURE support (MySQL) +-- Removed throw statements from mysql_rowid_backend and mysql_blob_backend destructors (MySQL) +-- Verify that prepared statements survive session::reconnect() operatoin (MySQL) +-- Improved support for time and date (MySQL, PostgreSQL) +-- Fixed bug with strings of length exceeding 255 characters (ODBC) +-- Improved interpretation of the connect string (Oracle) +-- Added handling of unsigned long long (Oracle, SQLite3, PostgreSQL) +-- Fixes in integral types support (PostgreSQL) +-- Support for colon-casts (PostgreSQL) +-- Added possibility for use BLOB (PostgreSQL) +-- Added support for connection property "synchronous=on|off" (SQLite3) +-- Improved BLOB data handling (SQLite3) +-- Improved boolean type suppport (SQLite3) +-- Session timeout support (SQLite3) +-- Improved tests clean-up (SQLite3) +-- Added missing typedef of sqlite3_destructor_type which has been defined in sqlite3.h but since 3.3.10 (see comment for reference to SQLite ticket) + +- Updated tests for various backends and SQL data types + +- Migrated from CVS to Git repository + +- Changed naming convensions and style across all the source code + +- Firebird backend removed from official release as not actively maintained. Available in the Git repository. + +--- +Version 3.0.0 differs from 2.2.0 in the following ways: + +- Exposed the session's locale object. + +- Moved the "no data" flag from indicators to statement. + +- Allowed const objects as "use" elements. + +- Added connection mode for Oracle. + +- Added RAII support for transactions. + +- Added the open/close/reconnect functionality. + +- Added support for long long as a fundamental data type. + +- Unified column names for dynamic rowset description, to overcome + differences between database servers. + +- Added the "simple" interface for interfacing from other languages. + +- Added thread-safe connection pool. + +- Added integrated support for Boost data types: gregorian_date, + fusion and tuple. + +- Added dynamic backend loading. + +- Changed the naming convention to comply with Boost recommendations. + +--- +Version 2.2.0 differs from 2.1.0 in the following ways: + +- Added true support for statement preparation with PostgreSQL. + +- Added support for the stream-like extraction from Row. + +- Added STL-compatible iterator interface for select statements. + +- Refactored the set of common tests to validate core library functionality + independently on the target database server. + +- Introduced new backends for MS SQL Server (via ODBC) and Firebird. + +- Provided complete build system for both Unix (autotools) and + Windows (solution and project files for MSVC++). + +--- +Version 2.1.0 differs from 2.0.1 in the following ways: + +- Added two additional backends: MySQL and SQLite3 + +- Restructured the source code layout so that the whole library was broken + into the "core" part and independent "backends", each in its own + directory together with appropriate tests. + +- Provided basic Makefiles for static and shared libraries on + Linux-compatible systems. + +- Added the general class and function reference to the documentation. + +--- +Version 2.0.1 differs from 2.0.0 in the following ways: + +- Corrected some typos in documentation. + +- Corrected handling of dynamic rowset description for those backends + which do not have dedicated description functionality. + +- A bug fix to correctly handle std::tm in the Oracle backend. + +- A bug fix to correctly handle object relational mapping when + Values::set() and Values::get() are called where T is a + TypeConversion-based type. + +--- +Version 2.0.0 differs from 1.2.1 in the following ways: + +- The whole library was internally re-architectured to allow operation + with many different backends. The top-level part of the library + (the syntax layer) provides essentially the same interface as in previous + versions of the library, but it can work with independent (and dynamically + selected) backends, possibly targeting different database engines. + As a prove of concept (and to encourage developments of new backends), + the PostgreSQL backend was provided in addition to the Oracle one. + During this re-architecturing, some minor bugs were fixed as well. + +- The old Boost-style license was changed to the new (v. 1.0) Boost license. + +--- +The version 1.2.1 differs from 1.2.0 in the following ways: + +- A bug was fixed that caused compile errors on MS VC++ compiler. + +--- +The version 1.2.0 differs from 1.1.0 in the following ways: + +- A memory leak when reading into Row objects was fixed. + +- Bulk (array) operations were introduced for high-performance + applications, where the number of network round-trips can be + significantly reduced by operating on whole arrays (vectors). + +--- +The version 1.1.0 differs from 1.0.1 in the following ways: + +- Explicit support for calling stored procedures was added. + +- Dynamic row recognition (type discovery) was added. + +- Support for user-defined data types was added. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000000..08810d066f --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,105 @@ +############################################################################### +# +# This file is part of CMake configuration for SOCI library +# +# Copyright (C) 2009-2013 Mateusz Loskot +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) +# +############################################################################### +# General settings +############################################################################### +cmake_minimum_required(VERSION 2.8.0 FATAL_ERROR) + +project(SOCI) + +############################################################################### +# SOCI CMake modules +############################################################################### + +# Path to additional CMake modules +set(CMAKE_MODULE_PATH ${SOCI_SOURCE_DIR}/cmake ${CMAKE_MODULE_PATH}) +set(CMAKE_MODULE_PATH ${SOCI_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH}) + +include(SociUtilities) + +colormsg(_HIBLUE_ "Configuring SOCI:") + +############################################################################### +# SOCI version information +############################################################################### +include(SociVersion) + +soci_version(MAJOR 3 MINOR 2 PATCH 2) + +############################################################################### +# Build features and variants +############################################################################### +include(SociSystemInfo) +include(SociConfig) + +boost_report_value(SOCI_PLATFORM_NAME) +boost_report_value(SOCI_COMPILER_NAME) + +option(SOCI_SHARED "Enable build of shared libraries" ON) +boost_report_value(SOCI_SHARED) + +option(SOCI_STATIC "Enable build of static libraries" ON) +boost_report_value(SOCI_STATIC) + +option(SOCI_TESTS "Enable build of collection of SOCI tests" ON) +boost_report_value(SOCI_TESTS) + +# Put the libaries and binaries that get built into directories at the +# top of the build tree rather than in hard-to-find leaf +# directories. This simplifies manual testing and the use of the build +# tree rather than installed Boost libraries. +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) + +############################################################################### +# Find SOCI dependencies +############################################################################### + +set(SOCI_CORE_DEPENDENCIES) + +include(SociDependencies) + +#message("${SOCI_CORE_DEPENDENCIES}") + +############################################################################### +# Installation +############################################################################### + +if(APPLE OR CMAKE_SIZEOF_VOID_P EQUAL 4) + set(SOCI_LIBDIR "lib") +else() + set(SOCI_LIBDIR "lib64") +endif() + +set(BINDIR "bin" CACHE PATH "The directory to install binaries into.") +set(LIBDIR ${SOCI_LIBDIR} CACHE PATH "The directory to install libraries into.") +set(DATADIR "share" CACHE PATH "The directory to install data files into.") +set(INCLUDEDIR "include" CACHE PATH "The directory to install includes into.") + +############################################################################### +# Enable tests +############################################################################### +enable_testing() +# Configure for testing with dashboard submissions to CDash +#include(CTest) # disabled as unused + +# Define "make check" as alias for "make test" +add_custom_target(check COMMAND ctest) + +############################################################################### +# Build configured components +############################################################################### +include(SociBackend) + +include_directories(${SOCI_SOURCE_DIR}/core) + +add_subdirectory(core) +add_subdirectory(backends) diff --git a/src/CTestConfig.cmake b/src/CTestConfig.cmake new file mode 100644 index 0000000000..7ecf20dca7 --- /dev/null +++ b/src/CTestConfig.cmake @@ -0,0 +1,13 @@ +## This file should be placed in the root directory of your project. +## Then modify the CMakeLists.txt file in the root directory of your +## project to incorporate the testing dashboard. +## # The following are required to uses Dart and the Cdash dashboard +## ENABLE_TESTING() +## INCLUDE(CTest) +set(CTEST_PROJECT_NAME "SOCI") +set(CTEST_NIGHTLY_START_TIME "00:00:00 EST") + +set(CTEST_DROP_METHOD "http") +set(CTEST_DROP_SITE "my.cdash.org") +set(CTEST_DROP_LOCATION "/submit.php?project=SOCI") +set(CTEST_DROP_SITE_CDASH TRUE) diff --git a/src/LICENSE_1_0.txt b/src/LICENSE_1_0.txt new file mode 100644 index 0000000000..36b7cd93cd --- /dev/null +++ b/src/LICENSE_1_0.txt @@ -0,0 +1,23 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/src/README b/src/README new file mode 100644 index 0000000000..3666448e48 --- /dev/null +++ b/src/README @@ -0,0 +1,35 @@ +SOCI - The C++ Database Access Library. + +Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton, + David Courtney, Pawel Aleksander Fedorynski, + Rafal Bobrowski, Mateusz Loskot + +The homepage of the SOCI library is: +http://soci.sourceforge.net/ + +--- + +You should see the following files and directories here: + +- README - This file. +- LICENSE_1_0.txt - Full text of the Boost license. +- CHANGES - The description of changes. + +- contrib - Acknowledgements for contributions. + +- doc/ - Documentation. + +- build/ - Additional build scripts and configuration files + for various compilation systems. + +- src/ - The source code of the library. + +- src/core/ - The main part of the SOCI library. + +- src/backends/ - Directory containing implementations and tests for + all available backends. + + +Please see the doc/structure.html file for compilation instructions. + +--- diff --git a/src/TODO b/src/TODO new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/backends/.gitignore b/src/backends/.gitignore new file mode 100644 index 0000000000..64ec3b6829 --- /dev/null +++ b/src/backends/.gitignore @@ -0,0 +1,5 @@ +*.o +*.la +*.lo +.deps +.libs diff --git a/src/backends/CMakeLists.txt b/src/backends/CMakeLists.txt new file mode 100644 index 0000000000..25837519c3 --- /dev/null +++ b/src/backends/CMakeLists.txt @@ -0,0 +1,48 @@ +############################################################################### +# +# This file is part of CMake configuration for SOCI library +# +# Copyright (C) 2010 Mateusz Loskot +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) +# +############################################################################### +colormsg(_HIBLUE_ "Configuring SOCI database backends:") + +# First, we'll investigate what can be found from database engines +foreach(dep ${SOCI_BACKENDS_DB_DEPENDENCIES}) + string(TOUPPER ${dep} depUP) + if (WITH_${depUP}) + find_package(${dep}) + endif() + if(${dep}_FOUND OR ${depUP}_FOUND) + set(${depUP}_FOUND ON) + else() + set(${depUP}_FOUND OFF) + endif() +endforeach() + +# get all files in backends +file(GLOB backend_dirs RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *) + +# empty backend always on by default +option(SOCI_EMPTY "Build empty backend" ON) +if(SOCI_EMPTY) + set(WITH_EMPTY ON) + set(EMPTY_FOUND ON) +endif() + +# enable only found backends +foreach(dir ${backend_dirs}) + if(IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${dir}) + if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${dir}/CMakeLists.txt) + string(TOUPPER ${dir} dirUP) + if(${dirUP}_FOUND AND WITH_${dirUP}) + add_subdirectory(${dir}) + endif() + endif() + endif() +endforeach() + +message(STATUS "") diff --git a/src/backends/db2/CMakeLists.txt b/src/backends/db2/CMakeLists.txt new file mode 100644 index 0000000000..66191cb79c --- /dev/null +++ b/src/backends/db2/CMakeLists.txt @@ -0,0 +1,18 @@ +############################################################################### +# +# This file is part of CMake configuration for SOCI library +# +# Copyright (C) 2011 Denis Chapligin +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) +# +############################################################################### +soci_backend(DB2 + DEPENDS DB2 + HEADERS soci-db2.h + DESCRIPTION "SOCI backend for DB2 Universal Database" + AUTHORS "Denis Chapligin" + MAINTAINERS "Denis Chapligin") + +add_subdirectory(test) diff --git a/src/backends/db2/blob.cpp b/src/backends/db2/blob.cpp new file mode 100644 index 0000000000..a4453485a7 --- /dev/null +++ b/src/backends/db2/blob.cpp @@ -0,0 +1,62 @@ +// +// Copyright (C) 2011-2013 Denis Chapligin +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_DB2_SOURCE +#include "soci-db2.h" + +#ifdef _MSC_VER +#pragma warning(disable:4355) +#endif + +using namespace soci; +using namespace soci::details; + + +db2_blob_backend::db2_blob_backend(db2_session_backend &session) + : session_(session) +{ + // ... +} + +db2_blob_backend::~db2_blob_backend() +{ + // ... +} + +std::size_t db2_blob_backend::get_len() +{ + // ... + return 0; +} + +std::size_t db2_blob_backend::read( + std::size_t /* offset */, char * /* buf */, std::size_t /* toRead */) +{ + // ... + return 0; +} + +std::size_t db2_blob_backend::write( + std::size_t /* offset */, char const * /* buf */, + std::size_t /* toWrite */) +{ + // ... + return 0; +} + +std::size_t db2_blob_backend::append( + char const * /* buf */, std::size_t /* toWrite */) +{ + // ... + return 0; +} + +void db2_blob_backend::trim(std::size_t /* newLen */) +{ + // ... +} diff --git a/src/backends/db2/common.h b/src/backends/db2/common.h new file mode 100644 index 0000000000..25a6619d1e --- /dev/null +++ b/src/backends/db2/common.h @@ -0,0 +1,21 @@ +// +// Copyright (C) 2013 Mateusz Loskot +// Copyright (C) 2011-2013 Denis Chapligin +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_DB2_COMMON_H_INCLUDED +#define SOCI_DB2_COMMON_H_INCLUDED + +#include + +namespace soci { namespace details { namespace db2 { + +const std::size_t cli_max_buffer = 1024 * 1024 * 1024; //CLI limit is about 3 GB, but 1GB should be enough + +}}} // namespace soci::details::db2 + +#endif // SOCI_DB2_COMMON_H_INCLUDED diff --git a/src/backends/db2/factory.cpp b/src/backends/db2/factory.cpp new file mode 100644 index 0000000000..a94bb1e5fd --- /dev/null +++ b/src/backends/db2/factory.cpp @@ -0,0 +1,40 @@ +// +// Copyright (C) 2011-2013 Denis Chapligin +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, David Courtney +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_DB2_SOURCE +#include "soci-db2.h" +#include + +using namespace soci; +using namespace soci::details; + + +// concrete factory for ODBC concrete strategies +db2_session_backend * db2_backend_factory::make_session( + connection_parameters const & parameters) const +{ + return new db2_session_backend(parameters); +} + +db2_backend_factory const soci::db2; + +extern "C" +{ + +// for dynamic backend loading +SOCI_DB2_DECL backend_factory const * factory_db2() +{ + return &soci::db2; +} + +SOCI_DB2_DECL void register_factory_db2() +{ + soci::dynamic_backends::register_backend("db2", soci::db2); +} + +} // extern "C" diff --git a/src/backends/db2/row-id.cpp b/src/backends/db2/row-id.cpp new file mode 100644 index 0000000000..1833ffaf61 --- /dev/null +++ b/src/backends/db2/row-id.cpp @@ -0,0 +1,28 @@ +// +// Copyright (C) 2011-2013 Denis Chapligin +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_DB2_SOURCE +#include "soci-db2.h" + +#ifdef _MSC_VER +#pragma warning(disable:4355) +#endif + +using namespace soci; +using namespace soci::details; + + +db2_rowid_backend::db2_rowid_backend(db2_session_backend & /* session */) +{ + // ... +} + +db2_rowid_backend::~db2_rowid_backend() +{ + // ... +} diff --git a/src/backends/db2/session.cpp b/src/backends/db2/session.cpp new file mode 100644 index 0000000000..2c82213f05 --- /dev/null +++ b/src/backends/db2/session.cpp @@ -0,0 +1,217 @@ +// +// Copyright (C) 2011-2013 Denis Chapligin +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_DB2_SOURCE +#include "soci-db2.h" +#include + +#ifdef _MSC_VER +#pragma warning(disable:4355) +#endif + +using namespace soci; +using namespace soci::details; + +const std::string db2_soci_error::sqlState(std::string const & msg,const SQLSMALLINT htype,const SQLHANDLE hndl) { + std::ostringstream ss(msg, std::ostringstream::app); + + + SQLCHAR message[SQL_MAX_MESSAGE_LENGTH + 1]; + SQLCHAR sqlstate[SQL_SQLSTATE_SIZE + 1]; + SQLINTEGER sqlcode; + SQLSMALLINT length; + + if ( SQLGetDiagRec(htype, + hndl, + 1, + sqlstate, + &sqlcode, + message, + SQL_MAX_MESSAGE_LENGTH + 1, + &length) == SQL_SUCCESS ) { + ss<<" SQLMESSAGE: "; + ss<dsn=value; + } + if (!key.compare("Uid")) { + this->username=value; + } + if (!key.compare("Pwd")) { + this->password=value; + } + this->autocommit=true; //Default value + if (!key.compare("autocommit")) { + if (!value.compare("off")) { + this->autocommit=false; + } + } +} + +/* DSN=SAMPLE;Uid=db2inst1;Pwd=db2inst1;AutoCommit=off */ +void db2_session_backend::parseConnectString(std::string const & connectString) { + std::string processingString(connectString); + size_t delimiter=processingString.find_first_of(";"); + while(delimiter!=std::string::npos) { + std::string keyVal=processingString.substr(0,delimiter); + parseKeyVal(keyVal); + processingString=processingString.erase(0,delimiter+1); + delimiter=processingString.find_first_of(";"); + } + if (!processingString.empty()) { + parseKeyVal(processingString); + } +} + +db2_session_backend::db2_session_backend( + connection_parameters const & parameters) : + in_transaction(false) +{ + parseConnectString(parameters.get_connect_string()); + SQLRETURN cliRC = SQL_SUCCESS; + + /* Prepare handles */ + cliRC = SQLAllocHandle(SQL_HANDLE_ENV,SQL_NULL_HANDLE,&hEnv); + if (cliRC != SQL_SUCCESS) { + throw db2_soci_error("Error while allocating the enironment handle",cliRC); + } + + cliRC = SQLAllocHandle(SQL_HANDLE_DBC, hEnv, &hDbc); + if (cliRC != SQL_SUCCESS) { + std::string msg=db2_soci_error::sqlState("Error while allocating the connection handle",SQL_HANDLE_ENV,hEnv); + SQLFreeHandle(SQL_HANDLE_ENV,hEnv); + throw db2_soci_error(msg,cliRC); + } + + /* Set autocommit */ + if(this->autocommit) { + cliRC = SQLSetConnectAttr(hDbc,SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_ON, SQL_NTS); + } else { + cliRC = SQLSetConnectAttr(hDbc,SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_OFF, SQL_NTS); + } + if (cliRC != SQL_SUCCESS) { + std::string msg=db2_soci_error::sqlState("Error while setting autocommit attribute",SQL_HANDLE_DBC,hDbc); + SQLFreeHandle(SQL_HANDLE_DBC,hDbc); + SQLFreeHandle(SQL_HANDLE_ENV,hEnv); + throw db2_soci_error(msg,cliRC); + } + + /* Connect to database */ + cliRC = SQLConnect(hDbc, const_cast((const SQLCHAR *) dsn.c_str()), SQL_NTS, + const_cast((const SQLCHAR *) username.c_str()), SQL_NTS, + const_cast((const SQLCHAR *) password.c_str()), SQL_NTS); + if (cliRC != SQL_SUCCESS) { + std::string msg=db2_soci_error::sqlState("Error connecting to database",SQL_HANDLE_DBC,hDbc); + SQLFreeHandle(SQL_HANDLE_DBC,hDbc); + SQLFreeHandle(SQL_HANDLE_ENV,hEnv); + throw db2_soci_error(msg,cliRC); + } +} + +db2_session_backend::~db2_session_backend() +{ + clean_up(); +} + +void db2_session_backend::begin() +{ + // In DB2, transations begin implicitly; however, autocommit must be disabled for the duration of the transaction + if(autocommit) + { + SQLRETURN cliRC = SQLSetConnectAttr(hDbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER) SQL_AUTOCOMMIT_OFF, SQL_NTS); + if (cliRC != SQL_SUCCESS && cliRC != SQL_SUCCESS_WITH_INFO) + { + std::string msg=db2_soci_error::sqlState("Clearing the autocommit attribute failed", SQL_HANDLE_DBC, hDbc); + SQLFreeHandle(SQL_HANDLE_DBC,hDbc); + SQLFreeHandle(SQL_HANDLE_ENV,hEnv); + throw db2_soci_error(msg,cliRC); + } + } + + in_transaction = true; +} + +void db2_session_backend::commit() +{ + if (!autocommit || in_transaction) { + in_transaction = false; + SQLRETURN cliRC = SQLEndTran(SQL_HANDLE_DBC,hDbc,SQL_COMMIT); + if(autocommit) + { + SQLRETURN cliRC2 = SQLSetConnectAttr(hDbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER) SQL_AUTOCOMMIT_ON, SQL_NTS); + if ((cliRC == SQL_SUCCESS || cliRC == SQL_SUCCESS_WITH_INFO) && + cliRC2 != SQL_SUCCESS && cliRC2 != SQL_SUCCESS_WITH_INFO) + { + std::string msg=db2_soci_error::sqlState("Setting the autocommit attribute failed", SQL_HANDLE_DBC, hDbc); + SQLFreeHandle(SQL_HANDLE_DBC,hDbc); + SQLFreeHandle(SQL_HANDLE_ENV,hEnv); + throw db2_soci_error(msg,cliRC); + } + } + if (cliRC != SQL_SUCCESS && cliRC != SQL_SUCCESS_WITH_INFO) { + throw db2_soci_error("Commit failed",cliRC); + } + } +} + +void db2_session_backend::rollback() +{ + if (!autocommit || in_transaction) { + in_transaction = false; + SQLRETURN cliRC = SQLEndTran(SQL_HANDLE_DBC,hDbc,SQL_ROLLBACK); + if(autocommit) + { + SQLRETURN cliRC2 = SQLSetConnectAttr(hDbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER) SQL_AUTOCOMMIT_ON, SQL_NTS); + if ((cliRC == SQL_SUCCESS || cliRC == SQL_SUCCESS_WITH_INFO) && + cliRC2 != SQL_SUCCESS && cliRC2 != SQL_SUCCESS_WITH_INFO) + { + std::string msg=db2_soci_error::sqlState("Setting the autocommit attribute failed", SQL_HANDLE_DBC, hDbc); + SQLFreeHandle(SQL_HANDLE_DBC,hDbc); + SQLFreeHandle(SQL_HANDLE_ENV,hEnv); + throw db2_soci_error(msg,cliRC); + } + } + if (cliRC != SQL_SUCCESS && cliRC != SQL_SUCCESS_WITH_INFO) { + throw db2_soci_error("Rollback failed",cliRC); + } + } +} + +void db2_session_backend::clean_up() +{ + // if a transaction is in progress, it will automatically be rolled back upon when the connection is disconnected/freed + in_transaction = false; + + SQLDisconnect(hDbc); + SQLFreeHandle(SQL_HANDLE_DBC,hDbc); + SQLFreeHandle(SQL_HANDLE_ENV,hEnv); +} + +db2_statement_backend * db2_session_backend::make_statement_backend() +{ + return new db2_statement_backend(*this); +} + +db2_rowid_backend * db2_session_backend::make_rowid_backend() +{ + return new db2_rowid_backend(*this); +} + +db2_blob_backend * db2_session_backend::make_blob_backend() +{ + return new db2_blob_backend(*this); +} diff --git a/src/backends/db2/soci-db2.h b/src/backends/db2/soci-db2.h new file mode 100644 index 0000000000..246e7eee76 --- /dev/null +++ b/src/backends/db2/soci-db2.h @@ -0,0 +1,282 @@ +// +// Copyright (C) 2011-2013 Denis Chapligin +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_DB2_H_INCLUDED +#define SOCI_DB2_H_INCLUDED + +#ifdef _WIN32 +# ifdef SOCI_DLL +# ifdef SOCI_DB2_SOURCE +# define SOCI_DB2_DECL __declspec(dllexport) +# else +# define SOCI_DB2_DECL __declspec(dllimport) +# endif // SOCI_DB2_SOURCE +# endif // SOCI_DLL +#endif // _WIN32 +// +// If SOCI_DB2_DECL isn't defined yet define it now +#ifndef SOCI_DB2_DECL +# define SOCI_DB2_DECL +#endif + +#include "soci-backend.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace soci +{ + namespace details { namespace db2 + { + enum binding_method + { + BOUND_BY_NONE, + BOUND_BY_NAME, + BOUND_BY_POSITION + }; + }} + + static const std::size_t maxBuffer = 1024 * 1024 * 1024; //CLI limit is about 3 GB, but 1GB should be enough + +class db2_soci_error : public soci_error { +public: + db2_soci_error(std::string const & msg, SQLRETURN rc) : soci_error(msg),errorCode(rc) {}; + ~db2_soci_error() throw() { }; + + //We have to extract error information before exception throwing, cause CLI handles could be broken at the construction time + static const std::string sqlState(std::string const & msg,const SQLSMALLINT htype,const SQLHANDLE hndl); + + SQLRETURN errorCode; +}; + +struct db2_statement_backend; + +struct SOCI_DB2_DECL db2_standard_into_type_backend : details::standard_into_type_backend +{ + db2_standard_into_type_backend(db2_statement_backend &st) + : statement_(st),buf(NULL) + {} + + void define_by_pos(int& position, void* data, details::exchange_type type); + + void pre_fetch(); + void post_fetch(bool gotData, bool calledFromFetch, indicator* ind); + + void clean_up(); + + db2_statement_backend& statement_; + + char* buf; + void *data; + details::exchange_type type; + int position; + SQLSMALLINT cType; + SQLLEN valueLen; +}; + +struct SOCI_DB2_DECL db2_vector_into_type_backend : details::vector_into_type_backend +{ + db2_vector_into_type_backend(db2_statement_backend &st) + : statement_(st),buf(NULL) + {} + + void define_by_pos(int& position, void* data, details::exchange_type type); + + void pre_fetch(); + void post_fetch(bool gotData, indicator* ind); + + void resize(std::size_t sz); + std::size_t size(); + + void clean_up(); + + db2_statement_backend& statement_; + + void prepare_indicators(std::size_t size); + + SQLLEN *indptr; + std::vector indVec; + void *data; + char *buf; + int position_; + details::exchange_type type; + SQLSMALLINT cType; + std::size_t colSize; +}; + +struct SOCI_DB2_DECL db2_standard_use_type_backend : details::standard_use_type_backend +{ + db2_standard_use_type_backend(db2_statement_backend &st) + : statement_(st),buf(NULL),ind(0) + {} + + void bind_by_pos(int& position, void* data, details::exchange_type type, bool readOnly); + void bind_by_name(std::string const& name, void* data, details::exchange_type type, bool readOnly); + + void pre_use(indicator const* ind); + void post_use(bool gotData, indicator* ind); + + void clean_up(); + + db2_statement_backend& statement_; + + void *prepare_for_bind(void *data, SQLLEN &size, SQLSMALLINT &sqlType, SQLSMALLINT &cType); + + void *data; + details::exchange_type type; + int position; + std::string name; + char* buf; + SQLLEN ind; +}; + +struct SOCI_DB2_DECL db2_vector_use_type_backend : details::vector_use_type_backend +{ + db2_vector_use_type_backend(db2_statement_backend &st) + : statement_(st),buf(NULL) {} + + void bind_by_pos(int& position, void* data, details::exchange_type type); + void bind_by_name(std::string const& name, void* data, details::exchange_type type); + + void pre_use(indicator const* ind); + + std::size_t size(); + + void clean_up(); + + db2_statement_backend& statement_; + + void prepare_indicators(std::size_t size); + void prepare_for_bind(void *&data, SQLUINTEGER &size,SQLSMALLINT &sqlType, SQLSMALLINT &cType); + void bind_helper(int &position, void *data, details::exchange_type type); + + SQLLEN *indptr; + std::vector indVec; + void *data; + char *buf; + details::exchange_type type; + std::size_t colSize; +}; + +struct db2_session_backend; +struct SOCI_DB2_DECL db2_statement_backend : details::statement_backend +{ + db2_statement_backend(db2_session_backend &session); + + void alloc(); + void clean_up(); + void prepare(std::string const& query, details::statement_type eType); + + exec_fetch_result execute(int number); + exec_fetch_result fetch(int number); + + long long get_affected_rows(); + int get_number_of_rows(); + + std::string rewrite_for_procedure_call(std::string const& query); + + int prepare_for_describe(); + void describe_column(int colNum, data_type& dtype, std::string& columnName); + std::size_t column_size(int col); + + db2_standard_into_type_backend* make_into_type_backend(); + db2_standard_use_type_backend* make_use_type_backend(); + db2_vector_into_type_backend* make_vector_into_type_backend(); + db2_vector_use_type_backend* make_vector_use_type_backend(); + + db2_session_backend& session_; + + SQLHANDLE hStmt; + std::string query_; + std::vector names; + bool hasVectorUseElements; + SQLUINTEGER numRowsFetched; + details::db2::binding_method use_binding_method_; +}; + +struct db2_rowid_backend : details::rowid_backend +{ + db2_rowid_backend(db2_session_backend &session); + + ~db2_rowid_backend(); +}; + +struct db2_blob_backend : details::blob_backend +{ + db2_blob_backend(db2_session_backend& session); + + ~db2_blob_backend(); + + std::size_t get_len(); + std::size_t read(std::size_t offset, char* buf, std::size_t toRead); + std::size_t write(std::size_t offset, char const* buf, std::size_t toWrite); + std::size_t append(char const* buf, std::size_t toWrite); + void trim(std::size_t newLen); + + db2_session_backend& session_; +}; + +struct db2_session_backend : details::session_backend +{ + db2_session_backend(connection_parameters const& parameters); + + ~db2_session_backend(); + + void begin(); + void commit(); + void rollback(); + + std::string get_backend_name() const { return "DB2"; } + + void clean_up(); + + db2_statement_backend* make_statement_backend(); + db2_rowid_backend* make_rowid_backend(); + db2_blob_backend* make_blob_backend(); + + void parseConnectString(std::string const &); + void parseKeyVal(std::string const &); + + std::string dsn; + std::string username; + std::string password; + bool autocommit; + bool in_transaction; + + SQLHANDLE hEnv; /* Environment handle */ + SQLHANDLE hDbc; /* Connection handle */ +}; + +struct SOCI_DB2_DECL db2_backend_factory : backend_factory +{ + db2_backend_factory() {} + db2_session_backend* make_session( + connection_parameters const & parameters) const; +}; + +extern SOCI_DB2_DECL db2_backend_factory const db2; + +extern "C" +{ + +// for dynamic backend loading +SOCI_DB2_DECL backend_factory const* factory_db2(); +SOCI_DB2_DECL void register_factory_db2(); + +} // extern "C" + +} // namespace soci + +#endif // SOCI_DB2_H_INCLUDED diff --git a/src/backends/db2/standard-into-type.cpp b/src/backends/db2/standard-into-type.cpp new file mode 100644 index 0000000000..f6f3dd1746 --- /dev/null +++ b/src/backends/db2/standard-into-type.cpp @@ -0,0 +1,168 @@ +// +// Copyright (C) 2011-2013 Denis Chapligin +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, David Courtney +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_DB2_SOURCE +#include "soci-db2.h" +#include "common.h" +#include + +using namespace soci; +using namespace soci::details; + + +void db2_standard_into_type_backend::define_by_pos( + int & position, void * data, exchange_type type) +{ + this->data = data; + this->type = type; + this->position = position; + position++; + + SQLUINTEGER size = 0; + + switch (type) + { + case x_char: + cType = SQL_C_CHAR; + size = sizeof(char) + 1; + buf = new char[size]; + data = buf; + break; + case x_stdstring: + cType = SQL_C_CHAR; + // Patch: set to min between column size and 100MB (used ot be 32769) + // Column size for text data type can be too large for buffer allocation + size = statement_.column_size(this->position); + size = size > details::db2::cli_max_buffer ? details::db2::cli_max_buffer : size; + size++; + buf = new char[size]; + data = buf; + break; + case x_short: + cType = SQL_C_SSHORT; + size = sizeof(short); + break; + case x_integer: + cType = SQL_C_SLONG; + size = sizeof(SQLINTEGER); + break; + case x_long_long: + cType = SQL_C_SBIGINT; + size = sizeof(long long); + break; + case x_unsigned_long_long: + cType = SQL_C_UBIGINT; + size = sizeof(unsigned long long); + break; + case x_double: + cType = SQL_C_DOUBLE; + size = sizeof(double); + break; + case x_stdtm: + cType = SQL_C_TYPE_TIMESTAMP; + size = sizeof(TIMESTAMP_STRUCT); + buf = new char[size]; + data = buf; + break; + case x_rowid: + cType = SQL_C_UBIGINT; + size = sizeof(unsigned long long); + break; + default: + throw soci_error("Into element used with non-supported type."); + } + + valueLen = 0; + + SQLRETURN cliRC = SQLBindCol(statement_.hStmt, static_cast(this->position), + static_cast(cType), data, size, &valueLen); + if (cliRC != SQL_SUCCESS) + { + throw db2_soci_error("Error while pre-fething into type",cliRC); + } +} + +void db2_standard_into_type_backend::pre_fetch() +{ + //... +} + +void db2_standard_into_type_backend::post_fetch( + bool gotData, bool calledFromFetch, indicator * ind) +{ + if (calledFromFetch == true && gotData == false) + { + // this is a normal end-of-rowset condition, + // no need to do anything (fetch() will return false) + return; + } + + if (gotData) + { + // first, deal with indicators + if (SQL_NULL_DATA == valueLen) + { + if (ind == NULL) + { + throw soci_error( + "Null value fetched and no indicator defined."); + } + + *ind = i_null; + return; + } + else + { + if (ind != NULL) + { + *ind = i_ok; + } + } + + // only std::string and std::tm need special handling + if (type == x_char) + { + char *c = static_cast(data); + *c = buf[0]; + } + if (type == x_stdstring) + { + std::string *s = static_cast(data); + *s = buf; + if (s->size() >= (details::db2::cli_max_buffer - 1)) + { + throw soci_error("Buffer size overflow; maybe got too large string"); + } + } + else if (type == x_stdtm) + { + std::tm *t = static_cast(data); + + TIMESTAMP_STRUCT * ts = reinterpret_cast(buf); + t->tm_isdst = -1; + t->tm_year = ts->year - 1900; + t->tm_mon = ts->month - 1; + t->tm_mday = ts->day; + t->tm_hour = ts->hour; + t->tm_min = ts->minute; + t->tm_sec = ts->second; + + // normalize and compute the remaining fields + std::mktime(t); + } + } +} + +void db2_standard_into_type_backend::clean_up() +{ + if (buf) + { + delete [] buf; + buf = 0; + } +} diff --git a/src/backends/db2/standard-use-type.cpp b/src/backends/db2/standard-use-type.cpp new file mode 100644 index 0000000000..f0b739c35f --- /dev/null +++ b/src/backends/db2/standard-use-type.cpp @@ -0,0 +1,201 @@ +// +// Copyright (C) 2011-2013 Denis Chapligin +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, David Courtney +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#define SOCI_DB2_SOURCE +#include "soci-db2.h" +#include +#include +#include +#include +#include + +using namespace soci; +using namespace soci::details; + +void *db2_standard_use_type_backend::prepare_for_bind( + void *data, SQLLEN &size, SQLSMALLINT &sqlType, SQLSMALLINT &cType) +{ + switch (type) + { + // simple cases + case x_short: + sqlType = SQL_SMALLINT; + cType = SQL_C_SSHORT; + size = sizeof(short); + break; + case x_integer: + sqlType = SQL_INTEGER; + cType = SQL_C_SLONG; + size = sizeof(int); + break; + case x_long_long: + sqlType = SQL_BIGINT; + cType = SQL_C_SBIGINT; + size = sizeof(long long); + break; + case x_unsigned_long_long: + sqlType = SQL_BIGINT; + cType = SQL_C_UBIGINT; + size = sizeof(unsigned long long); + break; + case x_double: + sqlType = SQL_DOUBLE; + cType = SQL_C_DOUBLE; + size = sizeof(double); + break; + + // cases that require adjustments and buffer management + case x_char: + { + sqlType = SQL_CHAR; + cType = SQL_C_CHAR; + size = sizeof(char) + 1; + buf = new char[size]; + char *c = static_cast(data); + buf[0] = *c; + buf[1] = '\0'; + ind = SQL_NTS; + } + break; + case x_stdstring: + { + // TODO: No textual value is assigned here! + + std::string* s = static_cast(data); + sqlType = SQL_LONGVARCHAR; + cType = SQL_C_CHAR; + size = s->size() + 1; + buf = new char[size]; + strncpy(buf, s->c_str(), size); + ind = SQL_NTS; + } + break; + case x_stdtm: + { + sqlType = SQL_TIMESTAMP; + cType = SQL_C_TIMESTAMP; + buf = new char[sizeof(TIMESTAMP_STRUCT)]; + std::tm *t = static_cast(data); + data = buf; + size = 19; // This number is not the size in bytes, but the number + // of characters in the date if it was written out + // yyyy-mm-dd hh:mm:ss + + TIMESTAMP_STRUCT * ts = reinterpret_cast(buf); + + ts->year = static_cast(t->tm_year + 1900); + ts->month = static_cast(t->tm_mon + 1); + ts->day = static_cast(t->tm_mday); + ts->hour = static_cast(t->tm_hour); + ts->minute = static_cast(t->tm_min); + ts->second = static_cast(t->tm_sec); + ts->fraction = 0; + } + break; + + case x_blob: + break; + case x_statement: + case x_rowid: + break; + } + + // Return either the pointer to C++ data itself or the buffer that we + // allocated, if any. + return buf ? buf : data; +} + +void db2_standard_use_type_backend::bind_by_pos( + int &position, void *data, exchange_type type, bool /* readOnly */) +{ + if (statement_.use_binding_method_ == details::db2::BOUND_BY_NAME) + { + throw soci_error("Binding for use elements must be either by position or by name."); + } + statement_.use_binding_method_ = details::db2::BOUND_BY_POSITION; + + this->data = data; // for future reference + this->type = type; // for future reference + this->position = position++; +} + +void db2_standard_use_type_backend::bind_by_name( + std::string const &name, void *data, exchange_type type, bool /* readOnly */) +{ + if (statement_.use_binding_method_ == details::db2::BOUND_BY_POSITION) + { + throw soci_error("Binding for use elements must be either by position or by name."); + } + statement_.use_binding_method_ = details::db2::BOUND_BY_NAME; + + int position = -1; + int count = 1; + + for (std::vector::iterator it = statement_.names.begin(); + it != statement_.names.end(); ++it) + { + if (*it == name) + { + position = count; + break; + } + count++; + } + + if (position != -1) + { + this->data = data; // for future reference + this->type = type; // for future reference + this->position = position; + } + else + { + std::ostringstream ss; + ss << "Unable to find name '" << name << "' to bind to"; + throw soci_error(ss.str().c_str()); + } +} + +void db2_standard_use_type_backend::pre_use(indicator const *ind_ptr) +{ + // first deal with data + SQLSMALLINT sqlType; + SQLSMALLINT cType; + SQLLEN size; + + void *sqlData = prepare_for_bind(data, size, sqlType, cType); + + SQLRETURN cliRC = SQLBindParameter(statement_.hStmt, + static_cast(position), + SQL_PARAM_INPUT, + cType, sqlType, size, 0, sqlData, size, &ind); + + if (cliRC != SQL_SUCCESS) + { + throw db2_soci_error("Error while binding value",cliRC); + } + + // then handle indicators + if (ind_ptr != NULL && *ind_ptr == i_null) + { + ind = SQL_NULL_DATA; // null + } +} + +void db2_standard_use_type_backend::post_use(bool /*gotData*/, indicator* /*ind*/) +{ + +} + +void db2_standard_use_type_backend::clean_up() +{ + if (buf != NULL) + { + delete [] buf; + buf = NULL; + } +} diff --git a/src/backends/db2/statement.cpp b/src/backends/db2/statement.cpp new file mode 100644 index 0000000000..5f57decdfc --- /dev/null +++ b/src/backends/db2/statement.cpp @@ -0,0 +1,322 @@ +// +// Copyright (C) 2011-2013 Denis Chapligin +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_DB2_SOURCE +#include "soci-db2.h" +#include + +#ifdef _MSC_VER +#pragma warning(disable:4355) +#endif + +using namespace soci; +using namespace soci::details; + + +db2_statement_backend::db2_statement_backend(db2_session_backend &session) + : session_(session),hasVectorUseElements(false),use_binding_method_(details::db2::BOUND_BY_NONE) +{ +} + +void db2_statement_backend::alloc() +{ + SQLRETURN cliRC = SQL_SUCCESS; + + cliRC = SQLAllocHandle(SQL_HANDLE_STMT,session_.hDbc,&hStmt); + if (cliRC != SQL_SUCCESS) { + throw db2_soci_error("Error while allocation statement handle",cliRC); + } +} + +void db2_statement_backend::clean_up() +{ + SQLRETURN cliRC = SQL_SUCCESS; + + cliRC=SQLFreeHandle(SQL_HANDLE_STMT,hStmt); + if (cliRC != SQL_SUCCESS) { + throw db2_soci_error(db2_soci_error::sqlState("Statement handle clean-up error",SQL_HANDLE_STMT,hStmt),cliRC); + } +} + +void db2_statement_backend::prepare(std::string const & query , + statement_type /* eType */) +{ + // rewrite the query by transforming all named parameters into + // the markers (:abc -> ?, etc.) + + enum { normal, in_quotes, in_name } state = normal; + + std::string name; + + for (std::string::const_iterator it = query.begin(), end = query.end(); + it != end; ++it) + { + switch (state) + { + case normal: + if (*it == '\'') + { + query_ += *it; + state = in_quotes; + } + else if (*it == ':') + { + // Check whether this is a cast operator (e.g. 23::float) + // and treat it as a special case, not as a named binding + const std::string::const_iterator next_it = it + 1; + if ((next_it != end) && (*next_it == ':')) + { + query_ += "::"; + ++it; + } + else + { + state = in_name; + } + } + else // regular character, stay in the same state + { + query_ += *it; + } + break; + case in_quotes: + if (*it == '\'') + { + query_ += *it; + state = normal; + } + else // regular quoted character + { + query_ += *it; + } + break; + case in_name: + if (std::isalnum(*it) || *it == '_') + { + name += *it; + } + else // end of name + { + names.push_back(name); + name.clear(); + std::ostringstream ss; + ss << '?'; + query_ += ss.str(); + query_ += *it; + state = normal; + + } + break; + } + } + + if (state == in_name) + { + names.push_back(name); + std::ostringstream ss; + ss << '?'; + query_ += ss.str(); + } + + SQLRETURN cliRC = SQLPrepare(hStmt, const_cast((const SQLCHAR *) query_.c_str()), SQL_NTS); + if (cliRC!=SQL_SUCCESS) { + throw db2_soci_error("Error while preparing query",cliRC); + } +} + +statement_backend::exec_fetch_result +db2_statement_backend::execute(int number ) +{ + SQLUINTEGER rows_processed = 0; + SQLRETURN cliRC; + + if (hasVectorUseElements) + { + SQLSetStmtAttr(hStmt, SQL_ATTR_PARAMS_PROCESSED_PTR, &rows_processed, 0); + } + + // if we are called twice for the same statement we need to close the open + // cursor or an "invalid cursor state" error will occur on execute + cliRC = SQLFreeStmt(hStmt,SQL_CLOSE); + if (cliRC != SQL_SUCCESS) + { + throw db2_soci_error(db2_soci_error::sqlState("Statement execution error",SQL_HANDLE_STMT,hStmt),cliRC); + } + + cliRC = SQLExecute(hStmt); + if (cliRC != SQL_SUCCESS && cliRC != SQL_SUCCESS_WITH_INFO) + { + throw db2_soci_error(db2_soci_error::sqlState("Statement execution error",SQL_HANDLE_STMT,hStmt),cliRC); + } + + SQLSMALLINT colCount; + SQLNumResultCols(hStmt, &colCount); + + if (number > 0 && colCount > 0) + { + return fetch(number); + } + + return ef_success; +} + +statement_backend::exec_fetch_result +db2_statement_backend::fetch(int number ) +{ + numRowsFetched = 0; + + SQLSetStmtAttr(hStmt, SQL_ATTR_ROW_BIND_TYPE, SQL_BIND_BY_COLUMN, 0); + SQLSetStmtAttr(hStmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER)number, 0); + SQLSetStmtAttr(hStmt, SQL_ATTR_ROWS_FETCHED_PTR, &numRowsFetched, 0); + + SQLRETURN cliRC = SQLFetch(hStmt); + + if (SQL_NO_DATA == cliRC) + { + return ef_no_data; + } + + if (cliRC != SQL_SUCCESS && cliRC != SQL_SUCCESS_WITH_INFO) + { + throw db2_soci_error(db2_soci_error::sqlState("Error while fetching data", SQL_HANDLE_STMT, hStmt), cliRC); + } + + return ef_success; +} + +long long db2_statement_backend::get_affected_rows() +{ + SQLLEN rows; + + SQLRETURN cliRC = SQLRowCount(hStmt, &rows); + if (cliRC != SQL_SUCCESS && cliRC != SQL_SUCCESS_WITH_INFO) + { + throw db2_soci_error(db2_soci_error::sqlState("Error while getting affected row count", SQL_HANDLE_STMT, hStmt), cliRC); + } + else if (rows == -1) + { + throw soci_error("Error getting affected row count: statement did not perform an update, insert, delete, or merge"); + } + + return rows; +} + +int db2_statement_backend::get_number_of_rows() +{ + return numRowsFetched; +} + +std::string db2_statement_backend::rewrite_for_procedure_call( + std::string const &query) +{ + return query; +} + +int db2_statement_backend::prepare_for_describe() +{ + SQLSMALLINT numCols; + SQLNumResultCols(hStmt, &numCols); + return numCols; +} + +void db2_statement_backend::describe_column(int colNum, + data_type & type, std::string & columnName ) +{ +SQLCHAR colNameBuffer[2048]; + SQLSMALLINT colNameBufferOverflow; + SQLSMALLINT dataType; + SQLULEN colSize; + SQLSMALLINT decDigits; + SQLSMALLINT isNullable; + + SQLRETURN cliRC = SQLDescribeCol(hStmt, static_cast(colNum), + colNameBuffer, 2048, + &colNameBufferOverflow, &dataType, + &colSize, &decDigits, &isNullable); + + if (cliRC != SQL_SUCCESS) + { + throw db2_soci_error(db2_soci_error::sqlState("Error while describing column",SQL_HANDLE_STMT,hStmt),cliRC); + } + + char const *name = reinterpret_cast(colNameBuffer); + columnName.assign(name, std::strlen(name)); + + switch (dataType) + { + case SQL_TYPE_DATE: + case SQL_TYPE_TIME: + case SQL_TYPE_TIMESTAMP: + type = dt_date; + break; + case SQL_DOUBLE: + case SQL_DECIMAL: + case SQL_REAL: + case SQL_FLOAT: + case SQL_NUMERIC: + type = dt_double; + break; + case SQL_TINYINT: + case SQL_SMALLINT: + case SQL_INTEGER: + type = dt_integer; + break; + case SQL_BIGINT: + type = dt_long_long; + break; + case SQL_CHAR: + case SQL_VARCHAR: + case SQL_LONGVARCHAR: + default: + type = dt_string; + break; + } +} + +std::size_t db2_statement_backend::column_size(int col) { + SQLCHAR colNameBuffer[2048]; + SQLSMALLINT colNameBufferOverflow; + SQLSMALLINT dataType; + SQLULEN colSize; + SQLSMALLINT decDigits; + SQLSMALLINT isNullable; + + SQLRETURN cliRC = SQLDescribeCol(hStmt, static_cast(col), + colNameBuffer, 2048, + &colNameBufferOverflow, &dataType, + &colSize, &decDigits, &isNullable); + + if (cliRC != SQL_SUCCESS) + { + throw db2_soci_error(db2_soci_error::sqlState("Error while detecting column size",SQL_HANDLE_STMT,hStmt),cliRC); + } + + return colSize; +} + +db2_standard_into_type_backend * db2_statement_backend::make_into_type_backend() +{ + return new db2_standard_into_type_backend(*this); +} + +db2_standard_use_type_backend * db2_statement_backend::make_use_type_backend() +{ + return new db2_standard_use_type_backend(*this); +} + +db2_vector_into_type_backend * +db2_statement_backend::make_vector_into_type_backend() +{ + return new db2_vector_into_type_backend(*this); +} + +db2_vector_use_type_backend * db2_statement_backend::make_vector_use_type_backend() +{ + hasVectorUseElements = true; + return new db2_vector_use_type_backend(*this); +} diff --git a/src/backends/db2/test/CMakeLists.txt b/src/backends/db2/test/CMakeLists.txt new file mode 100644 index 0000000000..cb8427504e --- /dev/null +++ b/src/backends/db2/test/CMakeLists.txt @@ -0,0 +1,14 @@ +############################################################################### +# +# This file is part of CMake configuration for SOCI library +# +# Copyright (C) 2011 Denis Chapligin +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) +# +############################################################################### +soci_backend_test( + BACKEND DB2 + SOURCE test-db2.cpp + CONNSTR "DSN=SAMPLE;Uid=db2inst1;Pwd=db2inst1;autocommit=off") diff --git a/src/backends/db2/test/test-db2.cpp b/src/backends/db2/test/test-db2.cpp new file mode 100644 index 0000000000..471e336da6 --- /dev/null +++ b/src/backends/db2/test/test-db2.cpp @@ -0,0 +1,454 @@ +// +// Copyright (C) 2011-2013 Denis Chapligin +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#include "soci.h" +#include "soci-db2.h" +#include "common-tests.h" +#include +#include +#include +#include +#include + +using namespace soci; +using namespace soci::tests; + +std::string connectString; +backend_factory const &backEnd = *soci::factory_db2(); + + +// +// Support for soci Common Tests +// + +struct table_creator_one : public table_creator_base +{ + table_creator_one(session & sql) + : table_creator_base(sql) + { + sql << "CREATE TABLE SOCI_TEST(ID INTEGER, VAL SMALLINT, C CHAR, STR VARCHAR(20), SH SMALLINT, UL NUMERIC(20), D DOUBLE, " + "TM TIMESTAMP, I1 INTEGER, I2 INTEGER, I3 INTEGER, NAME VARCHAR(20))"; + } +}; + +struct table_creator_two : public table_creator_base +{ + table_creator_two(session & sql) + : table_creator_base(sql) + { + sql << "CREATE TABLE SOCI_TEST(NUM_FLOAT DOUBLE, NUM_INT INTEGER, NAME VARCHAR(20), SOMETIME TIMESTAMP, CHR CHAR)"; + } +}; + +struct table_creator_three : public table_creator_base +{ + table_creator_three(session & sql) + : table_creator_base(sql) + { + sql << "CREATE TABLE SOCI_TEST(NAME VARCHAR(100) NOT NULL, PHONE VARCHAR(15))"; + } +}; + +struct table_creator_for_get_affected_rows : table_creator_base +{ + table_creator_for_get_affected_rows(session & sql) + : table_creator_base(sql) + { + sql << "CREATE TABLE SOCI_TEST(VAL INTEGER)"; + } +}; + +class test_context :public test_context_base +{ +public: + test_context(backend_factory const & pi_back_end, std::string const & pi_connect_string) + : test_context_base(pi_back_end, pi_connect_string) {} + + table_creator_base* table_creator_1(session & pr_s) const + { + pr_s << "SET CURRENT SCHEMA = 'DB2INST1'"; + return new table_creator_one(pr_s); + } + + table_creator_base* table_creator_2(session & pr_s) const + { + pr_s << "SET CURRENT SCHEMA = 'DB2INST1'"; + return new table_creator_two(pr_s); + } + + table_creator_base* table_creator_3(session & pr_s) const + { + pr_s << "SET CURRENT SCHEMA = 'DB2INST1'"; + return new table_creator_three(pr_s); + } + + table_creator_base* table_creator_4(session& s) const + { + return new table_creator_for_get_affected_rows(s); + } + + std::string to_date_time(std::string const & pi_datdt_string) const + { + return "to_date('" + pi_datdt_string + "', 'YYYY-MM-DD HH24:MI:SS')"; + } +}; + + +// +// Additional tests to exercise the DB2 backend +// + +void test1() +{ + { + session sql(backEnd, connectString); + + sql << "SELECT CURRENT TIMESTAMP FROM SYSIBM.SYSDUMMY1"; + sql << "SELECT " << 123 << " FROM SYSIBM.SYSDUMMY1"; + + std::string query = "CREATE TABLE DB2INST1.SOCI_TEST (ID BIGINT,DATA VARCHAR(8))"; + sql << query; + + { + const int i = 7; + sql << "insert into db2inst1.SOCI_TEST (id) values (:id)", use(i,"id"); + int j = 0; + sql << "select id from db2inst1.SOCI_TEST where id=7", into(j); + assert(j == i); + } + + { + const long int li = 9; + sql << "insert into db2inst1.SOCI_TEST (id) values (:id)", use(li,"id"); + long int lj = 0;; + sql << "select id from db2inst1.SOCI_TEST where id=9", into(lj); + assert(lj == li); + } + + { + const long long ll = 11; + sql << "insert into db2inst1.SOCI_TEST (id) values (:id)", use(ll,"id"); + long long lj = 0; + sql << "select id from db2inst1.SOCI_TEST where id=11", into(lj); + assert(lj == ll); + } + + { + const int i = 13; + indicator i_ind = i_ok; + sql << "insert into db2inst1.SOCI_TEST (id) values (:id)", use(i,i_ind,"id"); + int j = 0; + indicator j_ind = i_null; + sql << "select id from db2inst1.SOCI_TEST where id=13", into(j,j_ind); + assert(j == i); + assert(j_ind == i_ok); + } + + { + std::vector numbers(100); + for (int i = 0 ; i < 100 ; i++) + { + numbers[i] = i + 1000; + } + sql << "insert into db2inst1.SOCI_TEST (id) values (:id)", use(numbers,"id"); + sql << "select id from db2inst1.SOCI_TEST where id >= 1000 and id < 2000 order by id", into(numbers); + for (int i = 0 ; i < 100 ; i++) + { + assert(numbers[i] == i + 1000); + } + } + + { + std::vector numbers(100); + std::vector inds(100); + for (int i = 0 ; i < 100 ; i++) + { + numbers[i] = i + 2000; + inds[i] = i_ok; + } + sql << "insert into db2inst1.SOCI_TEST (id) values (:id)", use(numbers,inds,"id"); + for (int i = 0 ; i < 100 ; i++) + { + numbers[i] = 0; + inds[i] = i_null; + } + sql << "select id from db2inst1.SOCI_TEST where id >= 2000 and id < 3000 order by id", into(numbers,inds); + for (int i = 0 ; i < 100 ; i++) + { + assert(numbers[i] == i + 2000); + assert(inds[i] == i_ok); + } + } + + { + int i = 0; + statement st = (sql.prepare << "select id from db2inst1.SOCI_TEST where id < 1000", into(i)); + st.execute(); + st.fetch(); + assert (i == 7); + st.fetch(); + assert (i == 9); + st.fetch(); + assert (i == 11); + st.fetch(); + assert (i == 13); + } + + { + int i = 0; + indicator i_ind = i_null; + std::string d; + indicator d_ind = i_ok; + statement st = (sql.prepare << "select id, data from db2inst1.SOCI_TEST where id = 13", into(i, i_ind), into(d, d_ind)); + st.execute(); + st.fetch(); + assert (i == 13); + assert (i_ind == i_ok); + assert (d_ind == i_null); + } + + { + std::vector numbers(100); + for (int i = 0 ; i < 100 ; i++) + { + numbers[i] = 0; + } + statement st = (sql.prepare << "select id from db2inst1.SOCI_TEST where id >= 1000 order by id", into(numbers)); + st.execute(); + st.fetch(); + for (int i = 0 ; i < 100 ; i++) + { + assert(numbers[i] == i + 1000); + } + st.fetch(); + for (int i = 0 ; i < 100 ; i++) + { + assert(numbers[i] == i + 2000); + } + } + + { + std::vector numbers(100); + std::vector inds(100); + for (int i = 0 ; i < 100 ; i++) + { + numbers[i] = 0; + inds[i] = i_null; + } + statement st = (sql.prepare << "select id from db2inst1.SOCI_TEST where id >= 1000 order by id", into(numbers, inds)); + st.execute(); + st.fetch(); + for (int i = 0 ; i < 100 ; i++) + { + assert(numbers[i] == i + 1000); + assert(inds[i] == i_ok); + } + st.fetch(); + for (int i = 0 ; i < 100 ; i++) + { + assert(numbers[i] == i + 2000); + assert(inds[i] == i_ok); + } + } + + { + // XXX: what is the purpose of this test?? what is the expected value? + int i = 0; + statement st = (sql.prepare << "select id from db2inst1.SOCI_TEST", use(i)); + } + + { + // XXX: what is the purpose of this test?? what is the expected value? + int i = 0; + indicator ind = i_ok; + statement st = (sql.prepare << "select id from db2inst1.SOCI_TEST", use(i, ind)); + } + + { + // XXX: what is the purpose of this test?? what is the expected value? + std::vector numbers(100); + statement st = (sql.prepare << "select id from db2inst1.SOCI_TEST", use(numbers)); + } + + { + // XXX: what is the purpose of this test?? what is the expected value? + std::vector numbers(100); + std::vector inds(100); + statement st = (sql.prepare << "select id from db2inst1.SOCI_TEST", use(numbers, inds)); + } + + sql<<"DROP TABLE DB2INST1.SOCI_TEST"; + + sql.commit(); + } + + std::cout << "test 1 passed" << std::endl; +} + +void test2() { + { + session sql(backEnd, connectString); + + std::string query = "CREATE TABLE DB2INST1.SOCI_TEST (ID BIGINT,DATA VARCHAR(8),DT TIMESTAMP)"; + sql << query; + + { + int i = 7; + std::string n("test"); + sql << "insert into db2inst1.SOCI_TEST (id,data) values (:id,:name)", use(i,"id"),use(n,"name"); + int j; + std::string m; + sql << "select id,data from db2inst1.SOCI_TEST where id=7", into(j),into(m); + assert (j == i); + assert (m == n); + } + + { + int i = 8; + sql << "insert into db2inst1.SOCI_TEST (id) values (:id)", use(i,"id"); + int j; + std::string m; + indicator ind = i_ok; + sql << "select id,data from db2inst1.SOCI_TEST where id=8", into(j),into(m,ind); + assert(j == i); + assert(ind==i_null); + } + + { + std::tm dt; + sql << "select current timestamp from sysibm.sysdummy1",into(dt); + sql << "insert into db2inst1.SOCI_TEST (dt) values (:dt)",use(dt,"dt"); + std::tm dt2; + sql << "select dt from db2inst1.SOCI_TEST where dt is not null", into(dt2); + assert(dt2.tm_year == dt.tm_year && dt2.tm_mon == dt.tm_mon && dt2.tm_mday == dt.tm_mday && + dt2.tm_hour == dt.tm_hour && dt2.tm_min == dt.tm_min && dt2.tm_sec == dt.tm_sec); + } + + sql<<"DROP TABLE DB2INST1.SOCI_TEST"; + sql.commit(); + } + + std::cout << "test 2 passed" << std::endl; +} + +void test3() { + { + session sql(backEnd, connectString); + int i; + + std::string query = "CREATE TABLE DB2INST1.SOCI_TEST (ID BIGINT,DATA VARCHAR(8),DT TIMESTAMP)"; + sql << query; + + std::vector ids(100); + std::vector data(100); + std::vector dts(100); + for (int i = 0; i < 100; i++) + { + ids[i] = 1000000000LL + i; + data[i] = "test"; + dts[i].tm_year = 112; + dts[i].tm_mon = 7; + dts[i].tm_mday = 17; + dts[i].tm_hour = 0; + dts[i].tm_min = 0; + dts[i].tm_sec = i % 60; + } + + sql << "insert into db2inst1.SOCI_TEST (id, data, dt) values (:id, :data, :dt)", + use(ids, "id"), use(data,"data"), use(dts, "dt"); + + i = 0; + rowset rs = (sql.prepare<<"SELECT ID, DATA, DT FROM DB2INST1.SOCI_TEST"); + for (rowset::const_iterator it = rs.begin(); it != rs.end(); it++) + { + const row & r = *it; + const long long id = r.get(0); + const std::string data = r.get(1); + const std::tm dt = r.get(2); + + assert(id == 1000000000LL + i); + assert(data == "test"); + assert(dt.tm_year == 112); + assert(dt.tm_mon == 7); + assert(dt.tm_mday == 17); + assert(dt.tm_hour == 0); + assert(dt.tm_min == 0); + assert(dt.tm_sec == i % 60); + + i += 1; + } + + sql<<"DROP TABLE DB2INST1.SOCI_TEST"; + sql.commit(); + } + + std::cout << "test 3 passed" << std::endl; +} + + +int main(int argc, char** argv) +{ + +#ifdef _MSC_VER + // Redirect errors, unrecoverable problems, and assert() failures to STDERR, + // instead of debug message window. + // This hack is required to run asser()-driven tests by Buildbot. + // NOTE: Comment this 2 lines for debugging with Visual C++ debugger to catch assertions inside. + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); +#endif //_MSC_VER + + if (argc == 2) + { + connectString = argv[1]; + } + else + { + std::cout << "usage: " << argv[0] + << " connectstring\n" + << "example: " << argv[0] + << " \'DSN=SAMPLE;Uid=db2inst1;Pwd=db2inst1;autocommit=off\'\n"; + std::exit(1); + } + + test_context tc(backEnd, connectString); + common_tests tests(tc); + tests.run(); + + try + { + std::cout<<"\nSOCI DB2 Tests:\n\n"; + + session sql(backEnd, connectString); + + try + { + // attempt to delete the test table from previous runs + sql << "DROP TABLE DB2INST1.SOCI_TEST"; + } + catch (soci_error const & e) + { + // if the table didn't exist, then proceed + } + + test1(); + test2(); + test3(); + // ... + + std::cout << "\nOK, all tests passed.\n\n"; + + return EXIT_SUCCESS; + } + catch (std::exception const & e) + { + std::cout << e.what() << '\n'; + } + + return EXIT_FAILURE; +} diff --git a/src/backends/db2/vector-into-type.cpp b/src/backends/db2/vector-into-type.cpp new file mode 100644 index 0000000000..e41f34e41e --- /dev/null +++ b/src/backends/db2/vector-into-type.cpp @@ -0,0 +1,411 @@ +// +// Copyright (C) 2011-2013 Denis Chapligin +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, David Courtney +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_DB2_SOURCE +#include "soci-db2.h" +#include +#include +#include +#include +#include + +using namespace soci; +using namespace soci::details; + +void db2_vector_into_type_backend::prepare_indicators(std::size_t size) +{ + if (size == 0) + { + throw soci_error("Vectors of size 0 are not allowed."); + } + + indVec.resize(size); + indptr = &indVec[0]; +} + +void db2_vector_into_type_backend::define_by_pos( + int &position, void *data, exchange_type type) +{ + this->data = data; // for future reference + this->type = type; // for future reference + + SQLINTEGER size = 0; // also dummy + + switch (type) + { + // simple cases + case x_short: + { + cType = SQL_C_SSHORT; + size = sizeof(short); + std::vector *vp = static_cast *>(data); + std::vector &v(*vp); + prepare_indicators(v.size()); + data = &v[0]; + } + break; + case x_integer: + { + cType = SQL_C_SLONG; + size = sizeof(SQLINTEGER); + std::vector *vp = static_cast *>(data); + std::vector &v(*vp); + prepare_indicators(v.size()); + data = &v[0]; + } + break; + case x_long_long: + { + cType = SQL_C_SBIGINT; + size = sizeof(long long); + std::vector *vp + = static_cast *>(data); + std::vector &v(*vp); + prepare_indicators(v.size()); + data = &v[0]; + } + break; + case x_unsigned_long_long: + { + cType = SQL_C_UBIGINT; + size = sizeof(unsigned long long); + std::vector *vp + = static_cast *>(data); + std::vector &v(*vp); + prepare_indicators(v.size()); + data = &v[0]; + } + break; + case x_double: + { + cType = SQL_C_DOUBLE; + size = sizeof(double); + std::vector *vp = static_cast *>(data); + std::vector &v(*vp); + prepare_indicators(v.size()); + data = &v[0]; + } + break; + + // cases that require adjustments and buffer management + + case x_char: + { + cType = SQL_C_CHAR; + + std::vector *v + = static_cast *>(data); + + prepare_indicators(v->size()); + + size = sizeof(char) * 2; + std::size_t bufSize = size * v->size(); + + colSize = size; + + buf = new char[bufSize]; + data = buf; + } + break; + case x_stdstring: + { + cType = SQL_C_CHAR; + std::vector *v + = static_cast *>(data); + colSize = statement_.column_size(position) + 1; + std::size_t bufSize = colSize * v->size(); + buf = new char[bufSize]; + + prepare_indicators(v->size()); + + size = static_cast(colSize); + data = buf; + } + break; + case x_stdtm: + { + cType = SQL_C_TYPE_TIMESTAMP; + std::vector *v + = static_cast *>(data); + + prepare_indicators(v->size()); + + size = sizeof(TIMESTAMP_STRUCT); + colSize = size; + + std::size_t bufSize = size * v->size(); + + buf = new char[bufSize]; + data = buf; + } + break; + + case x_statement: break; // not supported + case x_rowid: break; // not supported + case x_blob: break; // not supported + } + + SQLRETURN cliRC = SQLBindCol(statement_.hStmt, static_cast(position++), + cType, data, size, indptr); + if (cliRC != SQL_SUCCESS) + { + throw db2_soci_error("Error while pre-fetching into vector",cliRC); + } +} + +void db2_vector_into_type_backend::pre_fetch() +{ + // nothing to do for the supported types +} + +void db2_vector_into_type_backend::post_fetch(bool gotData, indicator *ind) +{ + if (gotData) + { + // first, deal with data + + // only std::string, std::tm and Statement need special handling + if (type == x_char) + { + std::vector *vp + = static_cast *>(data); + + std::vector &v(*vp); + char *pos = buf; + std::size_t const vsize = v.size(); + for (std::size_t i = 0; i != vsize; ++i) + { + v[i] = *pos; + pos += colSize; + } + } + if (type == x_stdstring) + { + std::vector *vp + = static_cast *>(data); + + std::vector &v(*vp); + + char *pos = buf; + std::size_t const vsize = v.size(); + for (std::size_t i = 0; i != vsize; ++i) + { + v[i].assign(pos, strlen(pos)); + pos += colSize; + } + } + else if (type == x_stdtm) + { + std::vector *vp + = static_cast *>(data); + + std::vector &v(*vp); + char *pos = buf; + std::size_t const vsize = v.size(); + for (std::size_t i = 0; i != vsize; ++i) + { + std::tm t; + + TIMESTAMP_STRUCT * ts = reinterpret_cast(pos); + t.tm_isdst = -1; + t.tm_year = ts->year - 1900; + t.tm_mon = ts->month - 1; + t.tm_mday = ts->day; + t.tm_hour = ts->hour; + t.tm_min = ts->minute; + t.tm_sec = ts->second; + + // normalize and compute the remaining fields + std::mktime(&t); + v[i] = t; + pos += colSize; + } + } + + // then - deal with indicators + if (ind != NULL) + { + std::size_t const indSize = statement_.get_number_of_rows(); + for (std::size_t i = 0; i != indSize; ++i) + { + if (indVec[i] > 0) + { + ind[i] = i_ok; + } + else if (indVec[i] == SQL_NULL_DATA) + { + ind[i] = i_null; + } + else + { + ind[i] = i_truncated; + } + } + } + else + { + std::size_t const indSize = statement_.get_number_of_rows(); + for (std::size_t i = 0; i != indSize; ++i) + { + if (indVec[i] == SQL_NULL_DATA) + { + // fetched null and no indicator - programming error! + throw soci_error( + "Null value fetched and no indicator defined."); + } + } + } + } + else // gotData == false + { + // nothing to do here, vectors are truncated anyway + } +} + +void db2_vector_into_type_backend::resize(std::size_t sz) +{ + indVec.resize(sz); + switch (type) + { + // simple cases + case x_char: + { + std::vector *v = static_cast *>(data); + v->resize(sz); + } + break; + case x_short: + { + std::vector *v = static_cast *>(data); + v->resize(sz); + } + break; + case x_integer: + { + std::vector *v = static_cast *>(data); + v->resize(sz); + } + break; + case x_long_long: + { + std::vector *v + = static_cast *>(data); + v->resize(sz); + } + break; + case x_unsigned_long_long: + { + std::vector *v + = static_cast *>(data); + v->resize(sz); + } + break; + case x_double: + { + std::vector *v + = static_cast *>(data); + v->resize(sz); + } + break; + case x_stdstring: + { + std::vector *v + = static_cast *>(data); + v->resize(sz); + } + break; + case x_stdtm: + { + std::vector *v + = static_cast *>(data); + v->resize(sz); + } + break; + + case x_statement: break; // not supported + case x_rowid: break; // not supported + case x_blob: break; // not supported + } +} + +std::size_t db2_vector_into_type_backend::size() +{ + std::size_t sz = 0; // dummy initialization to please the compiler + switch (type) + { + // simple cases + case x_char: + { + std::vector *v = static_cast *>(data); + sz = v->size(); + } + break; + case x_short: + { + std::vector *v = static_cast *>(data); + sz = v->size(); + } + break; + case x_integer: + { + std::vector *v = static_cast *>(data); + sz = v->size(); + } + break; + case x_long_long: + { + std::vector *v + = static_cast *>(data); + sz = v->size(); + } + break; + case x_unsigned_long_long: + { + std::vector *v + = static_cast *>(data); + sz = v->size(); + } + break; + case x_double: + { + std::vector *v + = static_cast *>(data); + sz = v->size(); + } + break; + case x_stdstring: + { + std::vector *v + = static_cast *>(data); + sz = v->size(); + } + break; + case x_stdtm: + { + std::vector *v + = static_cast *>(data); + sz = v->size(); + } + break; + + case x_statement: break; // not supported + case x_rowid: break; // not supported + case x_blob: break; // not supported + } + + return sz; +} + +void db2_vector_into_type_backend::clean_up() +{ + if (buf != NULL) + { + delete [] buf; + buf = NULL; + } +} diff --git a/src/backends/db2/vector-use-type.cpp b/src/backends/db2/vector-use-type.cpp new file mode 100644 index 0000000000..05a810464d --- /dev/null +++ b/src/backends/db2/vector-use-type.cpp @@ -0,0 +1,395 @@ +// +// Copyright (C) 2011-2013 Denis Chapligin +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, David Courtney +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_DB2_SOURCE +#include "soci-db2.h" +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +// disables the warning about converting int to void*. This is a 64 bit compatibility +// warning, but odbc requires the value to be converted on this line +// SQLSetStmtAttr(statement_.hstmt_, SQL_ATTR_PARAMSET_SIZE, (SQLPOINTER)arraySize, 0); +#pragma warning(disable:4312) +#endif + + +using namespace soci; +using namespace soci::details; + +void db2_vector_use_type_backend::prepare_indicators(std::size_t size) +{ + if (size == 0) + { + throw soci_error("Vectors of size 0 are not allowed."); + } + + indVec.resize(size); + indptr = &indVec[0]; +} + +void db2_vector_use_type_backend::prepare_for_bind(void *&data, SQLUINTEGER &size, + SQLSMALLINT &sqlType, SQLSMALLINT &cType) +{ + switch (type) + { // simple cases + case x_short: + { + sqlType = SQL_SMALLINT; + cType = SQL_C_SSHORT; + size = sizeof(short); + std::vector *vp = static_cast *>(data); + std::vector &v(*vp); + prepare_indicators(v.size()); + data = &v[0]; + } + break; + case x_integer: + { + sqlType = SQL_INTEGER; + cType = SQL_C_SLONG; + size = sizeof(int); + std::vector *vp = static_cast *>(data); + std::vector &v(*vp); + prepare_indicators(v.size()); + data = &v[0]; + } + break; + case x_long_long: + { + sqlType = SQL_BIGINT; + cType = SQL_C_SBIGINT; + size = sizeof(long long); + std::vector *vp + = static_cast *>(data); + std::vector &v(*vp); + prepare_indicators(v.size()); + data = &v[0]; + } + break; + case x_unsigned_long_long: + { + sqlType = SQL_BIGINT; + cType = SQL_C_UBIGINT; + size = sizeof(unsigned long long); + std::vector *vp + = static_cast *>(data); + std::vector &v(*vp); + prepare_indicators(v.size()); + data = &v[0]; + } + break; + case x_double: + { + sqlType = SQL_DOUBLE; + cType = SQL_C_DOUBLE; + size = sizeof(double); + std::vector *vp = static_cast *>(data); + std::vector &v(*vp); + prepare_indicators(v.size()); + data = &v[0]; + } + break; + + // cases that require adjustments and buffer management + case x_char: + { + std::vector *vp + = static_cast *>(data); + std::size_t const vsize = vp->size(); + + prepare_indicators(vsize); + + size = sizeof(char) * 2; + buf = new char[size * vsize]; + + char *pos = buf; + + for (std::size_t i = 0; i != vsize; ++i) + { + *pos++ = (*vp)[i]; + *pos++ = 0; + } + + sqlType = SQL_CHAR; + cType = SQL_C_CHAR; + data = buf; + } + break; + case x_stdstring: + { + sqlType = SQL_CHAR; + cType = SQL_C_CHAR; + + std::vector *vp + = static_cast *>(data); + std::vector &v(*vp); + + std::size_t maxSize = 0; + std::size_t const vecSize = v.size(); + prepare_indicators(vecSize); + for (std::size_t i = 0; i != vecSize; ++i) + { + std::size_t sz = v[i].length() + 1; // add one for null + indVec[i] = static_cast(sz); + maxSize = sz > maxSize ? sz : maxSize; + } + + buf = new char[maxSize * vecSize]; + memset(buf, 0, maxSize * vecSize); + + char *pos = buf; + for (std::size_t i = 0; i != vecSize; ++i) + { + strncpy(pos, v[i].c_str(), v[i].length()); + pos += maxSize; + } + + data = buf; + size = static_cast(maxSize); + } + break; + case x_stdtm: + { + std::vector *vp + = static_cast *>(data); + + prepare_indicators(vp->size()); + + buf = new char[sizeof(TIMESTAMP_STRUCT) * vp->size()]; + + sqlType = SQL_TYPE_TIMESTAMP; + cType = SQL_C_TYPE_TIMESTAMP; + data = buf; + size = 19; // This number is not the size in bytes, but the number + // of characters in the date if it was written out + // yyyy-mm-dd hh:mm:ss + } + break; + + case x_statement: break; // not supported + case x_rowid: break; // not supported + case x_blob: break; // not supported + } + + colSize = size; +} + +void db2_vector_use_type_backend::bind_helper(int &position, void *data, details::exchange_type type) +{ + this->data = data; // for future reference + this->type = type; // for future reference + + SQLSMALLINT sqlType; + SQLSMALLINT cType; + SQLUINTEGER size; + + prepare_for_bind(data, size, sqlType, cType); + + SQLINTEGER arraySize = (SQLINTEGER)indVec.size(); + SQLSetStmtAttr(statement_.hStmt, SQL_ATTR_PARAMSET_SIZE, (SQLPOINTER)arraySize, 0); + + SQLRETURN cliRC = SQLBindParameter(statement_.hStmt, static_cast(position++), + SQL_PARAM_INPUT, cType, sqlType, size, 0, + static_cast(data), size, indptr); + + if ( cliRC != SQL_SUCCESS ) + { + throw db2_soci_error("Error while binding value to column", cliRC); + } +} + +void db2_vector_use_type_backend::bind_by_pos(int &position, + void *data, exchange_type type) +{ + if (statement_.use_binding_method_ == details::db2::BOUND_BY_NAME) + { + throw soci_error("Binding for use elements must be either by position or by name."); + } + statement_.use_binding_method_ = details::db2::BOUND_BY_POSITION; + + bind_helper(position, data, type); +} + +void db2_vector_use_type_backend::bind_by_name( + std::string const &name, void *data, exchange_type type) +{ + int position = -1; + int count = 1; + + if (statement_.use_binding_method_ == details::db2::BOUND_BY_POSITION) + { + throw soci_error("Binding for use elements must be either by position or by name."); + } + statement_.use_binding_method_ = details::db2::BOUND_BY_NAME; + + for (std::vector::iterator it = statement_.names.begin(); + it != statement_.names.end(); ++it) + { + if (*it == name) + { + position = count; + break; + } + count++; + } + + if (position != -1) + { + bind_helper(position, data, type); + } + else + { + std::ostringstream ss; + ss << "Unable to find name '" << name << "' to bind to"; + throw soci_error(ss.str().c_str()); + } +} + +void db2_vector_use_type_backend::pre_use(indicator const *ind) +{ + // first deal with data + if (type == x_stdtm) + { + std::vector *vp + = static_cast *>(data); + + std::vector &v(*vp); + + char *pos = buf; + std::size_t const vsize = v.size(); + for (std::size_t i = 0; i != vsize; ++i) + { + std::tm t = v[i]; + TIMESTAMP_STRUCT * ts = reinterpret_cast(pos); + + ts->year = static_cast(t.tm_year + 1900); + ts->month = static_cast(t.tm_mon + 1); + ts->day = static_cast(t.tm_mday); + ts->hour = static_cast(t.tm_hour); + ts->minute = static_cast(t.tm_min); + ts->second = static_cast(t.tm_sec); + ts->fraction = 0; + pos += sizeof(TIMESTAMP_STRUCT); + } + } + + // then handle indicators + if (ind != NULL) + { + std::size_t const vsize = size(); + for (std::size_t i = 0; i != vsize; ++i, ++ind) + { + if (*ind == i_null) + { + indVec[i] = SQL_NULL_DATA; // null + } + else + { + // for strings we have already set the values + if (type != x_stdstring) + { + indVec[i] = SQL_NTS; // value is OK + } + } + } + } + else + { + // no indicators - treat all fields as OK + std::size_t const vsize = size(); + for (std::size_t i = 0; i != vsize; ++i, ++ind) + { + // for strings we have already set the values + if (type != x_stdstring) + { + indVec[i] = SQL_NTS; // value is OK + } + } + } +} + +std::size_t db2_vector_use_type_backend::size() +{ + std::size_t sz = 0; // dummy initialization to please the compiler + switch (type) + { + // simple cases + case x_char: + { + std::vector *vp = static_cast *>(data); + sz = vp->size(); + } + break; + case x_short: + { + std::vector *vp = static_cast *>(data); + sz = vp->size(); + } + break; + case x_integer: + { + std::vector *vp = static_cast *>(data); + sz = vp->size(); + } + break; + case x_long_long: + { + std::vector *vp + = static_cast *>(data); + sz = vp->size(); + } + break; + case x_unsigned_long_long: + { + std::vector *vp + = static_cast *>(data); + sz = vp->size(); + } + break; + case x_double: + { + std::vector *vp + = static_cast *>(data); + sz = vp->size(); + } + break; + case x_stdstring: + { + std::vector *vp + = static_cast *>(data); + sz = vp->size(); + } + break; + case x_stdtm: + { + std::vector *vp + = static_cast *>(data); + sz = vp->size(); + } + break; + + case x_statement: break; // not supported + case x_rowid: break; // not supported + case x_blob: break; // not supported + } + + return sz; +} + +void db2_vector_use_type_backend::clean_up() +{ + if (buf != NULL) + { + delete [] buf; + buf = NULL; + } +} diff --git a/src/backends/empty/CMakeLists.txt b/src/backends/empty/CMakeLists.txt new file mode 100644 index 0000000000..be3942a68a --- /dev/null +++ b/src/backends/empty/CMakeLists.txt @@ -0,0 +1,17 @@ +############################################################################### +# +# This file is part of CMake configuration for SOCI library +# +# Copyright (C) 2010 Mateusz Loskot +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) +# +############################################################################### +soci_backend(Empty + HEADERS soci-empty.h + DESCRIPTION "SOCI backend skeleton for development of new backends" + AUTHORS "Maciej Sobczak, Stephen Hutton" + MAINTAINERS "Maciej Sobczak") + +add_subdirectory(test) diff --git a/src/backends/empty/Makefile.basic b/src/backends/empty/Makefile.basic new file mode 100644 index 0000000000..7fe0f91072 --- /dev/null +++ b/src/backends/empty/Makefile.basic @@ -0,0 +1,88 @@ +# The following variable is specific to this backend and its correct +# values might depend on your environment - feel free to set it accordingly. + +EMPTYINCLUDEDIR = + +# The rest of the Makefile is indepentent of the target environment. + +COMPILER = g++ +CXXFLAGS = -Wall -pedantic -Wno-long-long +CXXFLAGSSO = ${CXXFLAGS} -fPIC +INCLUDEDIRS = -I../../core ${EMPTYINCLUDEDIR} + + +OBJECTS = blob.o factory.o row-id.o session.o standard-into-type.o \ + standard-use-type.o statement.o vector-into-type.o vector-use-type.o + +OBJECTSSO = blob-s.o factory-s.o row-id-s.o session-s.o \ + standard-into-type-s.o standard-use-type-s.o statement-s.o \ + vector-into-type-s.o vector-use-type-s.o + + +libsoci_empty.a : ${OBJECTS} + ar rv $@ $? + rm *.o + + +blob.o : blob.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +factory.o : factory.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +row-id.o : row-id.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +session.o : session.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +standard-into-type.o : standard-into-type.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +standard-use-type.o : standard-use-type.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +statement.o : statement.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +vector-into-type.o : vector-into-type.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +vector-use-type.o : vector-use-type.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + + +shared : ${OBJECTSSO} + ${COMPILER} -shared -o libsoci_empty.so ${OBJECTSSO} + rm *.o + +blob-s.o : blob.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +factory-s.o : factory.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +row-id-s.o : row-id.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +session-s.o : session.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +standard-into-type-s.o : standard-into-type.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +standard-use-type-s.o : standard-use-type.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +statement-s.o : statement.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +vector-into-type-s.o : vector-into-type.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +vector-use-type-s.o : vector-use-type.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + + +clean : + rm -f libsoci_empty.a libsoci_empty.so diff --git a/src/backends/empty/blob.cpp b/src/backends/empty/blob.cpp new file mode 100644 index 0000000000..fac33c801e --- /dev/null +++ b/src/backends/empty/blob.cpp @@ -0,0 +1,61 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_EMPTY_SOURCE +#include "soci-empty.h" + +#ifdef _MSC_VER +#pragma warning(disable:4355) +#endif + +using namespace soci; +using namespace soci::details; + + +empty_blob_backend::empty_blob_backend(empty_session_backend &session) + : session_(session) +{ + // ... +} + +empty_blob_backend::~empty_blob_backend() +{ + // ... +} + +std::size_t empty_blob_backend::get_len() +{ + // ... + return 0; +} + +std::size_t empty_blob_backend::read( + std::size_t /* offset */, char * /* buf */, std::size_t /* toRead */) +{ + // ... + return 0; +} + +std::size_t empty_blob_backend::write( + std::size_t /* offset */, char const * /* buf */, + std::size_t /* toWrite */) +{ + // ... + return 0; +} + +std::size_t empty_blob_backend::append( + char const * /* buf */, std::size_t /* toWrite */) +{ + // ... + return 0; +} + +void empty_blob_backend::trim(std::size_t /* newLen */) +{ + // ... +} diff --git a/src/backends/empty/factory.cpp b/src/backends/empty/factory.cpp new file mode 100644 index 0000000000..ed744dcb30 --- /dev/null +++ b/src/backends/empty/factory.cpp @@ -0,0 +1,42 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_EMPTY_SOURCE +#include "soci-empty.h" +#include + +#ifdef _MSC_VER +#pragma warning(disable:4355) +#endif + +using namespace soci; +using namespace soci::details; + +// concrete factory for Empty concrete strategies +empty_session_backend* empty_backend_factory::make_session( + connection_parameters const& parameters) const +{ + return new empty_session_backend(parameters); +} + +empty_backend_factory const soci::empty; + +extern "C" +{ + +// for dynamic backend loading +SOCI_EMPTY_DECL backend_factory const* factory_empty() +{ + return &soci::empty; +} + +SOCI_EMPTY_DECL void register_factory_empty() +{ + soci::dynamic_backends::register_backend("empty", soci::empty); +} + +} // extern "C" diff --git a/src/backends/empty/row-id.cpp b/src/backends/empty/row-id.cpp new file mode 100644 index 0000000000..35265e6c34 --- /dev/null +++ b/src/backends/empty/row-id.cpp @@ -0,0 +1,27 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_EMPTY_SOURCE +#include "soci-empty.h" + +#ifdef _MSC_VER +#pragma warning(disable:4355) +#endif + +using namespace soci; +using namespace soci::details; + + +empty_rowid_backend::empty_rowid_backend(empty_session_backend & /* session */) +{ + // ... +} + +empty_rowid_backend::~empty_rowid_backend() +{ + // ... +} diff --git a/src/backends/empty/session.cpp b/src/backends/empty/session.cpp new file mode 100644 index 0000000000..8195473821 --- /dev/null +++ b/src/backends/empty/session.cpp @@ -0,0 +1,63 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_EMPTY_SOURCE +#include "soci-empty.h" + +#ifdef _MSC_VER +#pragma warning(disable:4355) +#endif + +using namespace soci; +using namespace soci::details; + + +empty_session_backend::empty_session_backend( + connection_parameters const & /* parameters */) +{ + // ... +} + +empty_session_backend::~empty_session_backend() +{ + clean_up(); +} + +void empty_session_backend::begin() +{ + // ... +} + +void empty_session_backend::commit() +{ + // ... +} + +void empty_session_backend::rollback() +{ + // ... +} + +void empty_session_backend::clean_up() +{ + // ... +} + +empty_statement_backend * empty_session_backend::make_statement_backend() +{ + return new empty_statement_backend(*this); +} + +empty_rowid_backend * empty_session_backend::make_rowid_backend() +{ + return new empty_rowid_backend(*this); +} + +empty_blob_backend * empty_session_backend::make_blob_backend() +{ + return new empty_blob_backend(*this); +} diff --git a/src/backends/empty/soci-empty.h b/src/backends/empty/soci-empty.h new file mode 100644 index 0000000000..ae63fe47a7 --- /dev/null +++ b/src/backends/empty/soci-empty.h @@ -0,0 +1,193 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_EMPTY_H_INCLUDED +#define SOCI_EMPTY_H_INCLUDED + +#ifdef _WIN32 +# ifdef SOCI_DLL +# ifdef SOCI_EMPTY_SOURCE +# define SOCI_EMPTY_DECL __declspec(dllexport) +# else +# define SOCI_EMPTY_DECL __declspec(dllimport) +# endif // SOCI_EMPTY_SOURCE +# endif // SOCI_DLL +#endif // _WIN32 +// +// If SOCI_EMPTY_DECL isn't defined yet define it now +#ifndef SOCI_EMPTY_DECL +# define SOCI_EMPTY_DECL +#endif + +#include "soci-backend.h" + +#include +#include + +namespace soci +{ + +struct empty_statement_backend; + +struct SOCI_EMPTY_DECL empty_standard_into_type_backend : details::standard_into_type_backend +{ + empty_standard_into_type_backend(empty_statement_backend &st) + : statement_(st) + {} + + void define_by_pos(int& position, void* data, details::exchange_type type); + + void pre_fetch(); + void post_fetch(bool gotData, bool calledFromFetch, indicator* ind); + + void clean_up(); + + empty_statement_backend& statement_; +}; + +struct SOCI_EMPTY_DECL empty_vector_into_type_backend : details::vector_into_type_backend +{ + empty_vector_into_type_backend(empty_statement_backend &st) + : statement_(st) + {} + + void define_by_pos(int& position, void* data, details::exchange_type type); + + void pre_fetch(); + void post_fetch(bool gotData, indicator* ind); + + void resize(std::size_t sz); + std::size_t size(); + + void clean_up(); + + empty_statement_backend& statement_; +}; + +struct SOCI_EMPTY_DECL empty_standard_use_type_backend : details::standard_use_type_backend +{ + empty_standard_use_type_backend(empty_statement_backend &st) + : statement_(st) + {} + + void bind_by_pos(int& position, void* data, details::exchange_type type, bool readOnly); + void bind_by_name(std::string const& name, void* data, details::exchange_type type, bool readOnly); + + void pre_use(indicator const* ind); + void post_use(bool gotData, indicator* ind); + + void clean_up(); + + empty_statement_backend& statement_; +}; + +struct SOCI_EMPTY_DECL empty_vector_use_type_backend : details::vector_use_type_backend +{ + empty_vector_use_type_backend(empty_statement_backend &st) + : statement_(st) {} + + void bind_by_pos(int& position, void* data, details::exchange_type type); + void bind_by_name(std::string const& name, void* data, details::exchange_type type); + + void pre_use(indicator const* ind); + + std::size_t size(); + + void clean_up(); + + empty_statement_backend& statement_; +}; + +struct empty_session_backend; +struct SOCI_EMPTY_DECL empty_statement_backend : details::statement_backend +{ + empty_statement_backend(empty_session_backend &session); + + void alloc(); + void clean_up(); + void prepare(std::string const& query, details::statement_type eType); + + exec_fetch_result execute(int number); + exec_fetch_result fetch(int number); + + long long get_affected_rows(); + int get_number_of_rows(); + + std::string rewrite_for_procedure_call(std::string const& query); + + int prepare_for_describe(); + void describe_column(int colNum, data_type& dtype, std::string& columnName); + + empty_standard_into_type_backend* make_into_type_backend(); + empty_standard_use_type_backend* make_use_type_backend(); + empty_vector_into_type_backend* make_vector_into_type_backend(); + empty_vector_use_type_backend* make_vector_use_type_backend(); + + empty_session_backend& session_; +}; + +struct empty_rowid_backend : details::rowid_backend +{ + empty_rowid_backend(empty_session_backend &session); + + ~empty_rowid_backend(); +}; + +struct empty_blob_backend : details::blob_backend +{ + empty_blob_backend(empty_session_backend& session); + + ~empty_blob_backend(); + + std::size_t get_len(); + std::size_t read(std::size_t offset, char* buf, std::size_t toRead); + std::size_t write(std::size_t offset, char const* buf, std::size_t toWrite); + std::size_t append(char const* buf, std::size_t toWrite); + void trim(std::size_t newLen); + + empty_session_backend& session_; +}; + +struct empty_session_backend : details::session_backend +{ + empty_session_backend(connection_parameters const& parameters); + + ~empty_session_backend(); + + void begin(); + void commit(); + void rollback(); + + std::string get_backend_name() const { return "empty"; } + + void clean_up(); + + empty_statement_backend* make_statement_backend(); + empty_rowid_backend* make_rowid_backend(); + empty_blob_backend* make_blob_backend(); +}; + +struct SOCI_EMPTY_DECL empty_backend_factory : backend_factory +{ + empty_backend_factory() {} + empty_session_backend* make_session(connection_parameters const& parameters) const; +}; + +extern SOCI_EMPTY_DECL empty_backend_factory const empty; + +extern "C" +{ + +// for dynamic backend loading +SOCI_EMPTY_DECL backend_factory const* factory_empty(); +SOCI_EMPTY_DECL void register_factory_empty(); + +} // extern "C" + +} // namespace soci + +#endif // SOCI_EMPTY_H_INCLUDED diff --git a/src/backends/empty/standard-into-type.cpp b/src/backends/empty/standard-into-type.cpp new file mode 100644 index 0000000000..677f2324ff --- /dev/null +++ b/src/backends/empty/standard-into-type.cpp @@ -0,0 +1,39 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_EMPTY_SOURCE +#include "soci-empty.h" + +#ifdef _MSC_VER +#pragma warning(disable:4355) +#endif + +using namespace soci; +using namespace soci::details; + + +void empty_standard_into_type_backend::define_by_pos( + int & /* position */, void * /* data */, exchange_type /* type */) +{ + // ... +} + +void empty_standard_into_type_backend::pre_fetch() +{ + // ... +} + +void empty_standard_into_type_backend::post_fetch( + bool /* gotData */, bool /* calledFromFetch */, indicator * /* ind */) +{ + // ... +} + +void empty_standard_into_type_backend::clean_up() +{ + // ... +} diff --git a/src/backends/empty/standard-use-type.cpp b/src/backends/empty/standard-use-type.cpp new file mode 100644 index 0000000000..4a9786e459 --- /dev/null +++ b/src/backends/empty/standard-use-type.cpp @@ -0,0 +1,47 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_EMPTY_SOURCE +#include "soci-empty.h" + +#ifdef _MSC_VER +#pragma warning(disable:4355) +#endif + +using namespace soci; +using namespace soci::details; + + +void empty_standard_use_type_backend::bind_by_pos( + int & /* position */, void * /* data */, + exchange_type /* type */, bool /* readOnly */) +{ + // ... +} + +void empty_standard_use_type_backend::bind_by_name( + std::string const & /* name */, void * /* data */, + exchange_type /* type */, bool /* readOnly */) +{ + // ... +} + +void empty_standard_use_type_backend::pre_use(indicator const * /* ind */) +{ + // ... +} + +void empty_standard_use_type_backend::post_use( + bool /* gotData */, indicator * /* ind */) +{ + // ... +} + +void empty_standard_use_type_backend::clean_up() +{ + // ... +} diff --git a/src/backends/empty/statement.cpp b/src/backends/empty/statement.cpp new file mode 100644 index 0000000000..702a7ab4b1 --- /dev/null +++ b/src/backends/empty/statement.cpp @@ -0,0 +1,103 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_EMPTY_SOURCE +#include "soci-empty.h" + +#ifdef _MSC_VER +#pragma warning(disable:4355) +#endif + +using namespace soci; +using namespace soci::details; + + +empty_statement_backend::empty_statement_backend(empty_session_backend &session) + : session_(session) +{ +} + +void empty_statement_backend::alloc() +{ + // ... +} + +void empty_statement_backend::clean_up() +{ + // ... +} + +void empty_statement_backend::prepare(std::string const & /* query */, + statement_type /* eType */) +{ + // ... +} + +statement_backend::exec_fetch_result +empty_statement_backend::execute(int /* number */) +{ + // ... + return ef_success; +} + +statement_backend::exec_fetch_result +empty_statement_backend::fetch(int /* number */) +{ + // ... + return ef_success; +} + +long long empty_statement_backend::get_affected_rows() +{ + // ... + return -1; +} + +int empty_statement_backend::get_number_of_rows() +{ + // ... + return 1; +} + +std::string empty_statement_backend::rewrite_for_procedure_call( + std::string const &query) +{ + return query; +} + +int empty_statement_backend::prepare_for_describe() +{ + // ... + return 0; +} + +void empty_statement_backend::describe_column(int /* colNum */, + data_type & /* type */, std::string & /* columnName */) +{ + // ... +} + +empty_standard_into_type_backend * empty_statement_backend::make_into_type_backend() +{ + return new empty_standard_into_type_backend(*this); +} + +empty_standard_use_type_backend * empty_statement_backend::make_use_type_backend() +{ + return new empty_standard_use_type_backend(*this); +} + +empty_vector_into_type_backend * +empty_statement_backend::make_vector_into_type_backend() +{ + return new empty_vector_into_type_backend(*this); +} + +empty_vector_use_type_backend * empty_statement_backend::make_vector_use_type_backend() +{ + return new empty_vector_use_type_backend(*this); +} diff --git a/src/backends/empty/test/.gitignore b/src/backends/empty/test/.gitignore new file mode 100644 index 0000000000..8e2773204c --- /dev/null +++ b/src/backends/empty/test/.gitignore @@ -0,0 +1 @@ +test_empty diff --git a/src/backends/empty/test/CMakeLists.txt b/src/backends/empty/test/CMakeLists.txt new file mode 100644 index 0000000000..bc1ddd963a --- /dev/null +++ b/src/backends/empty/test/CMakeLists.txt @@ -0,0 +1,14 @@ +############################################################################### +# +# This file is part of CMake configuration for SOCI library +# +# Copyright (C) 2010 Mateusz Loskot +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) +# +############################################################################### +soci_backend_test( + BACKEND Empty + SOURCE test-empty.cpp + CONNSTR "dummy") \ No newline at end of file diff --git a/src/backends/empty/test/Makefile.basic b/src/backends/empty/test/Makefile.basic new file mode 100644 index 0000000000..5d049b0fe1 --- /dev/null +++ b/src/backends/empty/test/Makefile.basic @@ -0,0 +1,13 @@ +COMPILER = g++ +CXXFLAGS = -Wall -pedantic -Wno-long-long +INCLUDEDIRS = -I.. -I../../../core +LIBDIRS = -L.. -L../../../core +LIBS = -lsoci_core -lsoci_empty -ldl + + +test-empty : test-empty.cpp + ${COMPILER} -o $@ $? ${CXXFLAGS} ${INCLUDEDIRS} ${LIBDIRS} ${LIBS} + + +clean : + rm -f test-empty diff --git a/src/backends/empty/test/test-empty.cpp b/src/backends/empty/test/test-empty.cpp new file mode 100644 index 0000000000..304153657f --- /dev/null +++ b/src/backends/empty/test/test-empty.cpp @@ -0,0 +1,170 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#include "soci.h" +#include "soci-empty.h" +#include +#include +#include +#include +#include + +using namespace soci; + +std::string connectString; +backend_factory const &backEnd = *soci::factory_empty(); + + +// NOTE: +// This file is supposed to serve two purposes: +// 1. To be a starting point for implementing new tests (for new backends). +// 2. To exercise (at least some of) the syntax and try the SOCI library +// against different compilers, even in those environments where there +// is no database. SOCI uses advanced template techniques which are known +// to cause problems on different versions of popular compilers, and this +// test is handy to verify that the code is accepted by as many compilers +// as possible. +// +// Both of these purposes mean that the actual code here is meaningless +// from the database-development point of view. For new tests, you may wish +// to remove this code and keep only the general structure of this file. + +struct Person +{ + int id; + std::string firstName; + std::string lastName; +}; + +namespace soci +{ + template<> struct type_conversion + { + typedef values base_type; + static void from_base(values & /* r */, indicator /* ind */, + Person & /* p */) + { + } + }; +} + +void test1() +{ + { + session sql(backEnd, connectString); + + sql << "Do what I want."; + sql << "Do what I want " << 123 << " times."; + + std::string query = "some query"; + sql << query; + + int i = 7; + sql << "insert", use(i); + sql << "select", into(i); + +#if defined (__LP64__) || ( __WORDSIZE == 64 ) + long int li = 9; + sql << "insert", use(li); + sql << "select", into(li); +#endif + + long long ll = 11; + sql << "insert", use(ll); + sql << "select", into(ll); + + indicator ind = i_ok; + sql << "insert", use(i, ind); + sql << "select", into(i, ind); + + std::vector numbers(100); + sql << "insert", use(numbers); + sql << "select", into(numbers); + + std::vector inds(100); + sql << "insert", use(numbers, inds); + sql << "select", into(numbers, inds); + + { + statement st = (sql.prepare << "select", into(i)); + st.execute(); + st.fetch(); + } + { + statement st = (sql.prepare << "select", into(i, ind)); + } + { + statement st = (sql.prepare << "select", into(numbers)); + } + { + statement st = (sql.prepare << "select", into(numbers, inds)); + } + { + statement st = (sql.prepare << "insert", use(i)); + } + { + statement st = (sql.prepare << "insert", use(i, ind)); + } + { + statement st = (sql.prepare << "insert", use(numbers)); + } + { + statement st = (sql.prepare << "insert", use(numbers, inds)); + } + { + Person p; + sql << "select person", into(p); + } + + } + + std::cout << "test 1 passed" << std::endl; +} + + +int main(int argc, char** argv) +{ + +#ifdef _MSC_VER + // Redirect errors, unrecoverable problems, and assert() failures to STDERR, + // instead of debug message window. + // This hack is required to run asser()-driven tests by Buildbot. + // NOTE: Comment this 2 lines for debugging with Visual C++ debugger to catch assertions inside. + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); +#endif //_MSC_VER + + if (argc == 2) + { + connectString = argv[1]; + } + else + { + std::cout << "usage: " << argv[0] + << " connectstring\n" + << "example: " << argv[0] + << " \'connect_string_for_empty_backend\'\n"; + std::exit(1); + } + + try + { + test1(); + // test2(); + // ... + + std::cout << "\nOK, all tests passed.\n\n"; + + return EXIT_SUCCESS; + } + catch (std::exception const & e) + { + std::cout << e.what() << '\n'; + } + + return EXIT_FAILURE; +} diff --git a/src/backends/empty/vector-into-type.cpp b/src/backends/empty/vector-into-type.cpp new file mode 100644 index 0000000000..84830dd0e5 --- /dev/null +++ b/src/backends/empty/vector-into-type.cpp @@ -0,0 +1,50 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_EMPTY_SOURCE +#include "soci-empty.h" + +#ifdef _MSC_VER +#pragma warning(disable:4355) +#endif + +using namespace soci; +using namespace soci::details; + + +void empty_vector_into_type_backend::define_by_pos( + int & /* position */, void * /* data */, exchange_type /* type */) +{ + // ... +} + +void empty_vector_into_type_backend::pre_fetch() +{ + // ... +} + +void empty_vector_into_type_backend::post_fetch( + bool /* gotData */, indicator * /* ind */) +{ + // ... +} + +void empty_vector_into_type_backend::resize(std::size_t /* sz */) +{ + // ... +} + +std::size_t empty_vector_into_type_backend::size() +{ + // ... + return 1; +} + +void empty_vector_into_type_backend::clean_up() +{ + // ... +} diff --git a/src/backends/empty/vector-use-type.cpp b/src/backends/empty/vector-use-type.cpp new file mode 100644 index 0000000000..154d1da9ff --- /dev/null +++ b/src/backends/empty/vector-use-type.cpp @@ -0,0 +1,46 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_EMPTY_SOURCE +#include "soci-empty.h" + +#ifdef _MSC_VER +#pragma warning(disable:4355) +#endif + +using namespace soci; +using namespace soci::details; + + +void empty_vector_use_type_backend::bind_by_pos(int & /* position */, + void * /* data */, exchange_type /* type */) +{ + // ... +} + +void empty_vector_use_type_backend::bind_by_name( + std::string const & /* name */, void * /* data */, + exchange_type /* type */) +{ + // ... +} + +void empty_vector_use_type_backend::pre_use(indicator const * /* ind */) +{ + // ... +} + +std::size_t empty_vector_use_type_backend::size() +{ + // ... + return 1; +} + +void empty_vector_use_type_backend::clean_up() +{ + // ... +} diff --git a/src/backends/firebird/CMakeLists.txt b/src/backends/firebird/CMakeLists.txt new file mode 100644 index 0000000000..7e34aaad7f --- /dev/null +++ b/src/backends/firebird/CMakeLists.txt @@ -0,0 +1,19 @@ +############################################################################### +# +# This file is part of CMake configuration for SOCI library +# +# Copyright (C) 2013 Viacheslav Naydenov +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) +# +############################################################################### + +soci_backend(Firebird + DEPENDS Firebird + HEADERS soci-firebird.h common.h + DESCRIPTION "SOCI backend for Firebird database engine" + AUTHORS "Rafał Bobrowski" + MAINTAINERS "Viacheslav Naydenov") + +add_subdirectory(test) diff --git a/src/backends/firebird/Makefile.basic b/src/backends/firebird/Makefile.basic new file mode 100644 index 0000000000..6258469d7c --- /dev/null +++ b/src/backends/firebird/Makefile.basic @@ -0,0 +1,101 @@ +# The following variable is specific to this backend and its correct +# values might depend on your environment - feel free to set it accordingly. + +FIREBIRDINCLUDEDIR = -I/usr/local/firebird/include + +# The rest of the Makefile is indepentent of the target environment. + +COMPILER = g++ +CXXFLAGS = -Wall -pedantic -Wno-long-long +CXXFLAGSSO = ${CXXFLAGS} -fPIC +INCLUDEDIRS = -I../../core ${FIREBIRDINCLUDEDIR} + +OBJECTS = blob.o factory.o row-id.o session.o standard-into-type.o \ + standard-use-type.o statement.o vector-into-type.o vector-use-type.o \ + error-firebird.o common.o + +OBJECTSSO = blob-s.o factory-s.o row-id-s.o session-s.o \ + standard-into-type-s.o standard-use-type-s.o statement-s.o \ + vector-into-type-s.o vector-use-type-s.o error-firebird-s.o common-s.o + +FIREBIRDLIBS = -lfbclient -lpthread + +libsoci_firebird.a : ${OBJECTS} + ar rv $@ $? + rm *.o + +soci-firebird.o : soci-firebird.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +blob.o : blob.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +error-firebird.o : error-firebird.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +common.o : common.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +factory.o : factory.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +row-id.o : row-id.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +session.o : session.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +standard-into-type.o : standard-into-type.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +standard-use-type.o : standard-use-type.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +statement.o : statement.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +vector-into-type.o : vector-into-type.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +vector-use-type.o : vector-use-type.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +shared : ${OBJECTSSO} + ${COMPILER} -shared -o libsoci_firebird.so ${OBJECTSSO} ${FIREBIRDLIBS} + rm *.o + +blob-s.o : blob.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +error-firebird-s.o : error-firebird.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +common-s.o : common.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +factory-s.o : factory.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +row-id-s.o : row-id.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +session-s.o : session.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +standard-into-type-s.o : standard-into-type.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +standard-use-type-s.o : standard-use-type.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +statement-s.o : statement.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +vector-into-type-s.o : vector-into-type.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +vector-use-type-s.o : vector-use-type.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +clean : + rm -f *.o libsoci_firebird.a libsoci_firebird.so diff --git a/src/backends/firebird/blob.cpp b/src/backends/firebird/blob.cpp new file mode 100644 index 0000000000..11907a5182 --- /dev/null +++ b/src/backends/firebird/blob.cpp @@ -0,0 +1,301 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, Rafal Bobrowski +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_FIREBIRD_SOURCE +#include "soci-firebird.h" +#include "error-firebird.h" + +using namespace soci; +using namespace soci::details::firebird; + +firebird_blob_backend::firebird_blob_backend(firebird_session_backend &session) + : session_(session), from_db_(false), bhp_(0), loaded_(false), + max_seg_size_(0) +{} + +firebird_blob_backend::~firebird_blob_backend() +{ + cleanUp(); +} + +std::size_t firebird_blob_backend::get_len() +{ + if (from_db_ && bhp_ == 0) + { + open(); + } + + return data_.size(); +} + +std::size_t firebird_blob_backend::read( + std::size_t offset, char * buf, std::size_t toRead) +{ + if (from_db_ && (loaded_ == false)) + { + // this is blob fetched from database, but not loaded yet + load(); + } + + std::size_t size = data_.size(); + + if (offset > size) + { + throw soci_error("Can't read past-the-end of BLOB data"); + } + + char * itr = buf; + std::size_t limit = size - offset < toRead ? size - offset : toRead; + std::size_t index = 0; + + while (index < limit) + { + *itr = data_[offset+index]; + ++index; + ++itr; + } + + return limit; +} + +std::size_t firebird_blob_backend::write(std::size_t offset, char const * buf, + std::size_t toWrite) +{ + if (from_db_ && (loaded_ == false)) + { + // this is blob fetched from database, but not loaded yet + load(); + } + + std::size_t size = data_.size(); + + if (offset > size) + { + throw soci_error("Can't write past-the-end of BLOB data"); + } + + // make sure there is enough space in buffer + if (toWrite > (size - offset)) + { + data_.resize(size + (toWrite - (size - offset))); + } + + writeBuffer(offset, buf, toWrite); + + return toWrite; +} + +std::size_t firebird_blob_backend::append( + char const * buf, std::size_t toWrite) +{ + if (from_db_ && (loaded_ == false)) + { + // this is blob fetched from database, but not loaded yet + load(); + } + + std::size_t size = data_.size(); + data_.resize(size + toWrite); + + writeBuffer(size, buf, toWrite); + + return toWrite; +} + +void firebird_blob_backend::trim(std::size_t newLen) +{ + if (from_db_ && (loaded_ == false)) + { + // this is blob fetched from database, but not loaded yet + load(); + } + + data_.resize(newLen); +} + +void firebird_blob_backend::writeBuffer(std::size_t offset, + char const * buf, std::size_t toWrite) +{ + char const * itr = buf; + char const * end_itr = buf + toWrite; + + while (itr!=end_itr) + { + data_[offset++] = *itr++; + } +} + +void firebird_blob_backend::open() +{ + if (bhp_ != 0) + { + // BLOB already opened + return; + } + + ISC_STATUS stat[20]; + + if (isc_open_blob2(stat, &session_.dbhp_, &session_.trhp_, &bhp_, + &bid_, 0, NULL)) + { + bhp_ = 0L; + throw_iscerror(stat); + } + + // get basic blob info + long blob_size = getBLOBInfo(); + + data_.resize(blob_size); +} + +void firebird_blob_backend::cleanUp() +{ + from_db_ = false; + loaded_ = false; + max_seg_size_ = 0; + data_.resize(0); + + if (bhp_ != 0) + { + // close blob + ISC_STATUS stat[20]; + if (isc_close_blob(stat, &bhp_)) + { + throw_iscerror(stat); + } + bhp_ = 0; + } +} + +// loads blob data into internal buffer +void firebird_blob_backend::load() +{ + if (bhp_ == 0) + { + open(); + } + + ISC_STATUS stat[20]; + unsigned short bytes; + std::vector::size_type total_bytes = 0; + bool keep_reading = false; + + do + { + bytes = 0; + // next segment of data + // data_ is large-enough because we know total size of blob + isc_get_segment(stat, &bhp_, &bytes, static_cast(max_seg_size_), + &data_[total_bytes]); + + total_bytes += bytes; + + if (total_bytes == data_.size()) + { + // we have all BLOB data + keep_reading = false; + } + else if (stat[1] == 0 || stat[1] == isc_segment) + { + // there is more data to read from current segment (0) + // or there is next segment (isc_segment) + keep_reading = true; + } + else if (stat[1] == isc_segstr_eof) + { + // BLOB is shorter then we expected ??? + keep_reading = false; + } + else + { + // an error has occured + throw_iscerror(stat); + } + } + while (keep_reading); + + loaded_ = true; +} + +// this method saves BLOB content to database +// (a new BLOB will be created at this point) +// BLOB will be closed after save. +void firebird_blob_backend::save() +{ + // close old blob if necessary + ISC_STATUS stat[20]; + if (bhp_ != 0) + { + if (isc_close_blob(stat, &bhp_)) + { + throw_iscerror(stat); + } + bhp_ = 0; + } + + // create new blob + if (isc_create_blob(stat, &session_.dbhp_, &session_.trhp_, + &bhp_, &bid_)) + { + throw_iscerror(stat); + } + + if (data_.size() > 0) + { + // write data + if (isc_put_segment(stat, &bhp_, + static_cast(data_.size()), &data_[0])) + { + throw_iscerror(stat); + } + } + + cleanUp(); + from_db_ = true; +} + +// retrives number of segments and total length of BLOB +// returns total length of BLOB +long firebird_blob_backend::getBLOBInfo() +{ + char blob_items[] = {isc_info_blob_max_segment, isc_info_blob_total_length}; + char res_buffer[20], *p, item; + short length; + long total_length = 0; + + ISC_STATUS stat[20]; + + if (isc_blob_info(stat, &bhp_, sizeof(blob_items), blob_items, + sizeof(res_buffer), res_buffer)) + { + throw_iscerror(stat); + } + + for (p = res_buffer; *p != isc_info_end ;) + { + item = *p++; + length = static_cast(isc_vax_integer(p, 2)); + p += 2; + switch (item) + { + case isc_info_blob_max_segment: + max_seg_size_ = isc_vax_integer(p, length); + break; + case isc_info_blob_total_length: + total_length = isc_vax_integer(p, length); + break; + case isc_info_truncated: + throw soci_error("Fatal Error: BLOB info truncated!"); + break; + default: + break; + } + p += length; + } + + return total_length; +} diff --git a/src/backends/firebird/common.cpp b/src/backends/firebird/common.cpp new file mode 100644 index 0000000000..263ea85982 --- /dev/null +++ b/src/backends/firebird/common.cpp @@ -0,0 +1,215 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, Rafal Bobrowski +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#include "common.h" +#include +#include // FireBird +#include +#include +#include +#include +#include +#include + +namespace soci +{ + +namespace details +{ + +namespace firebird +{ + +char * allocBuffer(XSQLVAR* var) +{ + std::size_t size; + int type = var->sqltype & ~1; + if (type == SQL_VARYING) + { + size = var->sqllen + sizeof(short); + } + else if (type == SQL_TIMESTAMP || type == SQL_TYPE_TIME + || type == SQL_TYPE_DATE) + { + size = sizeof(std::tm); + } + else + { + size = var->sqllen; + } + + return new char[size]; +} + +void tmEncode(short type, std::tm * src, void * dst) +{ + switch (type & ~1) + { + // In Interbase v6 DATE represents a date-only data type, + // in InterBase v5 DATE represents a date+time data type. + case SQL_TIMESTAMP: + isc_encode_timestamp(src, static_cast(dst)); + break; + case SQL_TYPE_TIME: + isc_encode_sql_time(src, static_cast(dst)); + break; + case SQL_TYPE_DATE: + isc_encode_sql_date(src, static_cast(dst)); + break; + default: + std::ostringstream msg; + msg << "Unexpected type of date/time field (" << type << ")"; + throw soci_error(msg.str()); + } +} + +void tmDecode(short type, void * src, std::tm * dst) +{ + switch (type & ~1) + { + case SQL_TIMESTAMP: + isc_decode_timestamp(static_cast(src), dst); + break; + case SQL_TYPE_TIME: + isc_decode_sql_time(static_cast(src), dst); + break; + case SQL_TYPE_DATE: + isc_decode_sql_date(static_cast(src), dst); + break; + default: + std::ostringstream msg; + msg << "Unexpected type of date/time field (" << type << ")"; + throw soci_error(msg.str()); + } +} + +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)) + { + sz = static_cast(size); + } + else + { + sz = var->sqllen; + } + + if ((var->sqltype & ~1) == SQL_VARYING) + { + std::memcpy(buf_, &sz, sizeof(short)); + std::memcpy(buf_ + sizeof(short), s, sz); + } + else if ((var->sqltype & ~1) == SQL_TEXT) + { + std::memcpy(buf_, s, sz); + if (sz < var->sqllen) + { + std::memset(buf_+sz, ' ', var->sqllen - sz); + } + } + else if ((var->sqltype & ~1) == SQL_SHORT) + { + parse_decimal(buf_, var, s); + } + else if ((var->sqltype & ~1) == SQL_LONG) + { + parse_decimal(buf_, var, s); + } + else if ((var->sqltype & ~1) == SQL_INT64) + { + parse_decimal(buf_, var, s); + } + else if ((var->sqltype & ~1) == SQL_TIMESTAMP + || (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."); + } +} + +std::string getTextParam(XSQLVAR const *var) +{ + //std::cerr << "getTextParam: var->sqltype=" << var->sqltype << std::endl; + short size; + std::size_t offset = 0; + + if ((var->sqltype & ~1) == SQL_VARYING) + { + size = *reinterpret_cast(var->sqldata); + offset = sizeof(short); + } + else if ((var->sqltype & ~1) == SQL_TEXT) + { + size = var->sqllen; + } + else if ((var->sqltype & ~1) == SQL_SHORT) + { + return format_decimal(var->sqldata, var->sqlscale); + } + else if ((var->sqltype & ~1) == SQL_LONG) + { + return format_decimal(var->sqldata, var->sqlscale); + } + else if ((var->sqltype & ~1) == SQL_INT64) + { + return format_decimal(var->sqldata, var->sqlscale); + } + else + throw soci_error("Unexpected string type"); + + return std::string(var->sqldata + offset, size); +} + +} // namespace firebird + +} // namespace details + +} // namespace soci diff --git a/src/backends/firebird/common.h b/src/backends/firebird/common.h new file mode 100644 index 0000000000..a97c3eeb12 --- /dev/null +++ b/src/backends/firebird/common.h @@ -0,0 +1,236 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, Rafal Bobrowski +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_FIREBIRD_COMMON_H_INCLUDED +#define SOCI_FIREBIRD_COMMON_H_INCLUDED + +#include "soci-firebird.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace soci +{ + +namespace details +{ + +namespace firebird +{ + +char * allocBuffer(XSQLVAR* var); + +void tmEncode(short type, std::tm * src, void * dst); + +void tmDecode(short type, void * src, std::tm * dst); + +void setTextParam(char const * s, std::size_t size, char * buf_, + XSQLVAR * var); + +std::string getTextParam(XSQLVAR const *var); + +template +const char *str2dec(const char * s, IntType &out, int &scale) +{ + int sign = 1; + if ('+' == *s) + ++s; + else if ('-' == *s) + { + sign = -1; + ++s; + } + scale = 0; + bool period = false; + IntType res = 0; + for (out = 0; *s; ++s, out = res) + { + if (*s == '.') + { + if (period) + return s; + period = true; + continue; + } + 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; + } + if (period) + ++scale; + } + return s; +} + +template +void to_isc(void * val, XSQLVAR * var, int x_scale = 0) +{ + T1 value = *reinterpret_cast(val); + short scale = var->sqlscale + x_scale; + short type = var->sqltype & ~1; + long long divisor = 1, multiplier = 1; + + if ((std::numeric_limits::is_integer == false) && scale >= 0 && + (type == SQL_SHORT || type == SQL_LONG || type == SQL_INT64)) + { + throw soci_error("Can't convert non-integral value to integral column type"); + } + + for (int i = 0; i > scale; --i) + multiplier *= 10; + for (int i = 0; i < scale; ++i) + divisor *= 10; + + switch (type) + { + case SQL_SHORT: + { + short tmp = static_cast(value*multiplier/divisor); + std::memcpy(var->sqldata, &tmp, sizeof(short)); + } + break; + case SQL_LONG: + { + int tmp = static_cast(value*multiplier/divisor); + std::memcpy(var->sqldata, &tmp, sizeof(int)); + } + break; + case SQL_INT64: + { + long long tmp = static_cast(value*multiplier/divisor); + std::memcpy(var->sqldata, &tmp, sizeof(long long)); + } + break; + case SQL_FLOAT: + { + float sql_value = static_cast(value); + std::memcpy(var->sqldata, &sql_value, sizeof(float)); + } + break; + case SQL_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"); + } +} + +template +void parse_decimal(void * val, XSQLVAR * var, const char * s) +{ + int scale; + UIntType t1; + IntType t2; + if (!*str2dec(s, t1, scale)) + std::memcpy(val, &t1, sizeof(t1)); + else if (!*str2dec(s, t2, scale)) + std::memcpy(val, &t2, sizeof(t2)); + else + throw soci_error("Could not parse decimal value."); + to_isc(val, var, scale); +} + +template +std::string format_decimal(const void *sqldata, int sqlscale) +{ + IntType x = *reinterpret_cast(sqldata); + std::stringstream out; + out << x; + std::string r = out.str(); + if (sqlscale < 0) + { + if (static_cast(r.size()) - (x < 0) <= -sqlscale) + { + r = std::string(size_t(x < 0), '-') + + std::string(-sqlscale - (r.size() - (x < 0)) + 1, '0') + + r.substr(size_t(x < 0), std::string::npos); + } + return r.substr(0, r.size() + sqlscale) + '.' + + r.substr(r.size() + sqlscale, std::string::npos); + } + return r + std::string(sqlscale, '0'); +} + +template +T1 from_isc(XSQLVAR * var) +{ + short scale = var->sqlscale; + T1 tens = 1; + + if (scale < 0) + { + if (std::numeric_limits::is_integer) + { + std::ostringstream msg; + msg << "Can't convert value with scale " << -scale + << " to integral type"; + throw soci_error(msg.str()); + } + + for (int i = 0; i > scale; --i) + { + tens *= 10; + } + } + + switch (var->sqltype & ~1) + { + case SQL_SHORT: + return static_cast(*reinterpret_cast(var->sqldata)/tens); + case SQL_LONG: + return static_cast(*reinterpret_cast(var->sqldata)/tens); + case SQL_INT64: + return static_cast(*reinterpret_cast(var->sqldata)/tens); + case SQL_FLOAT: + return static_cast(*reinterpret_cast(var->sqldata)); + case SQL_DOUBLE: + return static_cast(*reinterpret_cast(var->sqldata)); + default: + throw soci_error("Incorrect data type for numeric conversion"); + } +} + +template +std::size_t getVectorSize(void *p) +{ + std::vector *v = static_cast *>(p); + return v->size(); +} + +template +void resizeVector(void *p, std::size_t sz) +{ + std::vector *v = static_cast *>(p); + v->resize(sz); +} + +} // namespace firebird + +} // namespace details + +} // namespace soci + +#endif // SOCI_FIREBIRD_COMMON_H_INCLUDED diff --git a/src/backends/firebird/error-firebird.cpp b/src/backends/firebird/error-firebird.cpp new file mode 100644 index 0000000000..bf834ab325 --- /dev/null +++ b/src/backends/firebird/error-firebird.cpp @@ -0,0 +1,87 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, Rafal Bobrowski +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_FIREBIRD_SOURCE +#include "soci-firebird.h" +#include "error-firebird.h" + +#include +#include + +namespace soci +{ + +firebird_soci_error::firebird_soci_error(std::string const & msg, ISC_STATUS const * status) + : soci_error(msg) +{ + if (status != 0) + { + std::size_t i = 0; + while (i < stat_size && status[i] != 0) + { + status_.push_back(status[i++]); + } + } +} + +namespace details +{ + +namespace firebird +{ + +void get_iscerror_details(ISC_STATUS * status_vector, std::string &msg) +{ + char msg_buffer[SOCI_FIREBIRD_ERRMSG]; + const ISC_STATUS *pvector = status_vector; + + try + { + // fetching first error message + fb_interpret(msg_buffer, SOCI_FIREBIRD_ERRMSG, &pvector); + msg = msg_buffer; + + // fetching next errors + while (fb_interpret(msg_buffer, SOCI_FIREBIRD_ERRMSG, &pvector)) + { + msg += "\n"; + msg += msg_buffer; + } + } + catch (...) + { + throw firebird_soci_error("Exception catched while fetching error information"); + } +} + +bool check_iscerror(ISC_STATUS const * status_vector, long errNum) +{ + std::size_t i=0; + while (status_vector[i] != 0) + { + if (status_vector[i] == 1 && status_vector[i+1] == errNum) + { + return true; + } + ++i; + } + + return false; +} +void throw_iscerror(ISC_STATUS * status_vector) +{ + std::string msg; + + get_iscerror_details(status_vector, msg); + throw firebird_soci_error(msg, status_vector); +} + +} // namespace firebird + +} // namespace details + +} // namespace soci diff --git a/src/backends/firebird/error-firebird.h b/src/backends/firebird/error-firebird.h new file mode 100644 index 0000000000..37ec3f5c58 --- /dev/null +++ b/src/backends/firebird/error-firebird.h @@ -0,0 +1,35 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, Rafal Bobrowski +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_FIREBIRD_ERROR_H_INCLUDED +#define SOCI_FIREBIRD_ERROR_H_INCLUDED + +#include "soci-firebird.h" +#include + +namespace soci +{ + +namespace details +{ + +namespace firebird +{ + +void SOCI_FIREBIRD_DECL get_iscerror_details(ISC_STATUS * status_vector, std::string &msg); + +bool SOCI_FIREBIRD_DECL check_iscerror(ISC_STATUS const * status_vector, long errNum); + +void SOCI_FIREBIRD_DECL throw_iscerror(ISC_STATUS * status_vector); + +} // namespace firebird + +} // namespace details + +} // namespace soci + +#endif // SOCI_FIREBIRD_ERROR_H_INCLUDED diff --git a/src/backends/firebird/factory.cpp b/src/backends/firebird/factory.cpp new file mode 100644 index 0000000000..cea362b81d --- /dev/null +++ b/src/backends/firebird/factory.cpp @@ -0,0 +1,36 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, Rafal Bobrowski +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_FIREBIRD_SOURCE +#include "soci-firebird.h" +#include + +using namespace soci; + +firebird_session_backend * firebird_backend_factory::make_session( + connection_parameters const & parameters) const +{ + return new firebird_session_backend(parameters); +} + +firebird_backend_factory const soci::firebird; + +extern "C" +{ + +// for dynamic backend loading +SOCI_FIREBIRD_DECL backend_factory const * factory_firebird() +{ + return &soci::firebird; +} + +SOCI_FIREBIRD_DECL void register_factory_firebird() +{ + soci::dynamic_backends::register_backend("firebird", soci::firebird); +} + +} // extern "C" diff --git a/src/backends/firebird/row-id.cpp b/src/backends/firebird/row-id.cpp new file mode 100644 index 0000000000..d791c9e284 --- /dev/null +++ b/src/backends/firebird/row-id.cpp @@ -0,0 +1,21 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, Rafal Bobrowski +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_FIREBIRD_SOURCE +#include "soci-firebird.h" + +using namespace soci; + +firebird_rowid_backend::firebird_rowid_backend(firebird_session_backend & /* session */) +{ + // Unsupported in Firebird backend + throw soci_error("RowIDs are not supported"); +} + +firebird_rowid_backend::~firebird_rowid_backend() +{ +} diff --git a/src/backends/firebird/session.cpp b/src/backends/firebird/session.cpp new file mode 100644 index 0000000000..8b353909c5 --- /dev/null +++ b/src/backends/firebird/session.cpp @@ -0,0 +1,375 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, Rafal Bobrowski +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_FIREBIRD_SOURCE +#include "soci-firebird.h" +#include "error-firebird.h" +#include "session.h" +#include +#include +#include +#include + +using namespace soci; +using namespace soci::details::firebird; + +namespace +{ + +// Helpers of explodeISCConnectString() for reading words from a string. "Word" +// here is defined very loosely as just a sequence of non-space characters. +// +// All these helper functions update the input iterator to point to the first +// character not consumed by them. + +// Advance the input iterator until the first non-space character or end of the +// string. +void skipWhiteSpace(std::string::const_iterator& i, std::string::const_iterator const &end) +{ + std::locale const loc; + for (; i != end; ++i) + { + if (!std::isspace(*i, loc)) + break; + } +} + +// Return the string of all characters until the first space or the specified +// delimiter. +// +// Throws if the first non-space character after the end of the word is not the +// delimiter. However just returns en empty string, without throwing, if +// nothing is left at all in the string except for white space. +std::string +getWordUntil(std::string const &s, std::string::const_iterator &i, char delim) +{ + std::string::const_iterator const end = s.end(); + skipWhiteSpace(i, end); + + // We need to handle this case specially because it's not an error if + // nothing at all remains in the string. But if anything does remain, then + // we must have the delimiter. + if (i == end) + return std::string(); + + // Simply put anything until the delimiter into the word, stopping at the + // first white space character. + std::string word; + std::locale const loc; + for (; i != end; ++i) + { + if (*i == delim) + break; + + if (std::isspace(*i, loc)) + { + skipWhiteSpace(i, end); + if (i == end || *i != delim) + { + std::ostringstream os; + os << "Expected '" << delim << "' at position " + << (i - s.begin() + 1) + << " in Firebird connection string \"" + << s << "\"."; + + throw soci_error(os.str()); + } + + break; + } + + word += *i; + } + + if (i == end) + { + std::ostringstream os; + os << "Expected '" << delim + << "' not found before the end of the string " + << "in Firebird connection string \"" + << s << "\"."; + + throw soci_error(os.str()); + } + + ++i; // Skip the delimiter itself. + + return word; +} + +// Return a possibly quoted word, i.e. either just a sequence of non-space +// characters or everything inside a double-quoted string. +// +// Throws if the word is quoted and the closing quote is not found. However +// doesn't throw, just returns an empty string if there is nothing left. +std::string +getPossiblyQuotedWord(std::string const &s, std::string::const_iterator &i) +{ + std::string::const_iterator const end = s.end(); + skipWhiteSpace(i, end); + + std::string word; + + if (i != end && *i == '"') + { + for (;;) + { + if (++i == end) + { + std::ostringstream os; + os << "Expected '\"' not found before the end of the string " + "in Firebird connection string \"" + << s << "\"."; + + throw soci_error(os.str()); + } + + if (*i == '"') + { + ++i; + break; + } + + word += *i; + } + } + else // Not quoted. + { + std::locale const loc; + for (; i != end; ++i) + { + if (std::isspace(*i, loc)) + break; + + word += *i; + } + } + + return word; +} + +// retrieves parameters from the uniform connect string which is supposed to be +// in the form "key=value[ key2=value2 ...]" and the values may be quoted to +// allow including spaces into them. Notice that currently there is no way to +// include both a space and a double quote in a value. +std::map +explodeISCConnectString(std::string const &connectString) +{ + std::map parameters; + + std::string key, value; + for (std::string::const_iterator i = connectString.begin(); ; ) + { + key = getWordUntil(connectString, i, '='); + if (key.empty()) + break; + + value = getPossiblyQuotedWord(connectString, i); + + parameters.insert(std::pair(key, value)); + } + + return parameters; +} + +// extracts given parameter from map previusly build with explodeISCConnectString +bool getISCConnectParameter(std::map const & m, std::string const & key, + std::string & value) +{ + std::map :: const_iterator i; + value.clear(); + + i = m.find(key); + + if (i != m.end()) + { + value = i->second; + return true; + } + else + { + return false; + } +} + +} // namespace anonymous + +firebird_session_backend::firebird_session_backend( + connection_parameters const & parameters) : dbhp_(0), trhp_(0) + , decimals_as_strings_(false) +{ + // extract connection parameters + std::map + params(explodeISCConnectString(parameters.get_connect_string())); + + ISC_STATUS stat[stat_size]; + std::string param; + + // preparing connection options + if (getISCConnectParameter(params, "user", param)) + { + setDPBOption(isc_dpb_user_name, param); + } + + if (getISCConnectParameter(params, "password", param)) + { + setDPBOption(isc_dpb_password, param); + } + + if (getISCConnectParameter(params, "role", param)) + { + setDPBOption(isc_dpb_sql_role_name, param); + } + + if (getISCConnectParameter(params, "charset", param)) + { + setDPBOption(isc_dpb_lc_ctype, param); + } + + if (getISCConnectParameter(params, "service", param) == false) + { + throw soci_error("Service name not specified."); + } + + // connecting data base + if (isc_attach_database(stat, static_cast(param.size()), + const_cast(param.c_str()), &dbhp_, + static_cast(dpb_.size()), const_cast(dpb_.c_str()))) + { + throw_iscerror(stat); + } + + if (getISCConnectParameter(params, "decimals_as_strings", param)) + { + decimals_as_strings_ = param == "1" || param == "Y" || param == "y"; + } + // starting transaction + begin(); +} + + +void firebird_session_backend::begin() +{ + // Transaction is always started in ctor, because Firebird can't work + // without active transaction. + // Transaction will be automatically commited in cleanUp method. + if (trhp_ == 0) + { + ISC_STATUS stat[stat_size]; + if (isc_start_transaction(stat, &trhp_, 1, &dbhp_, 0, NULL)) + { + throw_iscerror(stat); + } + } +} + +firebird_session_backend::~firebird_session_backend() +{ + cleanUp(); +} + +void firebird_session_backend::setDPBOption(int const option, std::string const & value) +{ + + if (dpb_.size() == 0) + { + dpb_.append(1, static_cast(isc_dpb_version1)); + } + + // now we are adding new option + dpb_.append(1, static_cast(option)); + dpb_.append(1, static_cast(value.size())); + dpb_.append(value); +} + +void firebird_session_backend::commit() +{ + ISC_STATUS stat[stat_size]; + + if (trhp_ != 0) + { + if (isc_commit_transaction(stat, &trhp_)) + { + throw_iscerror(stat); + } + + trhp_ = 0; + } + +#ifndef SOCI_FIREBIRD_NORESTARTTRANSACTION + begin(); +#endif + +} + +void firebird_session_backend::rollback() +{ + ISC_STATUS stat[stat_size]; + + if (trhp_ != 0) + { + if (isc_rollback_transaction(stat, &trhp_)) + { + throw_iscerror(stat); + } + + trhp_ = 0; + } + +#ifndef SOCI_FIREBIRD_NORESTARTTRANSACTION + begin(); +#endif + +} + +void firebird_session_backend::cleanUp() +{ + ISC_STATUS stat[stat_size]; + + // at the end of session our transaction is finally commited. + if (trhp_ != 0) + { + if (isc_commit_transaction(stat, &trhp_)) + { + throw_iscerror(stat); + } + + trhp_ = 0; + } + + if (isc_detach_database(stat, &dbhp_)) + { + throw_iscerror(stat); + } + + dbhp_ = 0L; +} + +bool firebird_session_backend::get_next_sequence_value( + session & s, std::string const & sequence, long & value) +{ + // We could use isq_execute2() directly but this is even simpler. + s << "select next value for " + sequence + " from rdb$database", + into(value); + + return true; +} + +firebird_statement_backend * firebird_session_backend::make_statement_backend() +{ + return new firebird_statement_backend(*this); +} + +firebird_rowid_backend * firebird_session_backend::make_rowid_backend() +{ + return new firebird_rowid_backend(*this); +} + +firebird_blob_backend * firebird_session_backend::make_blob_backend() +{ + return new firebird_blob_backend(*this); +} diff --git a/src/backends/firebird/soci-firebird.h b/src/backends/firebird/soci-firebird.h new file mode 100644 index 0000000000..4435ccea3d --- /dev/null +++ b/src/backends/firebird/soci-firebird.h @@ -0,0 +1,349 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, Rafal Bobrowski +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// + +#ifndef SOCI_FIREBIRD_H_INCLUDED +#define SOCI_FIREBIRD_H_INCLUDED + +#ifdef _WIN32 +# ifdef SOCI_DLL +# ifdef SOCI_FIREBIRD_SOURCE +# define SOCI_FIREBIRD_DECL __declspec(dllexport) +# else +# define SOCI_FIREBIRD_DECL __declspec(dllimport) +# endif // SOCI_DLL +# endif // SOCI_FIREBIRD_SOURCE +#endif // _WIN32 + +// +// If SOCI_FIREBIRD_DECL isn't defined yet define it now +#ifndef SOCI_FIREBIRD_DECL +# define SOCI_FIREBIRD_DECL +#endif + +#ifdef _WIN32 +#include // To understand and/or/not on MSVC9 +#endif +#include +#include // FireBird +#include +#include +#include + +namespace soci +{ + +std::size_t const stat_size = 20; + +// size of buffer for error messages. All examples use this value. +// Anyone knows, where it is stated that 512 bytes is enough ? +std::size_t const SOCI_FIREBIRD_ERRMSG = 512; + +class SOCI_FIREBIRD_DECL firebird_soci_error : public soci_error +{ +public: + firebird_soci_error(std::string const & msg, + ISC_STATUS const * status = 0); + + ~firebird_soci_error() throw() {}; + + std::vector status_; +}; + +enum BuffersType +{ + eStandard, eVector +}; + +struct firebird_statement_backend; +struct firebird_standard_into_type_backend : details::standard_into_type_backend +{ + firebird_standard_into_type_backend(firebird_statement_backend &st) + : statement_(st), buf_(NULL) + {} + + virtual void define_by_pos(int &position, + void *data, details::exchange_type type); + + virtual void pre_fetch(); + virtual void post_fetch(bool gotData, bool calledFromFetch, + indicator *ind); + + virtual void clean_up(); + + firebird_statement_backend &statement_; + virtual void exchangeData(); + + void *data_; + details::exchange_type type_; + int position_; + + char *buf_; + short indISCHolder_; +}; + +struct firebird_vector_into_type_backend : details::vector_into_type_backend +{ + firebird_vector_into_type_backend(firebird_statement_backend &st) + : statement_(st), buf_(NULL) + {} + + virtual void define_by_pos(int &position, + void *data, details::exchange_type type); + + virtual void pre_fetch(); + virtual void post_fetch(bool gotData, indicator *ind); + + virtual void resize(std::size_t sz); + virtual std::size_t size(); + + virtual void clean_up(); + + firebird_statement_backend &statement_; + virtual void exchangeData(std::size_t row); + + void *data_; + details::exchange_type type_; + int position_; + + char *buf_; + short indISCHolder_; +}; + +struct firebird_standard_use_type_backend : details::standard_use_type_backend +{ + firebird_standard_use_type_backend(firebird_statement_backend &st) + : statement_(st), buf_(NULL), indISCHolder_(0) + {} + + virtual void bind_by_pos(int &position, + void *data, details::exchange_type type, bool readOnly); + virtual void bind_by_name(std::string const &name, + void *data, details::exchange_type type, bool readOnly); + + virtual void pre_use(indicator const *ind); + virtual void post_use(bool gotData, indicator *ind); + + virtual void clean_up(); + + firebird_statement_backend &statement_; + virtual void exchangeData(); + + void *data_; + details::exchange_type type_; + int position_; + + char *buf_; + short indISCHolder_; +}; + +struct firebird_vector_use_type_backend : details::vector_use_type_backend +{ + firebird_vector_use_type_backend(firebird_statement_backend &st) + : statement_(st), inds_(NULL), buf_(NULL), indISCHolder_(0) + {} + + virtual void bind_by_pos(int &position, + void *data, details::exchange_type type); + virtual void bind_by_name(std::string const &name, + void *data, details::exchange_type type); + + virtual void pre_use(indicator const *ind); + + virtual std::size_t size(); + + virtual void clean_up(); + + firebird_statement_backend &statement_; + virtual void exchangeData(std::size_t row); + + void *data_; + details::exchange_type type_; + int position_; + indicator const *inds_; + + char *buf_; + short indISCHolder_; +}; + +struct firebird_session_backend; +struct firebird_statement_backend : details::statement_backend +{ + firebird_statement_backend(firebird_session_backend &session); + + virtual void alloc(); + virtual void clean_up(); + virtual void prepare(std::string const &query, + details::statement_type eType); + + virtual exec_fetch_result execute(int number); + virtual exec_fetch_result fetch(int number); + + virtual long long get_affected_rows(); + virtual int get_number_of_rows(); + + virtual std::string rewrite_for_procedure_call(std::string const &query); + + virtual int prepare_for_describe(); + virtual void describe_column(int colNum, data_type &dtype, + std::string &columnName); + + virtual firebird_standard_into_type_backend * make_into_type_backend(); + virtual firebird_standard_use_type_backend * make_use_type_backend(); + virtual firebird_vector_into_type_backend * make_vector_into_type_backend(); + virtual firebird_vector_use_type_backend * make_vector_use_type_backend(); + + firebird_session_backend &session_; + + isc_stmt_handle stmtp_; + XSQLDA * sqldap_; + XSQLDA * sqlda2p_; + + bool boundByName_; + bool boundByPos_; + + friend struct firebird_vector_into_type_backend; + friend struct firebird_standard_into_type_backend; + friend struct firebird_vector_use_type_backend; + friend struct firebird_standard_use_type_backend; + +protected: + int rowsFetched_; + bool endOfRowSet_; + + long long rowsAffectedBulk_; // number of rows affected by the last bulk operation + + virtual void exchangeData(bool gotData, int row); + virtual void prepareSQLDA(XSQLDA ** sqldap, int size = 10); + virtual void rewriteQuery(std::string const & query, + std::vector & buffer); + virtual void rewriteParameters(std::string const & src, + std::vector & dst); + + BuffersType intoType_; + BuffersType useType_; + + std::vector > inds_; + std::vector intos_; + std::vector uses_; + + // named parameters + std::map names_; + + bool procedure_; +}; + +struct firebird_rowid_backend : details::rowid_backend +{ + firebird_rowid_backend(firebird_session_backend &session); + + ~firebird_rowid_backend(); +}; + +struct firebird_blob_backend : details::blob_backend +{ + firebird_blob_backend(firebird_session_backend &session); + + ~firebird_blob_backend(); + + virtual std::size_t get_len(); + virtual std::size_t read(std::size_t offset, char *buf, + std::size_t toRead); + virtual std::size_t write(std::size_t offset, char const *buf, + std::size_t toWrite); + virtual std::size_t append(char const *buf, std::size_t toWrite); + virtual void trim(std::size_t newLen); + + firebird_session_backend &session_; + + virtual void save(); + virtual void assign(ISC_QUAD const & bid) + { + cleanUp(); + + bid_ = bid; + from_db_ = true; + } + + // BLOB id from in database + ISC_QUAD bid_; + + // BLOB id was fetched from database (true) + // or this is new BLOB + bool from_db_; + + // BLOB handle + isc_blob_handle bhp_; + +protected: + + virtual void open(); + virtual long getBLOBInfo(); + virtual void load(); + virtual void writeBuffer(std::size_t offset, char const * buf, + std::size_t toWrite); + virtual void cleanUp(); + + // buffer for BLOB data + std::vector data_; + + bool loaded_; + long max_seg_size_; +}; + +struct firebird_session_backend : details::session_backend +{ + firebird_session_backend(connection_parameters const & parameters); + + ~firebird_session_backend(); + + virtual void begin(); + virtual void commit(); + virtual void rollback(); + + virtual bool get_next_sequence_value(session & s, + std::string const & sequence, long & value); + + virtual std::string get_backend_name() const { return "firebird"; } + + void cleanUp(); + + virtual firebird_statement_backend * make_statement_backend(); + virtual firebird_rowid_backend * make_rowid_backend(); + virtual firebird_blob_backend * make_blob_backend(); + + virtual void setDPBOption(int const option, std::string const & value); + + bool get_option_decimals_as_strings() { return decimals_as_strings_; } + + isc_db_handle dbhp_; + isc_tr_handle trhp_; + std::string dpb_; + bool decimals_as_strings_; +}; + +struct firebird_backend_factory : backend_factory +{ + firebird_backend_factory() {} + virtual firebird_session_backend * make_session( + connection_parameters const & parameters) const; +}; + +extern SOCI_FIREBIRD_DECL firebird_backend_factory const firebird; + +extern "C" +{ + +// for dynamic backend loading +SOCI_FIREBIRD_DECL backend_factory const * factory_firebird(); +SOCI_FIREBIRD_DECL void register_factory_firebird(); + +} // extern "C" + +} // namespace soci + +#endif // SOCI_FIREBIRD_H_INCLUDED diff --git a/src/backends/firebird/standard-into-type.cpp b/src/backends/firebird/standard-into-type.cpp new file mode 100644 index 0000000000..98a4c7ce87 --- /dev/null +++ b/src/backends/firebird/standard-into-type.cpp @@ -0,0 +1,147 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, Rafal Bobrowski +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_FIREBIRD_SOURCE +#include "soci-firebird.h" +#include "common.h" +#include + +using namespace soci; +using namespace soci::details; +using namespace soci::details::firebird; + +void firebird_standard_into_type_backend::define_by_pos( + int & position, void * data, exchange_type type) +{ + position_ = position-1; + data_ = data; + type_ = type; + + ++position; + + statement_.intoType_ = eStandard; + statement_.intos_.push_back(static_cast(this)); + + XSQLVAR *var = statement_.sqldap_->sqlvar+position_; + + buf_ = allocBuffer(var); + var->sqldata = buf_; + var->sqlind = &indISCHolder_; +} + +void firebird_standard_into_type_backend::pre_fetch() +{ + // nothing to do +} + +void firebird_standard_into_type_backend::post_fetch( + bool gotData, bool calledFromFetch, indicator * ind) +{ + if (calledFromFetch && (gotData == false)) + { + // this is a normal end-of-rowset condition, + // no need to set anything (fetch() will return false) + return; + } + + if (gotData) + { + if (i_null == statement_.inds_[position_][0] && NULL == ind) + { + throw soci_error("Null value fetched and no indicator defined."); + } + else if (NULL != ind) + { + *ind = statement_.inds_[position_][0]; + } + } +} + + +void firebird_standard_into_type_backend::exchangeData() +{ + XSQLVAR *var = statement_.sqldap_->sqlvar+position_; + + switch (type_) + { + // simple cases + case x_char: + *reinterpret_cast(data_) = getTextParam(var)[0]; + break; + case x_short: + { + short t = from_isc(var); + *reinterpret_cast(data_) = t; + } + break; + case x_integer: + { + int t = from_isc(var); + *reinterpret_cast(data_) = t; + } + break; + case x_long_long: + { + long long t = from_isc(var); + *reinterpret_cast(data_) = t; + } + break; + case x_double: + { + double t = from_isc(var); + *reinterpret_cast(data_) = t; + } + break; + + // cases that require adjustments and buffer management + case x_stdstring: + *(reinterpret_cast(data_)) = getTextParam(var); + break; + case x_stdtm: + tmDecode(var->sqltype, + buf_, static_cast(data_)); + + // isc_decode_timestamp() used by tmDecode() incorrectly sets + // tm_isdst to 0 in the struct that it creates, see + // http://tracker.firebirdsql.org/browse/CORE-3877, work around it + // by pretending the DST is actually unknown. + static_cast(data_)->tm_isdst = -1; + break; + + // cases that require special handling + case x_blob: + { + blob *tmp = reinterpret_cast(data_); + + firebird_blob_backend *blob = + dynamic_cast(tmp->get_backend()); + + if (0 == blob) + { + throw soci_error("Can't get Firebid BLOB BackEnd"); + } + + blob->assign(*reinterpret_cast(buf_)); + } + break; + default: + throw soci_error("Into element used with non-supported type."); + } // switch +} + +void firebird_standard_into_type_backend::clean_up() +{ + if (buf_ != NULL) + { + delete [] buf_; + buf_ = NULL; + } + std::vector::iterator it = + std::find(statement_.intos_.begin(), statement_.intos_.end(), this); + if (it != statement_.intos_.end()) + statement_.intos_.erase(it); +} diff --git a/src/backends/firebird/standard-use-type.cpp b/src/backends/firebird/standard-use-type.cpp new file mode 100644 index 0000000000..58a2fd80ad --- /dev/null +++ b/src/backends/firebird/standard-use-type.cpp @@ -0,0 +1,182 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, Rafal Bobrowski +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_FIREBIRD_SOURCE +#include "soci-firebird.h" +#include "common.h" +#include + +using namespace soci; +using namespace soci::details; +using namespace soci::details::firebird; + +void firebird_standard_use_type_backend::bind_by_pos( + int & position, void * data, exchange_type type, bool /* readOnly */) +{ + if (statement_.boundByName_) + { + throw soci_error( + "Binding for use elements must be either by position or by name."); + } + + position_ = position-1; + data_ = data; + type_ = type; + + ++position; + + statement_.useType_ = eStandard; + statement_.uses_.push_back(static_cast(this)); + + XSQLVAR *var = statement_.sqlda2p_->sqlvar+position_; + + buf_ = allocBuffer(var); + var->sqldata = buf_; + var->sqlind = &indISCHolder_; + + statement_.boundByPos_ = true; +} + +void firebird_standard_use_type_backend::bind_by_name( + std::string const & name, void * data, + exchange_type type, bool /* readOnly */) +{ + if (statement_.boundByPos_) + { + throw soci_error( + "Binding for use elements must be either by position or by name."); + } + + std::map :: iterator idx = + statement_.names_.find(name); + + if (idx == statement_.names_.end()) + { + throw soci_error("Missing use element for bind by name (" + name + ")"); + } + + position_ = idx->second; + data_ = data; + type_ = type; + + statement_.useType_ = eStandard; + statement_.uses_.push_back(static_cast(this)); + + XSQLVAR *var = statement_.sqlda2p_->sqlvar+position_; + + buf_ = allocBuffer(var); + var->sqldata = buf_; + var->sqlind = &indISCHolder_; + + statement_.boundByName_ = true; +} + +void firebird_standard_use_type_backend::pre_use(indicator const * ind) +{ + indISCHolder_ = 0; + if (ind) + { + switch (*ind) + { + case i_null: + indISCHolder_ = -1; + break; + case i_ok: + indISCHolder_ = 0; + break; + default: + throw soci_error("Unsupported indicator value."); + } + } +} + +void firebird_standard_use_type_backend::exchangeData() +{ + XSQLVAR *var = statement_.sqlda2p_->sqlvar+position_; + + if (0 != indISCHolder_) + return; + + switch (type_) + { + case x_char: + setTextParam(static_cast(data_), 1, buf_, var); + break; + case x_short: + to_isc(data_, var); + break; + case x_integer: + to_isc(data_, var); + break; + case x_long_long: + to_isc(data_, var); + break; + case x_double: + to_isc(data_, var); + break; + + case x_stdstring: + { + std::string *tmp = static_cast(data_); + setTextParam(tmp->c_str(), tmp->size(), buf_, var); + } + break; + case x_stdtm: + tmEncode(var->sqltype, + static_cast(data_), buf_); + break; + + // cases that require special handling + case x_blob: + { + blob *tmp = static_cast(data_); + + firebird_blob_backend* blob = + dynamic_cast(tmp->get_backend()); + + if (NULL == blob) + { + throw soci_error("Can't get Firebid BLOB BackEnd"); + } + + blob->save(); + memcpy(buf_, &blob->bid_, var->sqllen); + } + break; + default: + throw soci_error("Use element used with non-supported type."); + } // switch +} + +void firebird_standard_use_type_backend::post_use( + bool /* gotData */, indicator * /* ind */) +{ + // TODO: Is it possible to have the bound element being overwritten + // by the database? + // If not, then nothing to do here, please remove this comment. + // If yes, then use the value of the readOnly parameter: + // - true: the given object should not be modified and the backend + // should detect if the modification was performed on the + // isolated buffer and throw an exception if the buffer was modified + // (this indicates logic error, because the user used const object + // and executed a query that attempted to modified it) + // - false: the modification should be propagated to the given object. + // ... +} + +void firebird_standard_use_type_backend::clean_up() +{ + if (buf_ != NULL) + { + delete [] buf_; + buf_ = NULL; + } + std::vector::iterator it = + std::find(statement_.uses_.begin(), statement_.uses_.end(), this); + if (it != statement_.uses_.end()) + statement_.uses_.erase(it); +} diff --git a/src/backends/firebird/statement.cpp b/src/backends/firebird/statement.cpp new file mode 100644 index 0000000000..e00d1461b9 --- /dev/null +++ b/src/backends/firebird/statement.cpp @@ -0,0 +1,724 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, Rafal Bobrowski +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_FIREBIRD_SOURCE +#include "soci-firebird.h" +#include "error-firebird.h" +#include +#include +#include + +using namespace soci; +using namespace soci::details; +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), endOfRowSet_(false), rowsAffectedBulk_(-1LL), + intoType_(eStandard), useType_(eStandard), procedure_(false) +{} + +void firebird_statement_backend::prepareSQLDA(XSQLDA ** sqldap, int size) +{ + if (*sqldap != NULL) + { + *sqldap = reinterpret_cast(realloc(*sqldap, XSQLDA_LENGTH(size))); + } + else + { + *sqldap = reinterpret_cast(malloc(XSQLDA_LENGTH(size))); + } + + (*sqldap)->sqln = size; + (*sqldap)->version = 1; +} + +void firebird_statement_backend::alloc() +{ + ISC_STATUS stat[stat_size]; + + if (isc_dsql_allocate_statement(stat, &session_.dbhp_, &stmtp_)) + { + throw_iscerror(stat); + } +} + +void firebird_statement_backend::clean_up() +{ + rowsAffectedBulk_ = -1LL; + + ISC_STATUS stat[stat_size]; + + if (stmtp_ != NULL) + { + if (isc_dsql_free_statement(stat, &stmtp_, DSQL_drop)) + { + throw_iscerror(stat); + } + stmtp_ = NULL; + } + + if (sqldap_ != NULL) + { + free(sqldap_); + sqldap_ = NULL; + } + + if (sqlda2p_ != NULL) + { + free(sqlda2p_); + sqlda2p_ = NULL; + } +} + +void firebird_statement_backend::rewriteParameters( + std::string const & src, std::vector & dst) +{ + std::vector::iterator dst_it = dst.begin(); + + // rewrite the query by transforming all named parameters into + // the Firebird question marks (:abc -> ?, etc.) + + enum { eNormal, eInQuotes, eInName } state = eNormal; + + std::string name; + int position = 0; + + for (std::string::const_iterator it = src.begin(), end = src.end(); + it != end; ++it) + { + switch (state) + { + case eNormal: + if (*it == '\'') + { + *dst_it++ = *it; + state = eInQuotes; + } + else if (*it == ':') + { + state = eInName; + } + else // regular character, stay in the same state + { + *dst_it++ = *it; + } + break; + case eInQuotes: + if (*it == '\'') + { + *dst_it++ = *it; + state = eNormal; + } + else // regular quoted character + { + *dst_it++ = *it; + } + break; + case eInName: + if (std::isalnum(*it) || *it == '_') + { + name += *it; + } + else // end of name + { + names_.insert(std::pair(name, position++)); + name.clear(); + *dst_it++ = '?'; + *dst_it++ = *it; + state = eNormal; + } + break; + } + } + + if (state == eInName) + { + names_.insert(std::pair(name, position++)); + *dst_it++ = '?'; + } + + *dst_it = '\0'; +} + +namespace +{ + int statementType(isc_stmt_handle stmt) + { + int stype; + int length; + char type_item[] = {isc_info_sql_stmt_type}; + char res_buffer[8]; + + ISC_STATUS stat[stat_size]; + + if (isc_dsql_sql_info(stat, &stmt, sizeof(type_item), + type_item, sizeof(res_buffer), res_buffer)) + { + throw_iscerror(stat); + } + + if (res_buffer[0] == isc_info_sql_stmt_type) + { + length = isc_vax_integer(res_buffer+1, 2); + stype = isc_vax_integer(res_buffer+3, length); + } + else + { + throw soci_error("Can't determine statement type."); + } + + return stype; + } +} + +void firebird_statement_backend::rewriteQuery( + std::string const &query, std::vector &buffer) +{ + // buffer for temporary query + std::vector tmpQuery; + std::vector::iterator qItr; + + // buffer for query with named parameters changed to standard ones + std::vector rewQuery(query.size() + 1); + + // take care of named parameters in original query + rewriteParameters(query, rewQuery); + + std::string const prefix("execute procedure "); + std::string const prefix2("select * from "); + + // for procedures, we are preparing statement to determine + // type of procedure. + if (procedure_) + { + tmpQuery.resize(prefix.size() + rewQuery.size()); + qItr = tmpQuery.begin(); + std::copy(prefix.begin(), prefix.end(), qItr); + qItr += prefix.size(); + } + else + { + tmpQuery.resize(rewQuery.size()); + qItr = tmpQuery.begin(); + } + + // prepare temporary query + std::copy(rewQuery.begin(), rewQuery.end(), qItr); + + // preparing buffers for output parameters + if (sqldap_ == NULL) + { + prepareSQLDA(&sqldap_); + } + + ISC_STATUS stat[stat_size]; + isc_stmt_handle tmpStmtp = 0; + + // allocate temporary statement to determine its type + if (isc_dsql_allocate_statement(stat, &session_.dbhp_, &tmpStmtp)) + { + throw_iscerror(stat); + } + + // prepare temporary statement + if (isc_dsql_prepare(stat, &(session_.trhp_), &tmpStmtp, 0, + &tmpQuery[0], SQL_DIALECT_V6, sqldap_)) + { + throw_iscerror(stat); + } + + // get statement type + int stType = statementType(tmpStmtp); + + // free temporary prepared statement + if (isc_dsql_free_statement(stat, &tmpStmtp, DSQL_drop)) + { + throw_iscerror(stat); + } + + // take care of special cases + if (procedure_) + { + // for procedures that return values, we need to use correct syntax + if (sqldap_->sqld != 0) + { + // this is "select" procedure, so we have to change syntax + buffer.resize(prefix2.size() + rewQuery.size()); + qItr = buffer.begin(); + std::copy(prefix2.begin(), prefix2.end(), qItr); + qItr += prefix2.size(); + std::copy(rewQuery.begin(), rewQuery.end(), qItr); + + // that won't be needed anymore + procedure_ = false; + + return; + } + } + else + { + // this is not procedure, so syntax is ok except for named + // parameters in ddl + if (stType == isc_info_sql_stmt_ddl) + { + // this statement is a DDL - we can't rewrite named parameters + // so, we will use original query + buffer.resize(query.size() + 1); + std::copy(query.begin(), query.end(), buffer.begin()); + + // that won't be needed anymore + procedure_ = false; + + return; + } + } + + // here we know, that temporary query is OK, so we leave it as is + buffer.resize(tmpQuery.size()); + std::copy(tmpQuery.begin(), tmpQuery.end(), buffer.begin()); + + // that won't be needed anymore + procedure_ = false; +} + +void firebird_statement_backend::prepare(std::string const & query, + statement_type /* eType */) +{ + //std::cerr << "prepare: query=" << query << std::endl; + // clear named parametes + names_.clear(); + + std::vector queryBuffer; + + // modify query's syntax and prepare buffer for use with + // firebird's api + rewriteQuery(query, queryBuffer); + + ISC_STATUS stat[stat_size]; + + // prepare real statement + if (isc_dsql_prepare(stat, &(session_.trhp_), &stmtp_, 0, + &queryBuffer[0], SQL_DIALECT_V6, sqldap_)) + { + throw_iscerror(stat); + } + + if (sqldap_->sqln < sqldap_->sqld) + { + // sqlda is too small for all columns. it must be reallocated + prepareSQLDA(&sqldap_, sqldap_->sqld); + + if (isc_dsql_describe(stat, &stmtp_, SQL_DIALECT_V6, sqldap_)) + { + throw_iscerror(stat); + } + } + + // preparing input parameters + if (sqlda2p_ == NULL) + { + prepareSQLDA(&sqlda2p_); + } + + if (isc_dsql_describe_bind(stat, &stmtp_, SQL_DIALECT_V6, sqlda2p_)) + { + throw_iscerror(stat); + } + + if (sqlda2p_->sqln < sqlda2p_->sqld) + { + // sqlda is too small for all columns. it must be reallocated + prepareSQLDA(&sqlda2p_, sqlda2p_->sqld); + + if (isc_dsql_describe_bind(stat, &stmtp_, SQL_DIALECT_V6, sqlda2p_)) + { + throw_iscerror(stat); + } + } + + // prepare buffers for indicators + inds_.clear(); + inds_.resize(sqldap_->sqld); + + // reset types of into buffers + intoType_ = eStandard; + intos_.resize(0); + + // reset types of use buffers + useType_ = eStandard; + uses_.resize(0); +} + + +namespace +{ + void checkSize(std::size_t actual, std::size_t expected, + std::string const & name) + { + if (actual != expected) + { + std::ostringstream msg; + msg << "Incorrect number of " << name << " variables. " + << "Expected " << expected << ", got " << actual; + throw soci_error(msg.str()); + } + } +} + +statement_backend::exec_fetch_result +firebird_statement_backend::execute(int number) +{ + ISC_STATUS stat[stat_size]; + XSQLDA *t = NULL; + + std::size_t usize = uses_.size(); + + // do we have enough into variables ? + checkSize(intos_.size(), sqldap_->sqld, "into"); + // do we have enough use variables ? + checkSize(usize, sqlda2p_->sqld, "use"); + + // do we have parameters ? + if (sqlda2p_->sqld) + { + t = sqlda2p_; + + if (useType_ == eStandard) + { + for (std::size_t col=0; col(uses_[col])->exchangeData(); + } + } + } + + // make sure there is no active cursor + if (isc_dsql_free_statement(stat, &stmtp_, DSQL_close)) + { + // ignore attempt to close already closed cursor + if (check_iscerror(stat, isc_dsql_cursor_close_err) == false) + { + throw_iscerror(stat); + } + } + + if (useType_ == eVector) + { + long long rowsAffectedBulkTemp = 0; + + // Here we have to explicitly loop to achieve the + // effect of inserting or updating with vector use elements. + std::size_t rows = static_cast(uses_[0])->size(); + for (std::size_t row=0; row < rows; ++row) + { + // first we have to prepare input parameters + for (std::size_t col=0; col(uses_[col])->exchangeData(row); + } + + // then execute query + if (isc_dsql_execute(stat, &session_.trhp_, &stmtp_, SQL_DIALECT_V6, t)) + { + // preserve the number of rows affected so far. + rowsAffectedBulk_ = rowsAffectedBulkTemp; + throw_iscerror(stat); + } + else + { + rowsAffectedBulkTemp += get_affected_rows(); + } + // soci does not allow bulk insert/update and bulk select operations + // in same query. So here, we know that into elements are not + // vectors. So, there is no need to fetch data here. + } + rowsAffectedBulk_ = rowsAffectedBulkTemp; + } + else + { + // use elements aren't vectors + if (isc_dsql_execute(stat, &session_.trhp_, &stmtp_, SQL_DIALECT_V6, t)) + { + throw_iscerror(stat); + } + } + + // Successfully re-executing the statement must reset the "end of rowset" + // flag, we might be able to fetch data again now. + endOfRowSet_ = false; + + if (sqldap_->sqld) + { + // query may return some data + if (number > 0) + { + // number contains size of input variables, so we may fetch() data here + return fetch(number); + } + else + { + // execute(0) was meant to only perform the query + return ef_success; + } + } + else + { + // query can't return any data + return ef_no_data; + } +} + +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) + { + inds_[i].resize(number > 0 ? number : 1); + } + + // Here we have to explicitly loop to achieve the effect of fetching + // vector into elements. After each fetch, we have to exchange data + // with into buffers. + rowsFetched_ = 0; + for (int i = 0; i < number; ++i) + { + long fetch_stat = isc_dsql_fetch(stat, &stmtp_, SQL_DIALECT_V6, sqldap_); + + // there is more data to read + if (fetch_stat == 0) + { + ++rowsFetched_; + exchangeData(true, i); + } + 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 + } + } // for + + return ef_success; +} + +// here we put data fetched from database into user buffers +void firebird_statement_backend::exchangeData(bool gotData, int row) +{ + if (gotData) + { + for (size_t i = 0; i < static_cast(sqldap_->sqld); ++i) + { + // first save indicators + if (((sqldap_->sqlvar+i)->sqltype & 1) == 0) + { + // there is no indicator for this column + inds_[i][row] = i_ok; + } + else if (*((sqldap_->sqlvar+i)->sqlind) == 0) + { + inds_[i][row] = i_ok; + } + else if (*((sqldap_->sqlvar+i)->sqlind) == -1) + { + inds_[i][row] = i_null; + } + else + { + throw soci_error("Unknown state in firebird_statement_backend::exchangeData()"); + } + + // then deal with data + if (inds_[i][row] != i_null) + { + if (intoType_ == eVector) + { + static_cast( + intos_[i])->exchangeData(row); + } + else + { + static_cast( + intos_[i])->exchangeData(); + } + } + } + } +} + +long long firebird_statement_backend::get_affected_rows() +{ + if (rowsAffectedBulk_ >= 0) + { + return rowsAffectedBulk_; + } + + ISC_STATUS_ARRAY stat; + char type_item[] = { isc_info_sql_records }; + char res_buffer[256]; + + if (isc_dsql_sql_info(stat, &stmtp_, sizeof(type_item), type_item, + sizeof(res_buffer), res_buffer)) + { + throw_iscerror(stat); + } + + // We must get back a isc_info_sql_records block, that we parse below, + // followed by isc_info_end. + if (res_buffer[0] != isc_info_sql_records) + { + throw soci_error("Can't determine the number of affected rows"); + } + + char* sql_rec_buf = res_buffer + 1; + const int length = isc_vax_integer(sql_rec_buf, 2); + sql_rec_buf += 2; + + if (sql_rec_buf[length] != isc_info_end) + { + throw soci_error("Unexpected isc_info_sql_records return format"); + } + + // Examine the 4 sub-blocks each of which has a header indicating the block + // type, its value length in bytes and the value itself. + long long row_count = 0; + + for ( char* p = sql_rec_buf; !row_count && p < sql_rec_buf + length; ) + { + switch (*p++) + { + case isc_info_req_select_count: + case isc_info_req_insert_count: + case isc_info_req_update_count: + case isc_info_req_delete_count: + { + int len = isc_vax_integer(p, 2); + p += 2; + + row_count += isc_vax_integer(p, len); + p += len; + } + break; + + case isc_info_end: + break; + + default: + throw soci_error("Unknown record counter"); + } + } + + return row_count; +} + +int firebird_statement_backend::get_number_of_rows() +{ + return rowsFetched_; +} + +std::string firebird_statement_backend::rewrite_for_procedure_call( + std::string const &query) +{ + procedure_ = true; + return query; +} + +int firebird_statement_backend::prepare_for_describe() +{ + return static_cast(sqldap_->sqld); +} + +void firebird_statement_backend::describe_column(int colNum, + data_type & type, std::string & columnName) +{ + XSQLVAR * var = sqldap_->sqlvar+(colNum-1); + + columnName.assign(var->aliasname, var->aliasname_length); + + switch (var->sqltype & ~1) + { + case SQL_TEXT: + case SQL_VARYING: + type = dt_string; + break; + case SQL_TYPE_DATE: + case SQL_TYPE_TIME: + case SQL_TIMESTAMP: + type = dt_date; + break; + case SQL_FLOAT: + case SQL_DOUBLE: + type = dt_double; + break; + case SQL_SHORT: + case SQL_LONG: + if (var->sqlscale < 0) + { + if (session_.get_option_decimals_as_strings()) + type = dt_string; + else + type = dt_double; + } + else + { + type = dt_integer; + } + break; + case SQL_INT64: + if (var->sqlscale < 0) + { + if (session_.get_option_decimals_as_strings()) + type = dt_string; + else + type = dt_double; + } + else + { + type = dt_long_long; + } + break; + /* case SQL_BLOB: + case SQL_ARRAY:*/ + default: + std::ostringstream msg; + msg << "Type of column ["<< colNum << "] \"" << columnName + << "\" is not supported for dynamic queries"; + throw soci_error(msg.str()); + break; + } +} + +firebird_standard_into_type_backend * firebird_statement_backend::make_into_type_backend() +{ + return new firebird_standard_into_type_backend(*this); +} + +firebird_standard_use_type_backend * firebird_statement_backend::make_use_type_backend() +{ + return new firebird_standard_use_type_backend(*this); +} + +firebird_vector_into_type_backend * firebird_statement_backend::make_vector_into_type_backend() +{ + return new firebird_vector_into_type_backend(*this); +} + +firebird_vector_use_type_backend * firebird_statement_backend::make_vector_use_type_backend() +{ + return new firebird_vector_use_type_backend(*this); +} diff --git a/src/backends/firebird/test/CMakeLists.txt b/src/backends/firebird/test/CMakeLists.txt new file mode 100644 index 0000000000..ecfac884a0 --- /dev/null +++ b/src/backends/firebird/test/CMakeLists.txt @@ -0,0 +1,15 @@ +############################################################################### +# +# This file is part of CMake configuration for SOCI library +# +# Copyright (C) 2013 Viacheslav Naydenov +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) +# +############################################################################### + +soci_backend_test( + BACKEND Firebird + SOURCE test-firebird.cpp + CONNSTR "dummy") diff --git a/src/backends/firebird/test/Makefile.basic b/src/backends/firebird/test/Makefile.basic new file mode 100644 index 0000000000..b2313ad07f --- /dev/null +++ b/src/backends/firebird/test/Makefile.basic @@ -0,0 +1,22 @@ +# The following variable is specific to this backend and its correct +# values might depend on your environment - feel free to set it accordingly. + +FIREBIRDINCLUDEDIR = -I/usr/local/firebird/include +FIREBIRDLIBDIR = -L/usr/local/firebird/lib +FIREBIRDLIBS = -lfbclient -lpthread + +# The rest of the Makefile is indepentent of the target environment. + +COMPILER = g++ +CXXFLAGS = -Wall -pedantic -Wno-long-long +INCLUDEDIRS = -I.. -I../../../core ${FIREBIRDINCLUDEDIR} +LIBDIRS = -L.. -L../../../core ${FIREBIRDLIBDIR} +LIBS = -lsoci_core -lsoci_firebird -ldl ${FIREBIRDLIBS} + + +test-firebird : test-firebird.cpp + ${COMPILER} -o $@ $? ${CXXFLAGS} ${INCLUDEDIRS} ${LIBDIRS} ${LIBS} + + +clean : + rm -f *.o test-firebird diff --git a/src/backends/firebird/test/test-firebird.cpp b/src/backends/firebird/test/test-firebird.cpp new file mode 100644 index 0000000000..4a331dea77 --- /dev/null +++ b/src/backends/firebird/test/test-firebird.cpp @@ -0,0 +1,1361 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, Rafal Bobrowski +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// + +#include "soci.h" +#include "soci-firebird.h" +#include "error-firebird.h" // soci::details::Firebird::throw_iscerror() +#include "common-tests.h" +#include "common.h" +#include +#include +#include +#include +#include +#include + +using namespace soci; + +std::string connectString; +soci::backend_factory const &backEnd = *factory_firebird(); + +// fundamental tests - transactions in Firebird +void test1() +{ + { + session sql(backEnd, connectString); + + // In Firebird transaction is always required and is started + // automatically when session is opened. There is no need to + // call session::begin(); it will do nothing if there is active + // transaction. + + // sql.begin(); + + try + { + sql << "drop table test1"; + } + catch (soci_error const &) + {} // ignore if error + + sql << "create table test1 (id integer)"; + + // After DDL statement transaction must be commited or changes + // won't be visible to active transaction. + sql.commit(); + + // After commit or rollback, transaction must be started manually. + sql.begin(); + + sql << "insert into test1(id) values(5)"; + sql << "drop table test1"; + + // Transaction is automatically commited in session's destructor + } + + std::cout << "test 1 passed" << std::endl; +} + +// character types +void test2() +{ + session sql(backEnd, connectString); + + try + { + sql << "drop table test2"; + } + catch (soci_error const &) + {} // ignore if error + + sql << "create table test2 (p1 char(10), p2 varchar(10))"; + sql.commit(); + + sql.begin(); + + { + char a('a'), b('b'), c1, c2; + + sql << "insert into test2(p1,p2) values(?,?)", use(a), use(b); + + sql << "select p1,p2 from test2", into(c1), into(c2); + assert(c1 == 'a' && c2 == 'b'); + + 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]; + char *b1 = buf1, *b2 = buf2, *b3 = buf3; + + strcpy(b1, msg); + + 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(!std::strcmp(buf2, buf3) && !std::strcmp(buf2, "Hello, Fir")); + + sql << "delete from test2"; + } + + { + char msg[] = "Hello, Firebird!"; + char buf1[100], buf2[100], buf3[100]; + strcpy(buf1, msg); + + sql << "insert into test2(p1, p2) values (?,?)", + use(buf1), use(buf1); + sql << "select p1, p2 from test2", into(buf2), into(buf3); + + 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"; + } + + { + // verify blank padding in CHAR fields + // In Firebird, CHAR fields are always padded with whitespaces. + char msg[] = "Hello"; + sql << "insert into test2(p1) values(\'" << msg << "\')"; + + char buf[20]; + 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); + + sql << "delete from test2"; + } + + { + std::string str1("Hello, Firebird!"), str2, str3; + sql << "insert into test2(p1, p2) values (?, ?)", + use(str1), use(str1); + + sql << "select p1, p2 from test2", into(str2), into(str3); + assert(str2 == "Hello, Fir" && str3 == "Hello, Fir"); + + sql << "delete from test2"; + } + + sql << "drop table test2"; + std::cout << "test 2 passed" << std::endl; +} + +// date and time +void test3() +{ + session sql(backEnd, connectString); + + try + { + sql << "drop table test3"; + } + catch (soci_error const &) + {} // ignore if error + + sql << "create table test3 (p1 timestamp, p2 date, p3 time)"; + sql.commit(); + + sql.begin(); + + std::tm t1, t2, t3; + std::time_t now = std::time(NULL); + std::tm t = *std::localtime(&now); + + sql << "insert into test3(p1, p2, p3) " + << "values (?,?,?)", use(t), use(t), use(t); + + sql << "select p1, p2, p3 from test3", into(t1), into(t2), into(t3); + + // timestamp + assert(t1.tm_year == t.tm_year); + assert(t1.tm_mon == t.tm_mon); + assert(t1.tm_mday == t.tm_mday); + assert(t1.tm_hour == t.tm_hour); + assert(t1.tm_min == t.tm_min); + assert(t1.tm_sec == t.tm_sec); + + // date + assert(t2.tm_year == t.tm_year); + assert(t2.tm_mon == t.tm_mon); + assert(t2.tm_mday == t.tm_mday); + assert(t2.tm_hour == 0); + assert(t2.tm_min == 0); + assert(t2.tm_sec == 0); + + // time + assert(t3.tm_year == 0); + assert(t3.tm_mon == 0); + assert(t3.tm_mday == 0); + assert(t3.tm_hour == t.tm_hour); + assert(t3.tm_min == t.tm_min); + assert(t3.tm_sec == t.tm_sec); + + sql << "drop table test3"; + std::cout << "test 3 passed" << std::endl; +} + +// floating points +void test4() +{ + session sql(backEnd, connectString); + + try + { + sql << "drop table test4"; + } + catch (soci_error const &) + {} // ignore if error + + sql << "create table test4 (p1 numeric(8,2), " + << "p2 decimal(14,8), p3 double precision, p4 integer)"; + sql.commit(); + + sql.begin(); + + double d1 = 1234.23, d2 = 1e8, d3 = 1.0/1440.0, + d4, d5, d6; + + sql << "insert into test4(p1, p2, p3) values (?,?,?)", + use(d1), use(d2), use(d3); + + sql << "select p1, p2, p3 from test4", + into(d4), into(d5), into(d6); + + assert(d1 == d4 && d2 == d5 && d3 == d6); + + // test negative doubles too + sql << "delete from test4"; + d1 = -d1; + d2 = -d2; + d3 = -d3; + + sql << "insert into test4(p1, p2, p3) values (?,?,?)", + use(d1), use(d2), use(d3); + + sql << "select p1, p2, p3 from test4", + into(d4), into(d5), into(d6); + + assert(d1 == d4 && d2 == d5 && d3 == d6); + + // verify an exception is thrown when fetching non-integral value + // to integral variable + try + { + int i; + sql << "select p1 from test4", into(i); + + // expecting error + assert(false); + } + catch (soci_error const &e) + { + std::string error = e.what(); + assert(error == + "Can't convert value with scale 2 to integral type"); + } + + // verify an exception is thrown when inserting non-integral value + // to integral column + try + { + sql << "insert into test4(p4) values(?)", use(d1); + + // expecting error + assert(false); + } + catch (soci_error const &e) + { + std::string error = e.what(); + assert(error == + "Can't convert non-integral value to integral column type"); + } + + sql << "drop table test4"; + std::cout << "test 4 passed" << std::endl; +} + +// integer types and indicators +void test5() +{ + session sql(backEnd, connectString); + + { + short sh(0); + sql << "select 3 from rdb$database", into(sh); + assert(sh == 3); + } + + { + int i(0); + sql << "select 5 from rdb$database", into(i); + assert(i == 5); + } + + { + unsigned long ul(0); + sql << "select 7 from rdb$database", into(ul); + assert(ul == 7); + } + + { + // test indicators + indicator ind; + int i; + + sql << "select 2 from rdb$database", into(i, ind); + assert(ind == i_ok); + + 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); + + try + { + // expect error + sql << "select NULL from rdb$database", into(i); + assert(false); + } + catch (soci_error const &e) + { + std::string error = e.what(); + assert(error == + "Null value 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; +} + +// repeated fetch and bulk operations for character types +void test6() +{ + session sql(backEnd, connectString); + + try + { + sql << "drop table test6"; + } + catch (soci_error const &) + {} // ignore if error + + sql << "create table test6 (p1 char(10), p2 varchar(10))"; + sql.commit(); + + sql.begin(); + + for (char c = 'a'; c <= 'z'; ++c) + { + sql << "insert into test6(p1, p2) values(?,?)", use(c), use(c); + } + + { + char c, c1, c2; + + statement st = (sql.prepare << + "select p1,p2 from test6 order by p1", into(c1), into(c2)); + + // Verify that fetch after re-executing the same statement works. + for (int n = 0; n < 2; ++n) + { + st.execute(); + + c='a'; + while (st.fetch()) + { + assert(c == c1 && c == c2); + ++c; + } + assert(c == 'z'+1); + } + } + + { + char c='a'; + + std::vector c1(10), c2(10); + + statement st = (sql.prepare << + "select p1,p2 from test6 order by p1", into(c1), into(c2)); + + st.execute(); + while (st.fetch()) + { + for (std::size_t i = 0; i != c1.size(); ++i) + { + assert(c == c1[i] && c == c2[i]); + ++c; + } + } + assert(c == 'z' + 1); + } + + { + // verify an exception is thrown when empty vector is used + std::vector vec; + try + { + sql << "select p1 from test6", into(vec); + assert(false); + } + catch (soci_error const &e) + { + std::string msg = e.what(); + assert(msg == "Vectors of size 0 are not allowed."); + } + } + + sql << "delete from test6"; + + // verifying std::string + int const rowsToTest = 10; + for (int i = 0; i != rowsToTest; ++i) + { + std::ostringstream ss; + ss << "Hello_" << i; + + std::string const &x = ss.str(); + + sql << "insert into test6(p1, p2) values(\'" + << x << "\', \'" << x << "\')"; + } + + int count; + sql << "select count(*) from test6", into(count); + assert(count == rowsToTest); + + { + int i = 0; + std::string s1, s2; + statement st = (sql.prepare << + "select p1, p2 from test6 order by p1", into(s1), into(s2)); + + st.execute(); + while (st.fetch()) + { + std::ostringstream ss; + ss << "Hello_" << i; + std::string const &x = ss.str(); + + // Note: CHAR fields are always padded with whitespaces + ss << " "; + assert(s1 == ss.str() && s2 == x); + ++i; + } + assert(i == rowsToTest); + } + + { + int i = 0; + + std::vector s1(4), s2(4); + statement st = (sql.prepare << + "select p1, p2 from test6 order by p1", into(s1), into(s2)); + st.execute(); + while (st.fetch()) + { + for (std::size_t j = 0; j != s1.size(); ++j) + { + std::ostringstream ss; + ss << "Hello_" << i; + std::string const &x = ss.str(); + + // Note: CHAR fields are always padded with whitespaces + ss << " "; + assert(ss.str() == s1[j] && x == s2[j]); + ++i; + } + } + assert(i == rowsToTest); + } + + sql << "drop table test6"; + std::cout << "test 6 passed" << std::endl; +} + +// blob test +void test7() +{ + session sql(backEnd, connectString); + + try + { + sql << "drop table test7"; + } + catch (std::runtime_error &) + {} // ignore if error + + sql << "create table test7(id integer, img blob)"; + sql.commit(); + + sql.begin(); + { + // verify empty blob + blob b(sql); + indicator ind; + + sql << "insert into test7(id, img) values(1,?)", use(b); + sql << "select img from test7 where id = 1", into(b, ind); + + assert(ind == i_ok); + assert(b.get_len() == 0); + + sql << "delete from test7"; + } + + { + // create a new blob + blob b(sql); + + char str1[] = "Hello"; + b.write(0, str1, strlen(str1)); + + char str2[20]; + std::size_t i = b.read(3, str2, 2); + str2[i] = '\0'; + assert(str2[0] == 'l' && str2[1] == 'o' && str2[2] == '\0'); + + char str3[] = ", Firebird!"; + b.append(str3, strlen(str3)); + + sql << "insert into test7(id, img) values(1,?)", use(b); + } + + { + // read & update blob + blob b(sql); + + sql << "select img from test7 where id = 1", into(b); + + std::vector text(b.get_len()); + b.read(0, &text[0], b.get_len()); + assert(strncmp(&text[0], "Hello, Firebird!", b.get_len()) == 0); + + char str1[] = "FIREBIRD"; + b.write(7, str1, strlen(str1)); + + // after modification blob must be written to database + sql << "update test7 set img=? where id=1", use(b); + } + + { + // read blob from database, modify and write to another record + blob b(sql); + + sql << "select img from test7 where id = 1", into(b); + + std::vector text(b.get_len()); + b.read(0, &text[0], b.get_len()); + + char str1[] = "HELLO"; + b.write(0, str1, strlen(str1)); + + b.read(0, &text[0], b.get_len()); + assert(strncmp(&text[0], "HELLO, FIREBIRD!", b.get_len()) == 0); + + b.trim(5); + sql << "insert into test7(id, img) values(2,?)", use(b); + } + + { + blob b(sql); + statement st = (sql.prepare << "select img from test7", into(b)); + + st.execute(); + + st.fetch(); + std::vector text(b.get_len()); + b.read(0, &text[0], b.get_len()); + assert(strncmp(&text[0], "Hello, FIREBIRD!", b.get_len()) == 0); + + st.fetch(); + text.resize(b.get_len()); + b.read(0, &text[0], b.get_len()); + assert(strncmp(&text[0], "HELLO", b.get_len()) == 0); + } + + { + // delete blob + blob b(sql); + indicator ind=i_null; + sql << "update test7 set img=? where id = 1", use(b, ind); + + sql << "select img from test7 where id = 2", into(b, ind); + assert(ind==i_ok); + + sql << "select img from test7 where id = 1", into(b, ind); + assert(ind==i_null); + } + + sql << "drop table test7"; + std::cout << "test 7 passed" << std::endl; +} + +// named parameters +void test8() +{ + session sql(backEnd, connectString); + + try + { + sql << "drop table test8"; + } + catch (std::runtime_error &) + {} // ignore if error + + sql << "create table test8(id1 integer, id2 integer)"; + sql.commit(); + + sql.begin(); + + int j = 13, k = 4, i, m; + sql << "insert into test8(id1, id2) values(:id1, :id2)", + use(k, "id2"), use(j, "id1"); + sql << "select id1, id2 from test8", into(i), into(m); + assert(i == j && m == k); + + sql << "delete from test8"; + + std::vector in1(3), in2(3); + in1[0] = 3; + in1[1] = 2; + in1[2] = 1; + in2[0] = 4; + in2[1] = 5; + in2[2] = 6; + + { + statement st = (sql.prepare << + "insert into test8(id1, id2) values(:id1, :id2)", + use(k, "id2"), use(j, "id1")); + + std::size_t s = in1.size(); + for (std::size_t x = 0; x < s; ++x) + { + j = in1[x]; + k = in2[x]; + st.execute(); + } + } + + { + statement st = ( + sql.prepare << "select id1, id2 from test8", into(i), into(m)); + st.execute(); + + std::size_t x(0); + while (st.fetch()) + { + assert(i = in1[x] && m == in2[x]); + ++x; + } + } + + sql << "delete from test8"; + + // test vectors + sql << "insert into test8(id1, id2) values(:id1, :id2)", + use(in1, "id1"), use(in2, "id2"); + + std::vector out1(3), out2(3); + + sql << "select id1, id2 from test8", into(out1), into(out2); + std::size_t s = out1.size(); + assert(s == 3); + + for (std::size_t x = 0; x(0) == 1); + assert(r.get(1) == "Hello"); + assert(r.get(2) == d); + + // get values by name + assert(r.get("ID") == 1); + assert(r.get("MSG") == "Hello"); + assert(r.get("NTEST") == d); + + st.fetch(); + assert(r.get(0) == 2); + assert(r.get("MSG") == "Firebird"); + assert(r.get_indicator(2) == i_null); + + // verify default values + assert(r.get("NTEST", 2) == 2); + bool caught = false; + try + { + double d1 = r.get("NTEST"); + std::cout << d1 << std::endl; // just for compiler + } + catch (soci_error&) + { + caught = true; + } + assert(caught); + + // verify exception thrown on invalid get<> + caught = false; + try + { + r.get(0); + } + catch (std::bad_cast const &) + { + caught = true; + } + assert(caught); + + sql << "drop table test9"; + std::cout << "test 9 passed" << std::endl; +} + +// stored procedures +void test10() +{ + session sql(backEnd, connectString); + + try + { + sql << "drop procedure sp_test10"; + } + catch (std::runtime_error &) + {} // ignore if error + + try + { + sql << "drop procedure sp_test10a"; + } + catch (std::runtime_error &) + {} // ignore if error + + try + { + sql << "drop table test10"; + } + catch (std::runtime_error &) + {} // ignore if error + + sql << "create table test10(id integer, id2 integer)"; + + sql << "create procedure sp_test10\n" + << "returns (rid integer, rid2 integer)\n" + << "as begin\n" + << "for select id, id2 from test10 into rid, rid2 do begin\n" + << "suspend;\n" + << "end\n" + << "end;\n"; + + sql << "create procedure sp_test10a (pid integer, pid2 integer)\n" + << "as begin\n" + << "insert into test10(id, id2) values (:pid, :pid2);\n" + << "end;\n"; + + sql.commit(); + + sql.begin(); + + row r; + int p1 = 3, p2 = 4; + + // calling procedures that do not return values requires + // 'execute procedure ...' statement + sql << "execute procedure sp_test10a ?, ?", use(p1), use(p2); + + // calling procedures that return values requires + // 'select ... from ...' statement + sql << "select * from sp_test10", into(r); + + assert(r.get(0) == p1 && r.get(1) == p2); + + sql << "delete from test10"; + + p1 = 5; + p2 = 6; + { + procedure proc = ( + sql.prepare << "sp_test10a :p1, :p2", + use(p2, "p2"), use(p1, "p1")); + proc.execute(1); + } + + { + row rw; + procedure proc = (sql.prepare << "sp_test10", into(rw)); + proc.execute(1); + + assert(rw.get(0) == p1 && rw.get(1) == p2); + } + + sql << "delete from test10"; + + // test vectors + std::vector in1(3), in2(3); + in1[0] = 3; + in1[1] = 2; + in1[2] = 1; + in2[0] = 4; + in2[1] = 5; + in2[2] = 6; + + { + procedure proc = ( + sql.prepare << "sp_test10a :p1, :p2", + use(in2, "p2"), use(in1, "p1")); + proc.execute(1); + } + + { + row rw; + procedure proc = (sql.prepare << "sp_test10", into(rw)); + + proc.execute(1); + assert(rw.get(0) == in1[0] && rw.get(1) == in2[0]); + proc.fetch(); + assert(rw.get(0) == in1[1] && rw.get(1) == in2[1]); + proc.fetch(); + assert(rw.get(0) == in1[2] && rw.get(1) == in2[2]); + assert(proc.fetch() == false); + } + + { + std::vector out1(3), out2(3); + procedure proc = (sql.prepare << "sp_test10", into(out1), into(out2)); + proc.execute(1); + + std::size_t s = out1.size(); + assert(s == 3); + + for (std::size_t x = 0; x < s; ++x) + { + assert(out1[x] == in1[x] && out2[x] == in2[x]); + } + } + + sql.rollback(); + + sql.begin(); + sql << "drop procedure sp_test10"; + sql << "drop procedure sp_test10a"; + sql << "drop table test10"; + + std::cout << "test 10 passed" << std::endl; +} + +// direct access to Firebird using handles exposed by +// soci::FirebirdStatmentBackend +namespace soci +{ + enum eRowCountType + { + eRowsSelected = isc_info_req_select_count, + eRowsInserted = isc_info_req_insert_count, + eRowsUpdated = isc_info_req_update_count, + eRowsDeleted = isc_info_req_delete_count + }; + + // Returns number of rows afected by last statement + // or -1 if there is no such counter available. + long getRowCount(soci::statement & statement, eRowCountType type) + { + ISC_STATUS stat[20]; + char cnt_req[2], cnt_info[128]; + + cnt_req[0]=isc_info_sql_records; + cnt_req[1]=isc_info_end; + + firebird_statement_backend* statementBackEnd + = static_cast(statement.get_backend()); + + // Note: This is very poorly documented function. + // It can extract number of rows returned by select statement, + // but it appears that this is only number of rows prefetched by + // client library, not total number of selected rows. + if (isc_dsql_sql_info(stat, &statementBackEnd->stmtp_, sizeof(cnt_req), + cnt_req, sizeof(cnt_info), cnt_info)) + { + soci::details::firebird::throw_iscerror(stat); + } + + long count = -1; + char type_ = static_cast(type); + for (char *ptr = cnt_info + 3; *ptr != isc_info_end;) + { + char count_type = *ptr++; + int m = isc_vax_integer(ptr, 2); + ptr += 2; + count = isc_vax_integer(ptr, m); + + if (count_type == type_) + { + // this is requested number + break; + } + ptr += m; + } + + return count; + } + +} // namespace soci + +void test11() +{ + session sql(backEnd, connectString); + + try + { + sql << "drop table test11"; + } + catch (std::runtime_error &) + {} // ignore if error + + sql << "create table test11(id integer)"; + sql.commit(); + + sql.begin(); + + { + std::vector in(3); + in[0] = 3; + in[1] = 2; + in[2] = 1; + + statement st = (sql.prepare << "insert into test11(id) values(?)", + use(in)); + st.execute(1); + + // Note: Firebird backend inserts every row with separate insert + // statement to achieve the effect of inserting vectors of values. + // Since getRowCount() returns number of rows affected by the *last* + // statement, it will return 1 here. + assert(getRowCount(st, eRowsInserted) == 1); + } + + { + int i = 5; + statement st = (sql.prepare << "update test11 set id = ? where id<3", + use(i)); + st.execute(1); + assert(getRowCount(st, eRowsUpdated) == 2); + + // verify that no rows were deleted + assert(getRowCount(st, eRowsDeleted) == 0); + } + + { + std::vector out(3); + statement st = (sql.prepare << "select id from test11", into(out)); + st.execute(1); + + assert(getRowCount(st, eRowsSelected) == 3); + } + + { + statement st = (sql.prepare << "delete from test11 where id=10"); + st.execute(1); + assert(getRowCount(st, eRowsDeleted) == 0); + } + + { + statement st = (sql.prepare << "delete from test11"); + st.execute(1); + assert(getRowCount(st, eRowsDeleted) == 3); + } + + sql << "drop table test11"; + std::cout << "test 11 passed" << std::endl; +} + +void test12() +{ + session sql(backEnd, connectString); + + try + { + sql << "drop table test12"; + } + catch (std::runtime_error &) + {} // ignore if error + + sql << "create table test12(a decimal(10,3), b timestamp, c date, d time)"; + sql.commit(); + sql.begin(); + + // Check if passing input parameters as strings works + // for different column types. + { + std::string a = "-3.14150", b = "2013-02-28 23:36:01", + c = "2013-02-28", d = "23:36:01"; + statement st = (sql.prepare << + "insert into test12(a, b, c, d) values (?, ?, ?, ?)", + use(a), use(b), use(c), use(d)); + st.execute(1); + assert(getRowCount(st, eRowsInserted) == 1); + } + + { + double a; + std::tm b, c, d; + sql << "select a, b, c, d from test12", + into(a), into(b), into(c), into(d); + assert(std::fabs(a - (-3.141)) < 0.000001); + assert(b.tm_year + 1900 == 2013 && b.tm_mon + 1 == 2 && b.tm_mday == 28); + assert(b.tm_hour == 23 && b.tm_min == 36 && b.tm_sec == 1); + assert(c.tm_year + 1900 == 2013 && c.tm_mon + 1 == 2 && c.tm_mday == 28); + assert(c.tm_hour == 0 && c.tm_min == 0 && c.tm_sec == 0); + assert(d.tm_hour == 23 && d.tm_min == 36 && d.tm_sec == 1); + } + + sql << "drop table test12"; + std::cout << "test 12 passed" << std::endl; +} + +// Dynamic binding to row objects: decimals_as_strings +void test13() +{ + using namespace soci::details::firebird; + + int a = -12345678; + assert(format_decimal(&a, 1) == "-123456780"); + assert(format_decimal(&a, 0) == "-12345678"); + assert(format_decimal(&a, -3) == "-12345.678"); + assert(format_decimal(&a, -8) == "-0.12345678"); + assert(format_decimal(&a, -9) == "-0.012345678"); + + a = 12345678; + assert(format_decimal(&a, 1) == "123456780"); + assert(format_decimal(&a, 0) == "12345678"); + assert(format_decimal(&a, -3) == "12345.678"); + assert(format_decimal(&a, -8) == "0.12345678"); + assert(format_decimal(&a, -9) == "0.012345678"); + + session sql(backEnd, connectString + " decimals_as_strings=1"); + + try + { + sql << "drop table test13"; + } + catch (std::runtime_error &) + {} // ignore if error + + sql << "create table test13(ntest1 decimal(10,2), " + << "ntest2 decimal(4,4), ntest3 decimal(3,1))"; + sql.commit(); + + sql.begin(); + + { + row r; + sql << "select * from test13", into(r); + assert(sql.got_data() == false); + } + + std::string d_str0("+03.140"), d_str1("3.14"), + d_str2("3.1400"), d_str3("3.1"); + indicator ind(i_ok); + + { + statement st((sql.prepare << + "insert into test13(ntest1, ntest2, ntest3) " + "values(:ntest1, :ntest2, :ntest3)", + use(d_str0, ind, "ntest1"), use(d_str0, "ntest2"), + use(d_str0, "ntest3"))); + + st.execute(1); + + ind = i_null; + st.execute(1); + } + + row r; + statement st = (sql.prepare << "select * from test13", into(r)); + st.execute(1); + + assert(r.size() == 3); + + // get properties by position + assert(r.get_properties(0).get_name() == "NTEST1"); + assert(r.get_properties(0).get_data_type() == dt_string); + assert(r.get_properties(1).get_name() == "NTEST2"); + assert(r.get_properties(1).get_data_type() == dt_string); + assert(r.get_properties(2).get_name() == "NTEST3"); + assert(r.get_properties(2).get_data_type() == dt_string); + + // get properties by name + assert(r.get_properties("NTEST1").get_name() == "NTEST1"); + assert(r.get_properties("NTEST1").get_data_type() == dt_string); + assert(r.get_properties("NTEST2").get_name() == "NTEST2"); + assert(r.get_properties("NTEST2").get_data_type() == dt_string); + assert(r.get_properties("NTEST3").get_name() == "NTEST3"); + assert(r.get_properties("NTEST3").get_data_type() == dt_string); + + // get values by position + assert(r.get(0) == d_str1); + assert(r.get(1) == d_str2); + assert(r.get(2) == d_str3); + + // get values by name + assert(r.get("NTEST1") == d_str1); + assert(r.get("NTEST2") == d_str2); + assert(r.get("NTEST3") == d_str3); + + st.fetch(); + assert(r.get_indicator(0) == i_null); + assert(r.get_indicator(1) == i_ok); + assert(r.get_indicator(2) == i_ok); + + sql << "drop table test13"; + std::cout << "test 13 passed" << std::endl; +} + +// +// Support for soci Common Tests +// + +struct TableCreator1 : public tests::table_creator_base +{ + TableCreator1(session & sql) + : tests::table_creator_base(sql) + { + sql << "create table soci_test(id integer, val integer, c char, " + "str varchar(20), sh smallint, ul bigint, d double precision, " + "tm timestamp, i1 integer, i2 integer, i3 integer, name varchar(20))"; + sql.commit(); + sql.begin(); + } +}; + +struct TableCreator2 : public tests::table_creator_base +{ + 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.commit(); + sql.begin(); + } +}; + +struct TableCreator3 : public tests::table_creator_base +{ + TableCreator3(session & sql) + : tests::table_creator_base(sql) + { + sql << "create table soci_test(name varchar(100) not null, " + "phone varchar(15))"; + sql.commit(); + sql.begin(); + } +}; + +struct TableCreator4 : public tests::table_creator_base +{ + TableCreator4(session & sql) + : tests::table_creator_base(sql) + { + sql << "create table soci_test(val integer)"; + sql.commit(); + sql.begin(); + } +}; + +class test_context : public tests::test_context_base +{ + public: + test_context(backend_factory const &backEnd, + std::string const &connectString) + : test_context_base(backEnd, connectString) + {} + + tests::table_creator_base* table_creator_1(session& s) const + { + return new TableCreator1(s); + } + + tests::table_creator_base* table_creator_2(session& s) const + { + return new TableCreator2(s); + } + + tests::table_creator_base* table_creator_3(session& s) const + { + return new TableCreator3(s); + } + + tests::table_creator_base* table_creator_4(session& s) const + { + return new TableCreator4(s); + } + + std::string to_date_time(std::string const &datdt_string) const + { + return "'" + datdt_string + "'"; + } +}; + + +int main(int argc, char** argv) +{ + +#ifdef _MSC_VER + // Redirect errors, unrecoverable problems, and assert() failures to STDERR, + // instead of debug message window. + // This hack is required to run asser()-driven tests by Buildbot. + // NOTE: Comment this 2 lines for debugging with Visual C++ debugger to catch assertions inside. + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); +#endif //_MSC_VER + + if (argc == 2) + { + connectString = argv[1]; + } + else + { + std::cout << "usage: " << argv[0] + << " connectstring\n" + << "example: " << argv[0] + << " \"service=/usr/local/firebird/db/test.fdb user=SYSDBA password=masterkey\"\n"; + return EXIT_FAILURE; + } + + try + { + test_context tc(backEnd, connectString); + tests::common_tests tests(tc); + tests.run(); + + std::cout << "\nSOCI Firebird Tests:\n\n"; + test1(); + test2(); + test3(); + test4(); + test5(); + test6(); + test7(); + test8(); + test9(); + test10(); + test11(); + test12(); + test13(); + + std::cout << "\nOK, all tests passed.\n\n"; + + return EXIT_SUCCESS; + } + catch (std::exception const & e) + { + std::cout << e.what() << '\n'; + } + return EXIT_FAILURE; +} diff --git a/src/backends/firebird/vector-into-type.cpp b/src/backends/firebird/vector-into-type.cpp new file mode 100644 index 0000000000..b7233a96f7 --- /dev/null +++ b/src/backends/firebird/vector-into-type.cpp @@ -0,0 +1,208 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, Rafal Bobrowski +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_FIREBIRD_SOURCE +#include "soci-firebird.h" +#include "common.h" + +using namespace soci; +using namespace soci::details; +using namespace soci::details::firebird; + +void firebird_vector_into_type_backend::define_by_pos( + int & position, void * data, exchange_type type) +{ + position_ = position-1; + data_ = data; + type_ = type; + + ++position; + + statement_.intoType_ = eVector; + statement_.intos_.push_back(static_cast(this)); + + XSQLVAR *var = statement_.sqldap_->sqlvar+position_; + + buf_ = allocBuffer(var); + var->sqldata = buf_; + var->sqlind = &indISCHolder_; +} + +void firebird_vector_into_type_backend::pre_fetch() +{ + // Nothing to do here. +} + +namespace // anonymous +{ +template +void setIntoVector(void *p, std::size_t indx, T const &val) +{ + std::vector *dest = + static_cast *>(p); + + std::vector &v = *dest; + v[indx] = val; +} + +} // namespace anonymous + +// this will exchange data with vector user buffers +void firebird_vector_into_type_backend::exchangeData(std::size_t row) +{ + XSQLVAR *var = statement_.sqldap_->sqlvar+position_; + + switch (type_) + { + // simple cases + case x_char: + setIntoVector(data_, row, getTextParam(var)[0]); + break; + case x_short: + { + short tmp = from_isc(var); + setIntoVector(data_, row, tmp); + } + break; + case x_integer: + { + int tmp = from_isc(var); + setIntoVector(data_, row, tmp); + } + break; + case x_long_long: + { + long long tmp = from_isc(var); + setIntoVector(data_, row, tmp); + } + break; + case x_double: + { + double tmp = from_isc(var); + setIntoVector(data_, row, tmp); + } + break; + + // cases that require adjustments and buffer management + case x_stdstring: + setIntoVector(data_, row, getTextParam(var)); + break; + case x_stdtm: + { + std::tm data; + tmDecode(var->sqltype, buf_, &data); + setIntoVector(data_, row, data); + } + break; + + default: + throw soci_error("Into vector element used with non-supported type."); + } // switch + +} + +void firebird_vector_into_type_backend::post_fetch( + bool gotData, indicator * ind) +{ + // Here we have to set indicators only. Data was exchanged with user + // buffers during fetch() + if (gotData) + { + std::size_t rows = statement_.rowsFetched_; + + for (std::size_t i = 0; i (data_, sz); + break; + case x_short: + resizeVector (data_, sz); + break; + case x_integer: + resizeVector (data_, sz); + break; + case x_long_long: + resizeVector (data_, sz); + break; + case x_double: + resizeVector (data_, sz); + break; + case x_stdstring: + resizeVector (data_, sz); + break; + case x_stdtm: + resizeVector (data_, sz); + break; + + default: + throw soci_error("Into vector element used with non-supported type."); + } +} + +std::size_t firebird_vector_into_type_backend::size() +{ + std::size_t sz = 0; // dummy initialization to please the compiler + switch (type_) + { + // simple cases + case x_char: + sz = getVectorSize (data_); + break; + case x_short: + sz = getVectorSize (data_); + break; + case x_integer: + sz = getVectorSize (data_); + break; + case x_long_long: + sz = getVectorSize (data_); + break; + case x_double: + sz = getVectorSize (data_); + break; + case x_stdstring: + sz = getVectorSize (data_); + break; + case x_stdtm: + sz = getVectorSize (data_); + break; + + default: + throw soci_error("Into vector element used with non-supported type."); + } + + return sz; +} + +void firebird_vector_into_type_backend::clean_up() +{ + if (buf_ != NULL) + { + delete [] buf_; + buf_ = NULL; + } + std::vector::iterator it = + std::find(statement_.intos_.begin(), statement_.intos_.end(), this); + if (it != statement_.intos_.end()) + statement_.intos_.erase(it); +} diff --git a/src/backends/firebird/vector-use-type.cpp b/src/backends/firebird/vector-use-type.cpp new file mode 100644 index 0000000000..c8cd4f6447 --- /dev/null +++ b/src/backends/firebird/vector-use-type.cpp @@ -0,0 +1,207 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, Rafal Bobrowski +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_FIREBIRD_SOURCE +#include "soci-firebird.h" +#include "common.h" + +using namespace soci; +using namespace soci::details; +using namespace soci::details::firebird; + +void firebird_vector_use_type_backend::bind_by_pos(int & position, + void * data, exchange_type type) +{ + if (statement_.boundByName_) + { + throw soci_error( + "Binding for use elements must be either by position or by name."); + } + + position_ = position-1; + data_ = data; + type_ = type; + + ++position; + + statement_.useType_ = eVector; + statement_.uses_.push_back(static_cast(this)); + + XSQLVAR *var = statement_.sqlda2p_->sqlvar+position_; + + buf_ = allocBuffer(var); + var->sqldata = buf_; + var->sqlind = &indISCHolder_; + + statement_.boundByPos_ = true; +} + +void firebird_vector_use_type_backend::bind_by_name( + std::string const & name, void * data, exchange_type type) +{ + if (statement_.boundByPos_) + { + throw soci_error( + "Binding for use elements must be either by position or by name."); + } + + std::map :: iterator idx = + statement_.names_.find(name); + + if (idx == statement_.names_.end()) + { + throw soci_error("Missing use element for bind by name (" + name + ")"); + } + + position_ = idx->second; + data_ = data; + type_ = type; + + statement_.useType_ = eVector; + statement_.uses_.push_back(static_cast(this)); + + XSQLVAR *var = statement_.sqlda2p_->sqlvar+position_; + + buf_ = allocBuffer(var); + var->sqldata = buf_; + var->sqlind = &indISCHolder_; + + statement_.boundByName_ = true; +} + +void firebird_vector_use_type_backend::pre_use(indicator const * ind) +{ + inds_ = ind; +} + +namespace +{ +template +T* getUseVectorValue(void *v, std::size_t index) +{ + std::vector *src = + static_cast *>(v); + + std::vector &v_ = *src; + return &(v_[index]); +} +} + +void firebird_vector_use_type_backend::exchangeData(std::size_t row) +{ + // first prepare indicators + if (inds_ != NULL) + { + switch (inds_[row]) + { + case i_null: + indISCHolder_ = -1; + break; + case i_ok: + indISCHolder_ = 0; + break; + default: + throw soci_error("Use element used with non-supported indicator type."); + } + } + + XSQLVAR * var = statement_.sqlda2p_->sqlvar+position_; + + // then set parameters for query execution + switch (type_) + { + // simple cases + case x_char: + setTextParam(getUseVectorValue(data_, row), 1, buf_, var); + break; + case x_short: + to_isc( + static_cast(getUseVectorValue(data_, row)), + var); + break; + case x_integer: + to_isc( + static_cast(getUseVectorValue(data_, row)), + var); + break; + case x_long_long: + to_isc( + static_cast(getUseVectorValue(data_, row)), + var); + break; + case x_double: + to_isc( + static_cast(getUseVectorValue(data_, row)), + var); + break; + + // cases that require adjustments and buffer management + case x_stdstring: + { + std::string *tmp = getUseVectorValue(data_, row); + setTextParam(tmp->c_str(), tmp->size(), buf_, var); + } + break; + case x_stdtm: + tmEncode(var->sqltype, + getUseVectorValue(data_, row), buf_); + break; + // Not supported + // case x_cstring: + // case x_blob: + default: + throw soci_error("Use element used with non-supported type."); + } // switch +} + +std::size_t firebird_vector_use_type_backend::size() +{ + std::size_t sz = 0; // dummy initialization to please the compiler + switch (type_) + { + // simple cases + case x_char: + sz = getVectorSize (data_); + break; + case x_short: + sz = getVectorSize (data_); + break; + case x_integer: + sz = getVectorSize (data_); + break; + case x_long_long: + sz = getVectorSize (data_); + break; + case x_double: + sz = getVectorSize (data_); + break; + case x_stdstring: + sz = getVectorSize (data_); + break; + case x_stdtm: + sz = getVectorSize (data_); + break; + + default: + throw soci_error("Use vector element used with non-supported type."); + } + + return sz; +} + +void firebird_vector_use_type_backend::clean_up() +{ + if (buf_ != NULL) + { + delete [] buf_; + buf_ = NULL; + } + std::vector::iterator it = + std::find(statement_.uses_.begin(), statement_.uses_.end(), this); + if (it != statement_.uses_.end()) + statement_.uses_.erase(it); +} diff --git a/src/backends/mysql/CMakeLists.txt b/src/backends/mysql/CMakeLists.txt new file mode 100644 index 0000000000..54cbb78843 --- /dev/null +++ b/src/backends/mysql/CMakeLists.txt @@ -0,0 +1,18 @@ +############################################################################### +# +# This file is part of CMake configuration for SOCI library +# +# Copyright (C) 2010 Mateusz Loskot +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) +# +############################################################################### +soci_backend(MySQL + DEPENDS MySQL + HEADERS soci-mysql.h common.h + DESCRIPTION "SOCI backend for MySQL database engine" + AUTHORS "Pawel Aleksander Fedorynski" + MAINTAINERS "Pawel Aleksander Fedorynski") + +add_subdirectory(test) diff --git a/src/backends/mysql/Makefile.basic b/src/backends/mysql/Makefile.basic new file mode 100644 index 0000000000..0073fadd36 --- /dev/null +++ b/src/backends/mysql/Makefile.basic @@ -0,0 +1,97 @@ +# The following variables are specific to this backend and their correct +# values might depend on your environment - feel free to set it accordingly. + +MYSQLLIBDIR = -L/usr/lib/mysql +MYSQLLIBS = -lmysqlclient -lz +MYSQLINCLUDEDIR = -I/usr/include/mysql + +# The rest of the Makefile is independent of the target environment. + +COMPILER = g++ +CXXFLAGS = -Wall -pedantic -Wno-long-long +CXXFLAGSSO = ${CXXFLAGS} -fPIC +INCLUDEDIRS = -I../../core ${MYSQLINCLUDEDIR} + + +OBJECTS = blob.o factory.o row-id.o session.o standard-into-type.o \ + standard-use-type.o statement.o vector-into-type.o vector-use-type.o \ + common.o + + +OBJECTSSO = blob-s.o factory-s.o row-id-s.o session-s.o \ + standard-into-type-s.o standard-use-type-s.o statement-s.o \ + vector-into-type-s.o vector-use-type-s.o common-s.o + + +libsoci_mysql.a : ${OBJECTS} + ar rv $@ $? + rm *.o + + +blob.o : blob.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +common.o : common.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +factory.o : factory.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +row-id.o : row-id.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +session.o : session.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +standard-into-type.o : standard-into-type.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +standard-use-type.o : standard-use-type.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +statement.o : statement.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +vector-into-type.o : vector-into-type.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +vector-use-type.o : vector-use-type.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +shared : ${OBJECTSSO} + ${COMPILER} -shared -o libsoci_mysql.so ${OBJECTSSO} + rm *.o + +blob-s.o : blob.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +common-s.o : common.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +factory-s.o : factory.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +row-id-s.o : row-id.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +session-s.o : session.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +standard-into-type-s.o : standard-into-type.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +standard-use-type-s.o : standard-use-type.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +statement-s.o : statement.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +vector-into-type-s.o : vector-into-type.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +vector-use-type-s.o : vector-use-type.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + + +clean : + rm -f libsoci_mysql.a libsoci_mysql.so diff --git a/src/backends/mysql/blob.cpp b/src/backends/mysql/blob.cpp new file mode 100644 index 0000000000..bd440c6a7a --- /dev/null +++ b/src/backends/mysql/blob.cpp @@ -0,0 +1,62 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton +// MySQL backend copyright (C) 2006 Pawel Aleksander Fedorynski +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_MYSQL_SOURCE +#include "soci-mysql.h" +#include + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4355 4702) +#endif + +using namespace soci; +using namespace soci::details; + +mysql_blob_backend::mysql_blob_backend(mysql_session_backend &session) + : session_(session) +{ + throw soci_error("BLOBs are not supported."); +} + +mysql_blob_backend::~mysql_blob_backend() +{ +} + +std::size_t mysql_blob_backend::get_len() +{ + throw soci_error("BLOBs are not supported."); +} + +std::size_t mysql_blob_backend::read( + std::size_t /* offset */, char * /* buf */, std::size_t /* toRead */) +{ + throw soci_error("BLOBs are not supported."); +} + +std::size_t mysql_blob_backend::write( + std::size_t /* offset */, char const * /* buf */, + std::size_t /* toWrite */) +{ + throw soci_error("BLOBs are not supported."); +} + +std::size_t mysql_blob_backend::append( + char const * /* buf */, std::size_t /* toWrite */) +{ + throw soci_error("BLOBs are not supported."); +} + +void mysql_blob_backend::trim(std::size_t /* newLen */) +{ + throw soci_error("BLOBs are not supported."); +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif diff --git a/src/backends/mysql/common.cpp b/src/backends/mysql/common.cpp new file mode 100644 index 0000000000..8721f31fc2 --- /dev/null +++ b/src/backends/mysql/common.cpp @@ -0,0 +1,87 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#include "common.h" +#include "soci-backend.h" +#include +#include +#include +#include + +namespace // anonymous +{ + +// helper function for parsing decimal data (for std::tm) +long parse10(char const *&p1, char *&p2, const char *msg) +{ + long v = std::strtol(p1, &p2, 10); + if (p2 != p1) + { + p1 = p2 + 1; + return v; + } + else + { + throw soci::soci_error(msg); + } +} + +} // namespace anonymous + + +void soci::details::mysql::parse_std_tm(char const *buf, std::tm &t) +{ + char const *p1 = buf; + char *p2; + long year, month, day; + long hour = 0, minute = 0, second = 0; + + const char *errMsg = "Cannot convert data to std::tm."; + + if (strchr(buf, '-') != NULL) + { + year = parse10(p1, p2, errMsg); + month = parse10(p1, p2, errMsg); + day = parse10(p1, p2, errMsg); + } + else + { + year = 2000; + month = 1; + day = 1; + } + + + if (strchr(buf, ':') != NULL) + { + // there is also the time of day available + hour = parse10(p1, p2, errMsg); + minute = parse10(p1, p2, errMsg); + second = parse10(p1, p2, errMsg); + } + + t.tm_isdst = -1; + t.tm_year = year - 1900; + t.tm_mon = month - 1; + t.tm_mday = day; + t.tm_hour = hour; + t.tm_min = minute; + t.tm_sec = second; + + std::mktime(&t); +} + +char * soci::details::mysql::quote(MYSQL * conn, const char *s, int len) +{ + char *retv = new char[2 * len + 3]; + retv[0] = '\''; + int len_esc = mysql_real_escape_string(conn, retv + 1, s, len); + retv[len_esc + 1] = '\''; + retv[len_esc + 2] = '\0'; + + return retv; +} diff --git a/src/backends/mysql/common.h b/src/backends/mysql/common.h new file mode 100644 index 0000000000..989bd6ff83 --- /dev/null +++ b/src/backends/mysql/common.h @@ -0,0 +1,76 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_MYSQL_COMMON_H_INCLUDED +#define SOCI_MYSQL_COMMON_H_INCLUDED + +#include "soci-mysql.h" +// std +#include +#include +#include +#include + +namespace soci +{ + +namespace details +{ + +namespace mysql +{ + +// helper function for parsing datetime values +void parse_std_tm(char const *buf, std::tm &t); + +// The idea is that infinity - infinity gives NaN, and NaN != NaN is true. +// +// This should work on any IEEE-754-compliant implementation, which is +// another way of saying that it does not always work (in particular, +// according to stackoverflow, it won't work with gcc with the --fast-math +// option), but I know of no better way of testing this portably in C++ prior +// to C++11. When soci moves to C++11 this should be replaced +// with std::isfinite(). +template +bool is_infinity_or_nan(T x) +{ + T y = x - x; + return (y != y); +} + +template +void parse_num(char const *buf, T &x) +{ + std::istringstream iss(buf); + iss >> x; + if (iss.fail() || (iss.eof() == false)) + { + throw soci_error("Cannot convert data."); + } + if (is_infinity_or_nan(x)) { + throw soci_error("Cannot convert data."); + } +} + +// helper for escaping strings +char * quote(MYSQL * conn, const char *s, int len); + +// helper for vector operations +template +std::size_t get_vector_size(void *p) +{ + std::vector *v = static_cast *>(p); + return v->size(); +} + +} // namespace mysql + +} // namespace details + +} // namespace soci + +#endif // SOCI_MYSQL_COMMON_H_INCLUDED diff --git a/src/backends/mysql/factory.cpp b/src/backends/mysql/factory.cpp new file mode 100644 index 0000000000..2940f0ab14 --- /dev/null +++ b/src/backends/mysql/factory.cpp @@ -0,0 +1,45 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton +// MySQL backend copyright (C) 2006 Pawel Aleksander Fedorynski +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_MYSQL_SOURCE +#include "soci-mysql.h" +#include +#include + +#ifdef _MSC_VER +#pragma warning(disable:4355) +#endif + +using namespace soci; +using namespace soci::details; + + +// concrete factory for MySQL concrete strategies +mysql_session_backend * mysql_backend_factory::make_session( + connection_parameters const & parameters) const +{ + return new mysql_session_backend(parameters); +} + +mysql_backend_factory const soci::mysql; + +extern "C" +{ + +// for dynamic backend loading +SOCI_MYSQL_DECL backend_factory const * factory_mysql() +{ + return &soci::mysql; +} + +SOCI_MYSQL_DECL void register_factory_mysql() +{ + soci::dynamic_backends::register_backend("mysql", soci::mysql); +} + +} // extern "C" diff --git a/src/backends/mysql/row-id.cpp b/src/backends/mysql/row-id.cpp new file mode 100644 index 0000000000..14177d0f94 --- /dev/null +++ b/src/backends/mysql/row-id.cpp @@ -0,0 +1,32 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton +// MySQL backend copyright (C) 2006 Pawel Aleksander Fedorynski +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_MYSQL_SOURCE +#include "soci-mysql.h" + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4355 4702) +#endif + +using namespace soci; +using namespace soci::details; + +mysql_rowid_backend::mysql_rowid_backend( + mysql_session_backend & /* session */) +{ + throw soci_error("RowIDs are not supported."); +} + +mysql_rowid_backend::~mysql_rowid_backend() +{ +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif diff --git a/src/backends/mysql/session.cpp b/src/backends/mysql/session.cpp new file mode 100644 index 0000000000..20b70bca66 --- /dev/null +++ b/src/backends/mysql/session.cpp @@ -0,0 +1,389 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton +// MySQL backend copyright (C) 2006 Pawel Aleksander Fedorynski +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_MYSQL_SOURCE +#include "soci-mysql.h" +#include +// std +#include +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(disable:4355) +#endif + +using namespace soci; +using namespace soci::details; +using std::string; + + +namespace +{ // anonymous + +void skip_white(std::string::const_iterator *i, + std::string::const_iterator const & end, bool endok) +{ + for (;;) + { + if (*i == end) + { + if (endok) + { + return; + } + else + { + throw soci_error("Unexpected end of connection string."); + } + } + if (std::isspace(**i)) + { + ++*i; + } + else + { + return; + } + } +} + +std::string param_name(std::string::const_iterator *i, + std::string::const_iterator const & end) +{ + std::string val(""); + for (;;) + { + if (*i == end or (not std::isalpha(**i) and **i != '_')) + { + break; + } + val += **i; + ++*i; + } + return val; +} + +string param_value(string::const_iterator *i, + string::const_iterator const & end) +{ + string err = "Malformed connection string."; + bool quot; + if (**i == '\'') + { + quot = true; + ++*i; + } + else + { + quot = false; + } + string val(""); + for (;;) + { + if (*i == end) + { + if (quot) + { + throw soci_error(err); + } + else + { + break; + } + } + if (**i == '\'') + { + if (quot) + { + ++*i; + break; + } + else + { + throw soci_error(err); + } + } + if (not quot and std::isspace(**i)) + { + break; + } + if (**i == '\\') + { + ++*i; + if (*i == end) + { + throw soci_error(err); + } + } + val += **i; + ++*i; + } + return val; +} + +bool valid_int(const string & s) +{ + char *tail; + const char *cstr = s.c_str(); + errno = 0; + long n = std::strtol(cstr, &tail, 10); + if (errno != 0 or n > INT_MAX or n < INT_MIN) + { + return false; + } + if (*tail != '\0') + { + return false; + } + return true; +} + +void parse_connect_string(const string & connectString, + string *host, bool *host_p, + string *user, bool *user_p, + string *password, bool *password_p, + string *db, bool *db_p, + string *unix_socket, bool *unix_socket_p, + int *port, bool *port_p, string *ssl_ca, bool *ssl_ca_p, + string *ssl_cert, bool *ssl_cert_p, string *ssl_key, bool *ssl_key_p, + int *local_infile, bool *local_infile_p, + string *charset, bool *charset_p) +{ + *host_p = false; + *user_p = false; + *password_p = false; + *db_p = false; + *unix_socket_p = false; + *port_p = false; + *ssl_ca_p = false; + *ssl_cert_p = false; + *ssl_key_p = false; + *local_infile_p = false; + *charset_p = false; + string err = "Malformed connection string."; + string::const_iterator i = connectString.begin(), + end = connectString.end(); + while (i != end) + { + skip_white(&i, end, true); + if (i == end) + { + return; + } + string par = param_name(&i, end); + skip_white(&i, end, false); + if (*i == '=') + { + ++i; + } + else + { + throw soci_error(err); + } + skip_white(&i, end, false); + string val = param_value(&i, end); + if (par == "port" and not *port_p) + { + if (not valid_int(val)) + { + throw soci_error(err); + } + *port = std::atoi(val.c_str()); + if (port < 0) + { + throw soci_error(err); + } + *port_p = true; + } + else if (par == "host" and not *host_p) + { + *host = val; + *host_p = true; + } + else if (par == "user" and not *user_p) + { + *user = val; + *user_p = true; + } + else if ((par == "pass" or par == "password") and not *password_p) + { + *password = val; + *password_p = true; + } + else if ((par == "db" or par == "dbname" or par == "service") and + not *db_p) + { + *db = val; + *db_p = true; + } + else if (par == "unix_socket" and not *unix_socket_p) + { + *unix_socket = val; + *unix_socket_p = true; + } + else if (par == "sslca" and not *ssl_ca_p) + { + *ssl_ca = val; + *ssl_ca_p = true; + } + else if (par == "sslcert" and not *ssl_cert_p) + { + *ssl_cert = val; + *ssl_cert_p = true; + } + else if (par == "sslkey" and not *ssl_key_p) + { + *ssl_key = val; + *ssl_key_p = true; + } + else if (par == "local_infile" and not *local_infile_p) + { + if (not valid_int(val)) + { + throw soci_error(err); + } + *local_infile = std::atoi(val.c_str()); + if (*local_infile != 0 and *local_infile != 1) + { + throw soci_error(err); + } + *local_infile_p = true; + } else if (par == "charset" and not *charset_p) + { + *charset = val; + *charset_p = true; + } + else + { + throw soci_error(err); + } + } +} + +} // namespace anonymous + +mysql_session_backend::mysql_session_backend( + connection_parameters const & parameters) +{ + string host, user, password, db, unix_socket, ssl_ca, ssl_cert, ssl_key, + charset; + int port, local_infile; + bool host_p, user_p, password_p, db_p, unix_socket_p, port_p, + ssl_ca_p, ssl_cert_p, ssl_key_p, local_infile_p, charset_p; + parse_connect_string(parameters.get_connect_string(), &host, &host_p, &user, &user_p, + &password, &password_p, &db, &db_p, + &unix_socket, &unix_socket_p, &port, &port_p, + &ssl_ca, &ssl_ca_p, &ssl_cert, &ssl_cert_p, &ssl_key, &ssl_key_p, + &local_infile, &local_infile_p, &charset, &charset_p); + conn_ = mysql_init(NULL); + if (conn_ == NULL) + { + throw soci_error("mysql_init() failed."); + } + if (charset_p) + { + if (0 != mysql_options(conn_, MYSQL_SET_CHARSET_NAME, charset.c_str())) + { + clean_up(); + throw soci_error("mysql_options(MYSQL_SET_CHARSET_NAME) failed."); + } + } + if (ssl_ca_p) + { + mysql_ssl_set(conn_, ssl_key_p ? ssl_key.c_str() : NULL, + ssl_cert_p ? ssl_cert.c_str() : NULL, + ssl_ca_p ? ssl_ca.c_str() : NULL, 0, 0); + } + if (local_infile_p and local_infile == 1) + { + if (0 != mysql_options(conn_, MYSQL_OPT_LOCAL_INFILE, NULL)) + { + clean_up(); + throw soci_error( + "mysql_options() failed when trying to set local-infile."); + } + } + if (mysql_real_connect(conn_, + host_p ? host.c_str() : NULL, + user_p ? user.c_str() : NULL, + password_p ? password.c_str() : NULL, + db_p ? db.c_str() : NULL, + port_p ? port : 0, + unix_socket_p ? unix_socket.c_str() : NULL, + CLIENT_FOUND_ROWS | CLIENT_MULTI_RESULTS) == NULL) + { + string errMsg = mysql_error(conn_); + unsigned int errNum = mysql_errno(conn_); + clean_up(); + throw mysql_soci_error(errMsg, errNum); + } +} + +mysql_session_backend::~mysql_session_backend() +{ + clean_up(); +} + +namespace // unnamed +{ + +// helper function for hardcoded queries +void hard_exec(MYSQL *conn, const string & query) +{ + if (0 != mysql_real_query(conn, query.c_str(), + static_cast(query.size()))) + { + throw soci_error(mysql_error(conn)); + } +} + +} // namespace unnamed + +void mysql_session_backend::begin() +{ + hard_exec(conn_, "BEGIN"); +} + +void mysql_session_backend::commit() +{ + hard_exec(conn_, "COMMIT"); +} + +void mysql_session_backend::rollback() +{ + hard_exec(conn_, "ROLLBACK"); +} + +void mysql_session_backend::clean_up() +{ + if (conn_ != NULL) + { + mysql_close(conn_); + conn_ = NULL; + } +} + +mysql_statement_backend * mysql_session_backend::make_statement_backend() +{ + return new mysql_statement_backend(*this); +} + +mysql_rowid_backend * mysql_session_backend::make_rowid_backend() +{ + return new mysql_rowid_backend(*this); +} + +mysql_blob_backend * mysql_session_backend::make_blob_backend() +{ + return new mysql_blob_backend(*this); +} diff --git a/src/backends/mysql/soci-mysql.h b/src/backends/mysql/soci-mysql.h new file mode 100644 index 0000000000..77848da79b --- /dev/null +++ b/src/backends/mysql/soci-mysql.h @@ -0,0 +1,273 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton +// MySQL backend copyright (C) 2006 Pawel Aleksander Fedorynski +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_MYSQL_H_INCLUDED +#define SOCI_MYSQL_H_INCLUDED + +#ifdef _WIN32 +# ifdef SOCI_DLL +# ifdef SOCI_MYSQL_SOURCE +# define SOCI_MYSQL_DECL __declspec(dllexport) +# else +# define SOCI_MYSQL_DECL __declspec(dllimport) +# endif // SOCI_DLL +# endif // SOCI_MYSQL_SOURCE +#endif // _WIN32 +// +// If SOCI_MYSQL_DECL isn't defined yet define it now +#ifndef SOCI_MYSQL_DECL +# define SOCI_MYSQL_DECL +#endif + +#include "soci-backend.h" +#ifdef _WIN32 +#include // SOCKET +#endif // _WIN32 +#include // MySQL Client +#include + + +namespace soci +{ + +class mysql_soci_error : public soci_error +{ +public: + mysql_soci_error(std::string const & msg, int errNum) + : soci_error(msg), err_num_(errNum) {} + + unsigned int err_num_; +}; + +struct mysql_statement_backend; +struct mysql_standard_into_type_backend : details::standard_into_type_backend +{ + mysql_standard_into_type_backend(mysql_statement_backend &st) + : statement_(st) {} + + virtual void define_by_pos(int &position, + void *data, details::exchange_type type); + + virtual void pre_fetch(); + virtual void post_fetch(bool gotData, bool calledFromFetch, + indicator *ind); + + virtual void clean_up(); + + mysql_statement_backend &statement_; + + void *data_; + details::exchange_type type_; + int position_; +}; + +struct mysql_vector_into_type_backend : details::vector_into_type_backend +{ + mysql_vector_into_type_backend(mysql_statement_backend &st) + : statement_(st) {} + + virtual void define_by_pos(int &position, + void *data, details::exchange_type type); + + virtual void pre_fetch(); + virtual void post_fetch(bool gotData, indicator *ind); + + virtual void resize(std::size_t sz); + virtual std::size_t size(); + + virtual void clean_up(); + + mysql_statement_backend &statement_; + + void *data_; + details::exchange_type type_; + int position_; +}; + +struct mysql_standard_use_type_backend : details::standard_use_type_backend +{ + mysql_standard_use_type_backend(mysql_statement_backend &st) + : statement_(st), position_(0), buf_(NULL) {} + + virtual void bind_by_pos(int &position, + void *data, details::exchange_type type, bool readOnly); + virtual void bind_by_name(std::string const &name, + void *data, details::exchange_type type, bool readOnly); + + virtual void pre_use(indicator const *ind); + virtual void post_use(bool gotData, indicator *ind); + + virtual void clean_up(); + + mysql_statement_backend &statement_; + + void *data_; + details::exchange_type type_; + int position_; + std::string name_; + char *buf_; +}; + +struct mysql_vector_use_type_backend : details::vector_use_type_backend +{ + mysql_vector_use_type_backend(mysql_statement_backend &st) + : statement_(st), position_(0) {} + + virtual void bind_by_pos(int &position, + void *data, details::exchange_type type); + virtual void bind_by_name(std::string const &name, + void *data, details::exchange_type type); + + virtual void pre_use(indicator const *ind); + + virtual std::size_t size(); + + virtual void clean_up(); + + mysql_statement_backend &statement_; + + void *data_; + details::exchange_type type_; + int position_; + std::string name_; + std::vector buffers_; +}; + +struct mysql_session_backend; +struct mysql_statement_backend : details::statement_backend +{ + mysql_statement_backend(mysql_session_backend &session); + + virtual void alloc(); + virtual void clean_up(); + virtual void prepare(std::string const &query, + details::statement_type eType); + + virtual exec_fetch_result execute(int number); + virtual exec_fetch_result fetch(int number); + + virtual long long get_affected_rows(); + virtual int get_number_of_rows(); + + virtual std::string rewrite_for_procedure_call(std::string const &query); + + virtual int prepare_for_describe(); + virtual void describe_column(int colNum, data_type &dtype, + std::string &columnName); + + virtual mysql_standard_into_type_backend * make_into_type_backend(); + virtual mysql_standard_use_type_backend * make_use_type_backend(); + virtual mysql_vector_into_type_backend * make_vector_into_type_backend(); + virtual mysql_vector_use_type_backend * make_vector_use_type_backend(); + + mysql_session_backend &session_; + + MYSQL_RES *result_; + + // The query is split into chunks, separated by the named parameters; + // e.g. for "SELECT id FROM ttt WHERE name = :foo AND gender = :bar" + // we will have query chunks "SELECT id FROM ttt WHERE name = ", + // "AND gender = " and names "foo", "bar". + std::vector queryChunks_; + std::vector names_; // list of names for named binds + + long long rowsAffectedBulk_; // number of rows affected by the last bulk operation + + int numberOfRows_; // number of rows retrieved from the server + int currentRow_; // "current" row number to consume in postFetch + int rowsToConsume_; // number of rows to be consumed in postFetch + + bool justDescribed_; // to optimize row description with immediately + // following actual statement execution + + // Prefetch the row offsets in order to use mysql_row_seek() for + // random access to rows, since mysql_data_seek() is expensive. + std::vector resultRowOffsets_; + + bool hasIntoElements_; + bool hasVectorIntoElements_; + bool hasUseElements_; + bool hasVectorUseElements_; + + // the following maps are used for finding data buffers according to + // use elements specified by the user + + typedef std::map UseByPosBuffersMap; + UseByPosBuffersMap useByPosBuffers_; + + typedef std::map UseByNameBuffersMap; + UseByNameBuffersMap useByNameBuffers_; +}; + +struct mysql_rowid_backend : details::rowid_backend +{ + mysql_rowid_backend(mysql_session_backend &session); + + ~mysql_rowid_backend(); +}; + +struct mysql_blob_backend : details::blob_backend +{ + mysql_blob_backend(mysql_session_backend &session); + + ~mysql_blob_backend(); + + virtual std::size_t get_len(); + virtual std::size_t read(std::size_t offset, char *buf, + std::size_t toRead); + virtual std::size_t write(std::size_t offset, char const *buf, + std::size_t toWrite); + virtual std::size_t append(char const *buf, std::size_t toWrite); + virtual void trim(std::size_t newLen); + + mysql_session_backend &session_; +}; + +struct mysql_session_backend : details::session_backend +{ + mysql_session_backend(connection_parameters const & parameters); + + ~mysql_session_backend(); + + virtual void begin(); + virtual void commit(); + virtual void rollback(); + + virtual std::string get_backend_name() const { return "mysql"; } + + void clean_up(); + + virtual mysql_statement_backend * make_statement_backend(); + virtual mysql_rowid_backend * make_rowid_backend(); + virtual mysql_blob_backend * make_blob_backend(); + + MYSQL *conn_; +}; + + +struct mysql_backend_factory : backend_factory +{ + mysql_backend_factory() {} + virtual mysql_session_backend * make_session( + connection_parameters const & parameters) const; +}; + +extern SOCI_MYSQL_DECL mysql_backend_factory const mysql; + +extern "C" +{ + +// for dynamic backend loading +SOCI_MYSQL_DECL backend_factory const * factory_mysql(); +SOCI_MYSQL_DECL void register_factory_mysql(); + +} // extern "C" + +} // namespace soci + +#endif // SOCI_MYSQL_H_INCLUDED diff --git a/src/backends/mysql/standard-into-type.cpp b/src/backends/mysql/standard-into-type.cpp new file mode 100644 index 0000000000..835c35982f --- /dev/null +++ b/src/backends/mysql/standard-into-type.cpp @@ -0,0 +1,141 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton +// MySQL backend copyright (C) 2006 Pawel Aleksander Fedorynski +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_MYSQL_SOURCE +#include "soci-mysql.h" +#include +#include "common.h" +// std +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(disable:4355) +#endif + +using namespace soci; +using namespace soci::details; +using namespace soci::details::mysql; + + +void mysql_standard_into_type_backend::define_by_pos( + int &position, void *data, exchange_type type) +{ + data_ = data; + type_ = type; + position_ = position++; +} + +void mysql_standard_into_type_backend::pre_fetch() +{ + // nothing to do here +} + +void mysql_standard_into_type_backend::post_fetch( + bool gotData, bool calledFromFetch, indicator *ind) +{ + if (calledFromFetch == true && gotData == false) + { + // this is a normal end-of-rowset condition, + // no need to do anything (fetch() will return false) + return; + } + + if (gotData) + { + int pos = position_ - 1; + //mysql_data_seek(statement_.result_, statement_.currentRow_); + mysql_row_seek(statement_.result_, + statement_.resultRowOffsets_[statement_.currentRow_]); + MYSQL_ROW row = mysql_fetch_row(statement_.result_); + if (row[pos] == NULL) + { + if (ind == NULL) + { + throw soci_error( + "Null value fetched and no indicator defined."); + } + *ind = i_null; + return; + } + else + { + if (ind != NULL) + { + *ind = i_ok; + } + } + const char *buf = row[pos] != NULL ? row[pos] : ""; + switch (type_) + { + case x_char: + { + char *dest = static_cast(data_); + *dest = *buf; + } + break; + case x_stdstring: + { + std::string *dest = static_cast(data_); + unsigned long * lengths = + mysql_fetch_lengths(statement_.result_); + dest->assign(buf, lengths[pos]); + } + break; + case x_short: + { + short *dest = static_cast(data_); + parse_num(buf, *dest); + } + break; + case x_integer: + { + int *dest = static_cast(data_); + parse_num(buf, *dest); + } + break; + case x_long_long: + { + long long *dest = static_cast(data_); + parse_num(buf, *dest); + } + break; + case x_unsigned_long_long: + { + unsigned long long *dest = + static_cast(data_); + parse_num(buf, *dest); + } + break; + case x_double: + { + double *dest = static_cast(data_); + parse_num(buf, *dest); + } + break; + case x_stdtm: + { + // attempt to parse the string and convert to std::tm + std::tm *dest = static_cast(data_); + parse_std_tm(buf, *dest); + } + break; + default: + throw soci_error("Into element used with non-supported type."); + } + } +} + +void mysql_standard_into_type_backend::clean_up() +{ + // nothing to do here +} diff --git a/src/backends/mysql/standard-use-type.cpp b/src/backends/mysql/standard-use-type.cpp new file mode 100644 index 0000000000..04eb32a341 --- /dev/null +++ b/src/backends/mysql/standard-use-type.cpp @@ -0,0 +1,172 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton +// MySQL backend copyright (C) 2006 Pawel Aleksander Fedorynski +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_MYSQL_SOURCE +#include "soci-mysql.h" +#include "common.h" +#include +// std +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(disable:4355) +#endif + +using namespace soci; +using namespace soci::details; +using namespace soci::details::mysql; + + +void mysql_standard_use_type_backend::bind_by_pos( + int &position, void *data, exchange_type type, bool /* readOnly */) +{ + data_ = data; + type_ = type; + position_ = position++; +} + +void mysql_standard_use_type_backend::bind_by_name( + std::string const &name, void *data, exchange_type type, bool /* readOnly */) +{ + data_ = data; + type_ = type; + name_ = name; +} + +void mysql_standard_use_type_backend::pre_use(indicator const *ind) +{ + if (ind != NULL && *ind == i_null) + { + buf_ = new char[5]; + std::strcpy(buf_, "NULL"); + } + else + { + // allocate and fill the buffer with text-formatted client data + switch (type_) + { + case x_char: + { + char buf[] = { *static_cast(data_), '\0' }; + buf_ = quote(statement_.session_.conn_, buf, 1); + } + break; + case x_stdstring: + { + std::string *s = static_cast(data_); + buf_ = quote(statement_.session_.conn_, + s->c_str(), s->size()); + } + break; + case x_short: + { + std::size_t const bufSize + = std::numeric_limits::digits10 + 3; + buf_ = new char[bufSize]; + snprintf(buf_, bufSize, "%d", + static_cast(*static_cast(data_))); + } + break; + case x_integer: + { + std::size_t const bufSize + = std::numeric_limits::digits10 + 3; + buf_ = new char[bufSize]; + snprintf(buf_, bufSize, "%d", *static_cast(data_)); + } + break; + case x_long_long: + { + std::size_t const bufSize + = std::numeric_limits::digits10 + 3; + buf_ = new char[bufSize]; + snprintf(buf_, bufSize, "%" LL_FMT_FLAGS "d", *static_cast(data_)); + } + break; + case x_unsigned_long_long: + { + std::size_t const bufSize + = std::numeric_limits::digits10 + 3; + buf_ = new char[bufSize]; + snprintf(buf_, bufSize, "%" LL_FMT_FLAGS "u", + *static_cast(data_)); + } + break; + + case x_double: + { + if (is_infinity_or_nan(*static_cast(data_))) { + throw soci_error( + "Use element used with infinity or NaN, which are " + "not supported by the MySQL server."); + } + + std::size_t const bufSize = 100; + buf_ = new char[bufSize]; + + snprintf(buf_, bufSize, "%.20g", + *static_cast(data_)); + } + break; + case x_stdtm: + { + std::size_t const bufSize = 22; + buf_ = new char[bufSize]; + + std::tm *t = static_cast(data_); + snprintf(buf_, bufSize, + "\'%d-%02d-%02d %02d:%02d:%02d\'", + t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, + t->tm_hour, t->tm_min, t->tm_sec); + } + break; + default: + throw soci_error("Use element used with non-supported type."); + } + } + + if (position_ > 0) + { + // binding by position + statement_.useByPosBuffers_[position_] = &buf_; + } + else + { + // binding by name + statement_.useByNameBuffers_[name_] = &buf_; + } +} + +void mysql_standard_use_type_backend::post_use(bool /*gotData*/, indicator* /*ind*/) +{ + // TODO: Is it possible to have the bound element being overwritten + // by the database? + // If not, then nothing to do here, please remove this comment. + // If yes, then use the value of the readOnly parameter: + // - true: the given object should not be modified and the backend + // should detect if the modification was performed on the + // isolated buffer and throw an exception if the buffer was modified + // (this indicates logic error, because the user used const object + // and executed a query that attempted to modified it) + // - false: the modification should be propagated to the given object. + // ... + + clean_up(); +} + +void mysql_standard_use_type_backend::clean_up() +{ + if (buf_ != NULL) + { + delete [] buf_; + buf_ = NULL; + } +} diff --git a/src/backends/mysql/statement.cpp b/src/backends/mysql/statement.cpp new file mode 100644 index 0000000000..f686160b5a --- /dev/null +++ b/src/backends/mysql/statement.cpp @@ -0,0 +1,480 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton +// MySQL backend copyright (C) 2006 Pawel Aleksander Fedorynski +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_MYSQL_SOURCE +#include "soci-mysql.h" +#include +#include +//#include + +#ifdef _MSC_VER +#pragma warning(disable:4355) +#endif + +using namespace soci; +using namespace soci::details; +using std::string; + + +mysql_statement_backend::mysql_statement_backend( + mysql_session_backend &session) + : session_(session), result_(NULL), + rowsAffectedBulk_(-1LL), justDescribed_(false), + hasIntoElements_(false), hasVectorIntoElements_(false), + hasUseElements_(false), hasVectorUseElements_(false) +{ +} + +void mysql_statement_backend::alloc() +{ + // nothing to do here. +} + +void mysql_statement_backend::clean_up() +{ + // 'reset' the value for a + // potential new execution. + rowsAffectedBulk_ = -1; + + if (result_ != NULL) + { + mysql_free_result(result_); + result_ = NULL; + } +} + +void mysql_statement_backend::prepare(std::string const & query, + statement_type /* eType */) +{ + queryChunks_.clear(); + enum { eNormal, eInQuotes, eInName } state = eNormal; + + std::string name; + queryChunks_.push_back(""); + + bool escaped = false; + for (std::string::const_iterator it = query.begin(), end = query.end(); + it != end; ++it) + { + switch (state) + { + case eNormal: + if (*it == '\'') + { + queryChunks_.back() += *it; + state = eInQuotes; + } + else if (*it == ':') + { + const std::string::const_iterator next_it = it + 1; + // Check whether this is an assignment (e.g. @x:=y) + // and treat it as a special case, not as a named binding. + if (next_it != end && *next_it == '=') + { + queryChunks_.back() += ":="; + ++it; + } + else + { + state = eInName; + } + } + else // regular character, stay in the same state + { + queryChunks_.back() += *it; + } + break; + case eInQuotes: + if (*it == '\'' && !escaped) + { + queryChunks_.back() += *it; + state = eNormal; + } + else // regular quoted character + { + queryChunks_.back() += *it; + } + escaped = *it == '\\' && !escaped; + break; + case eInName: + if (std::isalnum(*it) || *it == '_') + { + name += *it; + } + else // end of name + { + names_.push_back(name); + name.clear(); + queryChunks_.push_back(""); + queryChunks_.back() += *it; + state = eNormal; + } + break; + } + } + + if (state == eInName) + { + names_.push_back(name); + } +/* + cerr << "Chunks: "; + for (std::vector::iterator i = queryChunks_.begin(); + i != queryChunks_.end(); ++i) + { + cerr << "\"" << *i << "\" "; + } + cerr << "\nNames: "; + for (std::vector::iterator i = names_.begin(); + i != names_.end(); ++i) + { + cerr << "\"" << *i << "\" "; + } + cerr << endl; +*/ +} + +statement_backend::exec_fetch_result +mysql_statement_backend::execute(int number) +{ + if (justDescribed_ == false) + { + clean_up(); + + if (number > 1 && hasIntoElements_) + { + throw soci_error( + "Bulk use with single into elements is not supported."); + } + // number - size of vectors (into/use) + // numberOfExecutions - number of loops to perform + int numberOfExecutions = 1; + if (number > 0) + { + numberOfExecutions = hasUseElements_ ? 1 : number; + } + + std::string query; + if (not useByPosBuffers_.empty() or not useByNameBuffers_.empty()) + { + if (not useByPosBuffers_.empty() and not useByNameBuffers_.empty()) + { + throw soci_error( + "Binding for use elements must be either by position " + "or by name."); + } + long long rowsAffectedBulkTemp = 0; + for (int i = 0; i != numberOfExecutions; ++i) + { + std::vector paramValues; + + if (not useByPosBuffers_.empty()) + { + // use elements bind by position + // the map of use buffers can be traversed + // in its natural order + + for (UseByPosBuffersMap::iterator + it = useByPosBuffers_.begin(), + end = useByPosBuffers_.end(); + it != end; ++it) + { + char **buffers = it->second; + //cerr<<"i: "<::iterator + it = names_.begin(), end = names_.end(); + it != end; ++it) + { + UseByNameBuffersMap::iterator b + = useByNameBuffers_.find(*it); + if (b == useByNameBuffers_.end()) + { + std::string msg( + "Missing use element for bind by name ("); + msg += *it; + msg += ")."; + throw soci_error(msg); + } + char **buffers = b->second; + paramValues.push_back(buffers[i]); + } + } + //cerr << "queryChunks_.size(): "<::const_iterator ci + = queryChunks_.begin(); + for (std::vector::const_iterator + pi = paramValues.begin(), end = paramValues.end(); + pi != end; ++ci, ++pi) + { + query += *ci; + query += *pi; + } + if (ci != queryChunks_.end()) + { + query += *ci; + } + if (numberOfExecutions > 1) + { + // bulk operation + //std::cerr << "bulk operation:\n" << query << std::endl; + if (0 != mysql_real_query(session_.conn_, query.c_str(), + query.size())) + { + // preserve the number of rows affected so far. + rowsAffectedBulk_ = rowsAffectedBulkTemp; + throw mysql_soci_error(mysql_error(session_.conn_), + mysql_errno(session_.conn_)); + } + else + { + rowsAffectedBulkTemp += static_cast(mysql_affected_rows(session_.conn_)); + } + if (mysql_field_count(session_.conn_) != 0) + { + throw soci_error("The query shouldn't have returned" + " any data but it did."); + } + query.clear(); + } + } + rowsAffectedBulk_ = rowsAffectedBulkTemp; + if (numberOfExecutions > 1) + { + // bulk + return ef_no_data; + } + } + else + { + query = queryChunks_.front(); + } + + //std::cerr << query << std::endl; + if (0 != mysql_real_query(session_.conn_, query.c_str(), + query.size())) + { + throw mysql_soci_error(mysql_error(session_.conn_), + mysql_errno(session_.conn_)); + } + result_ = mysql_store_result(session_.conn_); + if (result_ == NULL and mysql_field_count(session_.conn_) != 0) + { + throw mysql_soci_error(mysql_error(session_.conn_), + mysql_errno(session_.conn_)); + } + if (result_ != NULL) + { + // Cache the rows offsets to have random access to the rows later. + // [mysql_data_seek() is O(n) so we don't want to use it]. + int numrows = static_cast(mysql_num_rows(result_)); + resultRowOffsets_.resize(numrows); + for (int i = 0; i < numrows; i++) + { + resultRowOffsets_[i] = mysql_row_tell(result_); + mysql_fetch_row(result_); + } + } + } + else + { + justDescribed_ = false; + } + + if (result_ != NULL) + { + currentRow_ = 0; + rowsToConsume_ = 0; + + numberOfRows_ = static_cast(mysql_num_rows(result_)); + if (numberOfRows_ == 0) + { + return ef_no_data; + } + else + { + if (number > 0) + { + // prepare for the subsequent data consumption + return fetch(number); + } + else + { + // execute(0) was meant to only perform the query + return ef_success; + } + } + } + else + { + // it was not a SELECT + return ef_no_data; + } +} + +statement_backend::exec_fetch_result +mysql_statement_backend::fetch(int number) +{ + // Note: This function does not actually fetch anything from anywhere + // - the data was already retrieved from the server in the execute() + // function, and the actual consumption of this data will take place + // in the postFetch functions, called for each into element. + // Here, we only prepare for this to happen (to emulate "the Oracle way"). + + // forward the "cursor" from the last fetch + currentRow_ += rowsToConsume_; + + if (currentRow_ >= numberOfRows_) + { + // all rows were already consumed + return ef_no_data; + } + else + { + if (currentRow_ + number > numberOfRows_) + { + rowsToConsume_ = numberOfRows_ - currentRow_; + + // this simulates the behaviour of Oracle + // - when EOF is hit, we return ef_no_data even when there are + // actually some rows fetched + return ef_no_data; + } + else + { + rowsToConsume_ = number; + return ef_success; + } + } +} + +long long mysql_statement_backend::get_affected_rows() +{ + if (rowsAffectedBulk_ >= 0) + { + return rowsAffectedBulk_; + } + return static_cast(mysql_affected_rows(session_.conn_)); +} + +int mysql_statement_backend::get_number_of_rows() +{ + return numberOfRows_ - currentRow_; +} + +std::string mysql_statement_backend::rewrite_for_procedure_call( + std::string const &query) +{ + std::string newQuery("select "); + newQuery += query; + return newQuery; +} + +int mysql_statement_backend::prepare_for_describe() +{ + execute(1); + justDescribed_ = true; + + int columns = mysql_field_count(session_.conn_); + return columns; +} + +void mysql_statement_backend::describe_column(int colNum, + data_type & type, std::string & columnName) +{ + int pos = colNum - 1; + MYSQL_FIELD *field = mysql_fetch_field_direct(result_, pos); + switch (field->type) + { + case FIELD_TYPE_CHAR: //MYSQL_TYPE_TINY: + case FIELD_TYPE_SHORT: //MYSQL_TYPE_SHORT: + case FIELD_TYPE_INT24: //MYSQL_TYPE_INT24: + type = dt_integer; + break; + case FIELD_TYPE_LONG: //MYSQL_TYPE_LONG: + type = field->flags & UNSIGNED_FLAG ? dt_long_long + : dt_integer; + break; + case FIELD_TYPE_LONGLONG: //MYSQL_TYPE_LONGLONG: + type = field->flags & UNSIGNED_FLAG ? dt_unsigned_long_long : + dt_long_long; + break; + case FIELD_TYPE_FLOAT: //MYSQL_TYPE_FLOAT: + case FIELD_TYPE_DOUBLE: //MYSQL_TYPE_DOUBLE: + case FIELD_TYPE_DECIMAL: //MYSQL_TYPE_DECIMAL: + // Prior to MySQL v. 5.x there was no column type corresponding + // to MYSQL_TYPE_NEWDECIMAL. However, MySQL server 5.x happily + // sends field type number 246, no matter which version of libraries + // the client is using. + case 246: //MYSQL_TYPE_NEWDECIMAL: + type = dt_double; + break; + case FIELD_TYPE_TIMESTAMP: //MYSQL_TYPE_TIMESTAMP: + case FIELD_TYPE_DATE: //MYSQL_TYPE_DATE: + case FIELD_TYPE_TIME: //MYSQL_TYPE_TIME: + case FIELD_TYPE_DATETIME: //MYSQL_TYPE_DATETIME: + case FIELD_TYPE_YEAR: //MYSQL_TYPE_YEAR: + case FIELD_TYPE_NEWDATE: //MYSQL_TYPE_NEWDATE: + type = dt_date; + break; +// case MYSQL_TYPE_VARCHAR: + case FIELD_TYPE_VAR_STRING: //MYSQL_TYPE_VAR_STRING: + case FIELD_TYPE_STRING: //MYSQL_TYPE_STRING: + case FIELD_TYPE_BLOB: // TEXT OR BLOB + case FIELD_TYPE_TINY_BLOB: + case FIELD_TYPE_MEDIUM_BLOB: + case FIELD_TYPE_LONG_BLOB: + type = dt_string; + break; + default: + //std::cerr << "field->type: " << field->type << std::endl; + throw soci_error("Unknown data type."); + } + columnName = field->name; +} + +mysql_standard_into_type_backend * +mysql_statement_backend::make_into_type_backend() +{ + hasIntoElements_ = true; + return new mysql_standard_into_type_backend(*this); +} + +mysql_standard_use_type_backend * +mysql_statement_backend::make_use_type_backend() +{ + hasUseElements_ = true; + return new mysql_standard_use_type_backend(*this); +} + +mysql_vector_into_type_backend * +mysql_statement_backend::make_vector_into_type_backend() +{ + hasVectorIntoElements_ = true; + return new mysql_vector_into_type_backend(*this); +} + +mysql_vector_use_type_backend * +mysql_statement_backend::make_vector_use_type_backend() +{ + hasVectorUseElements_ = true; + return new mysql_vector_use_type_backend(*this); +} diff --git a/src/backends/mysql/test/.gitignore b/src/backends/mysql/test/.gitignore new file mode 100644 index 0000000000..4a78f40aeb --- /dev/null +++ b/src/backends/mysql/test/.gitignore @@ -0,0 +1 @@ +test_mysql diff --git a/src/backends/mysql/test/CMakeLists.txt b/src/backends/mysql/test/CMakeLists.txt new file mode 100644 index 0000000000..47dd07493b --- /dev/null +++ b/src/backends/mysql/test/CMakeLists.txt @@ -0,0 +1,14 @@ +############################################################################### +# +# This file is part of CMake configuration for SOCI library +# +# Copyright (C) 2010 Mateusz Loskot +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) +# +############################################################################### +soci_backend_test( + BACKEND MySQL + SOURCE test-mysql.cpp + CONNSTR "dummy") \ No newline at end of file diff --git a/src/backends/mysql/test/Makefile.basic b/src/backends/mysql/test/Makefile.basic new file mode 100644 index 0000000000..0792406b2e --- /dev/null +++ b/src/backends/mysql/test/Makefile.basic @@ -0,0 +1,22 @@ +# The following variables are specific to this backend and their correct +# values might depend on your environment - feel free to set it accordingly. + +MYSQLLIBDIR = -L/usr/lib/mysql +MYSQLLIBS = -lmysqlclient -lz +MYSQLINCLUDEDIR = -I/usr/include/mysql + +# The rest of the Makefile is independent of the target environment. + +COMPILER = g++ +CXXFLAGS = -Wall -pedantic -Wno-long-long +INCLUDEDIRS = -I.. -I../../../core ${MYSQLINCLUDEDIR} +LIBDIRS = -L.. -L../../../core ${MYSQLLIBDIR} +LIBS = -lsoci_core -lsoci_mysql -ldl ${MYSQLLIBS} + + +test-mysql : test-mysql.cpp + ${COMPILER} -o $@ $? ${CXXFLAGS} ${INCLUDEDIRS} ${LIBDIRS} ${LIBS} + + +clean : + rm -f test-mysql diff --git a/src/backends/mysql/test/test-mysql.cpp b/src/backends/mysql/test/test-mysql.cpp new file mode 100644 index 0000000000..6e3e46f957 --- /dev/null +++ b/src/backends/mysql/test/test-mysql.cpp @@ -0,0 +1,1002 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton +// MySQL backend copyright (C) 2006 Pawel Aleksander Fedorynski +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#include "soci.h" +#include "soci-mysql.h" +#include "test/common-tests.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace soci; +using namespace soci::tests; + +std::string connectString; +backend_factory const &backEnd = *soci::factory_mysql(); + + +// procedure call test +void test1() +{ + { + session sql(backEnd, connectString); + + mysql_session_backend *sessionBackEnd + = static_cast(sql.get_backend()); + std::string version = mysql_get_server_info(sessionBackEnd->conn_); + int v; + std::istringstream iss(version); + if ((iss >> v) and v < 5) + { + std::cout << "skipping test 1 (MySQL server version "; + std::cout << version << " does not support stored procedures)\n"; + return; + } + + try { sql << "drop function myecho"; } + catch (soci_error const &) {} + + sql << + "create function myecho(msg text) " + "returns text deterministic " + " return msg; "; + + std::string in("my message"); + std::string out; + + statement st = (sql.prepare << + "select myecho(:input)", + into(out), + use(in, "input")); + + st.execute(1); + assert(out == in); + + // explicit procedure syntax + { + std::string in("my message2"); + std::string out; + + procedure proc = (sql.prepare << + "myecho(:input)", + into(out), use(in, "input")); + + proc.execute(1); + assert(out == in); + } + + sql << "drop function myecho"; + } + + std::cout << "test 1 passed" << std::endl; +} + +// MySQL error reporting test. +void test2() +{ + { + try + { + session sql(backEnd, "host=test.soci.invalid"); + } + catch (mysql_soci_error const &e) + { + assert(e.err_num_ == CR_UNKNOWN_HOST || + e.err_num_ == CR_CONN_HOST_ERROR); + } + } + + { + session sql(backEnd, connectString); + sql << "create table soci_test (id integer)"; + try + { + int n; + sql << "select id from soci_test_nosuchtable", into(n); + } + catch (mysql_soci_error const &e) + { + assert(e.err_num_ == ER_NO_SUCH_TABLE); + } + try + { + sql << "insert into soci_test (invalid) values (256)"; + } + catch (mysql_soci_error const &e) + { + assert(e.err_num_ == ER_BAD_FIELD_ERROR); + } + // A bulk operation. + try + { + std::vector v(3, 5); + sql << "insert into soci_test_nosuchtable values (:n)", use(v); + } + catch (mysql_soci_error const &e) + { + assert(e.err_num_ == ER_NO_SUCH_TABLE); + } + sql << "drop table soci_test"; + } + + std::cout << "test 2 passed" << std::endl; +} + +struct bigint_table_creator : table_creator_base +{ + bigint_table_creator(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(val bigint)"; + } +}; + +struct bigint_unsigned_table_creator : table_creator_base +{ + bigint_unsigned_table_creator(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(val bigint unsigned)"; + } +}; + +// long long test +void test3() +{ + { + session sql(backEnd, connectString); + + bigint_table_creator tableCreator(sql); + + long long v1 = 1000000000000LL; + assert(v1 / 1000000 == 1000000); + + sql << "insert into soci_test(val) values(:val)", use(v1); + + long long v2 = 0LL; + sql << "select val from soci_test", into(v2); + + assert(v2 == v1); + } + + // vector + { + session sql(backEnd, connectString); + + bigint_table_creator tableCreator(sql); + + std::vector v1; + v1.push_back(1000000000000LL); + v1.push_back(1000000000001LL); + v1.push_back(1000000000002LL); + v1.push_back(1000000000003LL); + v1.push_back(1000000000004LL); + + sql << "insert into soci_test(val) values(:val)", use(v1); + + std::vector v2(10); + sql << "select val from soci_test order by val desc", into(v2); + + assert(v2.size() == 5); + assert(v2[0] == 1000000000004LL); + assert(v2[1] == 1000000000003LL); + assert(v2[2] == 1000000000002LL); + assert(v2[3] == 1000000000001LL); + assert(v2[4] == 1000000000000LL); + } + + { + session sql(backEnd, connectString); + + bigint_unsigned_table_creator tableCreator(sql); + + sql << "insert into soci_test set val = 18446744073709551615"; + row v; + sql << "select * from soci_test", into(v); + } + + { + session sql(backEnd, connectString); + + bigint_unsigned_table_creator tableCreator(sql); + + const char* source = "18446744073709551615"; + sql << "insert into soci_test set val = " << source; + unsigned long long vv = 0; + sql << "select val from soci_test", into(vv); + std::stringstream buf; + buf << vv; + assert(buf.str() == source); + } + + { + session sql(backEnd, connectString); + + bigint_unsigned_table_creator tableCreator(sql); + + const char* source = "18446744073709551615"; + sql << "insert into soci_test set val = " << source; + std::vector v(1); + sql << "select val from soci_test", into(v); + std::stringstream buf; + buf << v.at(0); + assert(buf.str() == source); + } + + { + session sql(backEnd, connectString); + + bigint_unsigned_table_creator tableCreator(sql); + + unsigned long long n = 18446744073709551615ULL; + sql << "insert into soci_test(val) values (:n)", use(n); + unsigned long long m = 0; + sql << "select val from soci_test", into(m); + assert(n == m); + } + + { + session sql(backEnd, connectString); + + bigint_unsigned_table_creator tableCreator(sql); + + std::vector v1; + v1.push_back(18446744073709551615ULL); + v1.push_back(18446744073709551614ULL); + v1.push_back(18446744073709551613ULL); + sql << "insert into soci_test(val) values(:val)", use(v1); + + std::vector v2(10); + sql << "select val from soci_test order by val", into(v2); + + assert(v2.size() == 3); + assert(v2[0] == 18446744073709551613ULL); + assert(v2[1] == 18446744073709551614ULL); + assert(v2[2] == 18446744073709551615ULL); + } + + std::cout << "test 3 passed" << std::endl; +} + +template +void test_num(const char* s, bool valid, T value) +{ + try + { + session sql(backEnd, connectString); + T val; + sql << "select \'" << s << "\'", into(val); + if (valid) + { + double v1 = static_cast(value); + double v2 = static_cast(val); + double d = std::fabs(v1 - v2); + double epsilon = 0.001; + assert(d < epsilon || + d < epsilon * (std::fabs(v1) + std::fabs(v2))); + } + else + { + std::cout << "string \"" << s << "\" parsed as " << val + << " but should have failed.\n"; + assert(false); + } + } + catch (soci_error const& e) + { + if (valid) + { + std::cout << "couldn't parse number: \"" << s << "\"\n"; + assert(false); + } + else + { + assert(std::string(e.what()) == "Cannot convert data."); + } + } +} + +// Number conversion test. +void test4() +{ + test_num("", false, 0); + test_num("foo", false, 0); + test_num("1", true, 1); + test_num("12", true, 12); + test_num("123", true, 123); + test_num("12345", true, 12345); + test_num("12341234123412341234123412341234123412341234123412341", + true, 1.23412e+52); + test_num("99999999999999999999999912222222222222222222222222223" + "9999999999999999999999991222222222222222222222222222333333333333" + "9999999999999999999999991222222222222222222222222222333333333333" + "9999999999999999999999991222222222222222222222222222333333333333" + "9999999999999999999999991222222222222222222222222222333333333333" + "9999999999999999999999991222222222222222222222222222333333333333" + "9999999999999999999999991222222222222222222222222222333333333333" + "9999999999999999999999991222222222222222222222222222333333333333" + "9999999999999999999999991222222222222222222222222222333333333333" + "9999999999999999999999991222222222222222222222222222333333333333" + "9999999999999999999999991222222222222222222222222222333333333333" + "9999999999999999999999991222222222222222222222222222333333333333" + "9999999999999999999999991222222222222222222222222222333333333333" + "9999999999999999999999991222222222222222222222222222333333333333" + "9999999999999999999999991222222222222222222222222222333333333333", + false, 0); + test_num("1e3", true, 1000); + test_num("1.2", true, 1.2); + test_num("1.2345e2", true, 123.45); + test_num("1 ", false, 0); + test_num(" 123", true, 123); + test_num("1,2", false, 0); + test_num("123abc", false, 0); + test_num("-0", true, 0); + + test_num("123", true, 123); + test_num("100000", false, 0); + + test_num("123", true, 123); + test_num("2147483647", true, 2147483647); + test_num("2147483647a", false, 0); + test_num("2147483648", false, 0); + // -2147483648 causes a warning because it is interpreted as + // 2147483648 (which doesn't fit in an integer) to which a negation + // is applied. + test_num("-2147483648", true, -2147483647 - 1); + test_num("-2147483649", false, 0); + test_num("-0", true, 0); + test_num("1.1", false, 0); + + test_num("123", true, 123); + test_num("9223372036854775807", true, 9223372036854775807LL); + test_num("9223372036854775808", false, 0); + + std::cout << "test 4 passed" << std::endl; +} + +void test5() +{ + session sql(backEnd, connectString); + std::tm t; + sql << "select maketime(19, 54, 52)", into(t); + assert(t.tm_year == 100); + assert(t.tm_mon == 0); + assert(t.tm_mday == 1); + assert(t.tm_hour == 19); + assert(t.tm_min == 54); + assert(t.tm_sec == 52); + + std::cout << "test 5 passed" << std::endl; +} + +// TEXT and BLOB types support test. +void test6() +{ + session sql(backEnd, connectString); + std::string a("asdfg\0hjkl", 10); + std::string b("lkjhg\0fd\0\0sa\0", 13); + std::string c("\\0aa\\0bb\\0cc\\0", 10); + // The maximum length for TEXT and BLOB is 65536. + std::string x(60000, 'X'); + std::string y(60000, 'Y'); + // The default max_allowed_packet value for a MySQL server is 1M, + // so let's limit ourselves to 800k, even though the maximum length + // for LONGBLOB is 4G. + std::string z(800000, 'Z'); + + sql << "create table soci_test (id int, text_value text, " + "blob_value blob, longblob_value longblob)"; + sql << "insert into soci_test values (1, \'foo\', \'bar\', \'baz\')"; + sql << "insert into soci_test " + << "values (2, \'qwerty\\0uiop\', \'zxcv\\0bnm\', " + << "\'qwerty\\0uiop\\0zxcvbnm\\0\')"; + sql << "insert into soci_test values (3, :a, :b, :c)", + use(a), use(b), use(c); + sql << "insert into soci_test values (4, :x, :y, :z)", + use(x), use(y), use(z); + + std::vector text_vec(100); + std::vector blob_vec(100); + std::vector longblob_vec(100); + sql << "select text_value, blob_value, longblob_value " + << "from soci_test order by id", + into(text_vec), into(blob_vec), into(longblob_vec); + assert(text_vec.size() == 4); + assert(blob_vec.size() == 4); + assert(longblob_vec.size() == 4); + assert(text_vec[0] == "foo"); + assert(blob_vec[0] == "bar"); + assert(longblob_vec[0] == "baz"); + assert(text_vec[1] == std::string("qwerty\0uiop", 11)); + assert(blob_vec[1] == std::string("zxcv\0bnm", 8)); + assert(longblob_vec[1] == std::string("qwerty\0uiop\0zxcvbnm\0", 20)); + assert(text_vec[2] == a); + assert(blob_vec[2] == b); + assert(longblob_vec[2] == c); + assert(text_vec[3] == x); + assert(blob_vec[3] == y); + assert(longblob_vec[3] == z); + + std::string text, blob, longblob; + sql << "select text_value, blob_value, longblob_value " + << "from soci_test where id = 1", + into(text), into(blob), into(longblob); + assert(text == "foo"); + assert(blob == "bar"); + assert(longblob == "baz"); + sql << "select text_value, blob_value, longblob_value " + << "from soci_test where id = 2", + into(text), into(blob), into(longblob); + assert(text == std::string("qwerty\0uiop", 11)); + assert(blob == std::string("zxcv\0bnm", 8)); + assert(longblob == std::string("qwerty\0uiop\0zxcvbnm\0", 20)); + sql << "select text_value, blob_value, longblob_value " + << "from soci_test where id = 3", + into(text), into(blob), into(longblob); + assert(text == a); + assert(blob == b); + assert(longblob == c); + sql << "select text_value, blob_value, longblob_value " + << "from soci_test where id = 4", + into(text), into(blob), into(longblob); + assert(text == x); + assert(blob == y); + assert(longblob == z); + + rowset rs = + (sql.prepare << "select text_value, blob_value, longblob_value " + "from soci_test order by id"); + rowset::const_iterator r = rs.begin(); + assert(r->get_properties(0).get_data_type() == dt_string); + assert(r->get(0) == "foo"); + assert(r->get_properties(1).get_data_type() == dt_string); + assert(r->get(1) == "bar"); + assert(r->get_properties(2).get_data_type() == dt_string); + assert(r->get(2) == "baz"); + ++r; + assert(r->get_properties(0).get_data_type() == dt_string); + assert(r->get(0) == std::string("qwerty\0uiop", 11)); + assert(r->get_properties(1).get_data_type() == dt_string); + assert(r->get(1) == std::string("zxcv\0bnm", 8)); + assert(r->get_properties(2).get_data_type() == dt_string); + assert(r->get(2) == + std::string("qwerty\0uiop\0zxcvbnm\0", 20)); + ++r; + assert(r->get_properties(0).get_data_type() == dt_string); + assert(r->get(0) == a); + assert(r->get_properties(1).get_data_type() == dt_string); + assert(r->get(1) == b); + assert(r->get_properties(2).get_data_type() == dt_string); + assert(r->get(2) == c); + ++r; + assert(r->get_properties(0).get_data_type() == dt_string); + assert(r->get(0) == x); + assert(r->get_properties(1).get_data_type() == dt_string); + assert(r->get(1) == y); + assert(r->get_properties(2).get_data_type() == dt_string); + assert(r->get(2) == z); + ++r; + assert(r == rs.end()); + + sql << "drop table soci_test"; + + std::cout << "test 6 passed" << std::endl; +} + +// test for number of affected rows + +struct integer_value_table_creator : table_creator_base +{ + integer_value_table_creator(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(val integer)"; + } +}; + +void test7() +{ + { + session sql(backEnd, connectString); + + integer_value_table_creator tableCreator(sql); + + for (int i = 0; i != 10; i++) + { + sql << "insert into soci_test(val) values(:val)", use(i); + } + + statement st1 = (sql.prepare << + "update soci_test set val = val + 1"); + st1.execute(false); + + assert(st1.get_affected_rows() == 10); + + statement st2 = (sql.prepare << + "delete from soci_test where val <= 5"); + st2.execute(false); + + assert(st2.get_affected_rows() == 5); + } + + std::cout << "test 7 passed" << std::endl; +} + + +// The prepared statements should survive session::reconnect(). +void test8() +{ + { + session sql(backEnd, connectString); + + integer_value_table_creator tableCreator(sql); + + int i; + statement st = (sql.prepare + << "insert into soci_test(val) values(:val)", use(i)); + i = 5; + st.execute(true); + + sql.reconnect(); + + i = 6; + st.execute(true); + + sql.close(); + sql.reconnect(); + + i = 7; + st.execute(true); + + std::vector v(5); + sql << "select val from soci_test order by val", into(v); + assert(v.size() == 3); + assert(v[0] == 5); + assert(v[1] == 6); + assert(v[2] == 7); + } + + std::cout << "test 8 passed" << std::endl; +} + +struct unsigned_value_table_creator : table_creator_base +{ + unsigned_value_table_creator(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(val int unsigned)"; + } +}; + +// rowset<> should be able to take INT UNSIGNED. +void test9() +{ + { + session sql(backEnd, connectString); + + unsigned_value_table_creator tableCreator(sql); + + unsigned int mask = 0xffffff00; + sql << "insert into soci_test set val = " << mask; + soci::rowset<> rows(sql.prepare << "select val from soci_test"); + int cnt = 0; + for (soci::rowset<>::iterator it = rows.begin(), end = rows.end(); + it != end; ++it) + { + cnt++; + } + assert(cnt == 1); + } + + std::cout << "test 9 passed" << std::endl; +} + +void test10() +{ + session sql(backEnd, connectString); + + row r; + + sql << "set @day = '5'"; + sql << "set @mm = 'december'"; + sql << "set @year = '2012'"; + sql << "select concat(@day,' ',@mm,' ',@year)", into(r); + + std::cout << "test 10 passed" << std::endl; +} + +struct double_value_table_creator : table_creator_base +{ + double_value_table_creator(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(val double)"; + } +}; + +void test11() +{ + const std::string expectedError = + "Use element used with infinity or NaN, which are " + "not supported by the MySQL server."; + { + session sql(backEnd, connectString); + + double x = std::numeric_limits::quiet_NaN(); + statement st = (sql.prepare << "SELECT :x", use(x, "x")); + try { + st.execute(true); + } catch (soci_error const &e) { + if (e.what() != expectedError) { + throw; + } + } + } + { + session sql(backEnd, connectString); + + double x = std::numeric_limits::infinity(); + statement st = (sql.prepare << "SELECT :x", use(x, "x")); + try { + st.execute(true); + } catch (soci_error const &e) { + if (e.what() != expectedError) { + throw; + } + } + } + { + session sql(backEnd, connectString); + double_value_table_creator tableCreator(sql); + + std::vector v(1, std::numeric_limits::quiet_NaN()); + try { + sql << "insert into soci_test (val) values (:val)", use(v); + } catch (soci_error const &e) { + if (e.what() != expectedError) { + throw; + } + } + } + { + session sql(backEnd, connectString); + double_value_table_creator tableCreator(sql); + + std::vector v(1, std::numeric_limits::infinity()); + try { + sql << "insert into soci_test (val) values (:val)", use(v); + } catch (soci_error const &e) { + if (e.what() != expectedError) { + throw; + } + } + } + + std::cout << "test 11 passed" << std::endl; +} + +struct tinyint_value_table_creator : table_creator_base +{ + tinyint_value_table_creator(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(val tinyint)"; + } +}; + +struct tinyint_unsigned_value_table_creator : table_creator_base +{ + tinyint_unsigned_value_table_creator(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(val tinyint unsigned)"; + } +}; + +void test12() +{ + { + session sql(backEnd, connectString); + unsigned_value_table_creator tableCreator(sql); + unsigned int mask = 0xffffff00; + sql << "insert into soci_test set val = " << mask; + row r; + sql << "select val from soci_test", into(r); + assert(r.size() == 1); + assert(r.get_properties("val").get_data_type() == dt_long_long); + assert(r.get("val") == 0xffffff00); + assert(r.get("val") == 0xffffff00); + } + { + session sql(backEnd, connectString); + tinyint_value_table_creator tableCreator(sql); + sql << "insert into soci_test set val = -123"; + row r; + sql << "select val from soci_test", into(r); + assert(r.size() == 1); + assert(r.get_properties("val").get_data_type() == dt_integer); + assert(r.get("val") == -123); + } + { + session sql(backEnd, connectString); + tinyint_unsigned_value_table_creator tableCreator(sql); + sql << "insert into soci_test set val = 123"; + row r; + sql << "select val from soci_test", into(r); + assert(r.size() == 1); + assert(r.get_properties("val").get_data_type() == dt_integer); + assert(r.get("val") == 123); + } + { + session sql(backEnd, connectString); + bigint_unsigned_table_creator tableCreator(sql); + sql << "insert into soci_test set val = 123456789012345"; + row r; + sql << "select val from soci_test", into(r); + assert(r.size() == 1); + assert(r.get_properties("val").get_data_type() == dt_unsigned_long_long); + assert(r.get("val") == 123456789012345ULL); + } + { + session sql(backEnd, connectString); + bigint_table_creator tableCreator(sql); + sql << "insert into soci_test set val = -123456789012345"; + row r; + sql << "select val from soci_test", into(r); + assert(r.size() == 1); + assert(r.get_properties("val").get_data_type() == dt_long_long); + assert(r.get("val") == -123456789012345LL); + } + + std::cout << "test 12 passed" << std::endl; +} + +struct strings_table_creator : table_creator_base +{ + strings_table_creator(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(s1 char(20), s2 varchar(20), " + "s3 tinytext, s4 mediumtext, s5 text, s6 longtext, " + "b1 binary(20), b2 varbinary(20), b3 tinyblob, b4 mediumblob, " + "b5 blob, b6 longblob, e1 enum ('foo', 'bar', 'baz'))"; + } +}; + +void test13() +{ + { + session sql(backEnd, connectString); + strings_table_creator tableCreator(sql); + std::string text = "Ala ma kota."; + std::string binary("Ala\0ma\0kota.........", 20); + sql << "insert into soci_test " + "(s1, s2, s3, s4, s5, s6, b1, b2, b3, b4, b5, b6, e1) values " + "(:s1, :s2, :s3, :s4, :d5, :s6, :b1, :b2, :b3, :b4, :b5, :b6, " + "\'foo\')", + use(text), use(text), use(text), use(text), use(text), use(text), + use(binary), use(binary), use(binary), use(binary), use(binary), + use(binary); + row r; + sql << "select s1, s2, s3, s4, s5, s6, b1, b2, b3, b4, b5, b6, e1 " + "from soci_test", into(r); + assert(r.size() == 13); + for (int i = 0; i < 13; i++) { + assert(r.get_properties(i).get_data_type() == dt_string); + if (i < 6) { + assert(r.get(i) == text); + } else if (i < 12) { + assert(r.get(i) == binary); + } else { + assert(r.get(i) == "foo"); + } + } + } + + std::cout << "test 13 passed" << std::endl; +} + +std::string escape_string(soci::session& sql, const std::string& s) +{ + mysql_session_backend* backend = static_cast( + sql.get_backend()); + char* escaped = new char[2 * s.size() + 1]; + mysql_real_escape_string(backend->conn_, escaped, s.data(), s.size()); + std::string retv = escaped; + delete [] escaped; + return retv; +} + +void test14() +{ + { + session sql(backEnd, connectString); + strings_table_creator tableCreator(sql); + std::string s = "word1'word2:word3"; + std::string escaped = escape_string(sql, s); + std::string query = "insert into soci_test (s5) values ('"; + query.append(escaped); + query.append("')"); + sql << query; + std::string s2; + sql << "select s5 from soci_test", into(s2); + assert(s == s2); + } + + std::cout << "test 14 passed" << std::endl; +} + +void test15() +{ + { + session sql(backEnd, connectString); + int n; + sql << "select @a := 123", into(n); + assert(n == 123); + } + + std::cout << "test 15 passed" << std::endl; +} + +// DDL Creation objects for common tests +struct table_creator_one : public table_creator_base +{ + table_creator_one(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(id integer, val integer, c char, " + "str varchar(20), sh int2, ul numeric(20), d float8, " + "tm datetime, i1 integer, i2 integer, i3 integer, " + "name varchar(20)) engine=InnoDB"; + } +}; + +struct table_creator_two : public table_creator_base +{ + table_creator_two(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(num_float float8, num_int integer," + " name varchar(20), sometime datetime, chr char)"; + } +}; + +struct table_creator_three : public table_creator_base +{ + table_creator_three(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(name varchar(100) not null, " + "phone varchar(15))"; + } +}; + +struct table_creator_for_get_affected_rows : table_creator_base +{ + table_creator_for_get_affected_rows(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(val integer)"; + } +}; + +// +// Support for SOCI Common Tests +// + +class test_context : public test_context_base +{ +public: + test_context(backend_factory const &backEnd, + std::string const &connectString) + : test_context_base(backEnd, connectString) {} + + table_creator_base* table_creator_1(session& s) const + { + return new table_creator_one(s); + } + + table_creator_base* table_creator_2(session& s) const + { + return new table_creator_two(s); + } + + table_creator_base* table_creator_3(session& s) const + { + return new table_creator_three(s); + } + + table_creator_base* table_creator_4(session& s) const + { + return new table_creator_for_get_affected_rows(s); + } + + std::string to_date_time(std::string const &datdt_string) const + { + return "\'" + datdt_string + "\'"; + } + +}; + +bool are_transactions_supported() +{ + session sql(backEnd, connectString); + sql << "drop table if exists soci_test"; + sql << "create table soci_test (id int) engine=InnoDB"; + row r; + sql << "show table status like \'soci_test\'", into(r); + bool retv = (r.get(1) == "InnoDB"); + sql << "drop table soci_test"; + return retv; +} + +int main(int argc, char** argv) +{ + if (argc == 2) + { + connectString = argv[1]; + } + else + { + std::cout << "usage: " << argv[0] + << " connectstring\n" + << "example: " << argv[0] + << " \"dbname=test user=root password=\'Ala ma kota\'\"\n"; + std::exit(1); + } + + try + { + test_context tc(backEnd, connectString); + common_tests tests(tc); + bool checkTransactions = are_transactions_supported(); + tests.run(checkTransactions); + + std::cout << "\nSOCI MySQL Tests:\n\n"; + + test1(); + test2(); + test3(); + test4(); + test5(); + test6(); + test7(); + // Test 8 commented out because a bug in soci can make it crash + // https://github.com/SOCI/soci/issues/136 + //test8(); + test9(); + test10(); + if (std::numeric_limits::is_iec559) { + test11(); + } else { + std::cout << "Skipping test11 " + << "(C++ implementation's double type is not IEC-559)\n"; + } + test12(); + test13(); + test14(); + test15(); + + std::cout << "\nOK, all tests passed.\n\n"; + return EXIT_SUCCESS; + } + catch (std::exception const & e) + { + std::cout << e.what() << '\n'; + } + + return EXIT_FAILURE; +} diff --git a/src/backends/mysql/vector-into-type.cpp b/src/backends/mysql/vector-into-type.cpp new file mode 100644 index 0000000000..93412f150c --- /dev/null +++ b/src/backends/mysql/vector-into-type.cpp @@ -0,0 +1,231 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton +// MySQL backend copyright (C) 2006 Pawel Aleksander Fedorynski +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_MYSQL_SOURCE +#include "soci-mysql.h" +#include "common.h" +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(disable:4355) +#endif + +using namespace soci; +using namespace soci::details; +using namespace soci::details::mysql; + + +void mysql_vector_into_type_backend::define_by_pos( + int &position, void *data, exchange_type type) +{ + data_ = data; + type_ = type; + position_ = position++; +} + +void mysql_vector_into_type_backend::pre_fetch() +{ + // nothing to do here +} + +namespace // anonymous +{ + +template +void set_invector_(void *p, int indx, T const &val) +{ + std::vector *dest = + static_cast *>(p); + + std::vector &v = *dest; + v[indx] = val; +} + +} // namespace anonymous + +void mysql_vector_into_type_backend::post_fetch(bool gotData, indicator *ind) +{ + if (gotData) + { + // Here, rowsToConsume_ in the Statement object designates + // the number of rows that need to be put in the user's buffers. + + // MySQL column positions start at 0 + int pos = position_ - 1; + + int const endRow = statement_.currentRow_ + statement_.rowsToConsume_; + + //mysql_data_seek(statement_.result_, statement_.currentRow_); + mysql_row_seek(statement_.result_, + statement_.resultRowOffsets_[statement_.currentRow_]); + for (int curRow = statement_.currentRow_, i = 0; + curRow != endRow; ++curRow, ++i) + { + MYSQL_ROW row = mysql_fetch_row(statement_.result_); + // first, deal with indicators + if (row[pos] == NULL) + { + if (ind == NULL) + { + throw soci_error( + "Null value fetched and no indicator defined."); + } + + ind[i] = i_null; + + // no need to convert data if it is null, go to next row + continue; + } + else + { + if (ind != NULL) + { + ind[i] = i_ok; + } + } + + // buffer with data retrieved from server, in text format + const char *buf = row[pos] != NULL ? row[pos] : ""; + + switch (type_) + { + case x_char: + set_invector_(data_, i, *buf); + break; + case x_stdstring: + { + unsigned long * lengths = + mysql_fetch_lengths(statement_.result_); + // Not sure if it's necessary, but the code below is used + // instead of + // set_invector_(data_, i, std::string(buf, lengths[pos]); + // to avoid copying the (possibly large) temporary string. + std::vector *dest = + static_cast *>(data_); + (*dest)[i].assign(buf, lengths[pos]); + } + break; + case x_short: + { + short val; + parse_num(buf, val); + set_invector_(data_, i, val); + } + break; + case x_integer: + { + int val; + parse_num(buf, val); + set_invector_(data_, i, val); + } + break; + case x_long_long: + { + long long val; + parse_num(buf, val); + set_invector_(data_, i, val); + } + break; + case x_unsigned_long_long: + { + unsigned long long val; + parse_num(buf, val); + set_invector_(data_, i, val); + } + break; + case x_double: + { + double val; + parse_num(buf, val); + set_invector_(data_, i, val); + } + break; + case x_stdtm: + { + // attempt to parse the string and convert to std::tm + std::tm t; + parse_std_tm(buf, t); + + set_invector_(data_, i, t); + } + break; + + default: + throw soci_error("Into element used with non-supported type."); + } + } + } + else // no data retrieved + { + // nothing to do, into vectors are already truncated + } +} + +namespace // anonymous +{ + +template +void resizevector_(void *p, std::size_t sz) +{ + std::vector *v = static_cast *>(p); + v->resize(sz); +} + +} // namespace anonymous + +void mysql_vector_into_type_backend::resize(std::size_t sz) +{ + switch (type_) + { + // simple cases + case x_char: resizevector_ (data_, sz); break; + case x_short: resizevector_ (data_, sz); break; + case x_integer: resizevector_ (data_, sz); break; + case x_long_long: resizevector_ (data_, sz); break; + case x_unsigned_long_long: + resizevector_(data_, sz); + break; + case x_double: resizevector_ (data_, sz); break; + case x_stdstring: resizevector_ (data_, sz); break; + case x_stdtm: resizevector_ (data_, sz); break; + + default: + throw soci_error("Into vector element used with non-supported type."); + } +} + +std::size_t mysql_vector_into_type_backend::size() +{ + std::size_t sz = 0; // dummy initialization to please the compiler + switch (type_) + { + // simple cases + case x_char: sz = get_vector_size (data_); break; + case x_short: sz = get_vector_size (data_); break; + case x_integer: sz = get_vector_size (data_); break; + case x_long_long: sz = get_vector_size (data_); break; + case x_unsigned_long_long: + sz = get_vector_size(data_); + break; + case x_double: sz = get_vector_size (data_); break; + case x_stdstring: sz = get_vector_size (data_); break; + case x_stdtm: sz = get_vector_size (data_); break; + + default: + throw soci_error("Into vector element used with non-supported type."); + } + + return sz; +} + +void mysql_vector_into_type_backend::clean_up() +{ + // nothing to do here +} diff --git a/src/backends/mysql/vector-use-type.cpp b/src/backends/mysql/vector-use-type.cpp new file mode 100644 index 0000000000..4a4935ad98 --- /dev/null +++ b/src/backends/mysql/vector-use-type.cpp @@ -0,0 +1,220 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton +// MySQL backend copyright (C) 2006 Pawel Aleksander Fedorynski +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_MYSQL_SOURCE +#include "soci-mysql.h" +#include "common.h" +#include +// std +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(disable:4355) +#endif + +using namespace soci; +using namespace soci::details; +using namespace soci::details::mysql; + + +void mysql_vector_use_type_backend::bind_by_pos(int &position, void *data, + exchange_type type) +{ + data_ = data; + type_ = type; + position_ = position++; +} + +void mysql_vector_use_type_backend::bind_by_name( + std::string const &name, void *data, exchange_type type) +{ + data_ = data; + type_ = type; + name_ = name; +} + +void mysql_vector_use_type_backend::pre_use(indicator const *ind) +{ + std::size_t const vsize = size(); + for (size_t i = 0; i != vsize; ++i) + { + char *buf; + + // the data in vector can be either i_ok or i_null + if (ind != NULL && ind[i] == i_null) + { + buf = new char[5]; + std::strcpy(buf, "NULL"); + } + else + { + // allocate and fill the buffer with text-formatted client data + switch (type_) + { + case x_char: + { + std::vector *pv + = static_cast *>(data_); + std::vector &v = *pv; + + char tmp[] = { v[i], '\0' }; + buf = quote(statement_.session_.conn_, tmp, 1); + } + break; + case x_stdstring: + { + std::vector *pv + = static_cast *>(data_); + std::vector &v = *pv; + + buf = quote(statement_.session_.conn_, + v[i].c_str(), v[i].size()); + } + break; + case x_short: + { + std::vector *pv + = static_cast *>(data_); + std::vector &v = *pv; + + std::size_t const bufSize + = std::numeric_limits::digits10 + 3; + buf = new char[bufSize]; + snprintf(buf, bufSize, "%d", static_cast(v[i])); + } + break; + case x_integer: + { + std::vector *pv + = static_cast *>(data_); + std::vector &v = *pv; + + std::size_t const bufSize + = std::numeric_limits::digits10 + 3; + buf = new char[bufSize]; + snprintf(buf, bufSize, "%d", v[i]); + } + break; + case x_long_long: + { + std::vector *pv + = static_cast *>(data_); + std::vector &v = *pv; + + std::size_t const bufSize + = std::numeric_limits::digits10 + 3; + buf = new char[bufSize]; + snprintf(buf, bufSize, "%" LL_FMT_FLAGS "d", v[i]); + } + break; + case x_unsigned_long_long: + { + std::vector *pv + = static_cast *>(data_); + std::vector &v = *pv; + + std::size_t const bufSize + = std::numeric_limits::digits10 + 3; + buf = new char[bufSize]; + snprintf(buf, bufSize, "%" LL_FMT_FLAGS "u", v[i]); + } + break; + case x_double: + { + std::vector *pv + = static_cast *>(data_); + std::vector &v = *pv; + + if (is_infinity_or_nan(v[i])) { + throw soci_error( + "Use element used with infinity or NaN, which are " + "not supported by the MySQL server."); + } + + std::size_t const bufSize = 100; + buf = new char[bufSize]; + + snprintf(buf, bufSize, "%.20g", v[i]); + } + break; + case x_stdtm: + { + std::vector *pv + = static_cast *>(data_); + std::vector &v = *pv; + + std::size_t const bufSize = 22; + buf = new char[bufSize]; + + snprintf(buf, bufSize, "\'%d-%02d-%02d %02d:%02d:%02d\'", + v[i].tm_year + 1900, v[i].tm_mon + 1, v[i].tm_mday, + v[i].tm_hour, v[i].tm_min, v[i].tm_sec); + } + break; + + default: + throw soci_error( + "Use vector element used with non-supported type."); + } + } + + buffers_.push_back(buf); + } + + if (position_ > 0) + { + // binding by position + statement_.useByPosBuffers_[position_] = &buffers_[0]; + } + else + { + // binding by name + statement_.useByNameBuffers_[name_] = &buffers_[0]; + } +} + +std::size_t mysql_vector_use_type_backend::size() +{ + std::size_t sz = 0; // dummy initialization to please the compiler + switch (type_) + { + // simple cases + case x_char: sz = get_vector_size (data_); break; + case x_short: sz = get_vector_size (data_); break; + case x_integer: sz = get_vector_size (data_); break; + case x_long_long: sz = get_vector_size (data_); break; + case x_unsigned_long_long: + sz = get_vector_size(data_); + break; + case x_double: sz = get_vector_size (data_); break; + case x_stdstring: sz = get_vector_size (data_); break; + case x_stdtm: sz = get_vector_size (data_); break; + + default: + throw soci_error("Use vector element used with non-supported type."); + } + + return sz; +} + +void mysql_vector_use_type_backend::clean_up() +{ + std::size_t const bsize = buffers_.size(); + for (std::size_t i = 0; i != bsize; ++i) + { + delete [] buffers_[i]; + } +} diff --git a/src/backends/odbc/CMakeLists.txt b/src/backends/odbc/CMakeLists.txt new file mode 100644 index 0000000000..adb8117faf --- /dev/null +++ b/src/backends/odbc/CMakeLists.txt @@ -0,0 +1,18 @@ +############################################################################### +# +# This file is part of CMake configuration for SOCI library +# +# Copyright (C) 2010 Mateusz Loskot +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) +# +############################################################################### +soci_backend(ODBC + DEPENDS ODBC + HEADERS soci-odbc.h utility.h + DESCRIPTION "SOCI backend for ODBC" + AUTHORS "Maciej Sobczak, Stephen Hutton, David Courtney" + MAINTAINERS "Vadim Zeitlin, Mateusz Loskot, Maciej Sobczak") + +add_subdirectory(test) diff --git a/src/backends/odbc/Makefile.basic b/src/backends/odbc/Makefile.basic new file mode 100644 index 0000000000..5082f914f3 --- /dev/null +++ b/src/backends/odbc/Makefile.basic @@ -0,0 +1,89 @@ +# The following variable is specific to this backend and its correct +# values might depend on your environment - feel free to set it accordingly. + +ODBCINCLUDEDIR = -I/usr/include + +# The rest of the Makefile is indepentent of the target environment. + +COMPILER = g++ +CXXFLAGS = -Wall -pedantic -Wno-long-long +CXXFLAGSSO = ${CXXFLAGS} -fPIC +INCLUDEDIRS = -I../../core ${ODBCINCLUDEDIR} + + +OBJECTS = blob.o factory.o row-id.o session.o standard-into-type.o \ + standard-use-type.o statement.o vector-into-type.o vector-use-type.o + +OBJECTSSO = blob-s.o factory-s.o row-id-s.o session-s.o \ + standard-into-type-s.o standard-use-type-s.o statement-s.o \ + vector-into-type-s.o vector-use-type-s.o + + +libsoci_odbc.a : ${OBJECTS} + ar rv $@ $? + ranlib $@ + rm *.o + + +blob.o : blob.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +factory.o : factory.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +row-id.o : row-id.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +session.o : session.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +standard-into-type.o : standard-into-type.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +standard-use-type.o : standard-use-type.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +statement.o : statement.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +vector-into-type.o : vector-into-type.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +vector-use-type.o : vector-use-type.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + + +shared : ${OBJECTSSO} + ${COMPILER} -shared -o libsoci_odbc.so ${OBJECTSSO} + rm *.o + +blob-s.o : blob.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +factory-s.o : factory.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +row-id-s.o : row-id.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +session-s.o : session.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +standard-into-type-s.o : standard-into-type.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +standard-use-type-s.o : standard-use-type.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +statement-s.o : statement.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +vector-into-type-s.o : vector-into-type.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +vector-use-type-s.o : vector-use-type.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + + +clean : + rm -f libsoci_odbc.a libsoci_odbc.so diff --git a/src/backends/odbc/blob.cpp b/src/backends/odbc/blob.cpp new file mode 100644 index 0000000000..cb5a3fd801 --- /dev/null +++ b/src/backends/odbc/blob.cpp @@ -0,0 +1,57 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, David Courtney +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_ODBC_SOURCE +#include "soci-odbc.h" + +using namespace soci; +using namespace soci::details; + + +odbc_blob_backend::odbc_blob_backend(odbc_session_backend &session) + : session_(session) +{ + // ... +} + +odbc_blob_backend::~odbc_blob_backend() +{ + // ... +} + +std::size_t odbc_blob_backend::get_len() +{ + // ... + return 0; +} + +std::size_t odbc_blob_backend::read( + std::size_t /* offset */, char * /* buf */, std::size_t /* toRead */) +{ + // ... + return 0; +} + +std::size_t odbc_blob_backend::write( + std::size_t /* offset */, char const * /* buf */, + std::size_t /* toWrite */) +{ + // ... + return 0; +} + +std::size_t odbc_blob_backend::append( + char const * /* buf */, std::size_t /* toWrite */) +{ + // ... + return 0; +} + +void odbc_blob_backend::trim(std::size_t /* newLen */) +{ + // ... +} diff --git a/src/backends/odbc/factory.cpp b/src/backends/odbc/factory.cpp new file mode 100644 index 0000000000..27f0d04d80 --- /dev/null +++ b/src/backends/odbc/factory.cpp @@ -0,0 +1,39 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, David Courtney +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_ODBC_SOURCE +#include "soci-odbc.h" +#include + +using namespace soci; +using namespace soci::details; + + +// concrete factory for ODBC concrete strategies +odbc_session_backend * odbc_backend_factory::make_session( + connection_parameters const & parameters) const +{ + return new odbc_session_backend(parameters); +} + +odbc_backend_factory const soci::odbc; + +extern "C" +{ + +// for dynamic backend loading +SOCI_ODBC_DECL backend_factory const * factory_odbc() +{ + return &soci::odbc; +} + +SOCI_ODBC_DECL void register_factory_odbc() +{ + soci::dynamic_backends::register_backend("odbc", soci::odbc); +} + +} // extern "C" diff --git a/src/backends/odbc/makefile.msvc b/src/backends/odbc/makefile.msvc new file mode 100644 index 0000000000..dcc5a96be8 --- /dev/null +++ b/src/backends/odbc/makefile.msvc @@ -0,0 +1,48 @@ +# The following variable is specific to this backend and its correct +# values might depend on your environment - feel free to set it accordingly. + +ODBCINCLUDEDIR="C:\Program Files\Microsoft Platform SDK\Include" + +# The rest of the Makefile is indepentent of the target environment. + +COMPILER = cl +CXXFLAGS = /nologo /EHsc /D_CRT_SECURE_NO_DEPRECATE +INCLUDEDIRS = /I..\..\core /I$(ODBCINCLUDEDIR) + +OBJECTS = blob.obj factory.obj row-id.obj session.obj standard-into-type.obj \ + standard-use-type.obj statement.obj vector-into-type.obj \ + vector-use-type.obj + +soci-odbc.lib : $(OBJECTS) + lib /NOLOGO /OUT:$@ $? + del *.obj + +blob.obj : blob.cpp + $(COMPILER) /c $? $(CXXFLAGS) $(INCLUDEDIRS) + +factory.obj : factory.cpp + $(COMPILER) /c $? $(CXXFLAGS) $(INCLUDEDIRS) + +row-id.obj : row-id.cpp + $(COMPILER) /c $? $(CXXFLAGS) $(INCLUDEDIRS) + +session.obj : session.cpp + $(COMPILER) /c $? $(CXXFLAGS) $(INCLUDEDIRS) + +standard-into-type.obj : standard-into-type.cpp + $(COMPILER) /c $? $(CXXFLAGS) $(INCLUDEDIRS) + +standard-use-type.obj : standard-use-type.cpp + $(COMPILER) /c $? $(CXXFLAGS) $(INCLUDEDIRS) + +statement.obj : statement.cpp + $(COMPILER) /c $? $(CXXFLAGS) $(INCLUDEDIRS) + +vector-into-type.obj : vector-into-type.cpp + $(COMPILER) /c $? $(CXXFLAGS) $(INCLUDEDIRS) + +vector-use-type.obj : vector-use-type.cpp + $(COMPILER) /c $? $(CXXFLAGS) $(INCLUDEDIRS) + +clean : + del soci-odbc.lib soci-odbc.dll diff --git a/src/backends/odbc/row-id.cpp b/src/backends/odbc/row-id.cpp new file mode 100644 index 0000000000..0461980ecd --- /dev/null +++ b/src/backends/odbc/row-id.cpp @@ -0,0 +1,23 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, David Courtney +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_ODBC_SOURCE +#include "soci-odbc.h" + +using namespace soci; +using namespace soci::details; + + +odbc_rowid_backend::odbc_rowid_backend(odbc_session_backend & /* session */) +{ + // ... +} + +odbc_rowid_backend::~odbc_rowid_backend() +{ + // ... +} diff --git a/src/backends/odbc/session.cpp b/src/backends/odbc/session.cpp new file mode 100644 index 0000000000..b61a3075c3 --- /dev/null +++ b/src/backends/odbc/session.cpp @@ -0,0 +1,296 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, David Courtney +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_ODBC_SOURCE +#include "soci-odbc.h" +#include "session.h" + +#include + +using namespace soci; +using namespace soci::details; + +char const * soci::odbc_option_driver_complete = "odbc.driver_complete"; + +odbc_session_backend::odbc_session_backend( + connection_parameters const & parameters) + : henv_(0), hdbc_(0), product_(prod_uninitialized) +{ + SQLRETURN rc; + + // Allocate environment handle + rc = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv_); + if (is_odbc_error(rc)) + { + throw soci_error("Unable to get environment handle"); + } + + // Set the ODBC version environment attribute + rc = SQLSetEnvAttr(henv_, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0); + if (is_odbc_error(rc)) + { + throw odbc_soci_error(SQL_HANDLE_ENV, henv_, + "Setting ODBC version"); + } + + // Allocate connection handle + rc = SQLAllocHandle(SQL_HANDLE_DBC, henv_, &hdbc_); + if (is_odbc_error(rc)) + { + throw odbc_soci_error(SQL_HANDLE_DBC, hdbc_, + "Allocating connection handle"); + } + + SQLCHAR outConnString[1024]; + SQLSMALLINT strLength; + + // Prompt the user for any missing information (typically UID/PWD) in the + // connection string by default but allow overriding this using "prompt" + // option. + SQLHWND hwnd_for_prompt = NULL; + unsigned completion = SQL_DRIVER_COMPLETE; + std::string completionString; + if (parameters.get_option(odbc_option_driver_complete, completionString)) + { + // The value of the option is supposed to be just the integer value of + // one of SQL_DRIVER_XXX constants but don't check for the exact value in + // case more of them are added in the future, the ODBC driver will return + // an error if we pass it an invalid value anyhow. + if (std::sscanf(completionString.c_str(), "%u", &completion) != 1) + { + throw soci_error("Invalid non-numeric driver completion option value \"" + + completionString + "\"."); + } + } + +#ifdef _WIN32 + if (completion != SQL_DRIVER_NOPROMPT) + hwnd_for_prompt = ::GetDesktopWindow(); +#endif // _WIN32 + + std::string const & connectString = parameters.get_connect_string(); + rc = SQLDriverConnect(hdbc_, hwnd_for_prompt, + (SQLCHAR *)connectString.c_str(), + (SQLSMALLINT)connectString.size(), + outConnString, 1024, &strLength, + static_cast(completion)); + + if (is_odbc_error(rc)) + { + throw odbc_soci_error(SQL_HANDLE_DBC, hdbc_, + "Error Connecting to database"); + } + + connection_string_.assign((const char*)outConnString, strLength); + + reset_transaction(); +} + +odbc_session_backend::~odbc_session_backend() +{ + clean_up(); +} + +void odbc_session_backend::begin() +{ + SQLRETURN rc = SQLSetConnectAttr( hdbc_, SQL_ATTR_AUTOCOMMIT, + (SQLPOINTER)SQL_AUTOCOMMIT_OFF, 0 ); + if (is_odbc_error(rc)) + { + throw odbc_soci_error(SQL_HANDLE_DBC, hdbc_, + "Begin Transaction"); + } +} + +void odbc_session_backend::commit() +{ + SQLRETURN rc = SQLEndTran(SQL_HANDLE_DBC, hdbc_, SQL_COMMIT); + if (is_odbc_error(rc)) + { + throw odbc_soci_error(SQL_HANDLE_DBC, hdbc_, + "Committing"); + } + reset_transaction(); +} + +void odbc_session_backend::rollback() +{ + SQLRETURN rc = SQLEndTran(SQL_HANDLE_DBC, hdbc_, SQL_ROLLBACK); + if (is_odbc_error(rc)) + { + throw odbc_soci_error(SQL_HANDLE_DBC, hdbc_, + "Rolling back"); + } + reset_transaction(); +} + +bool odbc_session_backend::get_next_sequence_value( + session & s, std::string const & sequence, long & value) +{ + std::string query; + + switch ( get_database_product() ) + { + case prod_firebird: + query = "select next value for " + sequence + " from rdb$database"; + break; + + case prod_oracle: + query = "select " + sequence + ".nextval from dual"; + break; + + case prod_postgresql: + query = "select nextval('" + sequence + "')"; + break; + + case prod_mssql: + case prod_mysql: + case prod_sqlite: + // These RDBMS implement get_last_insert_id() instead. + return false; + + case prod_unknown: + // For this one we can't do anything at all. + return false; + + case prod_uninitialized: + // This is not supposed to happen at all but still cover this case + // here to avoid gcc warnings about unhandled enum values in a + // switch. + return false; + } + + s << query, into(value); + + return true; +} + +bool odbc_session_backend::get_last_insert_id( + session & s, std::string const & table, long & value) +{ + std::string query; + + switch ( get_database_product() ) + { + case prod_mssql: + query = "select ident_current('" + table + "')"; + break; + + case prod_mysql: + query = "select last_insert_id()"; + break; + + case prod_sqlite: + query = "select last_insert_rowid()"; + break; + + case prod_firebird: + case prod_oracle: + case prod_postgresql: + // For these RDBMS get_next_sequence_value() should have been used. + return false; + + + case prod_unknown: + // For this one we can't do anything at all. + return false; + + case prod_uninitialized: + // As above, this is not supposed to happen but put it here to + // mollify gcc. + return false; + } + + s << query, into(value); + + return true; +} + +void odbc_session_backend::reset_transaction() +{ + SQLRETURN rc = SQLSetConnectAttr( hdbc_, SQL_ATTR_AUTOCOMMIT, + (SQLPOINTER)SQL_AUTOCOMMIT_ON, 0 ); + if (is_odbc_error(rc)) + { + throw odbc_soci_error(SQL_HANDLE_DBC, hdbc_, + "Set Auto Commit"); + } +} + + +void odbc_session_backend::clean_up() +{ + SQLRETURN rc = SQLDisconnect(hdbc_); + if (is_odbc_error(rc)) + { + throw odbc_soci_error(SQL_HANDLE_DBC, hdbc_, + "SQLDisconnect"); + } + + rc = SQLFreeHandle(SQL_HANDLE_DBC, hdbc_); + if (is_odbc_error(rc)) + { + throw odbc_soci_error(SQL_HANDLE_DBC, hdbc_, + "SQLFreeHandle DBC"); + } + + rc = SQLFreeHandle(SQL_HANDLE_ENV, henv_); + if (is_odbc_error(rc)) + { + throw odbc_soci_error(SQL_HANDLE_ENV, henv_, + "SQLFreeHandle ENV"); + } +} + +odbc_statement_backend * odbc_session_backend::make_statement_backend() +{ + return new odbc_statement_backend(*this); +} + +odbc_rowid_backend * odbc_session_backend::make_rowid_backend() +{ + return new odbc_rowid_backend(*this); +} + +odbc_blob_backend * odbc_session_backend::make_blob_backend() +{ + return new odbc_blob_backend(*this); +} + +odbc_session_backend::database_product +odbc_session_backend::get_database_product() +{ + // Cache the product type, it's not going to change during our life time. + if (product_ != prod_uninitialized) + return product_; + + char product_name[1024]; + SQLSMALLINT len = sizeof(product_name); + SQLRETURN rc = SQLGetInfo(hdbc_, SQL_DBMS_NAME, product_name, len, &len); + if (is_odbc_error(rc)) + { + throw odbc_soci_error(SQL_HANDLE_DBC, henv_, + "SQLGetInfo(SQL_DBMS_NAME)"); + } + + if (strcmp(product_name, "Firebird") == 0) + product_ = prod_firebird; + else if (strcmp(product_name, "Microsoft SQL Server") == 0) + product_ = prod_mssql; + else if (strcmp(product_name, "MySQL") == 0) + product_ = prod_mysql; + else if (strcmp(product_name, "Oracle") == 0) + product_ = prod_oracle; + else if (strcmp(product_name, "PostgreSQL") == 0) + product_ = prod_postgresql; + else if (strcmp(product_name, "SQLite") == 0) + product_ = prod_sqlite; + else + product_ = prod_unknown; + + return product_; +} diff --git a/src/backends/odbc/soci-odbc.h b/src/backends/odbc/soci-odbc.h new file mode 100644 index 0000000000..c820e8d55e --- /dev/null +++ b/src/backends/odbc/soci-odbc.h @@ -0,0 +1,432 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, David Courtney +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_ODBC_H_INCLUDED +#define SOCI_ODBC_H_INCLUDED + +#ifdef _WIN32 +# ifdef SOCI_DLL +# ifdef SOCI_ODBC_SOURCE +# define SOCI_ODBC_DECL __declspec(dllexport) +# else +# define SOCI_ODBC_DECL __declspec(dllimport) +# endif // SOCI_ODBC_SOURCE +# endif // SOCI_DLL +#endif // _WIN32 +// +// If SOCI_ODBC_DECL isn't defined yet define it now +#ifndef SOCI_ODBC_DECL +# define SOCI_ODBC_DECL +#endif + +#include +#include +#if defined(_MSC_VER) || defined(__MINGW32__) +#include +#include +#endif +#include // ODBC +#include // strcpy() + +namespace soci +{ + + // TODO: Do we want to make it a part of public interface? --mloskot +namespace details +{ + std::size_t const odbc_max_buffer_length = 100 * 1024 * 1024; +} + +// Option allowing to specify the "driver completion" parameter of +// SQLDriverConnect(). Its possible values are the same as the allowed values +// for this parameter in the official ODBC, i.e. one of SQL_DRIVER_XXX (in +// string form as all options are strings currently). +extern SOCI_ODBC_DECL char const * odbc_option_driver_complete; + +struct odbc_statement_backend; + +// Helper of into and use backends. +class odbc_standard_type_backend_base +{ +protected: + odbc_standard_type_backend_base(odbc_statement_backend &st) + : statement_(st) {} + + // Check if we need to pass 64 bit integers as strings to the database as + // some drivers don't support them directly. + inline bool use_string_for_bigint() const; + + // If we do need to use strings for 64 bit integers, this constant defines + // the maximal string length needed. + enum + { + // This is the length of decimal representation of UINT64_MAX + 1. + max_bigint_length = 21 + }; + + odbc_statement_backend &statement_; +}; + +struct odbc_standard_into_type_backend : details::standard_into_type_backend, + private odbc_standard_type_backend_base +{ + odbc_standard_into_type_backend(odbc_statement_backend &st) + : odbc_standard_type_backend_base(st), buf_(0) + {} + + virtual void define_by_pos(int &position, + void *data, details::exchange_type type); + + virtual void pre_fetch(); + virtual void post_fetch(bool gotData, bool calledFromFetch, + indicator *ind); + + virtual void clean_up(); + + char *buf_; // generic buffer + void *data_; + details::exchange_type type_; + int position_; + SQLSMALLINT odbcType_; + SQLLEN valueLen_; +}; + +struct odbc_vector_into_type_backend : details::vector_into_type_backend, + private odbc_standard_type_backend_base +{ + odbc_vector_into_type_backend(odbc_statement_backend &st) + : odbc_standard_type_backend_base(st), indHolders_(NULL), + data_(NULL), buf_(NULL) {} + + virtual void define_by_pos(int &position, + void *data, details::exchange_type type); + + virtual void pre_fetch(); + virtual void post_fetch(bool gotData, indicator *ind); + + virtual void resize(std::size_t sz); + virtual std::size_t size(); + + virtual void clean_up(); + + // helper function for preparing indicators + // (as part of the define_by_pos) + void prepare_indicators(std::size_t size); + + + SQLLEN *indHolders_; + std::vector indHolderVec_; + void *data_; + char *buf_; // generic buffer + details::exchange_type type_; + std::size_t colSize_; // size of the string column (used for strings) + SQLSMALLINT odbcType_; +}; + +struct odbc_standard_use_type_backend : details::standard_use_type_backend, + private odbc_standard_type_backend_base +{ + odbc_standard_use_type_backend(odbc_statement_backend &st) + : odbc_standard_type_backend_base(st), + position_(-1), data_(0), buf_(0), indHolder_(0) {} + + virtual void bind_by_pos(int &position, + void *data, details::exchange_type type, bool readOnly); + virtual void bind_by_name(std::string const &name, + void *data, details::exchange_type type, bool readOnly); + + virtual void pre_use(indicator const *ind); + virtual void post_use(bool gotData, indicator *ind); + + virtual void clean_up(); + + // Return the pointer to the buffer containing data to be used by ODBC. + // This can be either data_ itself or buf_, that is allocated by this + // function if necessary. + // + // Also fill in the size of the data and SQL and C types of it. + void* prepare_for_bind(SQLLEN &size, + SQLSMALLINT &sqlType, SQLSMALLINT &cType); + + int position_; + void *data_; + details::exchange_type type_; + char *buf_; + SQLLEN indHolder_; +}; + +struct odbc_vector_use_type_backend : details::vector_use_type_backend, + private odbc_standard_type_backend_base +{ + odbc_vector_use_type_backend(odbc_statement_backend &st) + : odbc_standard_type_backend_base(st), indHolders_(NULL), + data_(NULL), buf_(NULL) {} + + // helper function for preparing indicators + // (as part of the define_by_pos) + void prepare_indicators(std::size_t size); + + // common part for bind_by_pos and bind_by_name + void prepare_for_bind(void *&data, SQLUINTEGER &size, SQLSMALLINT &sqlType, SQLSMALLINT &cType); + void bind_helper(int &position, + void *data, details::exchange_type type); + + virtual void bind_by_pos(int &position, + void *data, details::exchange_type type); + virtual void bind_by_name(std::string const &name, + void *data, details::exchange_type type); + + virtual void pre_use(indicator const *ind); + + virtual std::size_t size(); + + virtual void clean_up(); + + + SQLLEN *indHolders_; + std::vector indHolderVec_; + void *data_; + details::exchange_type type_; + char *buf_; // generic buffer + std::size_t colSize_; // size of the string column (used for strings) + // used for strings only + std::size_t maxSize_; +}; + +struct odbc_session_backend; +struct odbc_statement_backend : details::statement_backend +{ + odbc_statement_backend(odbc_session_backend &session); + + virtual void alloc(); + virtual void clean_up(); + virtual void prepare(std::string const &query, + details::statement_type eType); + + virtual exec_fetch_result execute(int number); + virtual exec_fetch_result fetch(int number); + + virtual long long get_affected_rows(); + virtual int get_number_of_rows(); + + virtual std::string rewrite_for_procedure_call(std::string const &query); + + virtual int prepare_for_describe(); + virtual void describe_column(int colNum, data_type &dtype, + std::string &columnName); + + // helper for defining into vector + std::size_t column_size(int position); + + virtual odbc_standard_into_type_backend * make_into_type_backend(); + virtual odbc_standard_use_type_backend * make_use_type_backend(); + virtual odbc_vector_into_type_backend * make_vector_into_type_backend(); + virtual odbc_vector_use_type_backend * make_vector_use_type_backend(); + + odbc_session_backend &session_; + SQLHSTMT hstmt_; + SQLULEN numRowsFetched_; + bool hasVectorUseElements_; + bool boundByName_; + bool boundByPos_; + + long long rowsAffected_; // number of rows affected by the last operation + + std::string query_; + std::vector names_; // list of names for named binds + +}; + +struct odbc_rowid_backend : details::rowid_backend +{ + odbc_rowid_backend(odbc_session_backend &session); + + ~odbc_rowid_backend(); +}; + +struct odbc_blob_backend : details::blob_backend +{ + odbc_blob_backend(odbc_session_backend &session); + + ~odbc_blob_backend(); + + virtual std::size_t get_len(); + virtual std::size_t read(std::size_t offset, char *buf, + std::size_t toRead); + virtual std::size_t write(std::size_t offset, char const *buf, + std::size_t toWrite); + virtual std::size_t append(char const *buf, std::size_t toWrite); + virtual void trim(std::size_t newLen); + + odbc_session_backend &session_; +}; + +struct odbc_session_backend : details::session_backend +{ + odbc_session_backend(connection_parameters const & parameters); + + ~odbc_session_backend(); + + virtual void begin(); + virtual void commit(); + virtual void rollback(); + + virtual bool get_next_sequence_value(session & s, + std::string const & sequence, long & value); + virtual bool get_last_insert_id(session & s, + std::string const & table, long & value); + + virtual std::string get_backend_name() const { return "odbc"; } + + void reset_transaction(); + + void clean_up(); + + virtual odbc_statement_backend * make_statement_backend(); + virtual odbc_rowid_backend * make_rowid_backend(); + virtual odbc_blob_backend * make_blob_backend(); + + enum database_product + { + prod_uninitialized, // Never returned by get_database_product(). + prod_firebird, + prod_mssql, + prod_mysql, + prod_oracle, + prod_postgresql, + prod_sqlite, + prod_unknown = -1 + }; + + // Determine the type of the database we're connected to. + database_product get_database_product(); + + // Return full ODBC connection string. + std::string get_connection_string() const { return connection_string_; } + + SQLHENV henv_; + SQLHDBC hdbc_; + + std::string connection_string_; + database_product product_; +}; + +class SOCI_ODBC_DECL odbc_soci_error : public soci_error +{ + SQLCHAR message_[SQL_MAX_MESSAGE_LENGTH + 1]; + SQLCHAR sqlstate_[SQL_SQLSTATE_SIZE + 1]; + SQLINTEGER sqlcode_; + +public: + odbc_soci_error(SQLSMALLINT htype, + SQLHANDLE hndl, + std::string const & msg) + : soci_error(msg) + { + const char* socierror = NULL; + + SQLSMALLINT length, i = 1; + switch ( SQLGetDiagRecA(htype, hndl, i, sqlstate_, &sqlcode_, + message_, SQL_MAX_MESSAGE_LENGTH + 1, + &length) ) + { + case SQL_SUCCESS: + // The error message was successfully retrieved. + break; + + case SQL_INVALID_HANDLE: + socierror = "[SOCI]: Invalid handle."; + break; + + case SQL_ERROR: + socierror = "[SOCI]: SQLGetDiagRec() error."; + break; + + case SQL_SUCCESS_WITH_INFO: + socierror = "[SOCI]: Error message too long."; + break; + + case SQL_NO_DATA: + socierror = "[SOCI]: No error."; + break; + + default: + socierror = "[SOCI]: Unexpected SQLGetDiagRec() return value."; + break; + } + + if (socierror) + { + // Use our own error message if we failed to retrieve the ODBC one. + strcpy(reinterpret_cast(message_), socierror); + + // Use "General warning" SQLSTATE code. + strcpy(reinterpret_cast(sqlstate_), "01000"); + + sqlcode_ = 0; + } + } + + SQLCHAR const * odbc_error_code() const + { + return reinterpret_cast(sqlstate_); + } + SQLINTEGER native_error_code() const + { + return sqlcode_; + } + SQLCHAR const * odbc_error_message() const + { + return reinterpret_cast(message_); + } +}; + +inline bool is_odbc_error(SQLRETURN rc) +{ + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO && rc != SQL_NO_DATA) + { + return true; + } + else + { + return false; + } +} + +inline bool odbc_standard_type_backend_base::use_string_for_bigint() const +{ + // Oracle ODBC driver doesn't support SQL_C_[SU]BIGINT data types + // (see appendix G.1 of Oracle Database Administrator's reference at + // http://docs.oracle.com/cd/B19306_01/server.102/b15658/app_odbc.htm), + // so we need a special workaround for this case and we represent 64 + // bit integers as strings and rely on ODBC driver for transforming + // them to SQL_NUMERIC. + return statement_.session_.get_database_product() + == odbc_session_backend::prod_oracle; +} + +struct odbc_backend_factory : backend_factory +{ + odbc_backend_factory() {} + virtual odbc_session_backend * make_session( + connection_parameters const & parameters) const; +}; + +extern SOCI_ODBC_DECL odbc_backend_factory const odbc; + +extern "C" +{ + +// for dynamic backend loading +SOCI_ODBC_DECL backend_factory const * factory_odbc(); +SOCI_ODBC_DECL void register_factory_odbc(); + +} // extern "C" + +} // namespace soci + +#endif // SOCI_EMPTY_H_INCLUDED diff --git a/src/backends/odbc/standard-into-type.cpp b/src/backends/odbc/standard-into-type.cpp new file mode 100644 index 0000000000..c59d9b49ae --- /dev/null +++ b/src/backends/odbc/standard-into-type.cpp @@ -0,0 +1,203 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, David Courtney +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_ODBC_SOURCE +#include +#include "soci-odbc.h" +#include +#include // sscanf() + +using namespace soci; +using namespace soci::details; + +void odbc_standard_into_type_backend::define_by_pos( + int & position, void * data, exchange_type type) +{ + data_ = data; + type_ = type; + position_ = position++; + + SQLUINTEGER size = 0; + + switch (type_) + { + case x_char: + odbcType_ = SQL_C_CHAR; + size = sizeof(char) + 1; + buf_ = new char[size]; + data = buf_; + break; + case x_stdstring: + odbcType_ = SQL_C_CHAR; + // Patch: set to min between column size and 100MB (used ot be 32769) + // Column size for text data type can be too large for buffer allocation + size = statement_.column_size(position_); + size = size > odbc_max_buffer_length ? odbc_max_buffer_length : size; + size++; + buf_ = new char[size]; + data = buf_; + break; + case x_short: + odbcType_ = SQL_C_SSHORT; + size = sizeof(short); + break; + case x_integer: + odbcType_ = SQL_C_SLONG; + size = sizeof(int); + break; + case x_long_long: + if (use_string_for_bigint()) + { + odbcType_ = SQL_C_CHAR; + size = max_bigint_length; + buf_ = new char[size]; + data = buf_; + } + else // Normal case, use ODBC support. + { + odbcType_ = SQL_C_SBIGINT; + size = sizeof(long long); + } + break; + case x_unsigned_long_long: + if (use_string_for_bigint()) + { + odbcType_ = SQL_C_CHAR; + size = max_bigint_length; + buf_ = new char[size]; + data = buf_; + } + else // Normal case, use ODBC support. + { + odbcType_ = SQL_C_UBIGINT; + size = sizeof(unsigned long long); + } + break; + case x_double: + odbcType_ = SQL_C_DOUBLE; + size = sizeof(double); + break; + case x_stdtm: + odbcType_ = SQL_C_TYPE_TIMESTAMP; + size = sizeof(TIMESTAMP_STRUCT); + buf_ = new char[size]; + data = buf_; + break; + case x_rowid: + odbcType_ = SQL_C_ULONG; + size = sizeof(unsigned long); + break; + default: + throw soci_error("Into element used with non-supported type."); + } + + valueLen_ = 0; + + SQLRETURN rc = SQLBindCol(statement_.hstmt_, static_cast(position_), + static_cast(odbcType_), data, size, &valueLen_); + if (is_odbc_error(rc)) + { + throw odbc_soci_error(SQL_HANDLE_STMT, statement_.hstmt_, + "into type pre_fetch"); + } +} + +void odbc_standard_into_type_backend::pre_fetch() +{ + //... +} + +void odbc_standard_into_type_backend::post_fetch( + bool gotData, bool calledFromFetch, indicator * ind) +{ + if (calledFromFetch == true && gotData == false) + { + // this is a normal end-of-rowset condition, + // no need to do anything (fetch() will return false) + return; + } + + if (gotData) + { + // first, deal with indicators + if (SQL_NULL_DATA == valueLen_) + { + if (ind == NULL) + { + throw soci_error( + "Null value fetched and no indicator defined."); + } + + *ind = i_null; + return; + } + else + { + if (ind != NULL) + { + *ind = i_ok; + } + } + + // only std::string and std::tm need special handling + if (type_ == x_char) + { + char *c = static_cast(data_); + *c = buf_[0]; + } + if (type_ == x_stdstring) + { + std::string *s = static_cast(data_); + *s = buf_; + if (s->size() >= (odbc_max_buffer_length - 1)) + { + throw soci_error("Buffer size overflow; maybe got too large string"); + } + } + else if (type_ == x_stdtm) + { + std::tm *t = static_cast(data_); + + TIMESTAMP_STRUCT * ts = reinterpret_cast(buf_); + t->tm_isdst = -1; + t->tm_year = ts->year - 1900; + t->tm_mon = ts->month - 1; + t->tm_mday = ts->day; + t->tm_hour = ts->hour; + t->tm_min = ts->minute; + t->tm_sec = ts->second; + + // normalize and compute the remaining fields + std::mktime(t); + } + else if (type_ == x_long_long && use_string_for_bigint()) + { + long long *ll = static_cast(data_); + if (sscanf(buf_, "%" LL_FMT_FLAGS "d", ll) != 1) + { + throw soci_error("Failed to parse the returned 64-bit integer value"); + } + } + else if (type_ == x_unsigned_long_long && use_string_for_bigint()) + { + unsigned long long *ll = static_cast(data_); + if (sscanf(buf_, "%" LL_FMT_FLAGS "u", ll) != 1) + { + throw soci_error("Failed to parse the returned 64-bit integer value"); + } + } + } +} + +void odbc_standard_into_type_backend::clean_up() +{ + if (buf_) + { + delete [] buf_; + buf_ = 0; + } +} diff --git a/src/backends/odbc/standard-use-type.cpp b/src/backends/odbc/standard-use-type.cpp new file mode 100644 index 0000000000..233c6c1e64 --- /dev/null +++ b/src/backends/odbc/standard-use-type.cpp @@ -0,0 +1,262 @@ +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, David Courtney +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#define SOCI_ODBC_SOURCE +#include +#include "soci-odbc.h" +#include +#include +#include +#include +#include + +using namespace soci; +using namespace soci::details; + +#ifdef _MSC_VER +#pragma warning(disable:4996) +#define snprintf _snprintf +#endif + + +void* odbc_standard_use_type_backend::prepare_for_bind( + SQLLEN &size, SQLSMALLINT &sqlType, SQLSMALLINT &cType) +{ + switch (type_) + { + // simple cases + case x_short: + sqlType = SQL_SMALLINT; + cType = SQL_C_SSHORT; + size = sizeof(short); + break; + case x_integer: + sqlType = SQL_INTEGER; + cType = SQL_C_SLONG; + size = sizeof(int); + break; + case x_long_long: + if (use_string_for_bigint()) + { + sqlType = SQL_NUMERIC; + cType = SQL_C_CHAR; + size = max_bigint_length; + buf_ = new char[size]; + snprintf(buf_, size, "%" LL_FMT_FLAGS "d", + *static_cast(data_)); + indHolder_ = SQL_NTS; + } + else // Normal case, use ODBC support. + { + sqlType = SQL_BIGINT; + cType = SQL_C_SBIGINT; + size = sizeof(long long); + } + break; + case x_unsigned_long_long: + if (use_string_for_bigint()) + { + sqlType = SQL_NUMERIC; + cType = SQL_C_CHAR; + size = max_bigint_length; + buf_ = new char[size]; + snprintf(buf_, size, "%" LL_FMT_FLAGS "u", + *static_cast(data_)); + indHolder_ = SQL_NTS; + } + else // Normal case, use ODBC support. + { + sqlType = SQL_BIGINT; + cType = SQL_C_UBIGINT; + size = sizeof(unsigned long long); + } + break; + case x_double: + sqlType = SQL_DOUBLE; + cType = SQL_C_DOUBLE; + size = sizeof(double); + break; + + case x_char: + sqlType = SQL_CHAR; + cType = SQL_C_CHAR; + size = 2; + buf_ = new char[size]; + buf_[0] = *static_cast(data_); + buf_[1] = '\0'; + indHolder_ = SQL_NTS; + break; + case x_stdstring: + { + std::string* s = static_cast(data_); + sqlType = SQL_VARCHAR; + cType = SQL_C_CHAR; + size = s->size(); + buf_ = new char[size+1]; + memcpy(buf_, s->c_str(), size); + buf_[size++] = '\0'; + indHolder_ = SQL_NTS; + } + break; + case x_stdtm: + { + std::tm *t = static_cast(data_); + + sqlType = SQL_TIMESTAMP; + cType = SQL_C_TIMESTAMP; + buf_ = new char[sizeof(TIMESTAMP_STRUCT)]; + size = 19; // This number is not the size in bytes, but the number + // of characters in the date if it was written out + // yyyy-mm-dd hh:mm:ss + + TIMESTAMP_STRUCT * ts = reinterpret_cast(buf_); + + ts->year = static_cast(t->tm_year + 1900); + ts->month = static_cast(t->tm_mon + 1); + ts->day = static_cast(t->tm_mday); + ts->hour = static_cast(t->tm_hour); + ts->minute = static_cast(t->tm_min); + ts->second = static_cast(t->tm_sec); + ts->fraction = 0; + } + break; + + case x_blob: + { +// sqlType = SQL_VARBINARY; +// cType = SQL_C_BINARY; + +// BLOB *b = static_cast(data); + +// odbc_blob_backend *bbe +// = static_cast(b->getBackEnd()); + +// size = 0; +// indHolder_ = size; + //TODO data = &bbe->lobp_; + } + break; + case x_statement: + case x_rowid: + // Unsupported data types. + return NULL; + } + + // Return either the pointer to C++ data itself or the buffer that we + // allocated, if any. + return buf_ ? buf_ : data_; +} + +void odbc_standard_use_type_backend::bind_by_pos( + int &position, void *data, exchange_type type, bool /* readOnly */) +{ + if (statement_.boundByName_) + { + throw soci_error( + "Binding for use elements must be either by position or by name."); + } + + position_ = position++; + data_ = data; + type_ = type; + + statement_.boundByPos_ = true; +} + +void odbc_standard_use_type_backend::bind_by_name( + std::string const &name, void *data, exchange_type type, bool /* readOnly */) +{ + if (statement_.boundByPos_) + { + throw soci_error( + "Binding for use elements must be either by position or by name."); + } + + int position = -1; + int count = 1; + + for (std::vector::iterator it = statement_.names_.begin(); + it != statement_.names_.end(); ++it) + { + if (*it == name) + { + position = count; + break; + } + count++; + } + + if (position == -1) + { + std::ostringstream ss; + ss << "Unable to find name '" << name << "' to bind to"; + throw soci_error(ss.str().c_str()); + } + + position_ = position; + data_ = data; + type_ = type; + + statement_.boundByName_ = true; +} + +void odbc_standard_use_type_backend::pre_use(indicator const *ind) +{ + // first deal with data + SQLSMALLINT sqlType; + SQLSMALLINT cType; + SQLLEN size; + + void* const sqlData = prepare_for_bind(size, sqlType, cType); + + SQLRETURN rc = SQLBindParameter(statement_.hstmt_, + static_cast(position_), + SQL_PARAM_INPUT, + cType, sqlType, size, 0, + sqlData, 0, &indHolder_); + + if (is_odbc_error(rc)) + { + throw odbc_soci_error(SQL_HANDLE_STMT, statement_.hstmt_, + "Binding"); + } + + // then handle indicators + if (ind != NULL && *ind == i_null) + { + indHolder_ = SQL_NULL_DATA; // null + } +} + +void odbc_standard_use_type_backend::post_use(bool gotData, indicator *ind) +{ + if (ind != NULL) + { + if (gotData) + { + if (indHolder_ == 0) + { + *ind = i_ok; + } + else if (indHolder_ == SQL_NULL_DATA) + { + *ind = i_null; + } + else + { + *ind = i_truncated; + } + } + } +} + +void odbc_standard_use_type_backend::clean_up() +{ + if (buf_ != NULL) + { + delete [] buf_; + buf_ = NULL; + } +} diff --git a/src/backends/odbc/statement.cpp b/src/backends/odbc/statement.cpp new file mode 100644 index 0000000000..ae688bee5c --- /dev/null +++ b/src/backends/odbc/statement.cpp @@ -0,0 +1,360 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, David Courtney +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_ODBC_SOURCE +#include "soci-odbc.h" +#include +#include +#include + +#ifdef _MSC_VER +// disables the warning about converting int to void*. This is a 64 bit compatibility +// warning, but odbc requires the value to be converted on this line +// SQLSetStmtAttr(hstmt_, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER)number, 0); +#pragma warning(disable:4312) +#endif + +using namespace soci; +using namespace soci::details; + + +odbc_statement_backend::odbc_statement_backend(odbc_session_backend &session) + : session_(session), hstmt_(0), numRowsFetched_(0), + hasVectorUseElements_(false), boundByName_(false), boundByPos_(false), + rowsAffected_(-1LL) +{ +} + +void odbc_statement_backend::alloc() +{ + SQLRETURN rc; + + // Allocate environment handle + rc = SQLAllocHandle(SQL_HANDLE_STMT, session_.hdbc_, &hstmt_); + if (is_odbc_error(rc)) + { + throw odbc_soci_error(SQL_HANDLE_DBC, session_.hdbc_, + "Allocating statement"); + } +} + +void odbc_statement_backend::clean_up() +{ + rowsAffected_ = -1LL; + + SQLFreeHandle(SQL_HANDLE_STMT, hstmt_); +} + + +void odbc_statement_backend::prepare(std::string const & query, + statement_type /* eType */) +{ + // rewrite the query by transforming all named parameters into + // the ODBC numbers ones (:abc -> $1, etc.) + + enum { eNormal, eInQuotes, eInName, eInAccessDate } state = eNormal; + + std::string name; + query_.reserve(query.length()); + + for (std::string::const_iterator it = query.begin(), end = query.end(); + it != end; ++it) + { + switch (state) + { + case eNormal: + if (*it == '\'') + { + query_ += *it; + state = eInQuotes; + } + else if (*it == '#') + { + query_ += *it; + state = eInAccessDate; + } + else if (*it == ':') + { + state = eInName; + } + else // regular character, stay in the same state + { + query_ += *it; + } + break; + case eInQuotes: + if (*it == '\'') + { + query_ += *it; + state = eNormal; + } + else // regular quoted character + { + query_ += *it; + } + break; + case eInName: + if (std::isalnum(*it) || *it == '_') + { + name += *it; + } + else // end of name + { + names_.push_back(name); + name.clear(); + query_ += "?"; + query_ += *it; + state = eNormal; + } + break; + case eInAccessDate: + if (*it == '#') + { + query_ += *it; + state = eNormal; + } + else // regular quoted character + { + query_ += *it; + } + break; + } + } + + if (state == eInName) + { + names_.push_back(name); + query_ += "?"; + } + + SQLRETURN rc = SQLPrepare(hstmt_, (SQLCHAR*)query_.c_str(), (SQLINTEGER)query_.size()); + if (is_odbc_error(rc)) + { + throw odbc_soci_error(SQL_HANDLE_STMT, hstmt_, + query_.c_str()); + } +} + +statement_backend::exec_fetch_result +odbc_statement_backend::execute(int number) +{ + // Store the number of rows processed by this call. + SQLULEN rows_processed = 0; + if (hasVectorUseElements_) + { + SQLSetStmtAttr(hstmt_, SQL_ATTR_PARAMS_PROCESSED_PTR, &rows_processed, 0); + } + + // if we are called twice for the same statement we need to close the open + // cursor or an "invalid cursor state" error will occur on execute + SQLCloseCursor(hstmt_); + + SQLRETURN rc = SQLExecute(hstmt_); + if (is_odbc_error(rc)) + { + // If executing bulk operation a partial + // number of rows affected may be available. + if (hasVectorUseElements_) + { + rowsAffected_ = 0; + + do + { + SQLLEN res = 0; + // SQLRowCount will return error after a partially executed statement. + // SQL_DIAG_ROW_COUNT returns the same info but must be collected immediatelly after the execution. + rc = SQLGetDiagField(SQL_HANDLE_STMT, hstmt_, 0, SQL_DIAG_ROW_COUNT, &res, 0, NULL); + if (!is_odbc_error(rc) && res > 0) // 'res' will be -1 for the where the statement failed. + { + rowsAffected_ += res; + } + --rows_processed; // Avoid unnecessary calls to SQLGetDiagField + } + // Move forward to the next result while there are rows processed. + while (rows_processed > 0 && SQLMoreResults(hstmt_) == SQL_SUCCESS); + } + throw odbc_soci_error(SQL_HANDLE_STMT, hstmt_, + "Statement Execute"); + } + // We should preserve the number of rows affected here + // where we know for sure that a bulk operation was executed. + else + { + rowsAffected_ = 0; + + do { + SQLLEN res = 0; + SQLRETURN rc = SQLRowCount(hstmt_, &res); + if (is_odbc_error(rc)) + { + throw odbc_soci_error(SQL_HANDLE_STMT, hstmt_, + "Getting number of affected rows"); + } + rowsAffected_ += res; + } + // Move forward to the next result if executing a bulk operation. + while (hasVectorUseElements_ && SQLMoreResults(hstmt_) == SQL_SUCCESS); + } + SQLSMALLINT colCount; + SQLNumResultCols(hstmt_, &colCount); + + if (number > 0 && colCount > 0) + { + return fetch(number); + } + + return ef_success; +} + +statement_backend::exec_fetch_result +odbc_statement_backend::fetch(int number) +{ + numRowsFetched_ = 0; + SQLULEN const row_array_size = static_cast(number); + + SQLSetStmtAttr(hstmt_, SQL_ATTR_ROW_BIND_TYPE, SQL_BIND_BY_COLUMN, 0); + SQLSetStmtAttr(hstmt_, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER)row_array_size, 0); + SQLSetStmtAttr(hstmt_, SQL_ATTR_ROWS_FETCHED_PTR, &numRowsFetched_, 0); + + SQLRETURN rc = SQLFetch(hstmt_); + + if (SQL_NO_DATA == rc) + { + return ef_no_data; + } + + if (is_odbc_error(rc)) + { + throw odbc_soci_error(SQL_HANDLE_STMT, hstmt_, + "Statement Fetch"); + } + + return ef_success; +} + +long long odbc_statement_backend::get_affected_rows() +{ + return rowsAffected_; +} + +int odbc_statement_backend::get_number_of_rows() +{ + return numRowsFetched_; +} + +std::string odbc_statement_backend::rewrite_for_procedure_call( + std::string const &query) +{ + return query; +} + +int odbc_statement_backend::prepare_for_describe() +{ + SQLSMALLINT numCols; + SQLNumResultCols(hstmt_, &numCols); + return numCols; +} + +void odbc_statement_backend::describe_column(int colNum, data_type & type, + std::string & columnName) +{ + SQLCHAR colNameBuffer[2048]; + SQLSMALLINT colNameBufferOverflow; + SQLSMALLINT dataType; + SQLULEN colSize; + SQLSMALLINT decDigits; + SQLSMALLINT isNullable; + + SQLRETURN rc = SQLDescribeCol(hstmt_, static_cast(colNum), + colNameBuffer, 2048, + &colNameBufferOverflow, &dataType, + &colSize, &decDigits, &isNullable); + + if (is_odbc_error(rc)) + { + throw odbc_soci_error(SQL_HANDLE_STMT, hstmt_, + "describe Column"); + } + + char const *name = reinterpret_cast(colNameBuffer); + columnName.assign(name, std::strlen(name)); + + switch (dataType) + { + case SQL_TYPE_DATE: + case SQL_TYPE_TIME: + case SQL_TYPE_TIMESTAMP: + type = dt_date; + break; + case SQL_DOUBLE: + case SQL_DECIMAL: + case SQL_REAL: + case SQL_FLOAT: + case SQL_NUMERIC: + type = dt_double; + break; + case SQL_TINYINT: + case SQL_SMALLINT: + case SQL_INTEGER: + type = dt_integer; + break; + case SQL_BIGINT: + type = dt_long_long; + break; + case SQL_CHAR: + case SQL_VARCHAR: + case SQL_LONGVARCHAR: + default: + type = dt_string; + break; + } +} + +std::size_t odbc_statement_backend::column_size(int colNum) +{ + SQLCHAR colNameBuffer[2048]; + SQLSMALLINT colNameBufferOverflow; + SQLSMALLINT dataType; + SQLULEN colSize; + SQLSMALLINT decDigits; + SQLSMALLINT isNullable; + + SQLRETURN rc = SQLDescribeCol(hstmt_, static_cast(colNum), + colNameBuffer, 2048, + &colNameBufferOverflow, &dataType, + &colSize, &decDigits, &isNullable); + + if (is_odbc_error(rc)) + { + throw odbc_soci_error(SQL_HANDLE_STMT, hstmt_, + "column size"); + } + + return colSize; +} + +odbc_standard_into_type_backend * odbc_statement_backend::make_into_type_backend() +{ + return new odbc_standard_into_type_backend(*this); +} + +odbc_standard_use_type_backend * odbc_statement_backend::make_use_type_backend() +{ + return new odbc_standard_use_type_backend(*this); +} + +odbc_vector_into_type_backend * +odbc_statement_backend::make_vector_into_type_backend() +{ + return new odbc_vector_into_type_backend(*this); +} + +odbc_vector_use_type_backend * odbc_statement_backend::make_vector_use_type_backend() +{ + hasVectorUseElements_ = true; + return new odbc_vector_use_type_backend(*this); +} diff --git a/src/backends/odbc/test/CMakeLists.txt b/src/backends/odbc/test/CMakeLists.txt new file mode 100644 index 0000000000..01f8ae672f --- /dev/null +++ b/src/backends/odbc/test/CMakeLists.txt @@ -0,0 +1,48 @@ +############################################################################### +# +# This file is part of CMake configuration for SOCI library +# +# Copyright (C) 2010 Mateusz Loskot +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) +# +############################################################################### + +if (WIN32) + # MDBTools driver seems unreliable + soci_backend_test( + NAME access + BACKEND ODBC + SOURCE test-odbc-access.cpp + CONNSTR "test-access.dsn") + + # We have no means to test SQL Server at travis-ci.org + soci_backend_test( + NAME mssql + BACKEND ODBC + SOURCE test-odbc-mssql.cpp + CONNSTR "test-mssql.dsn") +endif() + +soci_backend_test( + NAME mysql + BACKEND ODBC + SOURCE test-odbc-mysql.cpp + CONNSTR "test-mysql.dsn") + +soci_backend_test( + NAME postgresql + BACKEND ODBC + SOURCE test-odbc-postgresql.cpp + CONNSTR "test-postgresql.dsn") + +# TODO: DB2 backend is tested by Travis CI on dedicated VM, separate from ODBC, +# in order to test DB2 with ODBC, it would be best to install DB2 driver only. +if (NOT $ENV{TRAVIS}) +soci_backend_test( + NAME db2 + BACKEND ODBC + SOURCE test-odbc-db2.cpp + CONNSTR "test-db2.dsn") +endif() diff --git a/src/backends/odbc/test/Makefile.basic b/src/backends/odbc/test/Makefile.basic new file mode 100644 index 0000000000..d21f9a8cff --- /dev/null +++ b/src/backends/odbc/test/Makefile.basic @@ -0,0 +1,24 @@ +# The following variable is specific to this backend and its correct +# values might depend on your environment - feel free to set it accordingly. + +ODBCINCLUDEDIR = -I/usr/include +ODBCLIBDIR = -L/usr/lib +ODBCLIBS = -lodbc -lodbcinst + +# The rest of the Makefile is independent of the target environment. + +COMPILER = g++ +CXXFLAGS = -Wall -pedantic -Wno-long-long +INCLUDEDIRS = -I.. -I../../../core ${ODBCINCLUDEDIR} +LIBDIRS = -L.. -L../../../core ${ODBCLIBDIR} +LIBS = -lsoci_core -lsoci_odbc -ldl ${ODBCLIBS} + + +test-odbc-mssql: test-odbc-mssql.cpp + ${COMPILER} -o $@ $? ${CXXFLAGS} ${INCLUDEDIRS} ${LIBDIRS} ${LIBS} + +test-odbc-access: test-odbc-access.cpp + ${COMPILER} -o $@ $? ${CXXFLAGS} ${INCLUDEDIRS} ${LIBDIRS} ${LIBS} + +clean : + rm -f test-odbc-access.exe test-odbc-mssql.exe diff --git a/src/backends/odbc/test/makefile.msvc b/src/backends/odbc/test/makefile.msvc new file mode 100644 index 0000000000..1426f83755 --- /dev/null +++ b/src/backends/odbc/test/makefile.msvc @@ -0,0 +1,19 @@ +ODBCINCLUDEDIR="C:\Program Files\Microsoft Platform SDK\Include" +ODBCLIBDIR="C:\Program Files\Microsoft Platform SDK\Lib" + +COMPILER = cl +CXXFLAGS = /nologo /EHsc +CXXFLAGSSO = $(CXXFLAGS) +INCLUDEDIRS = /I.. /I..\..\..\core /I..\..\..\core\test /I$(ODBCINCLUDEDIR) +LIBS = ..\..\..\core\soci-core.lib ..\soci-odbc.lib $(ODBCLIBDIR)\uuid.lib $(ODBCLIBDIR)\odbc32.lib + +mssql: test-odbc-mssql.cpp + $(COMPILER) $? $(CXXFLAGS) $(INCLUDEDIRS) $(LIBS) + +access: test-odbc-access.cpp + $(COMPILER) $? $(CXXFLAGS) $(INCLUDEDIRS) $(LIBS) + +clean: + del *.exe + del *.obj + diff --git a/src/backends/odbc/test/test-access.dsn b/src/backends/odbc/test/test-access.dsn new file mode 100644 index 0000000000..597ff131d2 --- /dev/null +++ b/src/backends/odbc/test/test-access.dsn @@ -0,0 +1,13 @@ +[ODBC] +DRIVER=Microsoft Access Driver (*.mdb) +UID=admin +UserCommitSync=Yes +Threads=3 +SafeTransactions=0 +PageTimeout=5 +MaxScanRows=8 +MaxBufferSize=2048 +FIL=MS Access +DriverId=25 +DefaultDir=.\ +DBQ=.\soci_test.mdb diff --git a/src/backends/odbc/test/test-mssql.dsn b/src/backends/odbc/test/test-mssql.dsn new file mode 100644 index 0000000000..f96dc70119 --- /dev/null +++ b/src/backends/odbc/test/test-mssql.dsn @@ -0,0 +1,8 @@ +[ODBC] +DRIVER=SQL Native Client +UID=David +DATABASE=soci_test +WSID=NANO +APP=Microsoft Data Access Components +Trusted_Connection=Yes +SERVER=localhost\SQLEXPRESS diff --git a/src/backends/odbc/test/test-mysql.dsn b/src/backends/odbc/test/test-mysql.dsn new file mode 100644 index 0000000000..59dff62191 --- /dev/null +++ b/src/backends/odbc/test/test-mysql.dsn @@ -0,0 +1,4 @@ +[ODBC] +DRIVER=MySQL +DATABASE=soci_test +OPTION=0 diff --git a/src/backends/odbc/test/test-odbc-access.cpp b/src/backends/odbc/test/test-odbc-access.cpp new file mode 100644 index 0000000000..9b8b41b6cf --- /dev/null +++ b/src/backends/odbc/test/test-odbc-access.cpp @@ -0,0 +1,155 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, David Courtney +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#include "soci.h" +#include "soci-odbc.h" +#include "common-tests.h" +#include +#include +#include +#include +#include + +using namespace soci; +using namespace soci::tests; + +std::string connectString; +backend_factory const &backEnd = *soci::factory_odbc(); + +// DDL Creation objects for common tests +struct table_creator_one : public table_creator_base +{ + table_creator_one(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(id integer, val integer, c char, " + "str varchar(20), sh integer, ul number, d float, " + "tm timestamp, i1 integer, i2 integer, i3 integer, " + "name varchar(20))"; + } +}; + +struct table_creator_two : public table_creator_base +{ + table_creator_two(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(num_float float, num_int integer," + " name varchar(20), sometime datetime, chr char)"; + } +}; + +struct table_creator_three : public table_creator_base +{ + table_creator_three(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(name varchar(100) not null, " + "phone varchar(15))"; + } +}; + +struct table_creator_for_get_affected_rows : table_creator_base +{ + table_creator_for_get_affected_rows(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(val integer)"; + } +}; + +// +// Support for SOCI Common Tests +// + +class test_context : public test_context_base +{ +public: + +test_context(backend_factory const &backEnd, std::string const &connectString) + : test_context_base(backEnd, connectString) {} + + table_creator_base * table_creator_1(session& s) const + { + return new table_creator_one(s); + } + + table_creator_base * table_creator_2(session& s) const + { + return new table_creator_two(s); + } + + table_creator_base * table_creator_3(session& s) const + { + return new table_creator_three(s); + } + + table_creator_base * table_creator_4(session& s) const + { + return new table_creator_for_get_affected_rows(s); + } + + std::string fromDual(std::string const &sql) const + { + return sql; + } + + std::string toDate(std::string const &datdt_string) const + { + return "#" + datdt_string + "#"; + } + + std::string to_date_time(std::string const &datdt_string) const + { + return "#" + datdt_string + "#"; + } +}; + +int main(int argc, char** argv) +{ + +#ifdef _MSC_VER + // Redirect errors, unrecoverable problems, and assert() failures to STDERR, + // instead of debug message window. + // This hack is required to run asser()-driven tests by Buildbot. + // NOTE: Comment this 2 lines for debugging with Visual C++ debugger to catch assertions inside. + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); +#endif //_MSC_VER + + if (argc == 2) + { + connectString = argv[1]; + } + else + { + connectString = "FILEDSN=./test-access.dsn"; + } + try + { + std::cout << "\nSOCI ODBC with MS Access Tests:\n\n"; + + test_context tc(backEnd, connectString); + common_tests tests(tc); + tests.run(); + + std::cout << "\nOK, all tests passed.\n\n"; + return EXIT_SUCCESS; + } + catch (soci::odbc_soci_error const & e) + { + std::cout << "ODBC Error Code: " << e.odbc_error_code() << std::endl + << "Native Error Code: " << e.native_error_code() << std::endl + << "SOCI Message: " << e.what() << std::endl + << "ODBC Message: " << e.odbc_error_message() << std::endl; + } + catch (std::exception const & e) + { + std::cout << "STD::EXECEPTION " << e.what() << '\n'; + } + return EXIT_FAILURE; +} diff --git a/src/backends/odbc/test/test-odbc-db2.cpp b/src/backends/odbc/test/test-odbc-db2.cpp new file mode 100644 index 0000000000..d5bbc2e5f0 --- /dev/null +++ b/src/backends/odbc/test/test-odbc-db2.cpp @@ -0,0 +1,306 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, David Courtney +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#include "soci.h" +#include "soci-odbc.h" +#include "common-tests.h" +#include +#include +#include +#include +#include + +using namespace soci; +using namespace soci::tests; + +std::string connectString; +backend_factory const &backEnd = *soci::factory_odbc(); + +// DDL Creation objects for common tests +struct table_creator_one : public table_creator_base +{ + table_creator_one(session & sql) + : table_creator_base(sql) + { + sql << "CREATE TABLE SOCI_TEST(ID INTEGER, VAL SMALLINT, C CHAR, STR VARCHAR(20), SH SMALLINT, UL NUMERIC(20), D DOUBLE, " + "TM TIMESTAMP(9), I1 INTEGER, I2 INTEGER, I3 INTEGER, NAME VARCHAR(20))"; + } +}; + +struct table_creator_two : public table_creator_base +{ + table_creator_two(session & sql) + : table_creator_base(sql) + { + sql << "CREATE TABLE SOCI_TEST(NUM_FLOAT DOUBLE, NUM_INT INTEGER, NAME VARCHAR(20), SOMETIME TIMESTAMP, CHR CHAR)"; + } +}; + +struct table_creator_three : public table_creator_base +{ + table_creator_three(session & sql) + : table_creator_base(sql) + { + sql << "CREATE TABLE SOCI_TEST(NAME VARCHAR(100) NOT NULL, PHONE VARCHAR(15))"; + } +}; + +struct table_creator_for_get_affected_rows : table_creator_base +{ + table_creator_for_get_affected_rows(session & sql) + : table_creator_base(sql) + { + sql << "CREATE TABLE SOCI_TEST(VAL INTEGER)"; + } +}; + +// +// Support for SOCI Common Tests +// + +class test_context : public test_context_base +{ +public: + test_context(backend_factory const &backEnd, + std::string const &connectString) + : test_context_base(backEnd, connectString) {} + + table_creator_base * table_creator_1(session& s) const + { + return new table_creator_one(s); + } + + table_creator_base * table_creator_2(session& s) const + { + return new table_creator_two(s); + } + + table_creator_base * table_creator_3(session& s) const + { + return new table_creator_three(s); + } + + table_creator_base * table_creator_4(session& s) const + { + return new table_creator_for_get_affected_rows(s); + } + + std::string to_date_time(std::string const &datdt_string) const + { + return "\'" + datdt_string + "\'"; + } +}; + +struct table_creator_bigint : table_creator_base +{ + table_creator_bigint(session & sql) + : table_creator_base(sql) + { + sql << "CREATE TABLE SOCI_TEST (VAL BIGINT)"; + } +}; + +void test_odbc_db2_long_long() +{ + const int num_recs = 100; + session sql(backEnd, connectString); + table_creator_bigint table(sql); + + { + long long n; + statement st = (sql.prepare << + "INSERT INTO SOCI_TEST (VAL) VALUES (:val)", use(n)); + for (int i = 0; i < num_recs; i++) + { + n = 1000000000LL + i; + st.execute(); + } + } + { + long long n2; + statement st = (sql.prepare << + "SELECT VAL FROM SOCI_TEST ORDER BY VAL", into(n2)); + st.execute(); + for (int i = 0; i < num_recs; i++) + { + st.fetch(); + assert(n2 == 1000000000LL + i); + } + } + + std::cout << "test odbc_db2_long_long passed" << std::endl; +} + +void test_odbc_db2_unsigned_long_long() +{ + const int num_recs = 100; + session sql(backEnd, connectString); + table_creator_bigint table(sql); + + { + unsigned long long n; + statement st = (sql.prepare << + "INSERT INTO SOCI_TEST (VAL) VALUES (:val)", use(n)); + for (int i = 0; i < num_recs; i++) + { + n = 1000000000LL + i; + st.execute(); + } + } + { + unsigned long long n2; + statement st = (sql.prepare << + "SELECT VAL FROM SOCI_TEST ORDER BY VAL", into(n2)); + st.execute(); + for (int i = 0; i < num_recs; i++) + { + st.fetch(); + assert(n2 == 1000000000LL + i); + } + } + + std::cout << "test odbc_db2_unsigned_long_long passed" << std::endl; +} + +void test_odbc_db2_long_long_vector() +{ + const std::size_t num_recs = 100; + session sql(backEnd, connectString); + table_creator_bigint table(sql); + + { + std::vector v(num_recs); + for (std::size_t i = 0; i < num_recs; i++) + { + v[i] = 1000000000LL + i; + } + + sql << "INSERT INTO SOCI_TEST (VAL) VALUES (:bi)", use(v); + } + { + std::size_t recs = 0; + + std::vector v(num_recs / 2 + 1); + statement st = (sql.prepare << + "SELECT VAL FROM SOCI_TEST ORDER BY VAL", into(v)); + st.execute(); + while (true) + { + if (!st.fetch()) + { + break; + } + + const std::size_t vsize = v.size(); + for (std::size_t i = 0; i < vsize; i++) + { + assert(v[i] == 1000000000LL + + static_cast(recs)); + recs++; + } + } + assert(recs == num_recs); + } + + std::cout << "test odbc_db2_long_long_vector passed" << std::endl; +} + +void test_odbc_db2_unsigned_long_long_vector() +{ + const std::size_t num_recs = 100; + session sql(backEnd, connectString); + table_creator_bigint table(sql); + + { + std::vector v(num_recs); + for (std::size_t i = 0; i < num_recs; i++) + { + v[i] = 1000000000LL + i; + } + + sql << "INSERT INTO SOCI_TEST (VAL) VALUES (:bi)", use(v); + } + { + std::size_t recs = 0; + + std::vector v(num_recs / 2 + 1); + statement st = (sql.prepare << + "SELECT VAL FROM SOCI_TEST ORDER BY VAL", into(v)); + st.execute(); + while (true) + { + if (!st.fetch()) + { + break; + } + + const std::size_t vsize = v.size(); + for (std::size_t i = 0; i < vsize; i++) + { + assert(v[i] == 1000000000LL + + static_cast(recs)); + recs++; + } + } + assert(recs == num_recs); + } + + std::cout << "test odbc_db2_unsigned_long_long_vector passed" << std::endl; +} + +int main(int argc, char** argv) +{ +#ifdef _MSC_VER + // Redirect errors, unrecoverable problems, and assert() failures to STDERR, + // instead of debug message window. + // This hack is required to run asser()-driven tests by Buildbot. + // NOTE: Comment this 2 lines for debugging with Visual C++ debugger to catch assertions inside. + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); +#endif //_MSC_VER + + if (argc == 2) + { + connectString = argv[1]; + } + else + { + std::cerr << std::endl << + "usage: test-odbc-db2 \"DSN=;Uid=;Pwd=\"" << + std::endl << std::endl; + return EXIT_FAILURE; + } + try + { + std::cout << "\nSOCI ODBC with DB2 Tests:\n\n"; + + test_context tc(backEnd, connectString); + common_tests tests(tc); + tests.run(); + + std::cout << "\nSOCI DB2 Specific Tests:\n\n"; + test_odbc_db2_long_long(); + test_odbc_db2_unsigned_long_long(); + test_odbc_db2_long_long_vector(); + test_odbc_db2_unsigned_long_long_vector(); + + std::cout << "\nOK, all tests passed.\n\n"; + return EXIT_SUCCESS; + } + catch (soci::odbc_soci_error const & e) + { + std::cout << "ODBC Error Code: " << e.odbc_error_code() << std::endl + << "Native Error Code: " << e.native_error_code() << std::endl + << "SOCI Message: " << e.what() << std::endl + << "ODBC Message: " << e.odbc_error_message() << std::endl; + } + catch (std::exception const & e) + { + std::cout << "STD::EXECEPTION " << e.what() << '\n'; + } + return EXIT_FAILURE; +} diff --git a/src/backends/odbc/test/test-odbc-mssql.cpp b/src/backends/odbc/test/test-odbc-mssql.cpp new file mode 100644 index 0000000000..ebce24aa15 --- /dev/null +++ b/src/backends/odbc/test/test-odbc-mssql.cpp @@ -0,0 +1,148 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, David Courtney +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#include "soci.h" +#include "soci-odbc.h" +#include "common-tests.h" +#include +#include +#include +#include +#include + +using namespace soci; +using namespace soci::tests; + +std::string connectString; +backend_factory const &backEnd = *soci::factory_odbc(); + +// DDL Creation objects for common tests +struct table_creator_one : public table_creator_base +{ + table_creator_one(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(id integer, val integer, c char, " + "str varchar(20), sh smallint, ul numeric(20), d float, " + "tm datetime, i1 integer, i2 integer, i3 integer, " + "name varchar(20))"; + } +}; + +struct table_creator_two : public table_creator_base +{ + table_creator_two(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(num_float float, num_int integer," + " name varchar(20), sometime datetime, chr char)"; + } +}; + +struct table_creator_three : public table_creator_base +{ + table_creator_three(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(name varchar(100) not null, " + "phone varchar(15))"; + } +}; + +struct table_creator_for_get_affected_rows : table_creator_base +{ + table_creator_for_get_affected_rows(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(val integer)"; + } +}; + +// +// Support for SOCI Common Tests +// + +class test_context : public test_context_base +{ +public: + test_context(backend_factory const &backEnd, + std::string const &connectString) + : test_context_base(backEnd, connectString) {} + + table_creator_base* table_creator_1(session& s) const + { + return new table_creator_one(s); + } + + table_creator_base* table_creator_2(session& s) const + { + return new table_creator_two(s); + } + + table_creator_base* table_creator_3(session& s) const + { + return new table_creator_three(s); + } + + table_creator_base * table_creator_4(session& s) const + { + return new table_creator_for_get_affected_rows(s); + } + + std::string to_date_time(std::string const &datdt_string) const + { + return "convert(datetime, \'" + datdt_string + "\', 120)"; + } +}; + +int main(int argc, char** argv) +{ +#ifdef _MSC_VER + // Redirect errors, unrecoverable problems, and assert() failures to STDERR, + // instead of debug message window. + // This hack is required to run asser()-driven tests by Buildbot. + // NOTE: Comment this 2 lines for debugging with Visual C++ debugger to catch assertions inside. + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); +#endif //_MSC_VER + + if (argc == 2) + { + connectString = argv[1]; + } + else + { + connectString = "FILEDSN=./test-mssql.dsn"; + } + try + { + std::cout << "\nSOCI ODBC with MS SQL Server Tests:\n\n"; + + test_context tc(backEnd, connectString); + common_tests tests(tc); + tests.run(); + + std::cout << "\nOK, all tests passed.\n\n"; + return EXIT_SUCCESS; + } + catch (soci::odbc_soci_error const & e) + { + std::cout << "ODBC Error Code: " << e.odbc_error_code() << std::endl + << "Native Error Code: " << e.native_error_code() << std::endl + << "SOCI Message: " << e.what() << std::endl + << "ODBC Message: " << e.odbc_error_message() << std::endl; + } + catch (soci::soci_error const & e) + { + std::cout << "SOCIERROR: " << e.what() << '\n'; + } + catch (std::exception const & e) + { + std::cout << "STD::EXECEPTION " << e.what() << '\n'; + } + return EXIT_FAILURE; +} \ No newline at end of file diff --git a/src/backends/odbc/test/test-odbc-mysql.cpp b/src/backends/odbc/test/test-odbc-mysql.cpp new file mode 100644 index 0000000000..2750d35858 --- /dev/null +++ b/src/backends/odbc/test/test-odbc-mysql.cpp @@ -0,0 +1,144 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, David Courtney +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#include "soci.h" +#include "soci-odbc.h" +#include "common-tests.h" +#include +#include +#include +#include +#include + +using namespace soci; +using namespace soci::tests; + +std::string connectString; +backend_factory const &backEnd = *soci::factory_odbc(); + +// DDL Creation objects for common tests +struct table_creator_one : public table_creator_base +{ + table_creator_one(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(id integer, val integer, c char, " + "str varchar(20), sh int2, ul numeric(20), d float8, " + "tm datetime, i1 integer, i2 integer, i3 integer, " + "name varchar(20))"; + } +}; + +struct table_creator_two : public table_creator_base +{ + table_creator_two(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(num_float float8, num_int integer," + " name varchar(20), sometime datetime, chr char)"; + } +}; + +struct table_creator_three : public table_creator_base +{ + table_creator_three(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(name varchar(100) not null, " + "phone varchar(15))"; + } +}; + +struct table_creator_for_get_affected_rows : table_creator_base +{ + table_creator_for_get_affected_rows(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(val integer)"; + } +}; + +// +// Support for SOCI Common Tests +// + +class test_context : public test_context_base +{ +public: + test_context(backend_factory const &backEnd, + std::string const &connectString) + : test_context_base(backEnd, connectString) {} + + table_creator_base * table_creator_1(session& s) const + { + return new table_creator_one(s); + } + + table_creator_base * table_creator_2(session& s) const + { + return new table_creator_two(s); + } + + table_creator_base * table_creator_3(session& s) const + { + return new table_creator_three(s); + } + + table_creator_base * table_creator_4(session& s) const + { + return new table_creator_for_get_affected_rows(s); + } + + std::string to_date_time(std::string const &datdt_string) const + { + return "\'" + datdt_string + "\'"; + } +}; + +int main(int argc, char** argv) +{ +#ifdef _MSC_VER + // Redirect errors, unrecoverable problems, and assert() failures to STDERR, + // instead of debug message window. + // This hack is required to run asser()-driven tests by Buildbot. + // NOTE: Comment this 2 lines for debugging with Visual C++ debugger to catch assertions inside. + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); +#endif //_MSC_VER + + if (argc == 2) + { + connectString = argv[1]; + } + else + { + connectString = "FILEDSN=./test-mysql.dsn"; + } + try + { + std::cout << "\nSOCI ODBC with MySQL Tests:\n\n"; + + test_context tc(backEnd, connectString); + common_tests tests(tc); + tests.run(); + + std::cout << "\nOK, all tests passed.\n\n"; + return EXIT_SUCCESS; + } + catch (soci::odbc_soci_error const & e) + { + std::cout << "ODBC Error Code: " << e.odbc_error_code() << std::endl + << "Native Error Code: " << e.native_error_code() << std::endl + << "SOCI Message: " << e.what() << std::endl + << "ODBC Message: " << e.odbc_error_message() << std::endl; + } + catch (std::exception const & e) + { + std::cout << "STD::EXECEPTION " << e.what() << '\n'; + } + return EXIT_FAILURE; +} diff --git a/src/backends/odbc/test/test-odbc-postgresql.cpp b/src/backends/odbc/test/test-odbc-postgresql.cpp new file mode 100644 index 0000000000..1422ce1631 --- /dev/null +++ b/src/backends/odbc/test/test-odbc-postgresql.cpp @@ -0,0 +1,146 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, David Courtney +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#include "soci.h" +#include "soci-odbc.h" +#include "common-tests.h" +#include +#include +#include +#include +#include + +using namespace soci; +using namespace soci::tests; + +std::string connectString; +backend_factory const &backEnd = *soci::factory_odbc(); + +// DDL Creation objects for common tests +struct table_creator_one : public table_creator_base +{ + table_creator_one(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(id integer, val integer, c char, " + "str varchar(20), sh int2, ul numeric(20), d float8, " + "tm timestamp, i1 integer, i2 integer, i3 integer, " + "name varchar(20))"; + } +}; + +struct table_creator_two : public table_creator_base +{ + table_creator_two(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(num_float float8, num_int integer," + " name varchar(20), sometime timestamp, chr char)"; + } +}; + +struct table_creator_three : public table_creator_base +{ + table_creator_three(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(name varchar(100) not null, " + "phone varchar(15))"; + } +}; + +struct table_creator_for_get_affected_rows : table_creator_base +{ + table_creator_for_get_affected_rows(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(val integer)"; + } +}; + +// +// Support for SOCI Common Tests +// + +class test_context : public test_context_base +{ +public: + test_context(backend_factory const &backEnd, + std::string const &connectString) + : test_context_base(backEnd, connectString) {} + + table_creator_base * table_creator_1(session& s) const + { + return new table_creator_one(s); + } + + table_creator_base * table_creator_2(session& s) const + { + return new table_creator_two(s); + } + + table_creator_base * table_creator_3(session& s) const + { + return new table_creator_three(s); + } + + table_creator_base * table_creator_4(session& s) const + { + return new table_creator_for_get_affected_rows(s); + } + + std::string to_date_time(std::string const &datdt_string) const + { + return "timestamptz(\'" + datdt_string + "\')"; + } + +}; + +int main(int argc, char** argv) +{ + +#ifdef _MSC_VER + // Redirect errors, unrecoverable problems, and assert() failures to STDERR, + // instead of debug message window. + // This hack is required to run asser()-driven tests by Buildbot. + // NOTE: Comment this 2 lines for debugging with Visual C++ debugger to catch assertions inside. + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); +#endif //_MSC_VER + + if (argc == 2) + { + connectString = argv[1]; + } + else + { + connectString = "FILEDSN=./test-postgresql.dsn"; + } + try + { + std::cout << "\nSOCI ODBC with PostgreSQL Tests:\n\n"; + + test_context tc(backEnd, connectString); + common_tests tests(tc); + tests.run(); + + std::cout << "\nOK, all tests passed.\n\n"; + return EXIT_SUCCESS; + } + catch (soci::odbc_soci_error const & e) + { + std::cout << "ODBC Error Code: " << e.odbc_error_code() << std::endl + << "Native Error Code: " << e.native_error_code() << std::endl + << "SOCI Message: " << e.what() << std::endl + << "ODBC Message: " << e.odbc_error_message() << std::endl; + } + catch (std::exception const & e) + { + std::cout << "STD::EXECEPTION " << e.what() << '\n'; + } + return EXIT_FAILURE; +} diff --git a/src/backends/odbc/test/test-postgresql.dsn b/src/backends/odbc/test/test-postgresql.dsn new file mode 100644 index 0000000000..cc54f6289d --- /dev/null +++ b/src/backends/odbc/test/test-postgresql.dsn @@ -0,0 +1,8 @@ +[ODBC] +Description=DSN for SOCI ODBC connection to PostgreSQL +Driver=PostgreSQL ANSI +Server=localhost +Port=5432 +Database=soci_test +UID=postgres +PWD= diff --git a/src/backends/odbc/utility.h b/src/backends/odbc/utility.h new file mode 100644 index 0000000000..5633c933ee --- /dev/null +++ b/src/backends/odbc/utility.h @@ -0,0 +1,62 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, David Courtney +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_UTILITY_H_INCLUDED +#define SOCI_UTILITY_H_INCLUDED + +#include "soci-backend.h" +#include + +namespace soci +{ + +inline void throw_odbc_error(SQLSMALLINT htype, SQLHANDLE hndl, char const * msg) +{ + SQLCHAR message[SQL_MAX_MESSAGE_LENGTH + 1]; + SQLCHAR sqlstate[SQL_SQLSTATE_SIZE + 1]; + SQLINTEGER sqlcode; + SQLSMALLINT length, i; + + std::stringstream ss; + + i = 1; + + /* get multiple field settings of diagnostic record */ + while (SQLGetDiagRecA(htype, + hndl, + i, + sqlstate, + &sqlcode, + message, + SQL_MAX_MESSAGE_LENGTH + 1, + &length) == SQL_SUCCESS) + { + ss << std::endl << "SOCI ODBC Error: " << msg << std::endl + << "SQLSTATE = " << sqlstate << std::endl + << "Native Error Code = " << sqlcode << std::endl + << message << std::endl; + ++i; + } + + throw soci_error(ss.str()); +} + +inline bool is_odbc_error(SQLRETURN rc) +{ + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) + { + return true; + } + else + { + return false; + } +} + +} + +#endif // SOCI_UTILITY_H_INCLUDED diff --git a/src/backends/odbc/vector-into-type.cpp b/src/backends/odbc/vector-into-type.cpp new file mode 100644 index 0000000000..73c715888f --- /dev/null +++ b/src/backends/odbc/vector-into-type.cpp @@ -0,0 +1,472 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, David Courtney +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_ODBC_SOURCE +#include "soci-odbc.h" +#include +#include +#include +#include +#include +#include +#include +#include // sscanf() + +using namespace soci; +using namespace soci::details; + +void odbc_vector_into_type_backend::prepare_indicators(std::size_t size) +{ + if (size == 0) + { + throw soci_error("Vectors of size 0 are not allowed."); + } + + indHolderVec_.resize(size); + indHolders_ = &indHolderVec_[0]; +} + +void odbc_vector_into_type_backend::define_by_pos( + int &position, void *data, exchange_type type) +{ + data_ = data; // for future reference + type_ = type; // for future reference + + SQLLEN size = 0; // also dummy + + switch (type) + { + // simple cases + case x_short: + { + odbcType_ = SQL_C_SSHORT; + size = sizeof(short); + std::vector *vp = static_cast *>(data); + std::vector &v(*vp); + prepare_indicators(v.size()); + data = &v[0]; + } + break; + case x_integer: + { + odbcType_ = SQL_C_SLONG; + size = sizeof(SQLINTEGER); + assert(sizeof(SQLINTEGER) == sizeof(int)); + std::vector *vp = static_cast *>(data); + std::vector &v(*vp); + prepare_indicators(v.size()); + data = &v[0]; + } + break; + case x_long_long: + { + std::vector *vp = + static_cast *>(data); + std::vector &v(*vp); + prepare_indicators(v.size()); + if (use_string_for_bigint()) + { + odbcType_ = SQL_C_CHAR; + size = max_bigint_length; + std::size_t bufSize = size * v.size(); + colSize_ = size; + buf_ = new char[bufSize]; + data = buf_; + } + else // Normal case, use ODBC support. + { + odbcType_ = SQL_C_SBIGINT; + size = sizeof(long long); + data = &v[0]; + } + } + break; + case x_unsigned_long_long: + { + std::vector *vp = + static_cast *>(data); + std::vector &v(*vp); + prepare_indicators(v.size()); + if (use_string_for_bigint()) + { + odbcType_ = SQL_C_CHAR; + size = max_bigint_length; + std::size_t bufSize = size * v.size(); + colSize_ = size; + buf_ = new char[bufSize]; + data = buf_; + } + else // Normal case, use ODBC support. + { + odbcType_ = SQL_C_UBIGINT; + size = sizeof(unsigned long long); + data = &v[0]; + } + } + break; + case x_double: + { + odbcType_ = SQL_C_DOUBLE; + size = sizeof(double); + std::vector *vp = static_cast *>(data); + std::vector &v(*vp); + prepare_indicators(v.size()); + data = &v[0]; + } + break; + + // cases that require adjustments and buffer management + + case x_char: + { + odbcType_ = SQL_C_CHAR; + + std::vector *v + = static_cast *>(data); + + prepare_indicators(v->size()); + + size = sizeof(char) * 2; + std::size_t bufSize = size * v->size(); + + colSize_ = size; + + buf_ = new char[bufSize]; + data = buf_; + } + break; + case x_stdstring: + { + odbcType_ = SQL_C_CHAR; + std::vector *v + = static_cast *>(data); + colSize_ = statement_.column_size(position) + 1; + std::size_t bufSize = colSize_ * v->size(); + buf_ = new char[bufSize]; + + prepare_indicators(v->size()); + + size = static_cast(colSize_); + data = buf_; + } + break; + case x_stdtm: + { + odbcType_ = SQL_C_TYPE_TIMESTAMP; + std::vector *v + = static_cast *>(data); + + prepare_indicators(v->size()); + + size = sizeof(TIMESTAMP_STRUCT); + colSize_ = size; + + std::size_t bufSize = size * v->size(); + + buf_ = new char[bufSize]; + data = buf_; + } + break; + + case x_statement: break; // not supported + case x_rowid: break; // not supported + case x_blob: break; // not supported + } + + SQLRETURN rc + = SQLBindCol(statement_.hstmt_, static_cast(position++), + odbcType_, static_cast(data), size, indHolders_); + if (is_odbc_error(rc)) + { + throw odbc_soci_error(SQL_HANDLE_STMT, statement_.hstmt_, + "vector into type define by pos"); + } +} + +void odbc_vector_into_type_backend::pre_fetch() +{ + // nothing to do for the supported types +} + +void odbc_vector_into_type_backend::post_fetch(bool gotData, indicator *ind) +{ + if (gotData) + { + // first, deal with data + + // only std::string, std::tm and Statement need special handling + if (type_ == x_char) + { + std::vector *vp + = static_cast *>(data_); + + std::vector &v(*vp); + char *pos = buf_; + std::size_t const vsize = v.size(); + for (std::size_t i = 0; i != vsize; ++i) + { + v[i] = *pos; + pos += colSize_; + } + } + if (type_ == x_stdstring) + { + std::vector *vp + = static_cast *>(data_); + + std::vector &v(*vp); + + char *pos = buf_; + std::size_t const vsize = v.size(); + for (std::size_t i = 0; i != vsize; ++i) + { + v[i].assign(pos, strlen(pos)); + pos += colSize_; + } + } + else if (type_ == x_stdtm) + { + std::vector *vp + = static_cast *>(data_); + + std::vector &v(*vp); + char *pos = buf_; + std::size_t const vsize = v.size(); + for (std::size_t i = 0; i != vsize; ++i) + { + std::tm t; + + TIMESTAMP_STRUCT * ts = reinterpret_cast(pos); + t.tm_isdst = -1; + t.tm_year = ts->year - 1900; + t.tm_mon = ts->month - 1; + t.tm_mday = ts->day; + t.tm_hour = ts->hour; + t.tm_min = ts->minute; + t.tm_sec = ts->second; + + // normalize and compute the remaining fields + std::mktime(&t); + v[i] = t; + pos += colSize_; + } + } + else if (type_ == x_long_long && use_string_for_bigint()) + { + std::vector *vp + = static_cast *>(data_); + std::vector &v(*vp); + char *pos = buf_; + std::size_t const vsize = v.size(); + for (std::size_t i = 0; i != vsize; ++i) + { + if (sscanf(pos, "%" LL_FMT_FLAGS "d", &v[i]) != 1) + { + throw soci_error("Failed to parse the returned 64-bit integer value"); + } + pos += colSize_; + } + } + else if (type_ == x_unsigned_long_long && use_string_for_bigint()) + { + std::vector *vp + = static_cast *>(data_); + std::vector &v(*vp); + char *pos = buf_; + std::size_t const vsize = v.size(); + for (std::size_t i = 0; i != vsize; ++i) + { + if (sscanf(pos, "%" LL_FMT_FLAGS "u", &v[i]) != 1) + { + throw soci_error("Failed to parse the returned 64-bit integer value"); + } + pos += colSize_; + } + } + + // then - deal with indicators + if (ind != NULL) + { + std::size_t const indSize = statement_.get_number_of_rows(); + for (std::size_t i = 0; i != indSize; ++i) + { + if (indHolderVec_[i] > 0) + { + ind[i] = i_ok; + } + else if (indHolderVec_[i] == SQL_NULL_DATA) + { + ind[i] = i_null; + } + else + { + ind[i] = i_truncated; + } + } + } + else + { + std::size_t const indSize = statement_.get_number_of_rows(); + for (std::size_t i = 0; i != indSize; ++i) + { + if (indHolderVec_[i] == SQL_NULL_DATA) + { + // fetched null and no indicator - programming error! + throw soci_error( + "Null value fetched and no indicator defined."); + } + } + } + } + else // gotData == false + { + // nothing to do here, vectors are truncated anyway + } +} + +void odbc_vector_into_type_backend::resize(std::size_t sz) +{ + indHolderVec_.resize(sz); + switch (type_) + { + // simple cases + case x_char: + { + std::vector *v = static_cast *>(data_); + v->resize(sz); + } + break; + case x_short: + { + std::vector *v = static_cast *>(data_); + v->resize(sz); + } + break; + case x_integer: + { + std::vector *v = static_cast *>(data_); + v->resize(sz); + } + break; + case x_long_long: + { + std::vector *v = + static_cast *>(data_); + v->resize(sz); + } + break; + case x_unsigned_long_long: + { + std::vector *v = + static_cast *>(data_); + v->resize(sz); + } + break; + case x_double: + { + std::vector *v + = static_cast *>(data_); + v->resize(sz); + } + break; + case x_stdstring: + { + std::vector *v + = static_cast *>(data_); + v->resize(sz); + } + break; + case x_stdtm: + { + std::vector *v + = static_cast *>(data_); + v->resize(sz); + } + break; + + case x_statement: break; // not supported + case x_rowid: break; // not supported + case x_blob: break; // not supported + } +} + +std::size_t odbc_vector_into_type_backend::size() +{ + std::size_t sz = 0; // dummy initialization to please the compiler + switch (type_) + { + // simple cases + case x_char: + { + std::vector *v = static_cast *>(data_); + sz = v->size(); + } + break; + case x_short: + { + std::vector *v = static_cast *>(data_); + sz = v->size(); + } + break; + case x_integer: + { + std::vector *v = static_cast *>(data_); + sz = v->size(); + } + break; + case x_long_long: + { + std::vector *v = + static_cast *>(data_); + sz = v->size(); + } + break; + case x_unsigned_long_long: + { + std::vector *v = + static_cast *>(data_); + sz = v->size(); + } + break; + case x_double: + { + std::vector *v + = static_cast *>(data_); + sz = v->size(); + } + break; + case x_stdstring: + { + std::vector *v + = static_cast *>(data_); + sz = v->size(); + } + break; + case x_stdtm: + { + std::vector *v + = static_cast *>(data_); + sz = v->size(); + } + break; + + case x_statement: break; // not supported + case x_rowid: break; // not supported + case x_blob: break; // not supported + } + + return sz; +} + +void odbc_vector_into_type_backend::clean_up() +{ + if (buf_ != NULL) + { + delete [] buf_; + buf_ = NULL; + } +} diff --git a/src/backends/odbc/vector-use-type.cpp b/src/backends/odbc/vector-use-type.cpp new file mode 100644 index 0000000000..ad9ea65f97 --- /dev/null +++ b/src/backends/odbc/vector-use-type.cpp @@ -0,0 +1,455 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, David Courtney +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_ODBC_SOURCE +#include "soci-odbc.h" +#include +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +// disables the warning about converting int to void*. This is a 64 bit compatibility +// warning, but odbc requires the value to be converted on this line +// SQLSetStmtAttr(statement_.hstmt_, SQL_ATTR_PARAMSET_SIZE, (SQLPOINTER)arraySize, 0); +#pragma warning(disable:4312) +#endif + +using namespace soci; +using namespace soci::details; + +void odbc_vector_use_type_backend::prepare_indicators(std::size_t size) +{ + if (size == 0) + { + throw soci_error("Vectors of size 0 are not allowed."); + } + + indHolderVec_.resize(size); + indHolders_ = &indHolderVec_[0]; +} + +void odbc_vector_use_type_backend::prepare_for_bind(void *&data, SQLUINTEGER &size, + SQLSMALLINT &sqlType, SQLSMALLINT &cType) +{ + switch (type_) + { // simple cases + case x_short: + { + sqlType = SQL_SMALLINT; + cType = SQL_C_SSHORT; + size = sizeof(short); + std::vector *vp = static_cast *>(data); + std::vector &v(*vp); + prepare_indicators(v.size()); + data = &v[0]; + } + break; + case x_integer: + { + sqlType = SQL_INTEGER; + cType = SQL_C_SLONG; + size = sizeof(SQLINTEGER); + assert(sizeof(SQLINTEGER) == sizeof(int)); + std::vector *vp = static_cast *>(data); + std::vector &v(*vp); + prepare_indicators(v.size()); + data = &v[0]; + } + break; + case x_long_long: + { + std::vector *vp = + static_cast *>(data); + std::vector &v(*vp); + std::size_t const vsize = v.size(); + prepare_indicators(vsize); + + if (use_string_for_bigint()) + { + sqlType = SQL_NUMERIC; + cType = SQL_C_CHAR; + size = max_bigint_length; + buf_ = new char[size * vsize]; + data = buf_; + } + else // Normal case, use ODBC support. + { + sqlType = SQL_BIGINT; + cType = SQL_C_SBIGINT; + size = sizeof(long long); + data = &v[0]; + } + } + break; + case x_unsigned_long_long: + { + std::vector *vp = + static_cast *>(data); + std::vector &v(*vp); + std::size_t const vsize = v.size(); + prepare_indicators(vsize); + + if (use_string_for_bigint()) + { + sqlType = SQL_NUMERIC; + cType = SQL_C_CHAR; + size = max_bigint_length; + buf_ = new char[size * vsize]; + data = buf_; + } + else // Normal case, use ODBC support. + { + sqlType = SQL_BIGINT; + cType = SQL_C_SBIGINT; + size = sizeof(unsigned long long); + data = &v[0]; + } + } + break; + case x_double: + { + sqlType = SQL_DOUBLE; + cType = SQL_C_DOUBLE; + size = sizeof(double); + std::vector *vp = static_cast *>(data); + std::vector &v(*vp); + prepare_indicators(v.size()); + data = &v[0]; + } + break; + + // cases that require adjustments and buffer management + case x_char: + { + std::vector *vp + = static_cast *>(data); + std::size_t const vsize = vp->size(); + + prepare_indicators(vsize); + + size = sizeof(char) * 2; + buf_ = new char[size * vsize]; + + char *pos = buf_; + + for (std::size_t i = 0; i != vsize; ++i) + { + *pos++ = (*vp)[i]; + *pos++ = 0; + } + + sqlType = SQL_CHAR; + cType = SQL_C_CHAR; + data = buf_; + } + break; + case x_stdstring: + { + sqlType = SQL_CHAR; + cType = SQL_C_CHAR; + + std::vector *vp + = static_cast *>(data); + std::vector &v(*vp); + + std::size_t maxSize = 0; + std::size_t const vecSize = v.size(); + prepare_indicators(vecSize); + for (std::size_t i = 0; i != vecSize; ++i) + { + std::size_t sz = v[i].length() + 1; // add one for null + indHolderVec_[i] = static_cast(sz); + maxSize = sz > maxSize ? sz : maxSize; + } + + buf_ = new char[maxSize * vecSize]; + memset(buf_, 0, maxSize * vecSize); + + char *pos = buf_; + for (std::size_t i = 0; i != vecSize; ++i) + { + strncpy(pos, v[i].c_str(), v[i].length()); + pos += maxSize; + } + + data = buf_; + size = static_cast(maxSize); + } + break; + case x_stdtm: + { + std::vector *vp + = static_cast *>(data); + + prepare_indicators(vp->size()); + + buf_ = new char[sizeof(TIMESTAMP_STRUCT) * vp->size()]; + + sqlType = SQL_TYPE_TIMESTAMP; + cType = SQL_C_TYPE_TIMESTAMP; + data = buf_; + size = 19; // This number is not the size in bytes, but the number + // of characters in the date if it was written out + // yyyy-mm-dd hh:mm:ss + } + break; + + case x_statement: break; // not supported + case x_rowid: break; // not supported + case x_blob: break; // not supported + } + + colSize_ = size; +} + +void odbc_vector_use_type_backend::bind_helper(int &position, void *data, exchange_type type) +{ + data_ = data; // for future reference + type_ = type; // for future reference + + SQLSMALLINT sqlType; + SQLSMALLINT cType; + SQLUINTEGER size; + + prepare_for_bind(data, size, sqlType, cType); + + SQLULEN const arraySize = static_cast(indHolderVec_.size()); + SQLSetStmtAttr(statement_.hstmt_, SQL_ATTR_PARAMSET_SIZE, (SQLPOINTER)arraySize, 0); + + SQLRETURN rc = SQLBindParameter(statement_.hstmt_, static_cast(position++), + SQL_PARAM_INPUT, cType, sqlType, size, 0, + static_cast(data), size, indHolders_); + + if (is_odbc_error(rc)) + { + throw odbc_soci_error(SQL_HANDLE_STMT, statement_.hstmt_, + "Error while binding value to column"); + } +} + +void odbc_vector_use_type_backend::bind_by_pos(int &position, + void *data, exchange_type type) +{ + if (statement_.boundByName_) + { + throw soci_error( + "Binding for use elements must be either by position or by name."); + } + + bind_helper(position, data, type); + + statement_.boundByPos_ = true; +} + +void odbc_vector_use_type_backend::bind_by_name( + std::string const &name, void *data, exchange_type type) +{ + if (statement_.boundByPos_) + { + throw soci_error( + "Binding for use elements must be either by position or by name."); + } + + int position = -1; + int count = 1; + + for (std::vector::iterator it = statement_.names_.begin(); + it != statement_.names_.end(); ++it) + { + if (*it == name) + { + position = count; + break; + } + count++; + } + + if (position != -1) + { + bind_helper(position, data, type); + } + else + { + std::ostringstream ss; + ss << "Unable to find name '" << name << "' to bind to"; + throw soci_error(ss.str().c_str()); + } + + statement_.boundByName_ = true; +} + +void odbc_vector_use_type_backend::pre_use(indicator const *ind) +{ + // first deal with data + if (type_ == x_stdtm) + { + std::vector *vp + = static_cast *>(data_); + + std::vector &v(*vp); + + char *pos = buf_; + std::size_t const vsize = v.size(); + for (std::size_t i = 0; i != vsize; ++i) + { + std::tm t = v[i]; + TIMESTAMP_STRUCT * ts = reinterpret_cast(pos); + + ts->year = static_cast(t.tm_year + 1900); + ts->month = static_cast(t.tm_mon + 1); + ts->day = static_cast(t.tm_mday); + ts->hour = static_cast(t.tm_hour); + ts->minute = static_cast(t.tm_min); + ts->second = static_cast(t.tm_sec); + ts->fraction = 0; + pos += sizeof(TIMESTAMP_STRUCT); + } + } + else if (type_ == x_long_long && use_string_for_bigint()) + { + std::vector *vp + = static_cast *>(data_); + std::vector &v(*vp); + + char *pos = buf_; + std::size_t const vsize = v.size(); + for (std::size_t i = 0; i != vsize; ++i) + { + snprintf(pos, max_bigint_length, "%" LL_FMT_FLAGS "d", v[i]); + pos += max_bigint_length; + } + } + else if (type_ == x_unsigned_long_long && use_string_for_bigint()) + { + std::vector *vp + = static_cast *>(data_); + std::vector &v(*vp); + + char *pos = buf_; + std::size_t const vsize = v.size(); + for (std::size_t i = 0; i != vsize; ++i) + { + snprintf(pos, max_bigint_length, "%" LL_FMT_FLAGS "u", v[i]); + pos += max_bigint_length; + } + } + + // then handle indicators + if (ind != NULL) + { + std::size_t const vsize = size(); + for (std::size_t i = 0; i != vsize; ++i, ++ind) + { + if (*ind == i_null) + { + indHolderVec_[i] = SQL_NULL_DATA; // null + } + else + { + // for strings we have already set the values + if (type_ != x_stdstring) + { + indHolderVec_[i] = SQL_NTS; // value is OK + } + } + } + } + else + { + // no indicators - treat all fields as OK + std::size_t const vsize = size(); + for (std::size_t i = 0; i != vsize; ++i, ++ind) + { + // for strings we have already set the values + if (type_ != x_stdstring) + { + indHolderVec_[i] = SQL_NTS; // value is OK + } + } + } +} + +std::size_t odbc_vector_use_type_backend::size() +{ + std::size_t sz = 0; // dummy initialization to please the compiler + switch (type_) + { + // simple cases + case x_char: + { + std::vector *vp = static_cast *>(data_); + sz = vp->size(); + } + break; + case x_short: + { + std::vector *vp = static_cast *>(data_); + sz = vp->size(); + } + break; + case x_integer: + { + std::vector *vp = static_cast *>(data_); + sz = vp->size(); + } + break; + case x_long_long: + { + std::vector *vp = + static_cast *>(data_); + sz = vp->size(); + } + break; + case x_unsigned_long_long: + { + std::vector *vp = + static_cast *>(data_); + sz = vp->size(); + } + break; + case x_double: + { + std::vector *vp + = static_cast *>(data_); + sz = vp->size(); + } + break; + case x_stdstring: + { + std::vector *vp + = static_cast *>(data_); + sz = vp->size(); + } + break; + case x_stdtm: + { + std::vector *vp + = static_cast *>(data_); + sz = vp->size(); + } + break; + + case x_statement: break; // not supported + case x_rowid: break; // not supported + case x_blob: break; // not supported + } + + return sz; +} + +void odbc_vector_use_type_backend::clean_up() +{ + if (buf_ != NULL) + { + delete [] buf_; + buf_ = NULL; + } +} diff --git a/src/backends/oracle/CMakeLists.txt b/src/backends/oracle/CMakeLists.txt new file mode 100644 index 0000000000..954dce0051 --- /dev/null +++ b/src/backends/oracle/CMakeLists.txt @@ -0,0 +1,18 @@ +############################################################################### +# +# This file is part of CMake configuration for SOCI library +# +# Copyright (C) 2010 Mateusz Loskot +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) +# +############################################################################### +soci_backend(Oracle + DEPENDS Oracle + HEADERS soci-oracle.h error.h + DESCRIPTION "SOCI backend for Oracle 10+" + AUTHORS "Maciej Sobczak, Stephen Hutton" + MAINTAINERS "Maciej Sobczak") + +add_subdirectory(test) diff --git a/src/backends/oracle/Makefile.basic b/src/backends/oracle/Makefile.basic new file mode 100644 index 0000000000..85e4df2e29 --- /dev/null +++ b/src/backends/oracle/Makefile.basic @@ -0,0 +1,95 @@ +# The following variable is specific to this backend and its correct +# values might depend on your environment - feel free to set it accordingly. + +ORACLEINCLUDEDIR = -I/usr/lib/oracle/xe/app/oracle/product/10.2.0/server/rdbms/public + +# The rest of the Makefile is indepentent of the target environment. + +COMPILER = g++ +CXXFLAGS = -Wall -pedantic -Wno-long-long +CXXFLAGSSO = ${CXXFLAGS} -fPIC +INCLUDEDIRS = -I../../core ${ORACLEINCLUDEDIR} + +OBJECTS = blob.o factory.o row-id.o session.o standard-into-type.o \ + standard-use-type.o statement.o vector-into-type.o vector-use-type.o \ + error.o + +OBJECTSSO = blob-s.o factory-s.o row-id-s.o session-s.o \ + standard-into-type-s.o standard-use-type-s.o statement-s.o \ + vector-into-type-s.o vector-use-type-s.o error-s.o + +libsoci_oracle.a : ${OBJECTS} + ar rv $@ $? + rm *.o + +soci-oracle.o : soci-oracle.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +blob.o : blob.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +error.o : error.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +factory.o : factory.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +row-id.o : row-id.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +session.o : session.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +standard-into-type.o : standard-into-type.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +standard-use-type.o : standard-use-type.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +statement.o : statement.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +vector-into-type.o : vector-into-type.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +vector-use-type.o : vector-use-type.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + + +shared : ${OBJECTSSO} + ${COMPILER} -shared -o libsoci_oracle.so ${OBJECTSSO} + rm *.o + +blob-s.o : blob.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +error-s.o : error.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +factory-s.o : factory.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +row-id-s.o : row-id.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +session-s.o : session.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +standard-into-type-s.o : standard-into-type.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +standard-use-type-s.o : standard-use-type.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +statement-s.o : statement.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +vector-into-type-s.o : vector-into-type.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +vector-use-type-s.o : vector-use-type.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + + +clean : + rm -f *.o libsoci_oracle.a libsoci_oracle.so diff --git a/src/backends/oracle/blob.cpp b/src/backends/oracle/blob.cpp new file mode 100644 index 0000000000..2636e665ee --- /dev/null +++ b/src/backends/oracle/blob.cpp @@ -0,0 +1,112 @@ +// +// Copyright (C) 2004-2007 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#include "soci-oracle.h" +#include "error.h" +#include "statement.h" +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(disable:4355) +#endif + +using namespace soci; +using namespace soci::details; +using namespace soci::details::oracle; + +oracle_blob_backend::oracle_blob_backend(oracle_session_backend &session) + : session_(session) +{ + sword res = OCIDescriptorAlloc(session.envhp_, + reinterpret_cast(&lobp_), OCI_DTYPE_LOB, 0, 0); + if (res != OCI_SUCCESS) + { + throw soci_error("Cannot allocate the LOB locator"); + } +} + +oracle_blob_backend::~oracle_blob_backend() +{ + OCIDescriptorFree(lobp_, OCI_DTYPE_LOB); +} + +std::size_t oracle_blob_backend::get_len() +{ + ub4 len; + + sword res = OCILobGetLength(session_.svchp_, session_.errhp_, + lobp_, &len); + + if (res != OCI_SUCCESS) + { + throw_oracle_soci_error(res, session_.errhp_); + } + + return static_cast(len); +} + +std::size_t oracle_blob_backend::read( + std::size_t offset, char *buf, std::size_t toRead) +{ + ub4 amt = static_cast(toRead); + + sword res = OCILobRead(session_.svchp_, session_.errhp_, lobp_, &amt, + static_cast(offset), reinterpret_cast(buf), + amt, 0, 0, 0, 0); + if (res != OCI_SUCCESS) + { + throw_oracle_soci_error(res, session_.errhp_); + } + + return static_cast(amt); +} + +std::size_t oracle_blob_backend::write( + std::size_t offset, char const *buf, std::size_t toWrite) +{ + ub4 amt = static_cast(toWrite); + + sword res = OCILobWrite(session_.svchp_, session_.errhp_, lobp_, &amt, + static_cast(offset), + reinterpret_cast(const_cast(buf)), + amt, OCI_ONE_PIECE, 0, 0, 0, 0); + if (res != OCI_SUCCESS) + { + throw_oracle_soci_error(res, session_.errhp_); + } + + return static_cast(amt); +} + +std::size_t oracle_blob_backend::append(char const *buf, std::size_t toWrite) +{ + ub4 amt = static_cast(toWrite); + + sword res = OCILobWriteAppend(session_.svchp_, session_.errhp_, lobp_, + &amt, reinterpret_cast(const_cast(buf)), + amt, OCI_ONE_PIECE, 0, 0, 0, 0); + if (res != OCI_SUCCESS) + { + throw_oracle_soci_error(res, session_.errhp_); + } + + return static_cast(amt); +} + +void oracle_blob_backend::trim(std::size_t newLen) +{ + sword res = OCILobTrim(session_.svchp_, session_.errhp_, lobp_, + static_cast(newLen)); + if (res != OCI_SUCCESS) + { + throw_oracle_soci_error(res, session_.errhp_); + } +} diff --git a/src/backends/oracle/error.cpp b/src/backends/oracle/error.cpp new file mode 100644 index 0000000000..12b72bcdea --- /dev/null +++ b/src/backends/oracle/error.cpp @@ -0,0 +1,60 @@ +// +// Copyright (C) 2004-2007 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_ORACLE_SOURCE +#include "soci-oracle.h" +#include "error.h" +#include +#include + +#ifdef _MSC_VER +#pragma warning(disable:4355) +#endif + +using namespace soci; +using namespace soci::details; +using namespace soci::details::oracle; + +oracle_soci_error::oracle_soci_error(std::string const & msg, int errNum) + : soci_error(msg), err_num_(errNum) +{ +} + +void soci::details::oracle::get_error_details(sword res, OCIError *errhp, + std::string &msg, int &errNum) +{ + text errbuf[512]; + sb4 errcode; + errNum = 0; + + switch (res) + { + case OCI_NO_DATA: + msg = "soci error: No data"; + break; + case OCI_ERROR: + OCIErrorGet(errhp, 1, 0, &errcode, + errbuf, sizeof(errbuf), OCI_HTYPE_ERROR); + msg = reinterpret_cast(errbuf); + errNum = static_cast(errcode); + break; + case OCI_INVALID_HANDLE: + msg = "soci error: Invalid handle"; + break; + default: + msg = "soci error: Unknown error code"; + } +} + +void soci::details::oracle::throw_oracle_soci_error(sword res, OCIError *errhp) +{ + std::string msg; + int errNum; + + get_error_details(res, errhp, msg, errNum); + throw oracle_soci_error(msg, errNum); +} diff --git a/src/backends/oracle/error.h b/src/backends/oracle/error.h new file mode 100644 index 0000000000..2dbe9214ed --- /dev/null +++ b/src/backends/oracle/error.h @@ -0,0 +1,33 @@ +// +// Copyright (C) 2004-2007 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_ORACLE_ERROR_H_INCLUDED +#define SOCI_ORACLE_ERROR_H_INCLUDED + +#include "soci-oracle.h" + +namespace soci +{ + +namespace details +{ + +namespace oracle +{ + +void throw_oracle_soci_error(sword res, OCIError *errhp); + +void get_error_details(sword res, OCIError *errhp, + std::string &msg, int &errNum); + +} // namespace oracle + +} // namespace details + +} // namespace soci + +#endif diff --git a/src/backends/oracle/factory.cpp b/src/backends/oracle/factory.cpp new file mode 100644 index 0000000000..7c80989670 --- /dev/null +++ b/src/backends/oracle/factory.cpp @@ -0,0 +1,139 @@ +// +// Copyright (C) 2004-2007 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_ORACLE_SOURCE +#include "soci-oracle.h" +#include +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(disable:4355) +#endif + +using namespace soci; +using namespace soci::details; + +// retrieves service name, user name and password from the +// uniform connect string +void chop_connect_string(std::string const & connectString, + std::string & serviceName, std::string & userName, + std::string & password, int & mode, bool & decimals_as_strings) +{ + // transform the connect string into a sequence of tokens + // separated by spaces, this is done by replacing each first '=' + // in each original token with space + // note: each original token is a key=value pair and only the first + // '=' there is replaced with space, so that potential '=' signs + // in the value part are left intact + + std::string tmp; + bool in_value = false; + for (std::string::const_iterator i = connectString.begin(), + end = connectString.end(); i != end; ++i) + { + if (*i == '=' && in_value == false) + { + // this is the first '=' in the key=value pair + tmp += ' '; + in_value = true; + } + else + { + tmp += *i; + if (*i == ' ' || *i == '\t') + { + // follow with the next key=value pair + in_value = false; + } + } + } + + serviceName.clear(); + userName.clear(); + password.clear(); + mode = OCI_DEFAULT; + decimals_as_strings = false; + + std::istringstream iss(tmp); + std::string key, value; + while (iss >> key >> value) + { + if (key == "service") + { + serviceName = value; + } + else if (key == "user") + { + userName = value; + } + else if (key == "password") + { + password = value; + } + else if (key == "mode") + { + if (value == "sysdba") + { + mode = OCI_SYSDBA; + } + else if (value == "sysoper") + { + mode = OCI_SYSOPER; + } + else if (value == "default") + { + mode = OCI_DEFAULT; + } + else + { + throw soci_error("Invalid connection mode."); + } + } + else if (key == "decimals_as_strings") + { + decimals_as_strings = value == "1" || value == "Y" || value == "y"; + } + } +} + +// concrete factory for Empty concrete strategies +oracle_session_backend * oracle_backend_factory::make_session( + connection_parameters const & parameters) const +{ + std::string serviceName, userName, password; + int mode; + bool decimals_as_strings; + + chop_connect_string(parameters.get_connect_string(), serviceName, userName, password, + mode, decimals_as_strings); + + return new oracle_session_backend(serviceName, userName, password, + mode, decimals_as_strings); +} + +oracle_backend_factory const soci::oracle; + +extern "C" +{ + +// for dynamic backend loading +SOCI_ORACLE_DECL backend_factory const * factory_oracle() +{ + return &soci::oracle; +} + +SOCI_ORACLE_DECL void register_factory_oracle() +{ + soci::dynamic_backends::register_backend("oracle", soci::oracle); +} + +} // extern "C" diff --git a/src/backends/oracle/row-id.cpp b/src/backends/oracle/row-id.cpp new file mode 100644 index 0000000000..966576ce65 --- /dev/null +++ b/src/backends/oracle/row-id.cpp @@ -0,0 +1,32 @@ +// +// Copyright (C) 2013 Mateusz Loskot +// Copyright (C) 2004-2007 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_ORACLE_SOURCE +#include "soci-oracle.h" + +#ifdef _MSC_VER +#pragma warning(disable:4355) +#endif + +using namespace soci; +using namespace soci::details; + +oracle_rowid_backend::oracle_rowid_backend(oracle_session_backend &session) +{ + sword res = OCIDescriptorAlloc(session.envhp_, + reinterpret_cast(&rowidp_), OCI_DTYPE_ROWID, 0, 0); + if (res != OCI_SUCCESS) + { + throw soci_error("Cannot allocate the ROWID descriptor"); + } +} + +oracle_rowid_backend::~oracle_rowid_backend() +{ + OCIDescriptorFree(rowidp_, OCI_DTYPE_ROWID); +} diff --git a/src/backends/oracle/session.cpp b/src/backends/oracle/session.cpp new file mode 100644 index 0000000000..b408d3cb59 --- /dev/null +++ b/src/backends/oracle/session.cpp @@ -0,0 +1,216 @@ +// +// Copyright (C) 2004-2007 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_ORACLE_SOURCE +#include "soci-oracle.h" +#include "error.h" +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(disable:4355) +#endif + +using namespace soci; +using namespace soci::details; +using namespace soci::details::oracle; + +oracle_session_backend::oracle_session_backend(std::string const & serviceName, + std::string const & userName, std::string const & password, int mode, + bool decimals_as_strings) + : envhp_(NULL), srvhp_(NULL), errhp_(NULL), svchp_(NULL), usrhp_(NULL) + , decimals_as_strings_(decimals_as_strings) +{ + sword res; + + // create the environment + res = OCIEnvCreate(&envhp_, OCI_THREADED | OCI_ENV_NO_MUTEX, + 0, 0, 0, 0, 0, 0); + if (res != OCI_SUCCESS) + { + throw soci_error("Cannot create environment"); + } + + // create the server handle + res = OCIHandleAlloc(envhp_, reinterpret_cast(&srvhp_), + OCI_HTYPE_SERVER, 0, 0); + if (res != OCI_SUCCESS) + { + clean_up(); + throw soci_error("Cannot create server handle"); + } + + // create the error handle + res = OCIHandleAlloc(envhp_, reinterpret_cast(&errhp_), + OCI_HTYPE_ERROR, 0, 0); + if (res != OCI_SUCCESS) + { + clean_up(); + throw soci_error("Cannot create error handle"); + } + + // create the server context + sb4 serviceNameLen = static_cast(serviceName.size()); + res = OCIServerAttach(srvhp_, errhp_, + reinterpret_cast(const_cast(serviceName.c_str())), + serviceNameLen, OCI_DEFAULT); + if (res != OCI_SUCCESS) + { + std::string msg; + int errNum; + get_error_details(res, errhp_, msg, errNum); + clean_up(); + throw oracle_soci_error(msg, errNum); + } + + // create service context handle + res = OCIHandleAlloc(envhp_, reinterpret_cast(&svchp_), + OCI_HTYPE_SVCCTX, 0, 0); + if (res != OCI_SUCCESS) + { + clean_up(); + throw soci_error("Cannot create service context"); + } + + // set the server attribute in the context handle + res = OCIAttrSet(svchp_, OCI_HTYPE_SVCCTX, srvhp_, 0, + OCI_ATTR_SERVER, errhp_); + if (res != OCI_SUCCESS) + { + std::string msg; + int errNum; + get_error_details(res, errhp_, msg, errNum); + clean_up(); + throw oracle_soci_error(msg, errNum); + } + + // allocate user session handle + res = OCIHandleAlloc(envhp_, reinterpret_cast(&usrhp_), + OCI_HTYPE_SESSION, 0, 0); + if (res != OCI_SUCCESS) + { + clean_up(); + throw soci_error("Cannot allocate user session handle"); + } + + // set username attribute in the user session handle + sb4 userNameLen = static_cast(userName.size()); + res = OCIAttrSet(usrhp_, OCI_HTYPE_SESSION, + reinterpret_cast(const_cast(userName.c_str())), + userNameLen, OCI_ATTR_USERNAME, errhp_); + if (res != OCI_SUCCESS) + { + clean_up(); + throw soci_error("Cannot set username"); + } + + // set password attribute + sb4 passwordLen = static_cast(password.size()); + res = OCIAttrSet(usrhp_, OCI_HTYPE_SESSION, + reinterpret_cast(const_cast(password.c_str())), + passwordLen, OCI_ATTR_PASSWORD, errhp_); + if (res != OCI_SUCCESS) + { + clean_up(); + throw soci_error("Cannot set password"); + } + + // begin the session + res = OCISessionBegin(svchp_, errhp_, usrhp_, + OCI_CRED_RDBMS, mode); + if (res != OCI_SUCCESS) + { + std::string msg; + int errNum; + get_error_details(res, errhp_, msg, errNum); + clean_up(); + throw oracle_soci_error(msg, errNum); + } + + // set the session in the context handle + res = OCIAttrSet(svchp_, OCI_HTYPE_SVCCTX, usrhp_, + 0, OCI_ATTR_SESSION, errhp_); + if (res != OCI_SUCCESS) + { + std::string msg; + int errNum; + get_error_details(res, errhp_, msg, errNum); + clean_up(); + throw oracle_soci_error(msg, errNum); + } +} + +oracle_session_backend::~oracle_session_backend() +{ + clean_up(); +} + +void oracle_session_backend::begin() +{ + // This code is commented out because it causes one of the transaction + // tests in common_tests::test10() to fail with error 'Invalid handle' + // With the code commented out, all tests pass. + // sword res = OCITransStart(svchp_, errhp_, 0, OCI_TRANS_NEW); + // if (res != OCI_SUCCESS) + // { + // throworacle_soci_error(res, errhp_); + // } +} + +void oracle_session_backend::commit() +{ + sword res = OCITransCommit(svchp_, errhp_, OCI_DEFAULT); + if (res != OCI_SUCCESS) + { + throw_oracle_soci_error(res, errhp_); + } +} + +void oracle_session_backend::rollback() +{ + sword res = OCITransRollback(svchp_, errhp_, OCI_DEFAULT); + if (res != OCI_SUCCESS) + { + throw_oracle_soci_error(res, errhp_); + } +} + +void oracle_session_backend::clean_up() +{ + if (svchp_ != NULL && errhp_ != NULL && usrhp_ != NULL) + { + OCISessionEnd(svchp_, errhp_, usrhp_, OCI_DEFAULT); + } + + if (usrhp_) { OCIHandleFree(usrhp_, OCI_HTYPE_SESSION); } + if (svchp_) { OCIHandleFree(svchp_, OCI_HTYPE_SVCCTX); } + if (srvhp_) + { + OCIServerDetach(srvhp_, errhp_, OCI_DEFAULT); + OCIHandleFree(srvhp_, OCI_HTYPE_SERVER); + } + if (errhp_) { OCIHandleFree(errhp_, OCI_HTYPE_ERROR); } + if (envhp_) { OCIHandleFree(envhp_, OCI_HTYPE_ENV); } +} + +oracle_statement_backend * oracle_session_backend::make_statement_backend() +{ + return new oracle_statement_backend(*this); +} + +oracle_rowid_backend * oracle_session_backend::make_rowid_backend() +{ + return new oracle_rowid_backend(*this); +} + +oracle_blob_backend * oracle_session_backend::make_blob_backend() +{ + return new oracle_blob_backend(*this); +} diff --git a/src/backends/oracle/soci-oracle.h b/src/backends/oracle/soci-oracle.h new file mode 100644 index 0000000000..c6ffb60a12 --- /dev/null +++ b/src/backends/oracle/soci-oracle.h @@ -0,0 +1,295 @@ +// +// Copyright (C) 2004-2007 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_ORACLE_H_INCLUDED +#define SOCI_ORACLE_H_INCLUDED + +#ifdef _WIN32 +# ifdef SOCI_DLL +# ifdef SOCI_ORACLE_SOURCE +# define SOCI_ORACLE_DECL __declspec(dllexport) +# else +# define SOCI_ORACLE_DECL __declspec(dllimport) +# endif // SOCI_ORACLE_SOURCE +# endif // SOCI_DLL +#endif // _WIN32 +// +// If SOCI_ORACLE_DECL isn't defined yet define it now +#ifndef SOCI_ORACLE_DECL +# define SOCI_ORACLE_DECL +#endif + +#include +#include // OCI +#include + +#ifdef _MSC_VER +#pragma warning(disable:4512 4511) +#endif + + +namespace soci +{ + +class SOCI_ORACLE_DECL oracle_soci_error : public soci_error +{ +public: + oracle_soci_error(std::string const & msg, int errNum = 0); + + int err_num_; +}; + + +struct oracle_statement_backend; +struct oracle_standard_into_type_backend : details::standard_into_type_backend +{ + oracle_standard_into_type_backend(oracle_statement_backend &st) + : statement_(st), defnp_(NULL), indOCIHolder_(0), + data_(NULL), buf_(NULL) {} + + virtual void define_by_pos(int &position, + void *data, details::exchange_type type); + + virtual void pre_fetch(); + virtual void post_fetch(bool gotData, bool calledFromFetch, + indicator *ind); + + virtual void clean_up(); + + oracle_statement_backend &statement_; + + OCIDefine *defnp_; + sb2 indOCIHolder_; + void *data_; + char *buf_; // generic buffer + details::exchange_type type_; + + ub2 rCode_; +}; + +struct oracle_vector_into_type_backend : details::vector_into_type_backend +{ + oracle_vector_into_type_backend(oracle_statement_backend &st) + : statement_(st), defnp_(NULL), indOCIHolders_(NULL), + data_(NULL), buf_(NULL) {} + + virtual void define_by_pos(int &position, + void *data, details::exchange_type type); + + virtual void pre_fetch(); + virtual void post_fetch(bool gotData, indicator *ind); + + virtual void resize(std::size_t sz); + virtual std::size_t size(); + + virtual void clean_up(); + + // helper function for preparing indicators and sizes_ vectors + // (as part of the define_by_pos) + void prepare_indicators(std::size_t size); + + oracle_statement_backend &statement_; + + OCIDefine *defnp_; + sb2 *indOCIHolders_; + std::vector indOCIHolderVec_; + void *data_; + char *buf_; // generic buffer + details::exchange_type type_; + std::size_t colSize_; // size of the string column (used for strings) + std::vector sizes_; // sizes of data fetched (used for strings) + + std::vector rCodes_; +}; + +struct oracle_standard_use_type_backend : details::standard_use_type_backend +{ + oracle_standard_use_type_backend(oracle_statement_backend &st) + : statement_(st), bindp_(NULL), indOCIHolder_(0), + data_(NULL), buf_(NULL) {} + + virtual void bind_by_pos(int &position, + void *data, details::exchange_type type, bool readOnly); + virtual void bind_by_name(std::string const &name, + void *data, details::exchange_type type, bool readOnly); + + // common part for bind_by_pos and bind_by_name + void prepare_for_bind(void *&data, sb4 &size, ub2 &oracleType, bool readOnly); + + virtual void pre_use(indicator const *ind); + virtual void post_use(bool gotData, indicator *ind); + + virtual void clean_up(); + + oracle_statement_backend &statement_; + + OCIBind *bindp_; + sb2 indOCIHolder_; + void *data_; + bool readOnly_; + char *buf_; // generic buffer + details::exchange_type type_; +}; + +struct oracle_vector_use_type_backend : details::vector_use_type_backend +{ + oracle_vector_use_type_backend(oracle_statement_backend &st) + : statement_(st), bindp_(NULL), indOCIHolders_(NULL), + data_(NULL), buf_(NULL) {} + + virtual void bind_by_pos(int &position, + void *data, details::exchange_type type); + virtual void bind_by_name(std::string const &name, + void *data, details::exchange_type type); + + // common part for bind_by_pos and bind_by_name + void prepare_for_bind(void *&data, sb4 &size, ub2 &oracleType); + + // helper function for preparing indicators and sizes_ vectors + // (as part of the bind_by_pos and bind_by_name) + void prepare_indicators(std::size_t size); + + virtual void pre_use(indicator const *ind); + + virtual std::size_t size(); + + virtual void clean_up(); + + oracle_statement_backend &statement_; + + OCIBind *bindp_; + std::vector indOCIHolderVec_; + sb2 *indOCIHolders_; + void *data_; + char *buf_; // generic buffer + details::exchange_type type_; + + // used for strings only + std::vector sizes_; + std::size_t maxSize_; +}; + +struct oracle_session_backend; +struct oracle_statement_backend : details::statement_backend +{ + oracle_statement_backend(oracle_session_backend &session); + + virtual void alloc(); + virtual void clean_up(); + virtual void prepare(std::string const &query, + details::statement_type eType); + + virtual exec_fetch_result execute(int number); + virtual exec_fetch_result fetch(int number); + + virtual long long get_affected_rows(); + virtual int get_number_of_rows(); + + virtual std::string rewrite_for_procedure_call(std::string const &query); + + virtual int prepare_for_describe(); + virtual void describe_column(int colNum, data_type &dtype, + std::string &columnName); + + // helper for defining into vector + std::size_t column_size(int position); + + virtual oracle_standard_into_type_backend * make_into_type_backend(); + virtual oracle_standard_use_type_backend * make_use_type_backend(); + virtual oracle_vector_into_type_backend * make_vector_into_type_backend(); + virtual oracle_vector_use_type_backend * make_vector_use_type_backend(); + + oracle_session_backend &session_; + + OCIStmt *stmtp_; + + bool boundByName_; + bool boundByPos_; + bool noData_; +}; + +struct oracle_rowid_backend : details::rowid_backend +{ + oracle_rowid_backend(oracle_session_backend &session); + + ~oracle_rowid_backend(); + + OCIRowid *rowidp_; +}; + +struct oracle_blob_backend : details::blob_backend +{ + oracle_blob_backend(oracle_session_backend &session); + + ~oracle_blob_backend(); + + virtual std::size_t get_len(); + virtual std::size_t read(std::size_t offset, char *buf, + std::size_t toRead); + virtual std::size_t write(std::size_t offset, char const *buf, + std::size_t toWrite); + virtual std::size_t append(char const *buf, std::size_t toWrite); + virtual void trim(std::size_t newLen); + + oracle_session_backend &session_; + + OCILobLocator *lobp_; +}; + +struct oracle_session_backend : details::session_backend +{ + oracle_session_backend(std::string const & serviceName, + std::string const & userName, + std::string const & password, + int mode, + bool decimals_as_strings = false); + + ~oracle_session_backend(); + + virtual void begin(); + virtual void commit(); + virtual void rollback(); + + virtual std::string get_backend_name() const { return "oracle"; } + + void clean_up(); + + virtual oracle_statement_backend * make_statement_backend(); + virtual oracle_rowid_backend * make_rowid_backend(); + virtual oracle_blob_backend * make_blob_backend(); + + bool get_option_decimals_as_strings() { return decimals_as_strings_; } + + OCIEnv *envhp_; + OCIServer *srvhp_; + OCIError *errhp_; + OCISvcCtx *svchp_; + OCISession *usrhp_; + bool decimals_as_strings_; +}; + +struct oracle_backend_factory : backend_factory +{ + oracle_backend_factory() {} + virtual oracle_session_backend * make_session( + connection_parameters const & parameters) const; +}; + +extern SOCI_ORACLE_DECL oracle_backend_factory const oracle; + +extern "C" +{ + +// for dynamic backend loading +SOCI_ORACLE_DECL backend_factory const * factory_oracle(); +SOCI_ORACLE_DECL void register_factory_oracle(); + +} // extern "C" + +} // namespace soci + +#endif diff --git a/src/backends/oracle/standard-into-type.cpp b/src/backends/oracle/standard-into-type.cpp new file mode 100644 index 0000000000..d12a93e88e --- /dev/null +++ b/src/backends/oracle/standard-into-type.cpp @@ -0,0 +1,274 @@ +// +// Copyright (C) 2004-2007 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_ORACLE_SOURCE +#include "soci-oracle.h" +#include "blob.h" +#include "error.h" +#include "rowid.h" +#include "statement.h" +#include +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(disable:4355) +#endif + +using namespace soci; +using namespace soci::details; +using namespace soci::details::oracle; + +oracle_standard_into_type_backend * +oracle_statement_backend::make_into_type_backend() +{ + return new oracle_standard_into_type_backend(*this); +} + +oracle_standard_use_type_backend * +oracle_statement_backend::make_use_type_backend() +{ + return new oracle_standard_use_type_backend(*this); +} + +oracle_vector_into_type_backend * +oracle_statement_backend::make_vector_into_type_backend() +{ + return new oracle_vector_into_type_backend(*this); +} + +oracle_vector_use_type_backend * +oracle_statement_backend::make_vector_use_type_backend() +{ + return new oracle_vector_use_type_backend(*this); +} + +void oracle_standard_into_type_backend::define_by_pos( + int &position, void *data, exchange_type type) +{ + data_ = data; // for future reference + type_ = type; // for future reference + + ub2 oracleType = 0; // dummy initialization to please the compiler + sb4 size = 0; // also dummy + + switch (type) + { + // simple cases + case x_char: + oracleType = SQLT_AFC; + size = sizeof(char); + break; + case x_short: + oracleType = SQLT_INT; + size = sizeof(short); + break; + case x_integer: + oracleType = SQLT_INT; + size = sizeof(int); + break; + case x_double: + oracleType = SQLT_FLT; + size = sizeof(double); + break; + + // cases that require adjustments and buffer management + case x_long_long: + case x_unsigned_long_long: + oracleType = SQLT_STR; + size = 100; // arbitrary buffer length + buf_ = new char[size]; + data = buf_; + break; + case x_stdstring: + oracleType = SQLT_STR; + size = 32769; // support selecting strings from LONG columns + buf_ = new char[size]; + data = buf_; + break; + case x_stdtm: + oracleType = SQLT_DAT; + size = 7 * sizeof(ub1); + buf_ = new char[size]; + data = buf_; + break; + + // cases that require special handling + case x_statement: + { + oracleType = SQLT_RSET; + + statement *st = static_cast(data); + st->alloc(); + + oracle_statement_backend *stbe + = static_cast(st->get_backend()); + size = 0; + data = &stbe->stmtp_; + } + break; + case x_rowid: + { + oracleType = SQLT_RDD; + + rowid *rid = static_cast(data); + + oracle_rowid_backend *rbe + = static_cast(rid->get_backend()); + + size = 0; + data = &rbe->rowidp_; + } + break; + case x_blob: + { + oracleType = SQLT_BLOB; + + blob *b = static_cast(data); + + oracle_blob_backend *bbe + = static_cast(b->get_backend()); + + size = 0; + data = &bbe->lobp_; + } + break; + } + + sword res = OCIDefineByPos(statement_.stmtp_, &defnp_, + statement_.session_.errhp_, + position++, data, size, oracleType, + &indOCIHolder_, 0, &rCode_, OCI_DEFAULT); + + if (res != OCI_SUCCESS) + { + throw_oracle_soci_error(res, statement_.session_.errhp_); + } +} + +void oracle_standard_into_type_backend::pre_fetch() +{ + // nothing to do except with Statement into objects + + if (type_ == x_statement) + { + statement *st = static_cast(data_); + st->undefine_and_bind(); + } +} + +void oracle_standard_into_type_backend::post_fetch( + bool gotData, bool calledFromFetch, indicator *ind) +{ + // first, deal with data + if (gotData) + { + // only std::string, std::tm and Statement need special handling + if (type_ == x_stdstring) + { + if (indOCIHolder_ != -1) + { + std::string *s = static_cast(data_); + *s = buf_; + } + } + else if (type_ == x_long_long) + { + if (indOCIHolder_ != -1) + { + long long *v = static_cast(data_); + *v = std::strtoll(buf_, NULL, 10); + } + } + else if (type_ == x_unsigned_long_long) + { + if (indOCIHolder_ != -1) + { + unsigned long long *v = static_cast(data_); + *v = std::strtoull(buf_, NULL, 10); + } + } + else if (type_ == x_stdtm) + { + if (indOCIHolder_ != -1) + { + std::tm *t = static_cast(data_); + + ub1 *pos = reinterpret_cast(buf_); + t->tm_isdst = -1; + t->tm_year = (*pos++ - 100) * 100; + t->tm_year += *pos++ - 2000; + t->tm_mon = *pos++ - 1; + t->tm_mday = *pos++; + t->tm_hour = *pos++ - 1; + t->tm_min = *pos++ - 1; + t->tm_sec = *pos++ - 1; + + // normalize and compute the remaining fields + std::mktime(t); + } + } + else if (type_ == x_statement) + { + statement *st = static_cast(data_); + st->define_and_bind(); + } + } + + // then - deal with indicators + if (calledFromFetch == true && gotData == false) + { + // this is a normal end-of-rowset condition, + // no need to set anything (fetch() will return false) + return; + } + if (ind != NULL) + { + if (gotData) + { + if (indOCIHolder_ == 0) + { + *ind = i_ok; + } + else if (indOCIHolder_ == -1) + { + *ind = i_null; + } + else + { + *ind = i_truncated; + } + } + } + else + { + if (indOCIHolder_ == -1) + { + // fetched null and no indicator - programming error! + throw soci_error("Null value fetched and no indicator defined."); + } + } +} + +void oracle_standard_into_type_backend::clean_up() +{ + if (defnp_ != NULL) + { + OCIHandleFree(defnp_, OCI_HTYPE_DEFINE); + defnp_ = NULL; + } + + if (buf_ != NULL) + { + delete [] buf_; + buf_ = NULL; + } +} diff --git a/src/backends/oracle/standard-use-type.cpp b/src/backends/oracle/standard-use-type.cpp new file mode 100644 index 0000000000..8e42b720b3 --- /dev/null +++ b/src/backends/oracle/standard-use-type.cpp @@ -0,0 +1,476 @@ +// +// Copyright (C) 2004-2007 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define soci_ORACLE_SOURCE +#include "soci-oracle.h" +#include "blob.h" +#include "error.h" +#include "rowid.h" +#include "statement.h" +#include +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(disable:4355) +#define snprintf _snprintf +#endif + +using namespace soci; +using namespace soci::details; +using namespace soci::details::oracle; + +void oracle_standard_use_type_backend::prepare_for_bind( + void *&data, sb4 &size, ub2 &oracleType, bool readOnly) +{ + readOnly_ = readOnly; + + switch (type_) + { + // simple cases + case x_char: + oracleType = SQLT_AFC; + size = sizeof(char); + if (readOnly) + { + buf_ = new char[size]; + data = buf_; + } + break; + case x_short: + oracleType = SQLT_INT; + size = sizeof(short); + if (readOnly) + { + buf_ = new char[size]; + data = buf_; + } + break; + case x_integer: + oracleType = SQLT_INT; + size = sizeof(int); + if (readOnly) + { + buf_ = new char[size]; + data = buf_; + } + break; + case x_double: + oracleType = SQLT_FLT; + size = sizeof(double); + if (readOnly) + { + buf_ = new char[size]; + data = buf_; + } + break; + + // cases that require adjustments and buffer management + case x_long_long: + case x_unsigned_long_long: + oracleType = SQLT_STR; + size = 100; // arbitrary buffer length + buf_ = new char[size]; + data = buf_; + break; + case x_stdstring: + oracleType = SQLT_STR; + // 4000 is Oracle max VARCHAR2 size; 32768 is max LONG size + size = 32769; + buf_ = new char[size]; + data = buf_; + break; + case x_stdtm: + oracleType = SQLT_DAT; + size = 7 * sizeof(ub1); + buf_ = new char[size]; + data = buf_; + break; + + // cases that require special handling + case x_statement: + { + oracleType = SQLT_RSET; + + statement *st = static_cast(data); + st->alloc(); + + oracle_statement_backend *stbe + = static_cast(st->get_backend()); + size = 0; + data = &stbe->stmtp_; + } + break; + case x_rowid: + { + oracleType = SQLT_RDD; + + rowid *rid = static_cast(data); + + oracle_rowid_backend *rbe + = static_cast(rid->get_backend()); + + size = 0; + data = &rbe->rowidp_; + } + break; + case x_blob: + { + oracleType = SQLT_BLOB; + + blob *b = static_cast(data); + + oracle_blob_backend *bbe + = static_cast(b->get_backend()); + + size = 0; + data = &bbe->lobp_; + } + break; + } +} + +void oracle_standard_use_type_backend::bind_by_pos( + int &position, void *data, exchange_type type, bool readOnly) +{ + if (statement_.boundByName_) + { + throw soci_error( + "Binding for use elements must be either by position or by name."); + } + + data_ = data; // for future reference + type_ = type; // for future reference + + ub2 oracleType; + sb4 size; + + prepare_for_bind(data, size, oracleType, readOnly); + + sword res = OCIBindByPos(statement_.stmtp_, &bindp_, + statement_.session_.errhp_, + position++, data, size, oracleType, + &indOCIHolder_, 0, 0, 0, 0, OCI_DEFAULT); + if (res != OCI_SUCCESS) + { + throw_oracle_soci_error(res, statement_.session_.errhp_); + } + + statement_.boundByPos_ = true; +} + +void oracle_standard_use_type_backend::bind_by_name( + std::string const &name, void *data, exchange_type type, bool readOnly) +{ + if (statement_.boundByPos_) + { + throw soci_error( + "Binding for use elements must be either by position or by name."); + } + + data_ = data; // for future reference + type_ = type; // for future reference + + ub2 oracleType; + sb4 size; + + prepare_for_bind(data, size, oracleType, readOnly); + + sword res = OCIBindByName(statement_.stmtp_, &bindp_, + statement_.session_.errhp_, + reinterpret_cast(const_cast(name.c_str())), + static_cast(name.size()), + data, size, oracleType, + &indOCIHolder_, 0, 0, 0, 0, OCI_DEFAULT); + if (res != OCI_SUCCESS) + { + throw_oracle_soci_error(res, statement_.session_.errhp_); + } + + statement_.boundByName_ = true; +} + +void oracle_standard_use_type_backend::pre_use(indicator const *ind) +{ + // first deal with data + switch (type_) + { + case x_char: + if (readOnly_) + { + buf_[0] = *static_cast(data_); + } + break; + case x_short: + if (readOnly_) + { + *static_cast(static_cast(buf_)) = *static_cast(data_); + } + break; + case x_integer: + if (readOnly_) + { + *static_cast(static_cast(buf_)) = *static_cast(data_); + } + break; + case x_long_long: + { + size_t const size = 100; // arbitrary, but consistent with prepare_for_bind + snprintf(buf_, size, "%" LL_FMT_FLAGS "d", *static_cast(data_)); + } + break; + case x_unsigned_long_long: + { + size_t const size = 100; // arbitrary, but consistent with prepare_for_bind + snprintf(buf_, size, "%" LL_FMT_FLAGS "u", *static_cast(data_)); + } + break; + case x_double: + if (readOnly_) + { + *static_cast(static_cast(buf_)) = *static_cast(data_); + } + break; + case x_stdstring: + { + std::string *s = static_cast(data_); + + // 4000 is Oracle max VARCHAR2 size; 32768 is max LONG size + std::size_t const bufSize = 32769; + std::size_t const sSize = s->size(); + std::size_t const toCopy = + sSize < bufSize -1 ? sSize + 1 : bufSize - 1; + strncpy(buf_, s->c_str(), toCopy); + buf_[toCopy] = '\0'; + } + break; + case x_stdtm: + { + std::tm *t = static_cast(data_); + ub1* pos = reinterpret_cast(buf_); + + *pos++ = static_cast(100 + (1900 + t->tm_year) / 100); + *pos++ = static_cast(100 + t->tm_year % 100); + *pos++ = static_cast(t->tm_mon + 1); + *pos++ = static_cast(t->tm_mday); + *pos++ = static_cast(t->tm_hour + 1); + *pos++ = static_cast(t->tm_min + 1); + *pos = static_cast(t->tm_sec + 1); + } + break; + case x_statement: + { + statement *s = static_cast(data_); + + s->undefine_and_bind(); + } + break; + case x_rowid: + case x_blob: + // nothing to do + break; + } + + // then handle indicators + if (ind != NULL && *ind == i_null) + { + indOCIHolder_ = -1; // null + } + else + { + indOCIHolder_ = 0; // value is OK + } +} + +void oracle_standard_use_type_backend::post_use(bool gotData, indicator *ind) +{ + // It is possible to have the bound element being overwritten + // by the database. + // + // With readOnly_ == true the propagation of modification should *not* + // take place and in addition the attempt of modification should be detected and reported. + + // first, deal with data + if (gotData) + { + switch (type_) + { + case x_char: + if (readOnly_) + { + const char original = *static_cast(data_); + const char bound = buf_[0]; + + if (original != bound) + { + throw soci_error("Attempted modification of const use element"); + } + } + break; + case x_short: + if (readOnly_) + { + const short original = *static_cast(data_); + const short bound = *static_cast(static_cast(buf_)); + + if (original != bound) + { + throw soci_error("Attempted modification of const use element"); + } + } + break; + case x_integer: + if (readOnly_) + { + const int original = *static_cast(data_); + const int bound = *static_cast(static_cast(buf_)); + + if (original != bound) + { + throw soci_error("Attempted modification of const use element"); + } + } + break; + case x_long_long: + if (readOnly_) + { + long long const original = *static_cast(data_); + long long const bound = std::strtoll(buf_, NULL, 10); + + if (original != bound) + { + throw soci_error("Attempted modification of const use element"); + } + } + break; + case x_unsigned_long_long: + if (readOnly_) + { + unsigned long long const original = *static_cast(data_); + unsigned long long const bound = std::strtoull(buf_, NULL, 10); + + if (original != bound) + { + throw soci_error("Attempted modification of const use element"); + } + } + break; + case x_double: + if (readOnly_) + { + const double original = *static_cast(data_); + const double bound = *static_cast(static_cast(buf_)); + + if (original != bound) + { + throw soci_error("Attempted modification of const use element"); + } + } + break; + case x_stdstring: + { + std::string & original = *static_cast(data_); + if (original != buf_) + { + if (readOnly_) + { + throw soci_error("Attempted modification of const use element"); + } + else + { + original = buf_; + } + } + } + break; + case x_stdtm: + { + std::tm & original = *static_cast(data_); + + std::tm bound; + ub1 *pos = reinterpret_cast(buf_); + bound.tm_isdst = -1; + bound.tm_year = (*pos++ - 100) * 100; + bound.tm_year += *pos++ - 2000; + bound.tm_mon = *pos++ - 1; + bound.tm_mday = *pos++; + bound.tm_hour = *pos++ - 1; + bound.tm_min = *pos++ - 1; + bound.tm_sec = *pos++ - 1; + + if (original.tm_year != bound.tm_year || + original.tm_mon != bound.tm_mon || + original.tm_mday != bound.tm_mday || + original.tm_hour != bound.tm_hour || + original.tm_min != bound.tm_min || + original.tm_sec != bound.tm_sec) + { + if (readOnly_) + { + throw soci_error("Attempted modification of const use element"); + } + else + { + original = bound; + + // normalize and compute the remaining fields + std::mktime(&original); + } + } + } + break; + case x_statement: + { + statement *s = static_cast(data_); + s->define_and_bind(); + } + break; + case x_rowid: + case x_blob: + // nothing to do here + break; + } + } + + if (ind != NULL) + { + if (gotData) + { + if (indOCIHolder_ == 0) + { + *ind = i_ok; + } + else if (indOCIHolder_ == -1) + { + *ind = i_null; + } + else + { + *ind = i_truncated; + } + } + } +} + +void oracle_standard_use_type_backend::clean_up() +{ + if (bindp_ != NULL) + { + OCIHandleFree(bindp_, OCI_HTYPE_DEFINE); + bindp_ = NULL; + } + + if (buf_ != NULL) + { + delete [] buf_; + buf_ = NULL; + } +} diff --git a/src/backends/oracle/statement.cpp b/src/backends/oracle/statement.cpp new file mode 100644 index 0000000000..843e889d93 --- /dev/null +++ b/src/backends/oracle/statement.cpp @@ -0,0 +1,343 @@ +// +// Copyright (C) 2004-2007 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define soci_ORACLE_SOURCE + +#include "soci-oracle.h" +#include "error.h" +#include +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(disable:4355) +#endif + +using namespace soci; +using namespace soci::details; +using namespace soci::details::oracle; + +oracle_statement_backend::oracle_statement_backend(oracle_session_backend &session) + : session_(session), stmtp_(NULL), boundByName_(false), boundByPos_(false), + noData_(false) +{ +} + +void oracle_statement_backend::alloc() +{ + sword res = OCIHandleAlloc(session_.envhp_, + reinterpret_cast(&stmtp_), + OCI_HTYPE_STMT, 0, 0); + if (res != OCI_SUCCESS) + { + throw soci_error("Cannot allocate statement handle"); + } +} + +void oracle_statement_backend::clean_up() +{ + // deallocate statement handle + if (stmtp_ != NULL) + { + OCIHandleFree(stmtp_, OCI_HTYPE_STMT); + stmtp_ = NULL; + } + + boundByName_ = false; + boundByPos_ = false; +} + +void oracle_statement_backend::prepare(std::string const &query, + statement_type /* eType */) +{ + sb4 stmtLen = static_cast(query.size()); + sword res = OCIStmtPrepare(stmtp_, + session_.errhp_, + reinterpret_cast(const_cast(query.c_str())), + stmtLen, OCI_V7_SYNTAX, OCI_DEFAULT); + if (res != OCI_SUCCESS) + { + throw_oracle_soci_error(res, session_.errhp_); + } +} + +statement_backend::exec_fetch_result oracle_statement_backend::execute(int number) +{ + sword res = OCIStmtExecute(session_.svchp_, stmtp_, session_.errhp_, + static_cast(number), 0, 0, 0, OCI_DEFAULT); + + if (res == OCI_SUCCESS || res == OCI_SUCCESS_WITH_INFO) + { + noData_ = false; + return ef_success; + } + else if (res == OCI_NO_DATA) + { + noData_ = true; + return ef_no_data; + } + else + { + throw_oracle_soci_error(res, session_.errhp_); + return ef_no_data; // unreachable dummy return to please the compiler + } +} + +statement_backend::exec_fetch_result oracle_statement_backend::fetch(int number) +{ + if (noData_) + { + return ef_no_data; + } + + sword res = OCIStmtFetch(stmtp_, session_.errhp_, + static_cast(number), OCI_FETCH_NEXT, OCI_DEFAULT); + + if (res == OCI_SUCCESS || res == OCI_SUCCESS_WITH_INFO) + { + return ef_success; + } + else if (res == OCI_NO_DATA) + { + noData_ = true; + return ef_no_data; + } + else + { + throw_oracle_soci_error(res, session_.errhp_); + return ef_no_data; // unreachable dummy return to please the compiler + } +} + +long long oracle_statement_backend::get_affected_rows() +{ + ub4 row_count; + sword res = OCIAttrGet(static_cast(stmtp_), + OCI_HTYPE_STMT, &row_count, + 0, OCI_ATTR_ROW_COUNT, session_.errhp_); + + if (res != OCI_SUCCESS) + { + throw_oracle_soci_error(res, session_.errhp_); + } + + return row_count; +} + +int oracle_statement_backend::get_number_of_rows() +{ + int rows; + sword res = OCIAttrGet(static_cast(stmtp_), + OCI_HTYPE_STMT, static_cast(&rows), + 0, OCI_ATTR_ROWS_FETCHED, session_.errhp_); + + if (res != OCI_SUCCESS) + { + throw_oracle_soci_error(res, session_.errhp_); + } + + return rows; +} + +std::string oracle_statement_backend::rewrite_for_procedure_call( + std::string const &query) +{ + std::string newQuery("begin "); + newQuery += query; + newQuery += "; end;"; + return newQuery; +} + +int oracle_statement_backend::prepare_for_describe() +{ + sword res = OCIStmtExecute(session_.svchp_, stmtp_, session_.errhp_, + 1, 0, 0, 0, OCI_DESCRIBE_ONLY); + if (res != OCI_SUCCESS) + { + throw_oracle_soci_error(res, session_.errhp_); + } + + int cols; + res = OCIAttrGet(static_cast(stmtp_), + static_cast(OCI_HTYPE_STMT), static_cast(&cols), + 0, static_cast(OCI_ATTR_PARAM_COUNT), session_.errhp_); + + if (res != OCI_SUCCESS) + { + throw_oracle_soci_error(res, session_.errhp_); + } + + return cols; +} + +void oracle_statement_backend::describe_column(int colNum, data_type &type, + std::string &columnName) +{ + int size; + int precision; + int scale; + + ub2 dbtype; + text* dbname; + ub4 nameLength; + + ub2 dbsize; + sb2 dbprec; + ub1 dbscale; //sb2 in some versions of Oracle? + + // Get the column handle + OCIParam* colhd; + sword res = OCIParamGet(reinterpret_cast(stmtp_), + static_cast(OCI_HTYPE_STMT), + reinterpret_cast(session_.errhp_), + reinterpret_cast(&colhd), + static_cast(colNum)); + if (res != OCI_SUCCESS) + { + throw_oracle_soci_error(res, session_.errhp_); + } + + // Get the column name + res = OCIAttrGet(reinterpret_cast(colhd), + static_cast(OCI_DTYPE_PARAM), + reinterpret_cast(&dbname), + reinterpret_cast(&nameLength), + static_cast(OCI_ATTR_NAME), + reinterpret_cast(session_.errhp_)); + if (res != OCI_SUCCESS) + { + throw_oracle_soci_error(res, session_.errhp_); + } + + // Get the column type + res = OCIAttrGet(reinterpret_cast(colhd), + static_cast(OCI_DTYPE_PARAM), + reinterpret_cast(&dbtype), + 0, + static_cast(OCI_ATTR_DATA_TYPE), + reinterpret_cast(session_.errhp_)); + if (res != OCI_SUCCESS) + { + throw_oracle_soci_error(res, session_.errhp_); + } + + // get the data size + res = OCIAttrGet(reinterpret_cast(colhd), + static_cast(OCI_DTYPE_PARAM), + reinterpret_cast(&dbsize), + 0, + static_cast(OCI_ATTR_DATA_SIZE), + reinterpret_cast(session_.errhp_)); + if (res != OCI_SUCCESS) + { + throw_oracle_soci_error(res, session_.errhp_); + } + + // get the precision + res = OCIAttrGet(reinterpret_cast(colhd), + static_cast(OCI_DTYPE_PARAM), + reinterpret_cast(&dbprec), + 0, + static_cast(OCI_ATTR_PRECISION), + reinterpret_cast(session_.errhp_)); + if (res != OCI_SUCCESS) + { + throw_oracle_soci_error(res, session_.errhp_); + } + + // get the scale + res = OCIAttrGet(reinterpret_cast(colhd), + static_cast(OCI_DTYPE_PARAM), + reinterpret_cast(&dbscale), + 0, + static_cast(OCI_ATTR_SCALE), + reinterpret_cast(session_.errhp_)); + if (res != OCI_SUCCESS) + { + throw_oracle_soci_error(res, session_.errhp_); + } + + columnName.assign(dbname, dbname + nameLength); + size = static_cast(dbsize); + precision = static_cast(dbprec); + scale = static_cast(dbscale); + + switch (dbtype) + { + case SQLT_CHR: + case SQLT_AFC: + type = dt_string; + break; + case SQLT_NUM: + if (scale > 0) + { + if (session_.get_option_decimals_as_strings()) + type = dt_string; + else + type = dt_double; + } + else if (precision <= std::numeric_limits::digits10) + { + type = dt_integer; + } + else + { + type = dt_long_long; + } + break; + case SQLT_DAT: + type = dt_date; + break; + } +} + +std::size_t oracle_statement_backend::column_size(int position) +{ + // Note: we may want to optimize so that the OCI_DESCRIBE_ONLY call + // happens only once per statement. + // Possibly use existing statement::describe() / make column prop + // access lazy at same time + + int colSize(0); + + sword res = OCIStmtExecute(session_.svchp_, stmtp_, + session_.errhp_, 1, 0, 0, 0, OCI_DESCRIBE_ONLY); + if (res != OCI_SUCCESS) + { + throw_oracle_soci_error(res, session_.errhp_); + } + + // Get The Column Handle + OCIParam* colhd; + res = OCIParamGet(reinterpret_cast(stmtp_), + static_cast(OCI_HTYPE_STMT), + reinterpret_cast(session_.errhp_), + reinterpret_cast(&colhd), + static_cast(position)); + if (res != OCI_SUCCESS) + { + throw_oracle_soci_error(res, session_.errhp_); + } + + // Get The Data Size + res = OCIAttrGet(reinterpret_cast(colhd), + static_cast(OCI_DTYPE_PARAM), + reinterpret_cast(&colSize), + 0, + static_cast(OCI_ATTR_DATA_SIZE), + reinterpret_cast(session_.errhp_)); + if (res != OCI_SUCCESS) + { + throw_oracle_soci_error(res, session_.errhp_); + } + + return static_cast(colSize); +} diff --git a/src/backends/oracle/test/CMakeLists.txt b/src/backends/oracle/test/CMakeLists.txt new file mode 100644 index 0000000000..219b8e63a8 --- /dev/null +++ b/src/backends/oracle/test/CMakeLists.txt @@ -0,0 +1,14 @@ +############################################################################### +# +# This file is part of CMake configuration for SOCI library +# +# Copyright (C) 2010 Mateusz Loskot +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) +# +############################################################################### +soci_backend_test( + BACKEND Oracle + SOURCE test-oracle.cpp + CONNSTR "dummy") \ No newline at end of file diff --git a/src/backends/oracle/test/Makefile.basic b/src/backends/oracle/test/Makefile.basic new file mode 100644 index 0000000000..c0caf3b79a --- /dev/null +++ b/src/backends/oracle/test/Makefile.basic @@ -0,0 +1,23 @@ +# The following variable is specific to this backend and its correct +# values might depend on your environment - feel free to set it accordingly. + +ORACLEINCLUDEDIR = -I/usr/lib/oracle/xe/app/oracle/product/10.2.0/server/rdbms/public +ORACLELIBDIR = -L/usr/lib/oracle/xe/app/oracle/product/10.2.0/server/lib +ORACLELIBS = -lclntsh -lnnz10 + +# The rest of the Makefile is indepentent of the target environment. + +COMPILER = g++ +CXXFLAGS = -Wall -pedantic -Wno-long-long +INCLUDEDIRS = -I.. -I../../../core -I../../../core/test ${ORACLEINCLUDEDIR} +LIBDIRS = -L.. -L../../../core ${ORACLELIBDIR} +LIBS = -lsoci_core -lsoci_oracle -ldl -lboost_date_time ${ORACLELIBS} + + +test-oracle : test-oracle.cpp +#../../../core/test/common-tests.h + ${COMPILER} -o $@ $? ${CXXFLAGS} ${INCLUDEDIRS} ${LIBDIRS} ${LIBS} + + +clean : + rm -f *.o test-oracle diff --git a/src/backends/oracle/test/test-oracle.cpp b/src/backends/oracle/test/test-oracle.cpp new file mode 100644 index 0000000000..9aca871ec5 --- /dev/null +++ b/src/backends/oracle/test/test-oracle.cpp @@ -0,0 +1,1241 @@ +// +// // Copyright (C) 2004-2007 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#include "soci.h" +#include "soci-oracle.h" +#include "common-tests.h" +#include +#include +#include +#include +#include + +using namespace soci; +using namespace soci::tests; + +std::string connectString; +backend_factory const &backEnd = *soci::factory_oracle(); + +// Extra tests for date/time +void test1() +{ + session sql(backEnd, connectString); + + { + std::time_t now = std::time(NULL); + std::tm t1, t2; + t2 = *std::localtime(&now); + + sql << "select t from (select :t as t from dual)", + into(t1), use(t2); + + assert(t1.tm_sec == t2.tm_sec); + assert(t1.tm_min == t2.tm_min); + assert(t1.tm_hour == t2.tm_hour); + assert(t1.tm_mday == t2.tm_mday); + assert(t1.tm_mon == t2.tm_mon); + assert(t1.tm_year == t2.tm_year); + assert(t1.tm_wday == t2.tm_wday); + assert(t1.tm_yday == t2.tm_yday); + assert(t1.tm_isdst == t2.tm_isdst); + + // make sure the date is stored properly in Oracle + char buf[25]; + strftime(buf, sizeof(buf), "%m-%d-%Y %H:%M:%S", &t2); + + std::string t_out; + std::string format("MM-DD-YYYY HH24:MI:SS"); + sql << "select to_char(t, :format) from (select :t as t from dual)", + into(t_out), use(format), use(t2); + + assert(t_out == std::string(buf)); + } + + { + // date and time - before year 2000 + std::time_t then = std::time(NULL) - 17*365*24*60*60; + std::tm t1, t2; + t2 = *std::localtime(&then); + + sql << "select t from (select :t as t from dual)", + into(t1), use(t2); + + assert(t1.tm_sec == t2.tm_sec); + assert(t1.tm_min == t2.tm_min); + assert(t1.tm_hour == t2.tm_hour); + assert(t1.tm_mday == t2.tm_mday); + assert(t1.tm_mon == t2.tm_mon); + assert(t1.tm_year == t2.tm_year); + assert(t1.tm_wday == t2.tm_wday); + assert(t1.tm_yday == t2.tm_yday); + assert(t1.tm_isdst == t2.tm_isdst); + + // make sure the date is stored properly in Oracle + char buf[25]; + strftime(buf, sizeof(buf), "%m-%d-%Y %H:%M:%S", &t2); + + std::string t_out; + std::string format("MM-DD-YYYY HH24:MI:SS"); + sql << "select to_char(t, :format) from (select :t as t from dual)", + into(t_out), use(format), use(t2); + + assert(t_out == std::string(buf)); + } + + std::cout << "test 1 passed" << std::endl; +} + +// explicit calls test +void test2() +{ + session sql(backEnd, connectString); + + statement st(sql); + st.alloc(); + int i = 0; + st.exchange(into(i)); + st.prepare("select 7 from dual"); + st.define_and_bind(); + st.execute(1); + assert(i == 7); + + std::cout << "test 2 passed" << std::endl; +} + +// DDL + blob test + +struct blob_table_creator : public table_creator_base +{ + blob_table_creator(session & sql) + : table_creator_base(sql) + { + sql << + "create table soci_test (" + " id number(10) not null," + " img blob" + ")"; + } +}; + +void test3() +{ + session sql(backEnd, connectString); + + blob_table_creator tableCreator(sql); + + char buf[] = "abcdefghijklmnopqrstuvwxyz"; + sql << "insert into soci_test (id, img) values (7, empty_blob())"; + + { + blob b(sql); + + oracle_session_backend *sessionBackEnd + = static_cast(sql.get_backend()); + + oracle_blob_backend *blobBackEnd + = static_cast(b.get_backend()); + + OCILobDisableBuffering(sessionBackEnd->svchp_, + sessionBackEnd->errhp_, blobBackEnd->lobp_); + + sql << "select img from soci_test where id = 7", into(b); + assert(b.get_len() == 0); + + // note: blob offsets start from 1 + b.write(1, buf, sizeof(buf)); + assert(b.get_len() == sizeof(buf)); + b.trim(10); + assert(b.get_len() == 10); + + // append does not work (Oracle bug #886191 ?) + //b.append(buf, sizeof(buf)); + //assert(b.get_len() == sizeof(buf) + 10); + sql.commit(); + } + + { + blob b(sql); + sql << "select img from soci_test where id = 7", into(b); + //assert(b.get_len() == sizeof(buf) + 10); + assert(b.get_len() == 10); + char buf2[100]; + b.read(1, buf2, 10); + assert(strncmp(buf2, "abcdefghij", 10) == 0); + } + + std::cout << "test 3 passed" << std::endl; +} + +// nested statement test +// (the same syntax is used for output cursors in PL/SQL) + +struct basic_table_creator : public table_creator_base +{ + basic_table_creator(session & sql) + : table_creator_base(sql) + { + sql << + "create table soci_test (" + " id number(5) not null," + " name varchar2(100)," + " code number(5)" + ")"; + } +}; + +void test4() +{ + session sql(backEnd, connectString); + basic_table_creator tableCreator(sql); + + int id; + std::string name; + { + statement st1 = (sql.prepare << + "insert into soci_test (id, name) values (:id, :name)", + use(id), use(name)); + + id = 1; name = "John"; st1.execute(1); + id = 2; name = "Anna"; st1.execute(1); + id = 3; name = "Mike"; st1.execute(1); + } + + statement stInner(sql); + statement stOuter = (sql.prepare << + "select cursor(select name from soci_test order by id)" + " from soci_test where id = 1", + into(stInner)); + stInner.exchange(into(name)); + stOuter.execute(); + stOuter.fetch(); + + std::vector names; + while (stInner.fetch()) { names.push_back(name); } + + assert(names.size() == 3); + assert(names[0] == "John"); + assert(names[1] == "Anna"); + assert(names[2] == "Mike"); + + std::cout << "test 4 passed" << std::endl; +} + + +// ROWID test +void test5() +{ + session sql(backEnd, connectString); + basic_table_creator tableCreator(sql); + + sql << "insert into soci_test(id, name) values(7, \'John\')"; + + rowid rid(sql); + sql << "select rowid from soci_test where id = 7", into(rid); + + int id; + std::string name; + sql << "select id, name from soci_test where rowid = :rid", + into(id), into(name), use(rid); + + assert(id == 7); + assert(name == "John"); + + std::cout << "test 5 passed" << std::endl; +} + +// Stored procedures +struct procedure_creator : procedure_creator_base +{ + procedure_creator(session & sql) + : procedure_creator_base(sql) + { + sql << + "create or replace procedure soci_test(output out varchar2," + "input in varchar2) as " + "begin output := input; end;"; + } +}; + +void test6() +{ + { + session sql(backEnd, connectString); + procedure_creator procedure_creator(sql); + + std::string in("my message"); + std::string out; + statement st = (sql.prepare << + "begin soci_test(:output, :input); end;", + use(out, "output"), + use(in, "input")); + st.execute(1); + assert(out == in); + + // explicit procedure syntax + { + std::string in("my message2"); + std::string out; + procedure proc = (sql.prepare << + "soci_test(:output, :input)", + use(out, "output"), use(in, "input")); + proc.execute(1); + assert(out == in); + } + } + + std::cout << "test 6 passed" << std::endl; +} + +// bind into user-defined objects +struct string_holder +{ + string_holder() {} + string_holder(const char* s) : s_(s) {} + string_holder(std::string s) : s_(s) {} + std::string get() const { return s_; } +private: + std::string s_; +}; + +namespace soci +{ + template <> + struct type_conversion + { + typedef std::string base_type; + static void from_base(const std::string &s, indicator /* ind */, + string_holder &sh) + { + sh = string_holder(s); + } + + static void to_base(const string_holder &sh, std::string &s, indicator &ind) + { + s = sh.get(); + ind = i_ok; + } + }; +} + +struct in_out_procedure_creator : public procedure_creator_base +{ + in_out_procedure_creator(session & sql) + : procedure_creator_base(sql) + { + sql << "create or replace procedure soci_test(s in out varchar2)" + " as begin s := s || s; end;"; + } +}; + +struct returns_null_procedure_creator : public procedure_creator_base +{ + returns_null_procedure_creator(session & sql) + : procedure_creator_base(sql) + { + sql << "create or replace procedure soci_test(s in out varchar2)" + " as begin s := NULL; end;"; + } +}; + +void test7() +{ + { + session sql(backEnd, connectString); + { + basic_table_creator tableCreator(sql); + + int id(1); + string_holder in("my string"); + sql << "insert into soci_test(id, name) values(:id, :name)", use(id), use(in); + + string_holder out; + sql << "select name from soci_test", into(out); + assert(out.get() == "my string"); + + row r; + sql << "select * from soci_test", into(r); + string_holder dynamicOut = r.get(1); + assert(dynamicOut.get() == "my string"); + } + } + std::cout << "test 7 passed" << std::endl; +} + +void test7inout() +{ + { + session sql(backEnd, connectString); + + // test procedure with user-defined type as in-out parameter + { + in_out_procedure_creator procedureCreator(sql); + + std::string sh("test"); + procedure proc = (sql.prepare << "soci_test(:s)", use(sh)); + proc.execute(1); + assert(sh == "testtest"); + } + + // test procedure with user-defined type as in-out parameter + { + in_out_procedure_creator procedureCreator(sql); + + string_holder sh("test"); + procedure proc = (sql.prepare << "soci_test(:s)", use(sh)); + proc.execute(1); + assert(sh.get() == "testtest"); + } + } + std::cout << "test 7-inout passed" << std::endl; +} + +void test7outnull() +{ + { + session sql(backEnd, connectString); + + // test procedure which returns null + { + returns_null_procedure_creator procedureCreator(sql); + + string_holder sh; + indicator ind = i_ok; + procedure proc = (sql.prepare << "soci_test(:s)", use(sh, ind)); + proc.execute(1); + assert(ind == i_null); + } + } + std::cout << "test 7-outnull passed" << std::endl; +} + +// test bulk insert features +void test8() +{ + session sql(backEnd, connectString); + + basic_table_creator tableCreator(sql); + + // verify exception is thrown if vectors of unequal size are passed in + { + std::vector ids; + ids.push_back(1); + ids.push_back(2); + std::vector codes; + codes.push_back(1); + std::string error; + + try + { + sql << "insert into soci_test(id,code) values(:id,:code)", + use(ids), use(codes); + } + catch (soci_error const &e) + { + error = e.what(); + } + assert(error.find("Bind variable size mismatch") + != std::string::npos); + + try + { + sql << "select from soci_test", into(ids), into(codes); + } + catch (std::exception const &e) + { + error = e.what(); + } + assert(error.find("Bind variable size mismatch") + != std::string::npos); + } + + // verify partial insert occurs when one of the records is bad + { + std::vector ids; + ids.push_back(100); + ids.push_back(1000000); // too big for column + + std::string error; + try + { + sql << "insert into soci_test (id) values(:id)", use(ids, "id"); + } + catch (soci_error const &e) + { + error = e.what(); + //TODO e could be made to tell which row(s) failed + } + sql.commit(); + assert(error.find("ORA-01438") != std::string::npos); + int count(7); + sql << "select count(*) from soci_test", into(count); + assert(count == 1); + sql << "delete from soci_test"; + } + + // test insert + { + std::vector ids; + for (int i = 0; i != 3; ++i) + { + ids.push_back(i+10); + } + + statement st = (sql.prepare << "insert into soci_test(id) values(:id)", + use(ids)); + st.execute(1); + int count; + sql << "select count(*) from soci_test", into(count); + assert(count == 3); + } + + //verify an exception is thrown if into vector is zero length + { + std::vector ids; + bool caught(false); + try + { + sql << "select id from soci_test", into(ids); + } + catch (soci_error const &) + { + caught = true; + } + assert(caught); + } + + // verify an exception is thrown if use vector is zero length + { + std::vector ids; + bool caught(false); + try + { + sql << "insert into soci_test(id) values(:id)", use(ids); + } + catch (soci_error const &) + { + caught = true; + } + assert(caught); + } + + // test "no data" condition + { + std::vector inds(3); + std::vector ids_out(3); + statement st = (sql.prepare << "select id from soci_test where 1=0", + into(ids_out, inds)); + + // false return value means "no data" + assert(st.execute(1) == false); + + // that's it - nothing else is guaranteed + // and nothing else is to be tested here + } + + // test NULL indicators + { + std::vector ids(3); + sql << "select id from soci_test", into(ids); + + std::vector inds_in; + inds_in.push_back(i_ok); + inds_in.push_back(i_null); + inds_in.push_back(i_ok); + + std::vector new_codes; + new_codes.push_back(10); + new_codes.push_back(11); + new_codes.push_back(10); + + sql << "update soci_test set code = :code where id = :id", + use(new_codes, inds_in), use(ids); + + std::vector inds_out(3); + std::vector codes(3); + + sql << "select code from soci_test", into(codes, inds_out); + assert(codes.size() == 3 && inds_out.size() == 3); + assert(codes[0] == 10 && codes[2] == 10); + assert(inds_out[0] == i_ok && inds_out[1] == i_null + && inds_out[2] == i_ok); + } + + // verify an exception is thrown if null is selected + // and no indicator was provided + { + std::string msg; + std::vector intos(3); + try + { + sql << "select code from soci_test", into(intos); + } + catch (soci_error const &e) + { + msg = e.what(); + } + assert(msg == "Null value fetched and no indicator defined." ); + } + + // test basic select + { + const size_t sz = 3; + std::vector inds(sz); + std::vector ids_out(sz); + statement st = (sql.prepare << "select id from soci_test", + into(ids_out, inds)); + const bool gotData = st.execute(true); + assert(gotData); + assert(ids_out.size() == sz); + assert(ids_out[0] == 10); + assert(ids_out[2] == 12); + assert(inds.size() == 3 && inds[0] == i_ok + && inds[1] == i_ok && inds[2] == i_ok); + } + + // verify execute(0) + { + std::vector ids_out(2); + statement st = (sql.prepare << "select id from soci_test", + into(ids_out)); + + st.execute(); + assert(ids_out.size() == 2); + bool gotData = st.fetch(); + assert(gotData); + assert(ids_out.size() == 2 && ids_out[0] == 10 && ids_out[1] == 11); + gotData = st.fetch(); + assert(gotData); + assert(ids_out.size() == 1 && ids_out[0] == 12); + gotData = st.fetch(); + assert(gotData == false); + } + + // verify resizing happens if vector is larger + // than number of rows returned + { + std::vector ids_out(4); // one too many + statement st2 = (sql.prepare << "select id from soci_test", + into(ids_out)); + bool gotData = st2.execute(true); + assert(gotData); + assert(ids_out.size() == 3); + assert(ids_out[0] == 10); + assert(ids_out[2] == 12); + } + + // verify resizing happens properly during fetch() + { + std::vector more; + more.push_back(13); + more.push_back(14); + sql << "insert into soci_test(id) values(:id)", use(more); + + std::vector ids(2); + statement st3 = (sql.prepare << "select id from soci_test", into(ids)); + bool gotData = st3.execute(true); + assert(gotData); + assert(ids[0] == 10); + assert(ids[1] == 11); + + gotData = st3.fetch(); + assert(gotData); + assert(ids[0] == 12); + assert(ids[1] == 13); + + gotData = st3.fetch(); + assert(gotData); + assert(ids.size() == 1); + assert(ids[0] == 14); + + gotData = st3.fetch(); + assert(gotData == false); + } + + std::cout << "test 8 passed" << std::endl; +} + +// more tests for bulk fetch +void test9() +{ + session sql(backEnd, connectString); + + basic_table_creator tableCreator(sql); + + std::vector in; + for (int i = 1; i <= 10; ++i) + { + in.push_back(i); + } + + sql << "insert into soci_test (id) values(:id)", use(in); + + int count(0); + sql << "select count(*) from soci_test", into(count); + assert(count == 10); + + // verify that the exception is thrown when trying to resize + // the output vector to the size that is bigger than that + // at the time of binding + { + std::vector out(4); + statement st = (sql.prepare << + "select id from soci_test", into(out)); + + st.execute(); + + st.fetch(); + assert(out.size() == 4); + assert(out[0] == 1); + assert(out[1] == 2); + assert(out[2] == 3); + assert(out[3] == 4); + out.resize(5); // this should be detected as error + try + { + st.fetch(); + assert(false); // should never reach here + } + catch (soci_error const &e) + { + assert(std::string(e.what()) == + "Increasing the size of the output vector is not supported."); + } + } + + // on the other hand, downsizing is OK + { + std::vector out(4); + statement st = (sql.prepare << + "select id from soci_test", into(out)); + + st.execute(); + + st.fetch(); + assert(out.size() == 4); + assert(out[0] == 1); + assert(out[1] == 2); + assert(out[2] == 3); + assert(out[3] == 4); + out.resize(3); // ok + st.fetch(); + assert(out.size() == 3); + assert(out[0] == 5); + assert(out[1] == 6); + assert(out[2] == 7); + out.resize(4); // ok, not bigger than initially + st.fetch(); + assert(out.size() == 3); // downsized because of end of data + assert(out[0] == 8); + assert(out[1] == 9); + assert(out[2] == 10); + bool gotData = st.fetch(); + assert(gotData == false); // end of data + } + + std::cout << "test 9 passed" << std::endl; +} + +struct person +{ + int id; + std::string firstName; + string_holder lastName; //test mapping of type_conversion-based types + std::string gender; +}; + +// Object-Relational Mapping +// Note: Use the values class as shown below in type_conversions +// to achieve object relational mapping. The values class should +// not be used directly in any other fashion. +namespace soci +{ + // name-based conversion + template<> struct type_conversion + { + typedef values base_type; + + static void from_base(values const &v, indicator /* ind */, person &p) + { + // ignoring possibility that the whole object might be NULL + + p.id = v.get("ID"); + p.firstName = v.get("FIRST_NAME"); + p.lastName = v.get("LAST_NAME"); + p.gender = v.get("GENDER", "unknown"); + } + + static void to_base(person const & p, values & v, indicator & ind) + { + v.set("ID", p.id); + v.set("FIRST_NAME", p.firstName); + v.set("LAST_NAME", p.lastName); + v.set("GENDER", p.gender, p.gender.empty() ? i_null : i_ok); + ind = i_ok; + } + }; +} + +struct person_table_creator : public table_creator_base +{ + person_table_creator(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(id numeric(5,0) NOT NULL," + << " last_name varchar2(20), first_name varchar2(20), " + " gender varchar2(10))"; + } +}; + +struct times100_procedure_creator : public procedure_creator_base +{ + times100_procedure_creator(session & sql) + : procedure_creator_base(sql) + { + sql << "create or replace procedure soci_test(id in out number)" + " as begin id := id * 100; end;"; + } +}; + +void test10() +{ + session sql(backEnd, connectString); + + { + person_table_creator tableCreator(sql); + + person p; + p.id = 1; + p.lastName = "Smith"; + p.firstName = "Pat"; + sql << "insert into soci_test(id, first_name, last_name, gender) " + << "values(:ID, :FIRST_NAME, :LAST_NAME, :GENDER)", use(p); + + // p should be unchanged + assert(p.id == 1); + assert(p.firstName == "Pat"); + assert(p.lastName.get() == "Smith"); + + person p1; + sql << "select * from soci_test", into(p1); + assert(p1.id == 1); + assert(p1.firstName + p1.lastName.get() == "PatSmith"); + assert(p1.gender == "unknown"); + + p.firstName = "Patricia"; + sql << "update soci_test set first_name = :FIRST_NAME " + "where id = :ID", use(p); + + // p should be unchanged + assert(p.id == 1); + assert(p.firstName == "Patricia"); + assert(p.lastName.get() == "Smith"); + // Note: gender is now "unknown" because of the mapping, not "" + assert(p.gender == "unknown"); + + person p2; + sql << "select * from soci_test", into(p2); + assert(p2.id == 1); + assert(p2.firstName + p2.lastName.get() == "PatriciaSmith"); + + // insert a second row so we can test fetching + person p3; + p3.id = 2; + p3.firstName = "Joe"; + p3.lastName = "Smith"; + sql << "insert into soci_test(id, first_name, last_name, gender) " + << "values(:ID, :FIRST_NAME, :LAST_NAME, :GENDER)", use(p3); + + person p4; + statement st = (sql.prepare << "select * from soci_test order by id", + into(p4)); + + st.execute(); + bool gotData = st.fetch(); + assert(gotData); + assert(p4.id == 1); + assert(p4.firstName == "Patricia"); + + gotData = st.fetch(); + assert(gotData); + assert(p4.id == 2); + assert(p4.firstName == "Joe"); + gotData = st.fetch(); + assert(gotData == false); + } + + // test with stored procedure + { + times100_procedure_creator procedureCreator(sql); + + person p; + p.id = 1; + p.firstName = "Pat"; + p.lastName = "Smith"; + procedure proc = (sql.prepare << "soci_test(:ID)", use(p)); + proc.execute(1); + assert(p.id == 100); + assert(p.firstName == "Pat"); + assert(p.lastName.get() == "Smith"); + } + + // test with stored procedure which returns null + { + returns_null_procedure_creator procedureCreator(sql); + + std::string msg; + person p; + try + { + procedure proc = (sql.prepare << "soci_test(:FIRST_NAME)", + use(p)); + proc.execute(1); + } + catch (soci_error& e) + { + msg = e.what(); + } + assert(msg == "Null value not allowed for this type"); + + procedure proc = (sql.prepare << "soci_test(:GENDER)", + use(p)); + proc.execute(1); + assert(p.gender == "unknown"); + + } + std::cout << "test 10 passed" << std::endl; +} + +// Experimental support for position based O/R Mapping + +// additional type for position-based test +struct person2 +{ + int id; + std::string firstName; + std::string lastName; + std::string gender; +}; + +// additional type for stream-like test +struct person3 : person2 {}; + +namespace soci +{ + // position-based conversion + template<> struct type_conversion + { + typedef values base_type; + + static void from_base(values const &v, indicator /* ind */, person2 &p) + { + p.id = v.get(0); + p.firstName = v.get(1); + p.lastName = v.get(2); + p.gender = v.get(3, "whoknows"); + } + + // What about the "to" part? Does it make any sense to have it? + }; + + // stream-like conversion + template<> struct type_conversion + { + typedef values base_type; + + static void from_base(values const &v, indicator /* ind */, person3 &p) + { + v >> p.id >> p.firstName >> p.lastName >> p.gender; + } + // TODO: The "to" part is certainly needed. + }; +} + +void test11() +{ + session sql(backEnd, connectString); + + person_table_creator tableCreator(sql); + + person p; + p.id = 1; + p.lastName = "Smith"; + p.firstName = "Patricia"; + sql << "insert into soci_test(id, first_name, last_name, gender) " + << "values(:ID, :FIRST_NAME, :LAST_NAME, :GENDER)", use(p); + + // test position-based conversion + person2 p3; + sql << "select id, first_name, last_name, gender from soci_test", into(p3); + assert(p3.id == 1); + assert(p3.firstName + p3.lastName == "PatriciaSmith"); + assert(p3.gender == "whoknows"); + + sql << "update soci_test set gender = 'F' where id = 1"; + + // additional test for stream-like conversion + person3 p4; + sql << "select id, first_name, last_name, gender from soci_test", into(p4); + assert(p4.id == 1); + assert(p4.firstName + p4.lastName == "PatriciaSmith"); + assert(p4.gender == "F"); + + std::cout << "test 11 passed" << std::endl; +} + +// +// Backwards compatibility - support use of large strings with +// columns of type LONG +/// +struct long_table_creator : public table_creator_base +{ + long_table_creator(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(l long)"; + } +}; + +void test12() +{ + session sql(backEnd, connectString); + long_table_creator creator(sql); + + const std::string::size_type max = 32768; + std::string in(max, 'X'); + + sql << "insert into soci_test values(:l)", use(in); + + std::string out; + sql << "select l from soci_test", into(out); + + assert(out.size() == max); + assert(in == out); + + std::cout << "test 12 passed" << std::endl; +} + +// test for modifiable and const use elements +void test13() +{ + session sql(backEnd, connectString); + + int i = 7; + sql << "begin " + "select 2 * :i into :i from dual; " + "end;", use(i); + assert(i == 14); + + const int j = 7; + try + { + sql << "begin " + "select 2 * :i into :i from dual;" + " end;", use(j); + + assert(false); // should never get here + } + catch (soci_error const & e) + { + const std::string msg = e.what(); + assert(msg == "Attempted modification of const use element"); + } + + std::cout << "test 13 passed" << std::endl; +} + +struct longlong_table_creator : table_creator_base +{ + longlong_table_creator(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(val number(20))"; + } +}; + +// long long test +void test14() +{ + { + session sql(backEnd, connectString); + + longlong_table_creator tableCreator(sql); + + long long v1 = 1000000000000LL; + assert(v1 / 1000000 == 1000000); + + sql << "insert into soci_test(val) values(:val)", use(v1); + + long long v2 = 0LL; + sql << "select val from soci_test", into(v2); + + assert(v2 == v1); + } + + // vector + { + session sql(backEnd, connectString); + + longlong_table_creator tableCreator(sql); + + std::vector v1; + v1.push_back(1000000000000LL); + v1.push_back(1000000000001LL); + v1.push_back(1000000000002LL); + v1.push_back(1000000000003LL); + v1.push_back(1000000000004LL); + + sql << "insert into soci_test(val) values(:val)", use(v1); + + std::vector v2(10); + sql << "select val from soci_test order by val desc", into(v2); + + assert(v2.size() == 5); + assert(v2[0] == 1000000000004LL); + assert(v2[1] == 1000000000003LL); + assert(v2[2] == 1000000000002LL); + assert(v2[3] == 1000000000001LL); + assert(v2[4] == 1000000000000LL); + } + + std::cout << "test 14 passed" << std::endl; +} + +// +// Support for soci Common Tests +// + +struct table_creator_one : public table_creator_base +{ + table_creator_one(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(id number(10,0), val number(4,0), c char, " + "str varchar2(20), sh number, ul number, d number, " + "tm date, i1 number, i2 number, i3 number, name varchar2(20))"; + } +}; + +struct table_creator_two : public table_creator_base +{ + table_creator_two(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(num_float number, num_int numeric(4,0)," + " name varchar2(20), sometime date, chr char)"; + } +}; + +struct table_creator_three : public table_creator_base +{ + table_creator_three(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(name varchar2(100) not null, " + "phone varchar2(15))"; + } +}; + +struct table_creator_four : public table_creator_base +{ + table_creator_four(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(val number)"; + } +}; + +class test_context :public test_context_base +{ +public: + test_context(backend_factory const &backEnd, + std::string const &connectString) + : test_context_base(backEnd, connectString) {} + + table_creator_base* table_creator_1(session& s) const + { + return new table_creator_one(s); + } + + table_creator_base* table_creator_2(session& s) const + { + return new table_creator_two(s); + } + + table_creator_base* table_creator_3(session& s) const + { + return new table_creator_three(s); + } + + table_creator_base* table_creator_4(session& s) const + { + return new table_creator_four(s); + } + + std::string to_date_time(std::string const &datdt_string) const + { + return "to_date('" + datdt_string + "', 'YYYY-MM-DD HH24:MI:SS')"; + } +}; + +int main(int argc, char** argv) +{ +#ifdef _MSC_VER + // Redirect errors, unrecoverable problems, and assert() failures to STDERR, + // instead of debug message window. + // This hack is required to run asser()-driven tests by Buildbot. + // NOTE: Comment this 2 lines for debugging with Visual C++ debugger to catch assertions inside. + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); +#endif //_MSC_VER + + if (argc == 2) + { + connectString = argv[1]; + } + else + { + std::cout << "usage: " << argv[0] + << " connectstring\n" + << "example: " << argv[0] + << " \'service=orcl user=scott password=tiger\'\n"; + std::exit(1); + } + + try + { + test_context tc(backEnd, connectString); + common_tests tests(tc); + tests.run(); + + std::cout << "\nsoci Oracle tests:\n\n"; + test1(); + test2(); + test3(); + test4(); + test5(); + test6(); + test7(); + test7inout(); + test7outnull(); + test8(); + test9(); + test10(); + test11(); + test12(); + test13(); + test14(); + + std::cout << "\nOK, all tests passed.\n\n"; + + return EXIT_SUCCESS; + } + catch (std::exception const & e) + { + std::cout << e.what() << '\n'; + } + return EXIT_FAILURE; +} diff --git a/src/backends/oracle/vector-into-type.cpp b/src/backends/oracle/vector-into-type.cpp new file mode 100644 index 0000000000..031d498c03 --- /dev/null +++ b/src/backends/oracle/vector-into-type.cpp @@ -0,0 +1,465 @@ +// +// Copyright (C) 2004-2007 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define soci_ORACLE_SOURCE +#include "soci-oracle.h" +#include "statement.h" +#include "error.h" +#include +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(disable:4355) +#endif + +using namespace soci; +using namespace soci::details; +using namespace soci::details::oracle; + +void oracle_vector_into_type_backend::prepare_indicators(std::size_t size) +{ + if (size == 0) + { + throw soci_error("Vectors of size 0 are not allowed."); + } + + indOCIHolderVec_.resize(size); + indOCIHolders_ = &indOCIHolderVec_[0]; + + sizes_.resize(size); + rCodes_.resize(size); +} + +void oracle_vector_into_type_backend::define_by_pos( + int &position, void *data, exchange_type type) +{ + data_ = data; // for future reference + type_ = type; // for future reference + + ub2 oracleType = 0; // dummy initialization to please the compiler + sb4 size = 0; // also dummy + + switch (type) + { + // simple cases + case x_char: + { + oracleType = SQLT_AFC; + size = sizeof(char); + std::vector *vp = static_cast *>(data); + std::vector &v(*vp); + prepare_indicators(v.size()); + data = &v[0]; + } + break; + case x_short: + { + oracleType = SQLT_INT; + size = sizeof(short); + std::vector *vp = static_cast *>(data); + std::vector &v(*vp); + prepare_indicators(v.size()); + data = &v[0]; + } + break; + case x_integer: + { + oracleType = SQLT_INT; + size = sizeof(int); + std::vector *vp = static_cast *>(data); + std::vector &v(*vp); + prepare_indicators(v.size()); + data = &v[0]; + } + break; + case x_double: + { + oracleType = SQLT_FLT; + size = sizeof(double); + std::vector *vp = static_cast *>(data); + std::vector &v(*vp); + prepare_indicators(v.size()); + data = &v[0]; + } + break; + + // cases that require adjustments and buffer management + + case x_long_long: + { + oracleType = SQLT_STR; + std::vector *v + = static_cast *>(data); + colSize_ = 100; // arbitrary buffer size for each entry + std::size_t const bufSize = colSize_ * v->size(); + buf_ = new char[bufSize]; + + prepare_indicators(v->size()); + + size = static_cast(colSize_); + data = buf_; + } + break; + case x_unsigned_long_long: + { + oracleType = SQLT_STR; + std::vector *v + = static_cast *>(data); + colSize_ = 100; // arbitrary buffer size for each entry + std::size_t const bufSize = colSize_ * v->size(); + buf_ = new char[bufSize]; + + prepare_indicators(v->size()); + + size = static_cast(colSize_); + data = buf_; + } + break; + case x_stdstring: + { + oracleType = SQLT_CHR; + std::vector *v + = static_cast *>(data); + colSize_ = statement_.column_size(position) + 1; + std::size_t bufSize = colSize_ * v->size(); + buf_ = new char[bufSize]; + + prepare_indicators(v->size()); + + size = static_cast(colSize_); + data = buf_; + } + break; + case x_stdtm: + { + oracleType = SQLT_DAT; + std::vector *v + = static_cast *>(data); + + prepare_indicators(v->size()); + + size = 7; // 7 is the size of SQLT_DAT + std::size_t bufSize = size * v->size(); + + buf_ = new char[bufSize]; + data = buf_; + } + break; + + case x_statement: break; // not supported + case x_rowid: break; // not supported + case x_blob: break; // not supported + } + + sword res = OCIDefineByPos(statement_.stmtp_, &defnp_, + statement_.session_.errhp_, + position++, data, size, oracleType, + indOCIHolders_, &sizes_[0], &rCodes_[0], OCI_DEFAULT); + if (res != OCI_SUCCESS) + { + throw_oracle_soci_error(res, statement_.session_.errhp_); + } +} + +void oracle_vector_into_type_backend::pre_fetch() +{ + // nothing to do for the supported types +} + +void oracle_vector_into_type_backend::post_fetch(bool gotData, indicator *ind) +{ + if (gotData) + { + // first, deal with data + + // only std::string, std::tm, long long and Statement need special handling + if (type_ == x_stdstring) + { + std::vector *vp + = static_cast *>(data_); + + std::vector &v(*vp); + + char *pos = buf_; + std::size_t const vsize = v.size(); + for (std::size_t i = 0; i != vsize; ++i) + { + if (indOCIHolderVec_[i] != -1) + { + v[i].assign(pos, sizes_[i]); + } + pos += colSize_; + } + } + else if (type_ == x_long_long) + { + std::vector *vp + = static_cast *>(data_); + + std::vector &v(*vp); + + char *pos = buf_; + std::size_t const vsize = v.size(); + for (std::size_t i = 0; i != vsize; ++i) + { + if (indOCIHolderVec_[i] != -1) + { + v[i] = std::strtoll(pos, NULL, 10); + } + pos += colSize_; + } + } + else if (type_ == x_unsigned_long_long) + { + std::vector *vp + = static_cast *>(data_); + + std::vector &v(*vp); + + char *pos = buf_; + std::size_t const vsize = v.size(); + for (std::size_t i = 0; i != vsize; ++i) + { + if (indOCIHolderVec_[i] != -1) + { + v[i] = std::strtoull(pos, NULL, 10); + } + pos += colSize_; + } + } + else if (type_ == x_stdtm) + { + std::vector *vp + = static_cast *>(data_); + + std::vector &v(*vp); + + ub1 *pos = reinterpret_cast(buf_); + std::size_t const vsize = v.size(); + for (std::size_t i = 0; i != vsize; ++i) + { + if (indOCIHolderVec_[i] == -1) + { + pos += 7; // size of SQLT_DAT + } + else + { + std::tm t; + t.tm_isdst = -1; + + t.tm_year = (*pos++ - 100) * 100; + t.tm_year += *pos++ - 2000; + t.tm_mon = *pos++ - 1; + t.tm_mday = *pos++; + t.tm_hour = *pos++ - 1; + t.tm_min = *pos++ - 1; + t.tm_sec = *pos++ - 1; + + // normalize and compute the remaining fields + std::mktime(&t); + v[i] = t; + } + } + } + else if (type_ == x_statement) + { + statement *st = static_cast(data_); + st->define_and_bind(); + } + + // then - deal with indicators + if (ind != NULL) + { + std::size_t const indSize = statement_.get_number_of_rows(); + for (std::size_t i = 0; i != indSize; ++i) + { + if (indOCIHolderVec_[i] == 0) + { + ind[i] = i_ok; + } + else if (indOCIHolderVec_[i] == -1) + { + ind[i] = i_null; + } + else + { + ind[i] = i_truncated; + } + } + } + else + { + std::size_t const indSize = indOCIHolderVec_.size(); + for (std::size_t i = 0; i != indSize; ++i) + { + if (indOCIHolderVec_[i] == -1) + { + // fetched null and no indicator - programming error! + throw soci_error( + "Null value fetched and no indicator defined."); + } + } + } + } + else // gotData == false + { + // nothing to do here, vectors are truncated anyway + } +} + +void oracle_vector_into_type_backend::resize(std::size_t sz) +{ + switch (type_) + { + // simple cases + case x_char: + { + std::vector *v = static_cast *>(data_); + v->resize(sz); + } + break; + case x_short: + { + std::vector *v = static_cast *>(data_); + v->resize(sz); + } + break; + case x_integer: + { + std::vector *v = static_cast *>(data_); + v->resize(sz); + } + break; + case x_long_long: + { + std::vector *v + = static_cast *>(data_); + v->resize(sz); + } + break; + case x_unsigned_long_long: + { + std::vector *v + = static_cast *>(data_); + v->resize(sz); + } + break; + case x_double: + { + std::vector *v + = static_cast *>(data_); + v->resize(sz); + } + break; + case x_stdstring: + { + std::vector *v + = static_cast *>(data_); + v->resize(sz); + } + break; + case x_stdtm: + { + std::vector *v + = static_cast *>(data_); + v->resize(sz); + } + break; + + case x_statement: break; // not supported + case x_rowid: break; // not supported + case x_blob: break; // not supported + } +} + +std::size_t oracle_vector_into_type_backend::size() +{ + std::size_t sz = 0; // dummy initialization to please the compiler + switch (type_) + { + // simple cases + case x_char: + { + std::vector *v = static_cast *>(data_); + sz = v->size(); + } + break; + case x_short: + { + std::vector *v = static_cast *>(data_); + sz = v->size(); + } + break; + case x_integer: + { + std::vector *v = static_cast *>(data_); + sz = v->size(); + } + break; + case x_long_long: + { + std::vector *v + = static_cast *>(data_); + sz = v->size(); + } + break; + case x_unsigned_long_long: + { + std::vector *v + = static_cast *>(data_); + sz = v->size(); + } + break; + case x_double: + { + std::vector *v + = static_cast *>(data_); + sz = v->size(); + } + break; + case x_stdstring: + { + std::vector *v + = static_cast *>(data_); + sz = v->size(); + } + break; + case x_stdtm: + { + std::vector *v + = static_cast *>(data_); + sz = v->size(); + } + break; + + case x_statement: break; // not supported + case x_rowid: break; // not supported + case x_blob: break; // not supported + } + + return sz; +} + +void oracle_vector_into_type_backend::clean_up() +{ + if (defnp_ != NULL) + { + OCIHandleFree(defnp_, OCI_HTYPE_DEFINE); + defnp_ = NULL; + } + + if (buf_ != NULL) + { + delete [] buf_; + buf_ = NULL; + } +} diff --git a/src/backends/oracle/vector-use-type.cpp b/src/backends/oracle/vector-use-type.cpp new file mode 100644 index 0000000000..24c71f0a77 --- /dev/null +++ b/src/backends/oracle/vector-use-type.cpp @@ -0,0 +1,397 @@ +// +// Copyright (C) 2004-2007 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define soci_ORACLE_SOURCE +#include "soci-oracle.h" +#include "error.h" +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(disable:4355) +#define snprintf _snprintf +#endif + +using namespace soci; +using namespace soci::details; +using namespace soci::details::oracle; + +void oracle_vector_use_type_backend::prepare_indicators(std::size_t size) +{ + if (size == 0) + { + throw soci_error("Vectors of size 0 are not allowed."); + } + + indOCIHolderVec_.resize(size); + indOCIHolders_ = &indOCIHolderVec_[0]; +} + +void oracle_vector_use_type_backend::prepare_for_bind( + void *&data, sb4 &size, ub2 &oracleType) +{ + switch (type_) + { + // simple cases + case x_char: + { + oracleType = SQLT_AFC; + size = sizeof(char); + std::vector *vp = static_cast *>(data); + std::vector &v(*vp); + prepare_indicators(v.size()); + data = &v[0]; + } + break; + case x_short: + { + oracleType = SQLT_INT; + size = sizeof(short); + std::vector *vp = static_cast *>(data); + std::vector &v(*vp); + prepare_indicators(v.size()); + data = &v[0]; + } + break; + case x_integer: + { + oracleType = SQLT_INT; + size = sizeof(int); + std::vector *vp = static_cast *>(data); + std::vector &v(*vp); + prepare_indicators(v.size()); + data = &v[0]; + } + break; + case x_double: + { + oracleType = SQLT_FLT; + size = sizeof(double); + std::vector *vp = static_cast *>(data); + std::vector &v(*vp); + prepare_indicators(v.size()); + data = &v[0]; + } + break; + + // cases that require adjustments and buffer management + + case x_long_long: + { + std::vector *vp + = static_cast *>(data); + std::vector &v(*vp); + + std::size_t const vecSize = v.size(); + std::size_t const entrySize = 100; // arbitrary + std::size_t const bufSize = entrySize * vecSize; + buf_ = new char[bufSize]; + + oracleType = SQLT_STR; + data = buf_; + size = entrySize; + + prepare_indicators(vecSize); + } + break; + case x_unsigned_long_long: + { + std::vector *vp + = static_cast *>(data); + std::vector &v(*vp); + + std::size_t const vecSize = v.size(); + std::size_t const entrySize = 100; // arbitrary + std::size_t const bufSize = entrySize * vecSize; + buf_ = new char[bufSize]; + + oracleType = SQLT_STR; + data = buf_; + size = entrySize; + + prepare_indicators(vecSize); + } + break; + case x_stdstring: + { + std::vector *vp + = static_cast *>(data); + std::vector &v(*vp); + + std::size_t maxSize = 0; + std::size_t const vecSize = v.size(); + prepare_indicators(vecSize); + for (std::size_t i = 0; i != vecSize; ++i) + { + std::size_t sz = v[i].length(); + sizes_.push_back(static_cast(sz)); + maxSize = sz > maxSize ? sz : maxSize; + } + + buf_ = new char[maxSize * vecSize]; + char *pos = buf_; + for (std::size_t i = 0; i != vecSize; ++i) + { + strncpy(pos, v[i].c_str(), v[i].length()); + pos += maxSize; + } + + oracleType = SQLT_CHR; + data = buf_; + size = static_cast(maxSize); + } + break; + case x_stdtm: + { + std::vector *vp + = static_cast *>(data); + + prepare_indicators(vp->size()); + + sb4 const dlen = 7; // size of SQLT_DAT + buf_ = new char[dlen * vp->size()]; + + oracleType = SQLT_DAT; + data = buf_; + size = dlen; + } + break; + + case x_statement: break; // not supported + case x_rowid: break; // not supported + case x_blob: break; // not supported + } +} + +void oracle_vector_use_type_backend::bind_by_pos(int &position, + void *data, exchange_type type) +{ + data_ = data; // for future reference + type_ = type; // for future reference + + ub2 oracleType; + sb4 size; + + prepare_for_bind(data, size, oracleType); + + ub2 *sizesP = 0; // used only for std::string + if (type == x_stdstring) + { + sizesP = &sizes_[0]; + } + + sword res = OCIBindByPos(statement_.stmtp_, &bindp_, + statement_.session_.errhp_, + position++, data, size, oracleType, + indOCIHolders_, sizesP, 0, 0, 0, OCI_DEFAULT); + if (res != OCI_SUCCESS) + { + throw_oracle_soci_error(res, statement_.session_.errhp_); + } +} + +void oracle_vector_use_type_backend::bind_by_name( + std::string const &name, void *data, exchange_type type) +{ + data_ = data; // for future reference + type_ = type; // for future reference + + ub2 oracleType; + sb4 size; + + prepare_for_bind(data, size, oracleType); + + ub2 *sizesP = 0; // used only for std::string + if (type == x_stdstring) + { + sizesP = &sizes_[0]; + } + + sword res = OCIBindByName(statement_.stmtp_, &bindp_, + statement_.session_.errhp_, + reinterpret_cast(const_cast(name.c_str())), + static_cast(name.size()), + data, size, oracleType, + indOCIHolders_, sizesP, 0, 0, 0, OCI_DEFAULT); + if (res != OCI_SUCCESS) + { + throw_oracle_soci_error(res, statement_.session_.errhp_); + } +} + +void oracle_vector_use_type_backend::pre_use(indicator const *ind) +{ + // first deal with data + if (type_ == x_stdstring) + { + // nothing to do - it's already done during bind + // (and it's probably impossible to separate them, because + // changes in the string size could not be handled here) + } + else if (type_ == x_long_long) + { + std::vector *vp + = static_cast *>(data_); + std::vector &v(*vp); + + char *pos = buf_; + std::size_t const entrySize = 100; // arbitrary, but consistent + std::size_t const vecSize = v.size(); + for (std::size_t i = 0; i != vecSize; ++i) + { + snprintf(pos, entrySize, "%" LL_FMT_FLAGS "d", v[i]); + pos += entrySize; + } + } + else if (type_ == x_unsigned_long_long) + { + std::vector *vp + = static_cast *>(data_); + std::vector &v(*vp); + + char *pos = buf_; + std::size_t const entrySize = 100; // arbitrary, but consistent + std::size_t const vecSize = v.size(); + for (std::size_t i = 0; i != vecSize; ++i) + { + snprintf(pos, entrySize, "%" LL_FMT_FLAGS "u", v[i]); + pos += entrySize; + } + } + else if (type_ == x_stdtm) + { + std::vector *vp + = static_cast *>(data_); + std::vector &v(*vp); + + ub1* pos = reinterpret_cast(buf_); + std::size_t const vsize = v.size(); + for (std::size_t i = 0; i != vsize; ++i) + { + *pos++ = static_cast(100 + (1900 + v[i].tm_year) / 100); + *pos++ = static_cast(100 + v[i].tm_year % 100); + *pos++ = static_cast(v[i].tm_mon + 1); + *pos++ = static_cast(v[i].tm_mday); + *pos++ = static_cast(v[i].tm_hour + 1); + *pos++ = static_cast(v[i].tm_min + 1); + *pos++ = static_cast(v[i].tm_sec + 1); + } + } + + // then handle indicators + if (ind != NULL) + { + std::size_t const vsize = size(); + for (std::size_t i = 0; i != vsize; ++i, ++ind) + { + if (*ind == i_null) + { + indOCIHolderVec_[i] = -1; // null + } + else + { + indOCIHolderVec_[i] = 0; // value is OK + } + } + } + else + { + // no indicators - treat all fields as OK + std::size_t const vsize = size(); + for (std::size_t i = 0; i != vsize; ++i, ++ind) + { + indOCIHolderVec_[i] = 0; // value is OK + } + } +} + +std::size_t oracle_vector_use_type_backend::size() +{ + std::size_t sz = 0; // dummy initialization to please the compiler + switch (type_) + { + // simple cases + case x_char: + { + std::vector *vp = static_cast *>(data_); + sz = vp->size(); + } + break; + case x_short: + { + std::vector *vp = static_cast *>(data_); + sz = vp->size(); + } + break; + case x_integer: + { + std::vector *vp = static_cast *>(data_); + sz = vp->size(); + } + break; + case x_long_long: + { + std::vector *vp + = static_cast *>(data_); + sz = vp->size(); + } + break; + case x_unsigned_long_long: + { + std::vector *vp + = static_cast *>(data_); + sz = vp->size(); + } + break; + case x_double: + { + std::vector *vp + = static_cast *>(data_); + sz = vp->size(); + } + break; + case x_stdstring: + { + std::vector *vp + = static_cast *>(data_); + sz = vp->size(); + } + break; + case x_stdtm: + { + std::vector *vp + = static_cast *>(data_); + sz = vp->size(); + } + break; + + case x_statement: break; // not supported + case x_rowid: break; // not supported + case x_blob: break; // not supported + } + + return sz; +} + +void oracle_vector_use_type_backend::clean_up() +{ + if (buf_ != NULL) + { + delete [] buf_; + buf_ = NULL; + } + + if (bindp_ != NULL) + { + OCIHandleFree(bindp_, OCI_HTYPE_DEFINE); + bindp_ = NULL; + } +} diff --git a/src/backends/postgresql/CMakeLists.txt b/src/backends/postgresql/CMakeLists.txt new file mode 100644 index 0000000000..65c96b7405 --- /dev/null +++ b/src/backends/postgresql/CMakeLists.txt @@ -0,0 +1,51 @@ +############################################################################### +# +# This file is part of CMake configuration for SOCI library +# +# Copyright (C) 2010 Mateusz Loskot +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) +# +############################################################################### + +include(CMakeDependentOption) + +option(SOCI_POSTGRESQL_NOPARAMS + "Do not use input parameters. PostgreSQL 7.x portability." + OFF) + +option(SOCI_POSTGRESQL_NOBINDBYNAME + "Disable query rewriting to native form. PostgreSQL 7.0 portability." + OFF) + +cmake_dependent_option(SOCI_POSTGRESQL_NOPREPARE + "Disable prepared statements. Set ON if SOCI_POSTGRESQL_NOBINDBYNAME is ON. PostgreSQL 7.0 portability." ON + SOCI_POSTGRESQL_NOBINDBYNAME OFF) + +if(SOCI_POSTGRESQL_NOPARAMS) + add_definitions(-DSOCI_POSTGRESQL_NOPARAMS=1) +endif() + +if(SOCI_POSTGRESQL_NOBINDBYNAME) +message("X") + add_definitions(-DSOCI_POSTGRESQL_NOBINDBYNAME=1) +endif() + +if(SOCI_POSTGRESQL_NOPREPARE) +message("Y") + add_definitions(-DSOCI_POSTGRESQL_NOPREPARE=1) +endif() + +soci_backend(PostgreSQL + DEPENDS PostgreSQL + HEADERS soci-postgresql.h common.h + DESCRIPTION "SOCI backend for PostgreSQL database engine" + AUTHORS "Maciej Sobczak, Stephen Hutton" + MAINTAINERS "Mateusz Loskot") + +boost_report_value(SOCI_POSTGRESQL_NOPARAMS) +boost_report_value(SOCI_POSTGRESQL_NOBINDBYNAME) +boost_report_value(SOCI_POSTGRESQL_NOPREPARE) + +add_subdirectory(test) diff --git a/src/backends/postgresql/Makefile.basic b/src/backends/postgresql/Makefile.basic new file mode 100644 index 0000000000..01a70fca70 --- /dev/null +++ b/src/backends/postgresql/Makefile.basic @@ -0,0 +1,114 @@ +# The following variable is specific to this backend and its correct +# values might depend on your environment - feel free to set it accordingly. + +PGSQLINCLUDEDIR = -I/usr/include +PGSQLLIBDIR = -L/usr/lib +PGSQLLIBS = -lpq + +# The rest of the Makefile is indepentent of the target environment. + +COMPILER = g++ +CXXFLAGS = -Wall -pedantic -Wno-long-long +SHARED_CXXFLAGS = ${CXXFLAGS} -fPIC +INCLUDEDIRS = -I../../core ${PGSQLINCLUDEDIR} + +SHARED_LIBDIRS = ${PGSQLLIBDIR} +SHARED_LIBS = ${PGSQLLIBS} ../../core/libsoci_core.a + +UNAME = $(shell uname) +ifeq ($(UNAME),Darwin) + SHARED_LINK_FLAGS = -dynamiclib -flat_namespace -undefined suppress +else + SHARED_LINK_FLAGS = -shared +endif + + +OBJECTS = blob.o error.o factory.o row-id.o session.o standard-into-type.o \ + standard-use-type.o statement.o vector-into-type.o vector-use-type.o \ + common.o + +SHARED_OBJECTS = blob-s.o error-s.o factory-s.o row-id-s.o session-s.o \ + standard-into-type-s.o standard-use-type-s.o statement-s.o \ + vector-into-type-s.o vector-use-type-s.o common-s.o + + +libsoci_postgresql.a : ${OBJECTS} + ar rv $@ $? + rm *.o + + +blob.o : blob.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +error.o : error.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +common.o : common.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +factory.o : factory.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +row-id.o : row-id.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +session.o : session.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +standard-into-type.o : standard-into-type.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +standard-use-type.o : standard-use-type.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +statement.o : statement.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +vector-into-type.o : vector-into-type.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +vector-use-type.o : vector-use-type.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + + +shared : ${SHARED_OBJECTS} + ${COMPILER} ${SHARED_LINK_FLAGS} -o libsoci_postgresql.so \ + ${SHARED_OBJECTS} ${SHARED_LIBDIRS} ${SHARED_LIBS} + rm *.o + +blob-s.o : blob.cpp + ${COMPILER} -c -o $@ $? ${SHARED_CXXFLAGS} ${INCLUDEDIRS} + +error-s.o : error.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGS} ${INCLUDEDIRS} + +common-s.o : common.cpp + ${COMPILER} -c -o $@ $? ${SHARED_CXXFLAGS} ${INCLUDEDIRS} + +factory-s.o : factory.cpp + ${COMPILER} -c -o $@ $? ${SHARED_CXXFLAGS} ${INCLUDEDIRS} + +row-id-s.o : row-id.cpp + ${COMPILER} -c -o $@ $? ${SHARED_CXXFLAGS} ${INCLUDEDIRS} + +session-s.o : session.cpp + ${COMPILER} -c -o $@ $? ${SHARED_CXXFLAGS} ${INCLUDEDIRS} + +standard-into-type-s.o : standard-into-type.cpp + ${COMPILER} -c -o $@ $? ${SHARED_CXXFLAGS} ${INCLUDEDIRS} + +standard-use-type-s.o : standard-use-type.cpp + ${COMPILER} -c -o $@ $? ${SHARED_CXXFLAGS} ${INCLUDEDIRS} + +statement-s.o : statement.cpp + ${COMPILER} -c -o $@ $? ${SHARED_CXXFLAGS} ${INCLUDEDIRS} + +vector-into-type-s.o : vector-into-type.cpp + ${COMPILER} -c -o $@ $? ${SHARED_CXXFLAGS} ${INCLUDEDIRS} + +vector-use-type-s.o : vector-use-type.cpp + ${COMPILER} -c -o $@ $? ${SHARED_CXXFLAGS} ${INCLUDEDIRS} + + +clean : + rm -f libsoci_postgresql.a libsoci_postgresql.so diff --git a/src/backends/postgresql/blob.cpp b/src/backends/postgresql/blob.cpp new file mode 100644 index 0000000000..f78205982f --- /dev/null +++ b/src/backends/postgresql/blob.cpp @@ -0,0 +1,116 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_POSTGRESQL_SOURCE +#include "soci-postgresql.h" +#include // libpq +#include +#include +#include +#include +#include + +#ifdef SOCI_POSTGRESQL_NOPARAMS +#ifndef SOCI_POSTGRESQL_NOBINDBYNAME +#define SOCI_POSTGRESQL_NOBINDBYNAME +#endif // SOCI_POSTGRESQL_NOBINDBYNAME +#endif // SOCI_POSTGRESQL_NOPARAMS + +#ifdef _MSC_VER +#pragma warning(disable:4355) +#endif + +using namespace soci; +using namespace soci::details; + + +postgresql_blob_backend::postgresql_blob_backend( + postgresql_session_backend & session) + : session_(session), fd_(-1) +{ + // nothing to do here, the descriptor is open in the postFetch + // method of the Into element +} + +postgresql_blob_backend::~postgresql_blob_backend() +{ + lo_close(session_.conn_, fd_); +} + +std::size_t postgresql_blob_backend::get_len() +{ + int const pos = lo_lseek(session_.conn_, fd_, 0, SEEK_END); + if (pos == -1) + { + throw soci_error("Cannot retrieve the size of BLOB."); + } + + return static_cast(pos); +} + +std::size_t postgresql_blob_backend::read( + std::size_t offset, char * buf, std::size_t toRead) +{ + int const pos = lo_lseek(session_.conn_, fd_, + static_cast(offset), SEEK_SET); + if (pos == -1) + { + throw soci_error("Cannot seek in BLOB."); + } + + int const readn = lo_read(session_.conn_, fd_, buf, toRead); + if (readn < 0) + { + throw soci_error("Cannot read from BLOB."); + } + + return static_cast(readn); +} + +std::size_t postgresql_blob_backend::write( + std::size_t offset, char const * buf, std::size_t toWrite) +{ + int const pos = lo_lseek(session_.conn_, fd_, + static_cast(offset), SEEK_SET); + if (pos == -1) + { + throw soci_error("Cannot seek in BLOB."); + } + + int const writen = lo_write(session_.conn_, fd_, + const_cast(buf), toWrite); + if (writen < 0) + { + throw soci_error("Cannot write to BLOB."); + } + + return static_cast(writen); +} + +std::size_t postgresql_blob_backend::append( + char const * buf, std::size_t toWrite) +{ + int const pos = lo_lseek(session_.conn_, fd_, 0, SEEK_END); + if (pos == -1) + { + throw soci_error("Cannot seek in BLOB."); + } + + int const writen = lo_write(session_.conn_, fd_, + const_cast(buf), toWrite); + if (writen < 0) + { + throw soci_error("Cannot append to BLOB."); + } + + return static_cast(writen); +} + +void postgresql_blob_backend::trim(std::size_t /* newLen */) +{ + throw soci_error("Trimming BLOBs is not supported."); +} diff --git a/src/backends/postgresql/common.cpp b/src/backends/postgresql/common.cpp new file mode 100644 index 0000000000..d06e8c830c --- /dev/null +++ b/src/backends/postgresql/common.cpp @@ -0,0 +1,111 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#include +#include +#include +#include +#include "common.h" + +namespace // anonymous +{ + +// helper function for parsing decimal data (for std::tm) +long parse10(char const * & p1, char * & p2, char const * msg) +{ + long v = std::strtol(p1, &p2, 10); + if (p2 != p1) + { + p1 = p2 + 1; + return v; + } + else + { + throw soci::soci_error(msg); + } +} + +} // namespace anonymous + + +void soci::details::postgresql::parse_std_tm(char const * buf, std::tm & t) +{ + char const * p1 = buf; + char * p2; + char separator; + long a, b, c; + long year = 1900, month = 1, day = 1; + long hour = 0, minute = 0, second = 0; + + char const * errMsg = "Cannot convert data to std::tm."; + + a = parse10(p1, p2, errMsg); + separator = *p2; + b = parse10(p1, p2, errMsg); + c = parse10(p1, p2, errMsg); + + if (*p2 == ' ') + { + // there are more elements to parse + // - assume that what was already parsed is a date part + // and that the remaining elements describe the time of day + year = a; + month = b; + day = c; + hour = parse10(p1, p2, errMsg); + minute = parse10(p1, p2, errMsg); + second = parse10(p1, p2, errMsg); + } + else + { + // only three values have been parsed + if (separator == '-') + { + // assume the date value was read + // (leave the time of day as 00:00:00) + year = a; + month = b; + day = c; + } + else + { + // assume the time of day was read + // (leave the date part as 1900-01-01) + hour = a; + minute = b; + second = c; + } + } + + t.tm_isdst = -1; + t.tm_year = year - 1900; + t.tm_mon = month - 1; + t.tm_mday = day; + t.tm_hour = hour; + t.tm_min = minute; + t.tm_sec = second; + + std::mktime(&t); +} + +double soci::details::postgresql::string_to_double(char const * buf) +{ + double t; + int n; + int const converted = sscanf(buf, "%lf%n", &t, &n); + if (converted == 1 && static_cast(n) == strlen(buf)) + { + // successfully converted to double + // and no other characters were found in the buffer + + return t; + } + else + { + throw soci_error("Cannot convert data."); + } +} diff --git a/src/backends/postgresql/common.h b/src/backends/postgresql/common.h new file mode 100644 index 0000000000..1f4d78db9a --- /dev/null +++ b/src/backends/postgresql/common.h @@ -0,0 +1,135 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_POSTGRESQL_COMMON_H_INCLUDED +#define SOCI_POSTGRESQL_COMMON_H_INCLUDED + +#include "soci-postgresql.h" +#include +#include +#include +#include +#include + +namespace soci +{ + +namespace details +{ + +namespace postgresql +{ + +// helper function for parsing integers +template +T string_to_integer(char const * buf) +{ + long long t(0); + int n(0); + int const converted = std::sscanf(buf, "%" LL_FMT_FLAGS "d%n", &t, &n); + if (converted == 1 && static_cast(n) == std::strlen(buf)) + { + // successfully converted to long long + // and no other characters were found in the buffer + + const T max = (std::numeric_limits::max)(); + const T min = (std::numeric_limits::min)(); + if (t <= static_cast(max) && + t >= static_cast(min)) + { + return static_cast(t); + } + else + { + // value out of target range + throw soci_error("Cannot convert data."); + } + } + else + { + // try additional conversion from boolean + // (PostgreSQL gives 't' or 'f' for boolean results) + + if (buf[0] == 't' && buf[1] == '\0') + { + return static_cast(1); + } + else if (buf[0] == 'f' && buf[1] == '\0') + { + return static_cast(0); + } + else + { + throw soci_error("Cannot convert data."); + } + } +} + +// helper function for parsing unsigned integers +template +T string_to_unsigned_integer(char const * buf) +{ + unsigned long long t(0); + int n(0); + int const converted = std::sscanf(buf, "%" LL_FMT_FLAGS "u%n", &t, &n); + if (converted == 1 && static_cast(n) == std::strlen(buf)) + { + // successfully converted to unsigned long long + // and no other characters were found in the buffer + + const T max = (std::numeric_limits::max)(); + if (t <= static_cast(max)) + { + return static_cast(t); + } + else + { + // value out of target range + throw soci_error("Cannot convert data."); + } + } + else + { + // try additional conversion from boolean + // (PostgreSQL gives 't' or 'f' for boolean results) + + if (buf[0] == 't' && buf[1] == '\0') + { + return static_cast(1); + } + else if (buf[0] == 'f' && buf[1] == '\0') + { + return static_cast(0); + } + else + { + throw soci_error("Cannot convert data."); + } + } +} + +// helper function for parsing doubles +double string_to_double(char const * buf); + +// helper function for parsing datetime values +void parse_std_tm(char const * buf, std::tm & t); + +// helper for vector operations +template +std::size_t get_vector_size(void * p) +{ + std::vector * v = static_cast *>(p); + return v->size(); +} + +} // namespace postgresql + +} // namespace details + +} // namespace soci + +#endif // SOCI_POSTGRESQL_COMMON_H_INCLUDED diff --git a/src/backends/postgresql/error.cpp b/src/backends/postgresql/error.cpp new file mode 100644 index 0000000000..ae674f051a --- /dev/null +++ b/src/backends/postgresql/error.cpp @@ -0,0 +1,73 @@ +// +// Copyright (C) 2011 Gevorg Voskanyan +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_POSTGRESQL_SOURCE +#include "soci-postgresql.h" +#include "error.h" +#include +#include + +using namespace soci; +using namespace soci::details; + +postgresql_soci_error::postgresql_soci_error( + std::string const & msg, char const *sqlst) + : soci_error(msg) +{ + assert(std::strlen(sqlst) == 5); + std::memcpy(sqlstate_, sqlst, 5); +} + +std::string postgresql_soci_error::sqlstate() const +{ + return std::string(sqlstate_, 5); +} + +void +details::postgresql_result::check_for_errors(char const* errMsg) const +{ + static_cast(check_for_data(errMsg)); +} + +bool +details::postgresql_result::check_for_data(char const* errMsg) const +{ + ExecStatusType const status = PQresultStatus(result_); + switch (status) + { + case PGRES_EMPTY_QUERY: + case PGRES_COMMAND_OK: + // No data but don't throw neither. + return false; + + case PGRES_TUPLES_OK: + return true; + + default: + // Some of the other status codes are not really errors but we're + // not prepared to handle them right now and shouldn't ever receive + // them so throw nevertheless + break; + } + + std::string msg(errMsg); + const char* const pqError = PQresultErrorMessage(result_); + if (pqError && *pqError) + { + msg += " "; + msg += pqError; + } + + const char* sqlstate = PQresultErrorField(result_, PG_DIAG_SQLSTATE); + const char* const blank_sql_state = " "; + if (!sqlstate) + { + sqlstate = blank_sql_state; + } + + throw postgresql_soci_error(msg, sqlstate); +} diff --git a/src/backends/postgresql/factory.cpp b/src/backends/postgresql/factory.cpp new file mode 100644 index 0000000000..90550080c3 --- /dev/null +++ b/src/backends/postgresql/factory.cpp @@ -0,0 +1,49 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_POSTGRESQL_SOURCE +#include "soci-postgresql.h" +#include +#include // libpq + +#ifdef SOCI_POSTGRESQL_NOPARAMS +#ifndef SOCI_POSTGRESQL_NOBINDBYNAME +#define SOCI_POSTGRESQL_NOBINDBYNAME +#endif // SOCI_POSTGRESQL_NOBINDBYNAME +#endif // SOCI_POSTGRESQL_NOPARAMS + +#ifdef _MSC_VER +#pragma warning(disable:4355) +#endif + +using namespace soci; +using namespace soci::details; + +// concrete factory for Empty concrete strategies +postgresql_session_backend * postgresql_backend_factory::make_session( + connection_parameters const & parameters) const +{ + return new postgresql_session_backend(parameters); +} + +postgresql_backend_factory const soci::postgresql; + +extern "C" +{ + +// for dynamic backend loading +SOCI_POSTGRESQL_DECL backend_factory const * factory_postgresql() +{ + return &soci::postgresql; +} + +SOCI_POSTGRESQL_DECL void register_factory_postgresql() +{ + soci::dynamic_backends::register_backend("postgresql", soci::postgresql); +} + +} // extern "C" diff --git a/src/backends/postgresql/row-id.cpp b/src/backends/postgresql/row-id.cpp new file mode 100644 index 0000000000..442371ce31 --- /dev/null +++ b/src/backends/postgresql/row-id.cpp @@ -0,0 +1,40 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_POSTGRESQL_SOURCE +#include "soci-postgresql.h" +#include // libpq +#include +#include +#include +#include +#include + +#ifdef SOCI_POSTGRESQL_NOPARAMS +#ifndef SOCI_POSTGRESQL_NOBINDBYNAME +#define SOCI_POSTGRESQL_NOBINDBYNAME +#endif // SOCI_POSTGRESQL_NOBINDBYNAME +#endif // SOCI_POSTGRESQL_NOPARAMS + +#ifdef _MSC_VER +#pragma warning(disable:4355) +#endif + +using namespace soci; +using namespace soci::details; + + +postgresql_rowid_backend::postgresql_rowid_backend( + postgresql_session_backend & /* session */) +{ + // nothing to do here +} + +postgresql_rowid_backend::~postgresql_rowid_backend() +{ + // nothing to do here +} diff --git a/src/backends/postgresql/session.cpp b/src/backends/postgresql/session.cpp new file mode 100644 index 0000000000..74d128cbf7 --- /dev/null +++ b/src/backends/postgresql/session.cpp @@ -0,0 +1,130 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_POSTGRESQL_SOURCE +#include "soci-postgresql.h" +#include "session.h" +#include +#include // libpq +#include +#include +#include +#include +#include + +#ifdef SOCI_POSTGRESQL_NOPARAMS +#ifndef SOCI_POSTGRESQL_NOBINDBYNAME +#define SOCI_POSTGRESQL_NOBINDBYNAME +#endif // SOCI_POSTGRESQL_NOBINDBYNAME +#endif // SOCI_POSTGRESQL_NOPARAMS + +#ifdef _MSC_VER +#pragma warning(disable:4355 4996) +#endif + +using namespace soci; +using namespace soci::details; + +postgresql_session_backend::postgresql_session_backend( + connection_parameters const& parameters) + : statementCount_(0) +{ + PGconn* conn = PQconnectdb(parameters.get_connect_string().c_str()); + if (0 == conn || CONNECTION_OK != PQstatus(conn)) + { + std::string msg = "Cannot establish connection to the database."; + if (0 != conn) + { + msg += '\n'; + msg += PQerrorMessage(conn); + PQfinish(conn); + } + + throw soci_error(msg); + } + + conn_ = conn; +} + +postgresql_session_backend::~postgresql_session_backend() +{ + clean_up(); +} + +namespace // unnamed +{ + +// helper function for hardcoded queries +void hard_exec(PGconn * conn, char const * query, char const * errMsg) +{ + postgresql_result(PQexec(conn, query)).check_for_errors(errMsg); +} + +} // namespace unnamed + +void postgresql_session_backend::begin() +{ + hard_exec(conn_, "BEGIN", "Cannot begin transaction."); +} + +void postgresql_session_backend::commit() +{ + hard_exec(conn_, "COMMIT", "Cannot commit transaction."); +} + +void postgresql_session_backend::rollback() +{ + hard_exec(conn_, "ROLLBACK", "Cannot rollback transaction."); +} + +void postgresql_session_backend::deallocate_prepared_statement( + const std::string & statementName) +{ + const std::string & query = "DEALLOCATE " + statementName; + + hard_exec(conn_, query.c_str(), + "Cannot deallocate prepared statement."); +} + +bool postgresql_session_backend::get_next_sequence_value( + session & s, std::string const & sequence, long & value) +{ + s << "select nextval('" + sequence + "')", into(value); + + return true; +} + +void postgresql_session_backend::clean_up() +{ + if (0 != conn_) + { + PQfinish(conn_); + conn_ = 0; + } +} + +std::string postgresql_session_backend::get_next_statement_name() +{ + char nameBuf[20] = { 0 }; // arbitrary length + sprintf(nameBuf, "st_%d", ++statementCount_); + return nameBuf; +} + +postgresql_statement_backend * postgresql_session_backend::make_statement_backend() +{ + return new postgresql_statement_backend(*this); +} + +postgresql_rowid_backend * postgresql_session_backend::make_rowid_backend() +{ + return new postgresql_rowid_backend(*this); +} + +postgresql_blob_backend * postgresql_session_backend::make_blob_backend() +{ + return new postgresql_blob_backend(*this); +} diff --git a/src/backends/postgresql/soci-postgresql.h b/src/backends/postgresql/soci-postgresql.h new file mode 100644 index 0000000000..6742c6416c --- /dev/null +++ b/src/backends/postgresql/soci-postgresql.h @@ -0,0 +1,357 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Copyright (C) 2011 Gevorg Voskanyan +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_POSTGRESQL_H_INCLUDED +#define SOCI_POSTGRESQL_H_INCLUDED + +#ifdef _WIN32 +# ifdef SOCI_DLL +# ifdef SOCI_POSTGRESQL_SOURCE +# define SOCI_POSTGRESQL_DECL __declspec(dllexport) +# else +# define SOCI_POSTGRESQL_DECL __declspec(dllimport) +# endif // SOCI_POSTGRESQL_SOURCE +# endif // SOCI_DLL +#endif // _WIN32 +// +// If SOCI_POSTGRESQL_DECL isn't defined yet define it now +#ifndef SOCI_POSTGRESQL_DECL +# define SOCI_POSTGRESQL_DECL +#endif + +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(disable:4512 4511) +#endif + +namespace soci +{ + +class postgresql_soci_error : public soci_error +{ +public: + postgresql_soci_error(std::string const & msg, char const * sqlst); + + std::string sqlstate() const; + +private: + char sqlstate_[ 5 ]; // not std::string to keep copy-constructor no-throw +}; + +namespace details +{ + +// A class thinly encapsulating PGresult. Its main purpose is to ensure that +// PQclear() is always called, avoiding result memory leaks. +class postgresql_result +{ +public: + // Creates a wrapper for the given, possibly NULL, result. The wrapper + // object takes ownership of the object and will call PQclear() on it. + explicit postgresql_result(PGresult* result = NULL) + { + init(result); + } + + // Frees any currently stored result pointer and takes ownership of the + // given one. + void reset(PGresult* result = NULL) + { + free(); + init(result); + } + + // Check whether the status is PGRES_COMMAND_OK and throw an exception if + // it is different. Notice that if the query can return any results, + // check_for_data() below should be used instead to verify whether anything + // was returned or not. + // + // The provided error message is used only for the exception being thrown + // and should describe the operation which yielded this result. + void check_for_errors(char const* errMsg) const; + + // Check whether the status indicates successful query completion, either + // with the return results (in which case true is returned) or without them + // (then false is returned). If the status corresponds to an error, throws + // an exception, just as check_for_errors(). + bool check_for_data(char const* errMsg) const; + + // Implicit conversion to const PGresult: this is somewhat dangerous but + // allows us to avoid changing the existing code that uses PGresult and + // avoids the really bad problem with calling PQclear() twice accidentally + // as this would require a conversion to non-const pointer that we do not + // provide. + operator const PGresult*() const { return result_; } + + // Get the associated result (which may be NULL). Unlike the implicit + // conversion above, this one returns a non-const pointer, so you should be + // careful to avoid really modifying it. + PGresult* get_result() const { return result_; } + + // Dtor frees the result. + ~postgresql_result() { free(); } + +private: + void init(PGresult* result) + { + result_ = result; + } + + void free() + { + // Notice that it is safe to call PQclear() with NULL pointer, it + // simply does nothing in this case. + PQclear(result_); + } + + PGresult* result_; + + // This class can't be copied as it owns result_ which can't be duplicated. + postgresql_result(postgresql_result const &); + postgresql_result& operator=(postgresql_result const &); +}; + +} // namespace details + +struct postgresql_statement_backend; +struct postgresql_standard_into_type_backend : details::standard_into_type_backend +{ + postgresql_standard_into_type_backend(postgresql_statement_backend & st) + : statement_(st) {} + + virtual void define_by_pos(int & position, + void * data, details::exchange_type type); + + virtual void pre_fetch(); + virtual void post_fetch(bool gotData, bool calledFromFetch, + indicator * ind); + + virtual void clean_up(); + + postgresql_statement_backend & statement_; + + void * data_; + details::exchange_type type_; + int position_; +}; + +struct postgresql_vector_into_type_backend : details::vector_into_type_backend +{ + postgresql_vector_into_type_backend(postgresql_statement_backend & st) + : statement_(st) {} + + virtual void define_by_pos(int & position, + void * data, details::exchange_type type); + + virtual void pre_fetch(); + virtual void post_fetch(bool gotData, indicator * ind); + + virtual void resize(std::size_t sz); + virtual std::size_t size(); + + virtual void clean_up(); + + postgresql_statement_backend & statement_; + + void * data_; + details::exchange_type type_; + int position_; +}; + +struct postgresql_standard_use_type_backend : details::standard_use_type_backend +{ + postgresql_standard_use_type_backend(postgresql_statement_backend & st) + : statement_(st), position_(0), buf_(NULL) {} + + virtual void bind_by_pos(int & position, + void * data, details::exchange_type type, bool readOnly); + virtual void bind_by_name(std::string const & name, + void * data, details::exchange_type type, bool readOnly); + + virtual void pre_use(indicator const * ind); + virtual void post_use(bool gotData, indicator * ind); + + virtual void clean_up(); + + postgresql_statement_backend & statement_; + + void * data_; + details::exchange_type type_; + int position_; + std::string name_; + char * buf_; +}; + +struct postgresql_vector_use_type_backend : details::vector_use_type_backend +{ + postgresql_vector_use_type_backend(postgresql_statement_backend & st) + : statement_(st), position_(0) {} + + virtual void bind_by_pos(int & position, + void * data, details::exchange_type type); + virtual void bind_by_name(std::string const & name, + void * data, details::exchange_type type); + + virtual void pre_use(indicator const * ind); + + virtual std::size_t size(); + + virtual void clean_up(); + + postgresql_statement_backend & statement_; + + void * data_; + details::exchange_type type_; + int position_; + std::string name_; + std::vector buffers_; +}; + +struct postgresql_session_backend; +struct postgresql_statement_backend : details::statement_backend +{ + postgresql_statement_backend(postgresql_session_backend & session); + ~postgresql_statement_backend(); + + virtual void alloc(); + virtual void clean_up(); + virtual void prepare(std::string const & query, + details::statement_type stType); + + virtual exec_fetch_result execute(int number); + virtual exec_fetch_result fetch(int number); + + virtual long long get_affected_rows(); + virtual int get_number_of_rows(); + + virtual std::string rewrite_for_procedure_call(std::string const & query); + + virtual int prepare_for_describe(); + virtual void describe_column(int colNum, data_type & dtype, + std::string & columnName); + + virtual postgresql_standard_into_type_backend * make_into_type_backend(); + virtual postgresql_standard_use_type_backend * make_use_type_backend(); + virtual postgresql_vector_into_type_backend * make_vector_into_type_backend(); + virtual postgresql_vector_use_type_backend * make_vector_use_type_backend(); + + postgresql_session_backend & session_; + + details::postgresql_result result_; + std::string query_; + details::statement_type stType_; + std::string statementName_; + std::vector names_; // list of names for named binds + + long long rowsAffectedBulk_; // number of rows affected by the last bulk operation + + int numberOfRows_; // number of rows retrieved from the server + int currentRow_; // "current" row number to consume in postFetch + int rowsToConsume_; // number of rows to be consumed in postFetch + + bool justDescribed_; // to optimize row description with immediately + // following actual statement execution + + bool hasIntoElements_; + bool hasVectorIntoElements_; + bool hasUseElements_; + bool hasVectorUseElements_; + + // the following maps are used for finding data buffers according to + // use elements specified by the user + + typedef std::map UseByPosBuffersMap; + UseByPosBuffersMap useByPosBuffers_; + + typedef std::map UseByNameBuffersMap; + UseByNameBuffersMap useByNameBuffers_; +}; + +struct postgresql_rowid_backend : details::rowid_backend +{ + postgresql_rowid_backend(postgresql_session_backend & session); + + ~postgresql_rowid_backend(); + + unsigned long value_; +}; + +struct postgresql_blob_backend : details::blob_backend +{ + postgresql_blob_backend(postgresql_session_backend & session); + + ~postgresql_blob_backend(); + + virtual std::size_t get_len(); + virtual std::size_t read(std::size_t offset, char * buf, + std::size_t toRead); + virtual std::size_t write(std::size_t offset, char const * buf, + std::size_t toWrite); + virtual std::size_t append(char const * buf, std::size_t toWrite); + virtual void trim(std::size_t newLen); + + postgresql_session_backend & session_; + + unsigned long oid_; // oid of the large object + int fd_; // descriptor of the large object +}; + +struct postgresql_session_backend : details::session_backend +{ + postgresql_session_backend(connection_parameters const & parameters); + + ~postgresql_session_backend(); + + virtual void begin(); + virtual void commit(); + virtual void rollback(); + + void deallocate_prepared_statement(const std::string & statementName); + + virtual bool get_next_sequence_value(session & s, + std::string const & sequence, long & value); + + virtual std::string get_backend_name() const { return "postgresql"; } + + void clean_up(); + + virtual postgresql_statement_backend * make_statement_backend(); + virtual postgresql_rowid_backend * make_rowid_backend(); + virtual postgresql_blob_backend * make_blob_backend(); + + std::string get_next_statement_name(); + + int statementCount_; + PGconn * conn_; +}; + + +struct postgresql_backend_factory : backend_factory +{ + postgresql_backend_factory() {} + virtual postgresql_session_backend * make_session( + connection_parameters const & parameters) const; +}; + +extern SOCI_POSTGRESQL_DECL postgresql_backend_factory const postgresql; + +extern "C" +{ + +// for dynamic backend loading +SOCI_POSTGRESQL_DECL backend_factory const * factory_postgresql(); +SOCI_POSTGRESQL_DECL void register_factory_postgresql(); + +} // extern "C" + +} // namespace soci + +#endif // SOCI_POSTGRESQL_H_INCLUDED diff --git a/src/backends/postgresql/standard-into-type.cpp b/src/backends/postgresql/standard-into-type.cpp new file mode 100644 index 0000000000..e6dd581036 --- /dev/null +++ b/src/backends/postgresql/standard-into-type.cpp @@ -0,0 +1,185 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_POSTGRESQL_SOURCE +#include +#include "soci-postgresql.h" +#include "common.h" +#include "rowid.h" +#include "blob.h" +#include // libpq +#include +#include +#include +#include +#include +#include + +#ifdef SOCI_POSTGRESQL_NOPARAMS +#ifndef SOCI_POSTGRESQL_NOBINDBYNAME +#define SOCI_POSTGRESQL_NOBINDBYNAME +#endif // SOCI_POSTGRESQL_NOBINDBYNAME +#endif // SOCI_POSTGRESQL_NOPARAMS + +using namespace soci; +using namespace soci::details; +using namespace soci::details::postgresql; + + +void postgresql_standard_into_type_backend::define_by_pos( + int & position, void * data, exchange_type type) +{ + data_ = data; + type_ = type; + position_ = position++; +} + +void postgresql_standard_into_type_backend::pre_fetch() +{ + // nothing to do here +} + +void postgresql_standard_into_type_backend::post_fetch( + bool gotData, bool calledFromFetch, indicator * ind) +{ + if (calledFromFetch == true && gotData == false) + { + // this is a normal end-of-rowset condition, + // no need to do anything (fetch() will return false) + return; + } + + if (gotData) + { + // postgresql_ positions start at 0 + int const pos = position_ - 1; + + // first, deal with indicators + if (PQgetisnull(statement_.result_, statement_.currentRow_, pos) != 0) + { + if (ind == NULL) + { + throw soci_error( + "Null value fetched and no indicator defined."); + } + + *ind = i_null; + + // no need to convert data if it is null + return; + } + else + { + if (ind != NULL) + { + *ind = i_ok; + } + } + + // raw data, in text format + char const * buf = PQgetvalue(statement_.result_, + statement_.currentRow_, pos); + + switch (type_) + { + case x_char: + { + char * dest = static_cast(data_); + *dest = *buf; + } + break; + case x_stdstring: + { + std::string * dest = static_cast(data_); + dest->assign(buf); + } + break; + case x_short: + { + short * dest = static_cast(data_); + *dest = string_to_integer(buf); + } + break; + case x_integer: + { + int * dest = static_cast(data_); + *dest = string_to_integer(buf); + } + break; + case x_long_long: + { + long long * dest = static_cast(data_); + *dest = string_to_integer(buf); + } + break; + case x_unsigned_long_long: + { + unsigned long long * dest = static_cast(data_); + *dest = string_to_unsigned_integer(buf); + } + break; + case x_double: + { + double * dest = static_cast(data_); + *dest = string_to_double(buf); + } + break; + case x_stdtm: + { + // attempt to parse the string and convert to std::tm + std::tm * dest = static_cast(data_); + parse_std_tm(buf, *dest); + } + break; + case x_rowid: + { + // RowID is internally identical to unsigned long + + rowid * rid = static_cast(data_); + postgresql_rowid_backend * rbe + = static_cast( + rid->get_backend()); + + rbe->value_ = string_to_unsigned_integer(buf); + } + break; + case x_blob: + { + unsigned long oid = + string_to_unsigned_integer(buf); + + int fd = lo_open(statement_.session_.conn_, oid, + INV_READ | INV_WRITE); + if (fd == -1) + { + throw soci_error("Cannot open the blob object."); + } + + blob * b = static_cast(data_); + postgresql_blob_backend * bbe + = static_cast(b->get_backend()); + + if (bbe->fd_ != -1) + { + lo_close(statement_.session_.conn_, bbe->fd_); + } + + bbe->fd_ = fd; + bbe->oid_ = oid; + } + break; + + default: + throw soci_error("Into element used with non-supported type."); + } + } +} + +void postgresql_standard_into_type_backend::clean_up() +{ + // nothing to do here +} diff --git a/src/backends/postgresql/standard-use-type.cpp b/src/backends/postgresql/standard-use-type.cpp new file mode 100644 index 0000000000..8e401513f2 --- /dev/null +++ b/src/backends/postgresql/standard-use-type.cpp @@ -0,0 +1,206 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_POSTGRESQL_SOURCE +#include "soci-postgresql.h" +#include "blob.h" +#include "rowid.h" +#include +#include // libpq +#include +#include +#include +#include +#include +#include + +#ifdef SOCI_POSTGRESQL_NOPARAMS +#ifndef SOCI_POSTGRESQL_NOBINDBYNAME +#define SOCI_POSTGRESQL_NOBINDBYNAME +#endif // SOCI_POSTGRESQL_NOBINDBYNAME +#endif // SOCI_POSTGRESQL_NOPARAMS + +#ifdef _MSC_VER +#pragma warning(disable:4355 4996) +#define snprintf _snprintf +#endif + +using namespace soci; +using namespace soci::details; + +void postgresql_standard_use_type_backend::bind_by_pos( + int & position, void * data, exchange_type type, bool /* readOnly */) +{ + // readOnly is ignored, because PostgreSQL does not support + // any data to be written back to used (bound) objects. + + data_ = data; + type_ = type; + position_ = position++; +} + +void postgresql_standard_use_type_backend::bind_by_name( + std::string const & name, void * data, exchange_type type, bool /* readOnly */) +{ + // readOnly is ignored, because PostgreSQL does not support + // any data to be written back to used (bound) objects. + + data_ = data; + type_ = type; + name_ = name; +} + +void postgresql_standard_use_type_backend::pre_use(indicator const * ind) +{ + if (ind != NULL && *ind == i_null) + { + // leave the working buffer as NULL + } + else + { + // allocate and fill the buffer with text-formatted client data + switch (type_) + { + case x_char: + { + buf_ = new char[2]; + buf_[0] = *static_cast(data_); + buf_[1] = '\0'; + } + break; + case x_stdstring: + { + std::string * s = static_cast(data_); + buf_ = new char[s->size() + 1]; + std::strcpy(buf_, s->c_str()); + } + break; + case x_short: + { + std::size_t const bufSize + = std::numeric_limits::digits10 + 3; + buf_ = new char[bufSize]; + snprintf(buf_, bufSize, "%d", + static_cast(*static_cast(data_))); + } + break; + case x_integer: + { + std::size_t const bufSize + = std::numeric_limits::digits10 + 3; + buf_ = new char[bufSize]; + snprintf(buf_, bufSize, "%d", + *static_cast(data_)); + } + break; + case x_long_long: + { + std::size_t const bufSize + = std::numeric_limits::digits10 + 3; + buf_ = new char[bufSize]; + snprintf(buf_, bufSize, "%" LL_FMT_FLAGS "d", + *static_cast(data_)); + } + break; + case x_unsigned_long_long: + { + std::size_t const bufSize + = std::numeric_limits::digits10 + 2; + buf_ = new char[bufSize]; + snprintf(buf_, bufSize, "%" LL_FMT_FLAGS "u", + *static_cast(data_)); + } + break; + case x_double: + { + // no need to overengineer it (KISS)... + + std::size_t const bufSize = 100; + buf_ = new char[bufSize]; + + snprintf(buf_, bufSize, "%.20g", + *static_cast(data_)); + } + break; + case x_stdtm: + { + std::size_t const bufSize = 20; + buf_ = new char[bufSize]; + + std::tm * t = static_cast(data_); + snprintf(buf_, bufSize, "%d-%02d-%02d %02d:%02d:%02d", + t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, + t->tm_hour, t->tm_min, t->tm_sec); + } + break; + case x_rowid: + { + // RowID is internally identical to unsigned long + + rowid * rid = static_cast(data_); + postgresql_rowid_backend * rbe + = static_cast( + rid->get_backend()); + + std::size_t const bufSize + = std::numeric_limits::digits10 + 2; + buf_ = new char[bufSize]; + + snprintf(buf_, bufSize, "%lu", rbe->value_); + } + break; + case x_blob: + { + blob * b = static_cast(data_); + postgresql_blob_backend * bbe = + static_cast(b->get_backend()); + + std::size_t const bufSize + = std::numeric_limits::digits10 + 2; + buf_ = new char[bufSize]; + snprintf(buf_, bufSize, "%lu", bbe->oid_); + } + break; + + default: + throw soci_error("Use element used with non-supported type."); + } + } + + if (position_ > 0) + { + // binding by position + statement_.useByPosBuffers_[position_] = &buf_; + } + else + { + // binding by name + statement_.useByNameBuffers_[name_] = &buf_; + } +} + +void postgresql_standard_use_type_backend::post_use( + bool /* gotData */, indicator * /* ind */) +{ + // PostgreSQL does not support any data moving back the same channel, + // so there is nothing to do here. + // In particular, there is nothing to protect, because both const and non-const + // objects will never be modified. + + // clean up the working buffer, it might be allocated anew in + // the next run of preUse + clean_up(); +} + +void postgresql_standard_use_type_backend::clean_up() +{ + if (buf_ != NULL) + { + delete [] buf_; + buf_ = NULL; + } +} diff --git a/src/backends/postgresql/statement.cpp b/src/backends/postgresql/statement.cpp new file mode 100644 index 0000000000..b93014ce54 --- /dev/null +++ b/src/backends/postgresql/statement.cpp @@ -0,0 +1,591 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_POSTGRESQL_SOURCE +#include "soci-postgresql.h" +#include +#include // libpq +#include +#include +#include +#include +#include +#include +#include + +#ifdef SOCI_POSTGRESQL_NOPARAMS +#ifndef SOCI_POSTGRESQL_NOBINDBYNAME +#define SOCI_POSTGRESQL_NOBINDBYNAME +#endif // SOCI_POSTGRESQL_NOBINDBYNAME +#endif // SOCI_POSTGRESQL_NOPARAMS + +#ifdef _MSC_VER +#pragma warning(disable:4355) +#endif + +using namespace soci; +using namespace soci::details; + +postgresql_statement_backend::postgresql_statement_backend( + postgresql_session_backend &session) + : session_(session) + , rowsAffectedBulk_(-1LL), justDescribed_(false) + , hasIntoElements_(false), hasVectorIntoElements_(false) + , hasUseElements_(false), hasVectorUseElements_(false) +{ +} + +postgresql_statement_backend::~postgresql_statement_backend() +{ + if (statementName_.empty() == false) + { + try + { + session_.deallocate_prepared_statement(statementName_); + } + catch (...) + { + // Don't allow exceptions to escape from dtor. Suppressing them is + // not ideal, but terminating the program, as would happen if we're + // already unwinding the stack because of a previous exception, + // would be even worse. + } + } +} + +void postgresql_statement_backend::alloc() +{ + // nothing to do here +} + +void postgresql_statement_backend::clean_up() +{ + // 'reset' the value for a + // potential new execution. + rowsAffectedBulk_ = -1; + + // nothing to do here +} + +void postgresql_statement_backend::prepare(std::string const & query, + statement_type stType) +{ +#ifdef SOCI_POSTGRESQL_NOBINDBYNAME + query_ = query; +#else + // rewrite the query by transforming all named parameters into + // the postgresql_ numbers ones (:abc -> $1, etc.) + + enum { normal, in_quotes, in_name } state = normal; + + std::string name; + int position = 1; + + for (std::string::const_iterator it = query.begin(), end = query.end(); + it != end; ++it) + { + switch (state) + { + case normal: + if (*it == '\'') + { + query_ += *it; + state = in_quotes; + } + else if (*it == ':') + { + // Check whether this is a cast operator (e.g. 23::float) + // and treat it as a special case, not as a named binding + const std::string::const_iterator next_it = it + 1; + if ((next_it != end) && (*next_it == ':')) + { + query_ += "::"; + ++it; + } + // Check whether this is an assignment(e.g. x:=y) + // and treat it as a special case, not as a named binding + else if ((next_it != end) && (*next_it == '=')) + { + query_ += ":="; + ++it; + } + else + { + state = in_name; + } + } + else // regular character, stay in the same state + { + query_ += *it; + } + break; + case in_quotes: + if (*it == '\'') + { + query_ += *it; + state = normal; + } + else // regular quoted character + { + query_ += *it; + } + break; + case in_name: + if (std::isalnum(*it) || *it == '_') + { + name += *it; + } + else // end of name + { + names_.push_back(name); + name.clear(); + std::ostringstream ss; + ss << '$' << position++; + query_ += ss.str(); + query_ += *it; + state = normal; + + // Check whether the named parameter is immediatelly + // followed by a cast operator (e.g. :name::float) + // and handle the additional colon immediately to avoid + // its misinterpretation later on. + if (*it == ':') + { + const std::string::const_iterator next_it = it + 1; + if ((next_it != end) && (*next_it == ':')) + { + query_ += ':'; + ++it; + } + } + } + break; + } + } + + if (state == in_name) + { + names_.push_back(name); + std::ostringstream ss; + ss << '$' << position++; + query_ += ss.str(); + } + +#endif // SOCI_POSTGRESQL_NOBINDBYNAME + +#ifndef SOCI_POSTGRESQL_NOPREPARE + + if (stType == st_repeatable_query) + { + assert(statementName_.empty()); + + // Holding the name temporarily in this var because + // if it fails to prepare it we can't DEALLOCATE it. + std::string statementName = session_.get_next_statement_name(); + + postgresql_result result( + PQprepare(session_.conn_, statementName.c_str(), + query_.c_str(), static_cast(names_.size()), NULL)); + result.check_for_errors("Cannot prepare statement."); + + // Now it's safe to save this info. + statementName_ = statementName; + } + + stType_ = stType; + +#endif // SOCI_POSTGRESQL_NOPREPARE +} + +statement_backend::exec_fetch_result +postgresql_statement_backend::execute(int number) +{ + // If the statement was "just described", then we know that + // it was actually executed with all the use elements + // already bound and pre-used. This means that the result of the + // query is already on the client side, so there is no need + // to re-execute it. + + if (justDescribed_ == false) + { + // This object could have been already filled with data before. + clean_up(); + + if (number > 1 && hasIntoElements_) + { + throw soci_error( + "Bulk use with single into elements is not supported."); + } + + // Since the bulk operations are not natively supported by postgresql_, + // we have to explicitly loop to achieve the bulk operations. + // On the other hand, looping is not needed if there are single + // use elements, even if there is a bulk fetch. + // We know that single use and bulk use elements in the same query are + // not supported anyway, so in the effect the 'number' parameter here + // specifies the size of vectors (into/use), but 'numberOfExecutions' + // specifies the number of loops that need to be performed. + + int numberOfExecutions = 1; + if (number > 0) + { + numberOfExecutions = hasUseElements_ ? 1 : number; + } + + if ((useByPosBuffers_.empty() == false) || + (useByNameBuffers_.empty() == false)) + { + if ((useByPosBuffers_.empty() == false) && + (useByNameBuffers_.empty() == false)) + { + throw soci_error( + "Binding for use elements must be either by position " + "or by name."); + } + long long rowsAffectedBulkTemp = 0; + for (int i = 0; i != numberOfExecutions; ++i) + { + std::vector paramValues; + + if (useByPosBuffers_.empty() == false) + { + // use elements bind by position + // the map of use buffers can be traversed + // in its natural order + + for (UseByPosBuffersMap::iterator + it = useByPosBuffers_.begin(), + end = useByPosBuffers_.end(); + it != end; ++it) + { + char ** buffers = it->second; + paramValues.push_back(buffers[i]); + } + } + else + { + // use elements bind by name + + for (std::vector::iterator + it = names_.begin(), end = names_.end(); + it != end; ++it) + { + UseByNameBuffersMap::iterator b + = useByNameBuffers_.find(*it); + if (b == useByNameBuffers_.end()) + { + std::string msg( + "Missing use element for bind by name ("); + msg += *it; + msg += ")."; + throw soci_error(msg); + } + char ** buffers = b->second; + paramValues.push_back(buffers[i]); + } + } + +#ifdef SOCI_POSTGRESQL_NOPARAMS + + throw soci_error("Queries with parameters are not supported."); + +#else + +#ifdef SOCI_POSTGRESQL_NOPREPARE + + result_.reset(PQexecParams(session_.conn_, query_.c_str(), + static_cast(paramValues.size()), + NULL, ¶mValues[0], NULL, NULL, 0)); +#else + if (stType_ == st_repeatable_query) + { + // this query was separately prepared + + result_.reset(PQexecPrepared(session_.conn_, + statementName_.c_str(), + static_cast(paramValues.size()), + ¶mValues[0], NULL, NULL, 0)); + } + else // stType_ == st_one_time_query + { + // this query was not separately prepared and should + // be executed as a one-time query + + result_.reset(PQexecParams(session_.conn_, query_.c_str(), + static_cast(paramValues.size()), + NULL, ¶mValues[0], NULL, NULL, 0)); + } + +#endif // SOCI_POSTGRESQL_NOPREPARE + +#endif // SOCI_POSTGRESQL_NOPARAMS + + if (numberOfExecutions > 1) + { + // there are only bulk use elements (no intos) + + // preserve the number of rows affected so far. + rowsAffectedBulk_ = rowsAffectedBulkTemp; + + result_.check_for_errors("Cannot execute query."); + + rowsAffectedBulkTemp += get_affected_rows(); + } + } + rowsAffectedBulk_ = rowsAffectedBulkTemp; + + if (numberOfExecutions > 1) + { + // it was a bulk operation + result_.reset(); + return ef_no_data; + } + + // otherwise (no bulk), follow the code below + } + else + { + // there are no use elements + // - execute the query without parameter information + +#ifdef SOCI_POSTGRESQL_NOPREPARE + + result_.reset(PQexec(session_.conn_, query_.c_str())); +#else + if (stType_ == st_repeatable_query) + { + // this query was separately prepared + + result_.reset(PQexecPrepared(session_.conn_, + statementName_.c_str(), 0, NULL, NULL, NULL, 0)); + } + else // stType_ == st_one_time_query + { + result_.reset(PQexec(session_.conn_, query_.c_str())); + } + +#endif // SOCI_POSTGRESQL_NOPREPARE + } + } + else + { + // The optimization based on the existing results + // from the row description can be performed only once. + // If the same statement is re-executed, + // it will be *really* re-executed, without reusing existing data. + + justDescribed_ = false; + } + + if (result_.check_for_data("Cannot execute query.")) + { + currentRow_ = 0; + rowsToConsume_ = 0; + + numberOfRows_ = PQntuples(result_); + if (numberOfRows_ == 0) + { + return ef_no_data; + } + else + { + if (number > 0) + { + // prepare for the subsequent data consumption + return fetch(number); + } + else + { + // execute(0) was meant to only perform the query + return ef_success; + } + } + } + else + { + return ef_no_data; + } +} + +statement_backend::exec_fetch_result +postgresql_statement_backend::fetch(int number) +{ + // Note: This function does not actually fetch anything from anywhere + // - the data was already retrieved from the server in the execute() + // function, and the actual consumption of this data will take place + // in the postFetch functions, called for each into element. + // Here, we only prepare for this to happen (to emulate "the Oracle way"). + + // forward the "cursor" from the last fetch + currentRow_ += rowsToConsume_; + + if (currentRow_ >= numberOfRows_) + { + // all rows were already consumed + return ef_no_data; + } + else + { + if (currentRow_ + number > numberOfRows_) + { + rowsToConsume_ = numberOfRows_ - currentRow_; + + // this simulates the behaviour of Oracle + // - when EOF is hit, we return ef_no_data even when there are + // actually some rows fetched + return ef_no_data; + } + else + { + rowsToConsume_ = number; + return ef_success; + } + } +} + +long long postgresql_statement_backend::get_affected_rows() +{ + // PQcmdTuples() doesn't really modify the result but it takes a non-const + // pointer to it, so we can't rely on implicit conversion here. + const char * const resultStr = PQcmdTuples(result_.get_result()); + char * end; + long long result = std::strtoll(resultStr, &end, 0); + if (end != resultStr) + { + return result; + } + else if (rowsAffectedBulk_ >= 0) + { + return rowsAffectedBulk_; + } + else + { + return -1; + } +} + +int postgresql_statement_backend::get_number_of_rows() +{ + return numberOfRows_ - currentRow_; +} + +std::string postgresql_statement_backend::rewrite_for_procedure_call( + std::string const & query) +{ + std::string newQuery("select "); + newQuery += query; + return newQuery; +} + +int postgresql_statement_backend::prepare_for_describe() +{ + execute(1); + justDescribed_ = true; + + int columns = PQnfields(result_); + return columns; +} + +void postgresql_statement_backend::describe_column(int colNum, data_type & type, + std::string & columnName) +{ + // In postgresql_ column numbers start from 0 + int const pos = colNum - 1; + + unsigned long const typeOid = PQftype(result_, pos); + switch (typeOid) + { + // Note: the following list of OIDs was taken from the pg_type table + // we do not claim that this list is exchaustive or even correct. + + // from pg_type: + + case 25: // text + case 1043: // varchar + case 2275: // cstring + case 18: // char + case 1042: // bpchar + case 142: // xml + case 114: // json + case 17: // bytea + type = dt_string; + break; + + case 702: // abstime + case 703: // reltime + case 1082: // date + case 1083: // time + case 1114: // timestamp + case 1184: // timestamptz + case 1266: // timetz + type = dt_date; + break; + + case 700: // float4 + case 701: // float8 + case 1700: // numeric + type = dt_double; + break; + + case 16: // bool + case 21: // int2 + case 23: // int4 + case 26: // oid + type = dt_integer; + break; + + case 20: // int8 + type = dt_long_long; + break; + + default: + { + int form = PQfformat(result_, pos); + int size = PQfsize(result_, pos); + if (form == 0 && size == -1) + { + type = dt_string; + } + else + { + std::stringstream message; + message << "unknown data type with typelem: " << typeOid << " for colNum: " << colNum << " with name: " << PQfname(result_, pos); + throw soci_error(message.str()); + } + } + } + + columnName = PQfname(result_, pos); +} + +postgresql_standard_into_type_backend * +postgresql_statement_backend::make_into_type_backend() +{ + hasIntoElements_ = true; + return new postgresql_standard_into_type_backend(*this); +} + +postgresql_standard_use_type_backend * +postgresql_statement_backend::make_use_type_backend() +{ + hasUseElements_ = true; + return new postgresql_standard_use_type_backend(*this); +} + +postgresql_vector_into_type_backend * +postgresql_statement_backend::make_vector_into_type_backend() +{ + hasVectorIntoElements_ = true; + return new postgresql_vector_into_type_backend(*this); +} + +postgresql_vector_use_type_backend * +postgresql_statement_backend::make_vector_use_type_backend() +{ + hasVectorUseElements_ = true; + return new postgresql_vector_use_type_backend(*this); +} diff --git a/src/backends/postgresql/test/.gitignore b/src/backends/postgresql/test/.gitignore new file mode 100644 index 0000000000..b97b4c72ce --- /dev/null +++ b/src/backends/postgresql/test/.gitignore @@ -0,0 +1 @@ +test_postgresql diff --git a/src/backends/postgresql/test/CMakeLists.txt b/src/backends/postgresql/test/CMakeLists.txt new file mode 100644 index 0000000000..b16c15af36 --- /dev/null +++ b/src/backends/postgresql/test/CMakeLists.txt @@ -0,0 +1,15 @@ +############################################################################### +# +# This file is part of CMake configuration for SOCI library +# +# Copyright (C) 2010 Mateusz Loskot +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) +# +############################################################################### + +soci_backend_test( + BACKEND PostgreSQL + SOURCE test-postgresql.cpp + CONNSTR "dummy") \ No newline at end of file diff --git a/src/backends/postgresql/test/Makefile.basic b/src/backends/postgresql/test/Makefile.basic new file mode 100644 index 0000000000..9c94275e6c --- /dev/null +++ b/src/backends/postgresql/test/Makefile.basic @@ -0,0 +1,22 @@ +# The following variable is specific to this backend and its correct +# values might depend on your environment - feel free to set it accordingly. + +PGSQLINCLUDEDIR = -I/usr/include +PGSQLLIBDIR = -L/usr/lib +PGSQLLIBS = -lpq + +# The rest of the Makefile is indepentent of the target environment. + +COMPILER = g++ +CXXFLAGS = -Wall -pedantic -Wno-long-long +INCLUDEDIRS = -I.. -I../../../core/test -I../../../core ${PGSQLINCLUDEDIR} +LIBDIRS = -L.. -L../../../core ${PGSQLLIBDIR} +LIBS = -lsoci_core -lsoci_postgresql -ldl ${PGSQLLIBS} + + +test-postgresql : test-postgresql.cpp + ${COMPILER} -o $@ $? ${CXXFLAGS} ${INCLUDEDIRS} ${LIBDIRS} ${LIBS} + + +clean : + rm -f test-postgresql diff --git a/src/backends/postgresql/test/test-postgresql.cpp b/src/backends/postgresql/test/test-postgresql.cpp new file mode 100644 index 0000000000..e43042509b --- /dev/null +++ b/src/backends/postgresql/test/test-postgresql.cpp @@ -0,0 +1,847 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#include "soci.h" +#include "soci-postgresql.h" +#include "common-tests.h" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace soci; +using namespace soci::tests; + +std::string connectString; +backend_factory const &backEnd = *soci::factory_postgresql(); + +// Postgres-specific tests + +struct oid_table_creator : public table_creator_base +{ + oid_table_creator(session& sql) + : table_creator_base(sql) + { + sql << "create table soci_test (" + " id integer," + " name varchar(100)" + ") with oids"; + } +}; + +// ROWID test +// Note: in PostgreSQL, there is no ROWID, there is OID. +// It is still provided as a separate type for "portability", +// whatever that means. +void test1() +{ + try + { + session sql(backEnd, connectString); + + oid_table_creator tableCreator(sql); + + sql << "insert into soci_test(id, name) values(7, \'John\')"; + + rowid rid(sql); + sql << "select oid from soci_test where id = 7", into(rid); + + int id; + std::string name; + +#ifndef SOCI_POSTGRESQL_NOPARAMS + + sql << "select id, name from soci_test where oid = :rid", + into(id), into(name), use(rid); + +#else + // Older PostgreSQL does not support use elements. + + postgresql_rowid_backend *rbe + = static_cast(rid.get_backend()); + + unsigned long oid = rbe->value_; + + sql << "select id, name from soci_test where oid = " << oid, + into(id), into(name); + +#endif // SOCI_POSTGRESQL_NOPARAMS + + assert(id == 7); + assert(name == "John"); + + // Must not cause the application to crash. + statement st(sql); + st.prepare(""); // Throws an exception in some versions. + } + catch(...) + { + } + std::cout << "test 1 passed" << std::endl; +} + +// function call test +class function_creator : function_creator_base +{ +public: + + function_creator(session & sql) + : function_creator_base(sql) + { + // before a language can be used it must be defined + // if it has already been defined then an error will occur + try { sql << "create language plpgsql"; } + catch (soci_error const &) {} // ignore if error + +#ifndef SOCI_POSTGRESQL_NOPARAMS + + sql << + "create or replace function soci_test(msg varchar) " + "returns varchar as $$ " + "declare x int := 1;" + "begin " + " return msg; " + "end $$ language plpgsql"; +#else + + sql << + "create or replace function soci_test(varchar) " + "returns varchar as \' " + "declare x int := 1;" + "begin " + " return $1; " + "end \' language plpgsql"; +#endif + } + +protected: + + std::string drop_statement() + { + return "drop function soci_test(varchar)"; + } +}; + +void test2() +{ + { + session sql(backEnd, connectString); + + function_creator functionCreator(sql); + + std::string in("my message"); + std::string out; + +#ifndef SOCI_POSTGRESQL_NOPARAMS + + statement st = (sql.prepare << + "select soci_test(:input)", + into(out), + use(in, "input")); + +#else + // Older PostgreSQL does not support use elements. + + statement st = (sql.prepare << + "select soci_test(\'" << in << "\')", + into(out)); + +#endif // SOCI_POSTGRESQL_NOPARAMS + + st.execute(true); + assert(out == in); + + // explicit procedure syntax + { + std::string in("my message2"); + std::string out; + +#ifndef SOCI_POSTGRESQL_NOPARAMS + + procedure proc = (sql.prepare << + "soci_test(:input)", + into(out), use(in, "input")); + +#else + // Older PostgreSQL does not support use elements. + + procedure proc = (sql.prepare << + "soci_test(\'" << in << "\')", into(out)); + +#endif // SOCI_POSTGRESQL_NOPARAMS + + proc.execute(true); + assert(out == in); + } + } + + std::cout << "test 2 passed" << std::endl; +} + +// BLOB test +struct blob_table_creator : public table_creator_base +{ + blob_table_creator(session & sql) + : table_creator_base(sql) + { + sql << + "create table soci_test (" + " id integer," + " img oid" + ")"; + } +}; + +void test3() +{ + { + session sql(backEnd, connectString); + + blob_table_creator tableCreator(sql); + + char buf[] = "abcdefghijklmnopqrstuvwxyz"; + + sql << "insert into soci_test(id, img) values(7, lo_creat(-1))"; + + // in PostgreSQL, BLOB operations must be within transaction block + transaction tr(sql); + + { + blob b(sql); + + sql << "select img from soci_test where id = 7", into(b); + assert(b.get_len() == 0); + + b.write(0, buf, sizeof(buf)); + assert(b.get_len() == sizeof(buf)); + + b.append(buf, sizeof(buf)); + assert(b.get_len() == 2 * sizeof(buf)); + } + { + blob b(sql); + sql << "select img from soci_test where id = 7", into(b); + assert(b.get_len() == 2 * sizeof(buf)); + char buf2[100]; + b.read(0, buf2, 10); + assert(std::strncmp(buf2, "abcdefghij", 10) == 0); + } + + unsigned long oid; + sql << "select img from soci_test where id = 7", into(oid); + sql << "select lo_unlink(" << oid << ")"; + } + + std::cout << "test 3 passed" << std::endl; +} + +struct longlong_table_creator : table_creator_base +{ + longlong_table_creator(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(val int8)"; + } +}; + +// long long test +void test4() +{ + { + session sql(backEnd, connectString); + + longlong_table_creator tableCreator(sql); + + long long v1 = 1000000000000LL; + assert(v1 / 1000000 == 1000000); + + sql << "insert into soci_test(val) values(:val)", use(v1); + + long long v2 = 0LL; + sql << "select val from soci_test", into(v2); + + assert(v2 == v1); + } + + // vector + { + session sql(backEnd, connectString); + + longlong_table_creator tableCreator(sql); + + std::vector v1; + v1.push_back(1000000000000LL); + v1.push_back(1000000000001LL); + v1.push_back(1000000000002LL); + v1.push_back(1000000000003LL); + v1.push_back(1000000000004LL); + + sql << "insert into soci_test(val) values(:val)", use(v1); + + std::vector v2(10); + sql << "select val from soci_test order by val desc", into(v2); + + assert(v2.size() == 5); + assert(v2[0] == 1000000000004LL); + assert(v2[1] == 1000000000003LL); + assert(v2[2] == 1000000000002LL); + assert(v2[3] == 1000000000001LL); + assert(v2[4] == 1000000000000LL); + } + + std::cout << "test 4 passed" << std::endl; +} + +// unsigned long long test +void test4ul() +{ + { + session sql(backEnd, connectString); + + longlong_table_creator tableCreator(sql); + + unsigned long long v1 = 1000000000000ULL; + assert(v1 / 1000000 == 1000000); + + sql << "insert into soci_test(val) values(:val)", use(v1); + + unsigned long long v2 = 0ULL; + sql << "select val from soci_test", into(v2); + + assert(v2 == v1); + } +} + +struct boolean_table_creator : table_creator_base +{ + boolean_table_creator(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(val boolean)"; + } +}; + +void test5() +{ + { + session sql(backEnd, connectString); + + boolean_table_creator tableCreator(sql); + + int i1 = 0; + + sql << "insert into soci_test(val) values(:val)", use(i1); + + int i2 = 7; + sql << "select val from soci_test", into(i2); + + assert(i2 == i1); + + sql << "update soci_test set val = true"; + sql << "select val from soci_test", into(i2); + assert(i2 == 1); + } + + std::cout << "test 5 passed" << std::endl; +} + +// dynamic backend test +void test6() +{ + try + { + session sql("nosuchbackend://" + connectString); + assert(false); + } + catch (soci_error const & e) + { + assert(e.what() == std::string("Failed to open: libsoci_nosuchbackend.so")); + } + + { + dynamic_backends::register_backend("pgsql", backEnd); + + std::vector backends = dynamic_backends::list_all(); + assert(backends.size() == 1); + assert(backends[0] == "pgsql"); + + { + session sql("pgsql://" + connectString); + } + + dynamic_backends::unload("pgsql"); + + backends = dynamic_backends::list_all(); + assert(backends.empty()); + } + + { + session sql("postgresql://" + connectString); + } + + std::cout << "test 6 passed" << std::endl; +} + +void test7() +{ + { + session sql(backEnd, connectString); + + int i; + sql << "select 123", into(i); + assert(i == 123); + + try + { + sql << "select 'ABC'", into (i); + assert(false); + } + catch (soci_error const & e) + { + assert(e.what() == std::string("Cannot convert data.")); + } + } + + std::cout << "test 7 passed" << std::endl; +} + +void test8() +{ + { + session sql(backEnd, connectString); + + assert(sql.get_backend_name() == "postgresql"); + } + + std::cout << "test 8 passed" << std::endl; +} + +// test for double-colon cast in SQL expressions +void test9() +{ + { + session sql(backEnd, connectString); + + int a = 123; + int b = 0; + sql << "select :a::integer", use(a), into(b); + assert(b == a); + } + + std::cout << "test 9 passed" << std::endl; +} + +// test for date, time and timestamp parsing +void test10() +{ + { + session sql(backEnd, connectString); + + std::string someDate = "2009-06-17 22:51:03.123"; + std::tm t1, t2, t3; + + sql << "select :sd::date, :sd::time, :sd::timestamp", + use(someDate, "sd"), into(t1), into(t2), into(t3); + + // t1 should contain only the date part + assert(t1.tm_year == 2009 - 1900); + assert(t1.tm_mon == 6 - 1); + assert(t1.tm_mday == 17); + assert(t1.tm_hour == 0); + assert(t1.tm_min == 0); + assert(t1.tm_sec == 0); + + // t2 should contain only the time of day part + assert(t2.tm_year == 0); + assert(t2.tm_mon == 0); + assert(t2.tm_mday == 1); + assert(t2.tm_hour == 22); + assert(t2.tm_min == 51); + assert(t2.tm_sec == 3); + + // t3 should contain all information + assert(t3.tm_year == 2009 - 1900); + assert(t3.tm_mon == 6 - 1); + assert(t3.tm_mday == 17); + assert(t3.tm_hour == 22); + assert(t3.tm_min == 51); + assert(t3.tm_sec == 3); + } + + std::cout << "test 10 passed" << std::endl; +} + +// test for number of affected rows + +struct table_creator_for_test11 : table_creator_base +{ + table_creator_for_test11(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(val integer)"; + } +}; + +void test11() +{ + { + session sql(backEnd, connectString); + + table_creator_for_test11 tableCreator(sql); + + for (int i = 0; i != 10; i++) + { + sql << "insert into soci_test(val) values(:val)", use(i); + } + + statement st1 = (sql.prepare << + "update soci_test set val = val + 1"); + st1.execute(false); + + assert(st1.get_affected_rows() == 10); + + statement st2 = (sql.prepare << + "delete from soci_test where val <= 5"); + st2.execute(false); + + assert(st2.get_affected_rows() == 5); + } + + std::cout << "test 11 passed" << std::endl; +} + +// test INSERT INTO ... RETURNING syntax + +struct table_creator_for_test12 : table_creator_base +{ + table_creator_for_test12(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(sid serial, txt text)"; + } +}; + +void test12() +{ + { + session sql(backEnd, connectString); + + table_creator_for_test12 tableCreator(sql); + + std::vector ids(10); + for (std::size_t i = 0; i != ids.size(); i++) + { + long sid(0); + std::string txt("abc"); + sql << "insert into soci_test(txt) values(:txt) returning sid", use(txt, "txt"), into(sid); + ids[i] = sid; + } + + std::vector ids2(ids.size()); + sql << "select sid from soci_test order by sid", into(ids2); + assert(std::equal(ids.begin(), ids.end(), ids2.begin())); + } + + std::cout << "test 12 passed" << std::endl; +} + +struct bytea_table_creator : public table_creator_base +{ + bytea_table_creator(session& sql) + : table_creator_base(sql) + { + sql << "drop table if exists soci_test;"; + sql << "create table soci_test ( val bytea null )"; + } +}; + +void test_bytea() +{ + { + session sql(backEnd, connectString); + bytea_table_creator tableCreator(sql); + + int v = 0x0A0B0C0D; + unsigned char* b = reinterpret_cast(&v); + std::string data; + std::copy(b, b + sizeof(v), std::back_inserter(data)); + { + + sql << "insert into soci_test(val) values(:val)", use(data); + + // 1) into string, no Oid mapping + std::string bin1; + sql << "select val from soci_test", into(bin1); + assert(bin1 == "\\x0d0c0b0a"); + + // 2) Oid-to-dt_string mapped + row r; + sql << "select * from soci_test", into(r); + + assert(r.size() == 1); + column_properties const& props = r.get_properties(0); + assert(props.get_data_type() == soci::dt_string); + std::string bin2 = r.get(0); + assert(bin2 == "\\x0d0c0b0a"); + } + } + std::cout << "test bytea passed" << std::endl; +} + +// json +struct table_creator_json : public table_creator_base +{ + table_creator_json(session& sql) + : table_creator_base(sql) + { + sql << "drop table if exists soci_json_test;"; + sql << "create table soci_json_test(data json)"; + } +}; + +// Return 9,2 for 9.2.3 +typedef std::pair server_version; + +server_version get_postgresql_version(session& sql) +{ + std::string version; + std::pair result; + sql << "select version()",into(version); + if (sscanf(version.c_str(),"PostgreSQL %i.%i", &result.first, &result.second) < 2) + { + throw std::runtime_error("Failed to retrieve PostgreSQL version number"); + } + return result; +} + +// Test JSON. Only valid for PostgreSQL Server 9.2++ +void test_json() +{ + session sql(backEnd, connectString); + server_version version = get_postgresql_version(sql); + if ( version >= server_version(9,2)) + { + bool exception = false; + std::string result; + std::string valid_input = "{\"tool\":\"soci\",\"result\":42}"; + std::string invalid_input = "{\"tool\":\"other\",\"result\":invalid}"; + + table_creator_json tableCreator(sql); + + sql << "insert into soci_json_test (data) values(:data)",use(valid_input); + sql << "select data from soci_json_test",into(result); + assert(result == valid_input); + + try + { + sql << "insert into soci_json_test (data) values(:data)",use(invalid_input); + } + catch(soci_error& e) + { + (void)e; + exception = true; + } + assert(exception); + std::cout << "test json passed" << std::endl; + } + else + { + std::cout << "test json skipped (PostgreSQL >= 9.2 required, found " << version.first << "." << version.second << ")" << std::endl; + } +} + +struct table_creator_text : public table_creator_base +{ + table_creator_text(session& sql) : table_creator_base(sql) + { + sql << "drop table if exists soci_test;"; + sql << "create table soci_test(name varchar(20))"; + } +}; + +// Test deallocate_prepared_statement called for non-existing statement +// which creation failed due to invalid SQL syntax. +// https://github.com/SOCI/soci/issues/116 +void test_statement_prepare_failure() +{ + { + session sql(backEnd, connectString); + table_creator_text tableCreator(sql); + + try + { + // types mismatch should lead to PQprepare failure + statement get_trades = + (sql.prepare + << "select * from soci_test where name=9999"); + assert(false); + } + catch(soci_error const& e) + { + std::string const msg(e.what()); + // poor-man heuristics + assert(msg.find("prepared statement") == std::string::npos); + assert(msg.find("operator does not exist") != std::string::npos); + } + } + std::cout << "test_statement_prepare_failure passed" << std::endl; +} + +// Test the support of PostgreSQL-style casts with ORM +void test_orm_cast() +{ + session sql(backEnd, connectString); + values v; + v.set("a", 1); + sql << "select :a::int", use(v); // Must not throw an exception! +} + +// +// Support for soci Common Tests +// + +// DDL Creation objects for common tests +struct table_creator_one : public table_creator_base +{ + table_creator_one(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(id integer, val integer, c char, " + "str varchar(20), sh int2, ul numeric(20), d float8, " + "tm timestamp, i1 integer, i2 integer, i3 integer, " + "name varchar(20))"; + } +}; + +struct table_creator_two : public table_creator_base +{ + table_creator_two(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(num_float float8, num_int integer," + " name varchar(20), sometime timestamp, chr char)"; + } +}; + +struct table_creator_three : public table_creator_base +{ + table_creator_three(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(name varchar(100) not null, " + "phone varchar(15))"; + } +}; + +struct table_creator_for_get_affected_rows : table_creator_base +{ + table_creator_for_get_affected_rows(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(val integer)"; + } +}; + +// Common tests context +class test_context : public test_context_base +{ +public: + test_context(backend_factory const &backEnd, std::string const &connectString) + : test_context_base(backEnd, connectString) + {} + + table_creator_base* table_creator_1(session& s) const + { + return new table_creator_one(s); + } + + table_creator_base* table_creator_2(session& s) const + { + return new table_creator_two(s); + } + + table_creator_base* table_creator_3(session& s) const + { + return new table_creator_three(s); + } + + table_creator_base* table_creator_4(session& s) const + { + return new table_creator_for_get_affected_rows(s); + } + + std::string to_date_time(std::string const &datdt_string) const + { + return "timestamptz(\'" + datdt_string + "\')"; + } +}; + +int main(int argc, char** argv) +{ + +#ifdef _MSC_VER + // Redirect errors, unrecoverable problems, and assert() failures to STDERR, + // instead of debug message window. + // This hack is required to run asser()-driven tests by Buildbot. + // NOTE: Comment this 2 lines for debugging with Visual C++ debugger to catch assertions inside. + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); +#endif //_MSC_VER + + if (argc == 2) + { + connectString = argv[1]; + } + else + { + std::cout << "usage: " << argv[0] + << " connectstring\n" + << "example: " << argv[0] + << " \'connect_string_for_PostgreSQL\'\n"; + return EXIT_FAILURE; + } + + try + { + test_context tc(backEnd, connectString); + common_tests tests(tc); + tests.run(); + + std::cout << "\nSOCI PostgreSQL Tests:\n\n"; + test1(); + test2(); + test3(); + test4(); + test4ul(); + test5(); + //test6(); + std::cout << "test 6 skipped (dynamic backend)\n"; + test7(); + test8(); + test9(); + test10(); + test11(); + test12(); + test_bytea(); + test_json(); + test_statement_prepare_failure(); + test_orm_cast(); + + std::cout << "\nOK, all tests passed.\n\n"; + + return EXIT_SUCCESS; + } + catch (std::exception const & e) + { + std::cout << e.what() << '\n'; + } + return EXIT_FAILURE; +} diff --git a/src/backends/postgresql/vector-into-type.cpp b/src/backends/postgresql/vector-into-type.cpp new file mode 100644 index 0000000000..a875d30125 --- /dev/null +++ b/src/backends/postgresql/vector-into-type.cpp @@ -0,0 +1,247 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_POSTGRESQL_SOURCE +#include +#include "soci-postgresql.h" +#include "common.h" +#include // libpq +#include +#include +#include +#include +#include +#include + +#ifdef SOCI_POSTGRESQL_NOPARAMS +#ifndef SOCI_POSTGRESQL_NOBINDBYNAME +#define SOCI_POSTGRESQL_NOBINDBYNAME +#endif // SOCI_POSTGRESQL_NOBINDBYNAME +#endif // SOCI_POSTGRESQL_NOPARAMS + +using namespace soci; +using namespace soci::details; +using namespace soci::details::postgresql; + + +void postgresql_vector_into_type_backend::define_by_pos( + int & position, void * data, exchange_type type) +{ + data_ = data; + type_ = type; + position_ = position++; +} + +void postgresql_vector_into_type_backend::pre_fetch() +{ + // nothing to do here +} + +namespace // anonymous +{ + +template +void set_invector_(void * p, int indx, T const & val) +{ + std::vector * dest = + static_cast *>(p); + + std::vector & v = *dest; + v[indx] = val; +} + +} // namespace anonymous + +void postgresql_vector_into_type_backend::post_fetch(bool gotData, indicator * ind) +{ + if (gotData) + { + // Here, rowsToConsume_ in the Statement object designates + // the number of rows that need to be put in the user's buffers. + + // postgresql_ column positions start at 0 + int const pos = position_ - 1; + + int const endRow = statement_.currentRow_ + statement_.rowsToConsume_; + + for (int curRow = statement_.currentRow_, i = 0; + curRow != endRow; ++curRow, ++i) + { + // first, deal with indicators + if (PQgetisnull(statement_.result_, curRow, pos) != 0) + { + if (ind == NULL) + { + throw soci_error( + "Null value fetched and no indicator defined."); + } + + ind[i] = i_null; + + // no need to convert data if it is null, go to next row + continue; + } + else + { + if (ind != NULL) + { + ind[i] = i_ok; + } + } + + // buffer with data retrieved from server, in text format + char * buf = PQgetvalue(statement_.result_, curRow, pos); + + switch (type_) + { + case x_char: + set_invector_(data_, i, *buf); + break; + case x_stdstring: + set_invector_(data_, i, buf); + break; + case x_short: + { + short const val = string_to_integer(buf); + set_invector_(data_, i, val); + } + break; + case x_integer: + { + int const val = string_to_integer(buf); + set_invector_(data_, i, val); + } + break; + case x_long_long: + { + long long const val = string_to_integer(buf); + set_invector_(data_, i, val); + } + break; + case x_unsigned_long_long: + { + unsigned long long const val = + string_to_unsigned_integer(buf); + set_invector_(data_, i, val); + } + break; + case x_double: + { + double const val = string_to_double(buf); + set_invector_(data_, i, val); + } + break; + case x_stdtm: + { + // attempt to parse the string and convert to std::tm + std::tm t; + parse_std_tm(buf, t); + + set_invector_(data_, i, t); + } + break; + + default: + throw soci_error("Into element used with non-supported type."); + } + } + } + else // no data retrieved + { + // nothing to do, into vectors are already truncated + } +} + +namespace // anonymous +{ + +template +void resizevector_(void * p, std::size_t sz) +{ + std::vector * v = static_cast *>(p); + v->resize(sz); +} + +} // namespace anonymous + +void postgresql_vector_into_type_backend::resize(std::size_t sz) +{ + assert(sz < 10u*std::numeric_limits::max()); // Not a strong constraint, for debugging only. Notice my fix is even worse + + switch (type_) + { + // simple cases + case x_char: + resizevector_(data_, sz); + break; + case x_short: + resizevector_(data_, sz); + break; + case x_integer: + resizevector_(data_, sz); + break; + case x_long_long: + resizevector_(data_, sz); + break; + case x_unsigned_long_long: + resizevector_(data_, sz); + break; + case x_double: + resizevector_(data_, sz); + break; + case x_stdstring: + resizevector_(data_, sz); + break; + case x_stdtm: + resizevector_(data_, sz); + break; + default: + throw soci_error("Into vector element used with non-supported type."); + } +} + +std::size_t postgresql_vector_into_type_backend::size() +{ + std::size_t sz = 0; // dummy initialization to please the compiler + switch (type_) + { + // simple cases + case x_char: + sz = get_vector_size(data_); + break; + case x_short: + sz = get_vector_size(data_); + break; + case x_integer: + sz = get_vector_size(data_); + break; + case x_long_long: + sz = get_vector_size(data_); + break; + case x_unsigned_long_long: + sz = get_vector_size(data_); + break; + case x_double: + sz = get_vector_size(data_); + break; + case x_stdstring: + sz = get_vector_size(data_); + break; + case x_stdtm: + sz = get_vector_size(data_); + break; + default: + throw soci_error("Into vector element used with non-supported type."); + } + + return sz; +} + +void postgresql_vector_into_type_backend::clean_up() +{ + // nothing to do here +} diff --git a/src/backends/postgresql/vector-use-type.cpp b/src/backends/postgresql/vector-use-type.cpp new file mode 100644 index 0000000000..00d47216a0 --- /dev/null +++ b/src/backends/postgresql/vector-use-type.cpp @@ -0,0 +1,232 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_POSTGRESQL_SOURCE +#include +#include "soci-postgresql.h" +#include "common.h" +#include // libpq +#include +#include +#include +#include +#include +#include + +#ifdef SOCI_POSTGRESQL_NOPARAMS +#ifndef SOCI_POSTGRESQL_NOBINDBYNAME +#define SOCI_POSTGRESQL_NOBINDBYNAME +#endif // SOCI_POSTGRESQL_NOBINDBYNAME +#endif // SOCI_POSTGRESQL_NOPARAMS + +#ifdef _MSC_VER +#pragma warning(disable:4355 4996) +#define snprintf _snprintf +#endif + +using namespace soci; +using namespace soci::details; +using namespace soci::details::postgresql; + + +void postgresql_vector_use_type_backend::bind_by_pos(int & position, + void * data, exchange_type type) +{ + data_ = data; + type_ = type; + position_ = position++; +} + +void postgresql_vector_use_type_backend::bind_by_name( + std::string const & name, void * data, exchange_type type) +{ + data_ = data; + type_ = type; + name_ = name; +} + +void postgresql_vector_use_type_backend::pre_use(indicator const * ind) +{ + std::size_t const vsize = size(); + for (size_t i = 0; i != vsize; ++i) + { + char * buf; + + // the data in vector can be either i_ok or i_null + if (ind != NULL && ind[i] == i_null) + { + buf = NULL; + } + else + { + // allocate and fill the buffer with text-formatted client data + switch (type_) + { + case x_char: + { + std::vector * pv + = static_cast *>(data_); + std::vector & v = *pv; + + buf = new char[2]; + buf[0] = v[i]; + buf[1] = '\0'; + } + break; + case x_stdstring: + { + std::vector * pv + = static_cast *>(data_); + std::vector & v = *pv; + + buf = new char[v[i].size() + 1]; + std::strcpy(buf, v[i].c_str()); + } + break; + case x_short: + { + std::vector * pv + = static_cast *>(data_); + std::vector & v = *pv; + + std::size_t const bufSize + = std::numeric_limits::digits10 + 3; + buf = new char[bufSize]; + snprintf(buf, bufSize, "%d", static_cast(v[i])); + } + break; + case x_integer: + { + std::vector * pv + = static_cast *>(data_); + std::vector & v = *pv; + + std::size_t const bufSize + = std::numeric_limits::digits10 + 3; + buf = new char[bufSize]; + snprintf(buf, bufSize, "%d", v[i]); + } + break; + case x_long_long: + { + std::vector* pv + = static_cast*>(data_); + std::vector& v = *pv; + + std::size_t const bufSize + = std::numeric_limits::digits10 + 3; + buf = new char[bufSize]; + snprintf(buf, bufSize, "%" LL_FMT_FLAGS "d", v[i]); + } + break; + case x_unsigned_long_long: + { + std::vector* pv + = static_cast*>(data_); + std::vector& v = *pv; + + std::size_t const bufSize + = std::numeric_limits::digits10 + 2; + buf = new char[bufSize]; + snprintf(buf, bufSize, "%" LL_FMT_FLAGS "u", v[i]); + } + break; + case x_double: + { + // no need to overengineer it (KISS)... + + std::vector * pv + = static_cast *>(data_); + std::vector & v = *pv; + + std::size_t const bufSize = 100; + buf = new char[bufSize]; + + snprintf(buf, bufSize, "%.20g", v[i]); + } + break; + case x_stdtm: + { + std::vector * pv + = static_cast *>(data_); + std::vector & v = *pv; + + std::size_t const bufSize = 20; + buf = new char[bufSize]; + + snprintf(buf, bufSize, "%d-%02d-%02d %02d:%02d:%02d", + v[i].tm_year + 1900, v[i].tm_mon + 1, v[i].tm_mday, + v[i].tm_hour, v[i].tm_min, v[i].tm_sec); + } + break; + + default: + throw soci_error( + "Use vector element used with non-supported type."); + } + } + + buffers_.push_back(buf); + } + + if (position_ > 0) + { + // binding by position + statement_.useByPosBuffers_[position_] = &buffers_[0]; + } + else + { + // binding by name + statement_.useByNameBuffers_[name_] = &buffers_[0]; + } +} + +std::size_t postgresql_vector_use_type_backend::size() +{ + std::size_t sz = 0; // dummy initialization to please the compiler + switch (type_) + { + // simple cases + case x_char: + sz = get_vector_size(data_); + break; + case x_short: + sz = get_vector_size(data_); + break; + case x_integer: + sz = get_vector_size(data_); + break; + case x_long_long: + sz = get_vector_size(data_); + break; + case x_unsigned_long_long: + sz = get_vector_size(data_); + break; + case x_double: + sz = get_vector_size(data_); + break; + case x_stdstring: + sz = get_vector_size(data_); + break; + case x_stdtm: + sz = get_vector_size(data_); + break; + default: + throw soci_error("Use vector element used with non-supported type."); + } + + return sz; +} + +void postgresql_vector_use_type_backend::clean_up() +{ + std::size_t const bsize = buffers_.size(); + for (std::size_t i = 0; i != bsize; ++i) + { + delete [] buffers_[i]; + } +} diff --git a/src/backends/sqlite3/CMakeLists.txt b/src/backends/sqlite3/CMakeLists.txt new file mode 100644 index 0000000000..99c42e5af3 --- /dev/null +++ b/src/backends/sqlite3/CMakeLists.txt @@ -0,0 +1,19 @@ +############################################################################### +# +# This file is part of CMake configuration for SOCI library +# +# Copyright (C) 2010 Mateusz Loskot +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) +# +############################################################################### + +soci_backend(SQLite3 + DEPENDS SQLite3 + HEADERS soci-sqlite3.h common.h + DESCRIPTION "SOCI backend for SQLite 3 database engine" + AUTHORS "Maciej Sobczak, Stephen Hutton, David Courtney" + MAINTAINERS "Maciej Sobczak, Mateusz Loskot") + +add_subdirectory(test) diff --git a/src/backends/sqlite3/Makefile.basic b/src/backends/sqlite3/Makefile.basic new file mode 100644 index 0000000000..374234224d --- /dev/null +++ b/src/backends/sqlite3/Makefile.basic @@ -0,0 +1,96 @@ +# The following variable is specific to this backend and its correct +# values might depend on your environment - feel free to set it accordingly. + +SQLITE3INCLUDEDIR = + +# The rest of the Makefile is indepentent of the target environment. + +COMPILER = g++ +CXXFLAGS = -Wall -pedantic -Wno-long-long +CXXFLAGSSO = ${CXXFLAGS} -fPIC +INCLUDEDIRS = -I../../core ${SQLITE3INCLUDEDIR} + + +OBJECTS = blob.o factory.o row-id.o session.o standard-into-type.o \ + standard-use-type.o statement.o vector-into-type.o vector-use-type.o \ + common.o + +OBJECTSSO = blob-s.o factory-s.o row-id-s.o session-s.o \ + standard-into-type-s.o standard-use-type-s.o statement-s.o \ + vector-into-type-s.o vector-use-type-s.o common-s.o + + +libsoci_sqlite3.a : ${OBJECTS} + ar rv $@ $? + ranlib $@ + rm *.o + + +blob.o : blob.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +common.o : common.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +factory.o : factory.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +row-id.o : row-id.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +session.o : session.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +standard-into-type.o : standard-into-type.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +standard-use-type.o : standard-use-type.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +statement.o : statement.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +vector-into-type.o : vector-into-type.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +vector-use-type.o : vector-use-type.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + + +shared : ${OBJECTSSO} + ${COMPILER} -shared -o libsoci_sqlite3.so ${OBJECTSSO} + rm *.o + +blob-s.o : blob.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +common-s.o : common.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +factory-s.o : factory.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +row-id-s.o : row-id.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +session-s.o : session.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +standard-into-type-s.o : standard-into-type.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +standard-use-type-s.o : standard-use-type.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +statement-s.o : statement.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +vector-into-type-s.o : vector-into-type.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + +vector-use-type-s.o : vector-use-type.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + + +clean : + rm -f libsoci_sqlite3.a libsoci_sqlite3.so diff --git a/src/backends/sqlite3/blob.cpp b/src/backends/sqlite3/blob.cpp new file mode 100644 index 0000000000..834477ffac --- /dev/null +++ b/src/backends/sqlite3/blob.cpp @@ -0,0 +1,116 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, David Courtney +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#include "soci-sqlite3.h" +#include +#include + +using namespace soci; + +sqlite3_blob_backend::sqlite3_blob_backend(sqlite3_session_backend &session) + : session_(session), buf_(0), len_(0) +{ +} + +sqlite3_blob_backend::~sqlite3_blob_backend() +{ + if (buf_) + { + delete [] buf_; + buf_ = 0; + len_ = 0; + } +} + +std::size_t sqlite3_blob_backend::get_len() +{ + return len_; +} + +std::size_t sqlite3_blob_backend::read( + std::size_t offset, char * buf, std::size_t toRead) +{ + size_t r = toRead; + + // make sure that we don't try to read + // past the end of the data + if (r > len_ - offset) + { + r = len_ - offset; + } + + memcpy(buf, buf_ + offset, r); + + return r; +} + + +std::size_t sqlite3_blob_backend::write( + std::size_t offset, char const * buf, + std::size_t toWrite) +{ + const char* oldBuf = buf_; + std::size_t oldLen = len_; + len_ = (std::max)(len_, offset + toWrite); + + buf_ = new char[len_]; + + if (oldBuf) + { + // we need to copy both old and new buffers + // it is possible that the new does not + // completely cover the old + memcpy(buf_, oldBuf, oldLen); + delete [] oldBuf; + } + memcpy(buf_ + offset, buf, toWrite); + + return len_; +} + + +std::size_t sqlite3_blob_backend::append( + char const * buf, std::size_t toWrite) +{ + const char* oldBuf = buf_; + + buf_ = new char[len_ + toWrite]; + + memcpy(buf_, oldBuf, len_); + + memcpy(buf_ + len_, buf, toWrite); + + delete [] oldBuf; + + len_ += toWrite; + + return len_; +} + + +void sqlite3_blob_backend::trim(std::size_t newLen) +{ + const char* oldBuf = buf_; + len_ = newLen; + + buf_ = new char[len_]; + + memcpy(buf_, oldBuf, len_); + + delete [] oldBuf; +} + +std::size_t sqlite3_blob_backend::set_data(char const *buf, std::size_t toWrite) +{ + if (buf_) + { + delete [] buf_; + buf_ = 0; + len_ = 0; + } + return write(0, buf, toWrite); +} diff --git a/src/backends/sqlite3/common.cpp b/src/backends/sqlite3/common.cpp new file mode 100644 index 0000000000..4e5d279db7 --- /dev/null +++ b/src/backends/sqlite3/common.cpp @@ -0,0 +1,67 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#include +#include "common.h" +#include "soci-backend.h" +// std +#include +#include + + +namespace // anonymous +{ + +// helper function for parsing decimal data (for std::tm) +long parse10(char const *&p1, char *&p2, char const* const msg) +{ + long v = std::strtol(p1, &p2, 10); + if (p2 != p1) + { + p1 = p2 + 1; + return v; + } + else + { + throw soci::soci_error(msg); + } +} + +} // namespace anonymous + + +void soci::details::sqlite3::parse_std_tm(char const *buf, std::tm &t) +{ + char const *p1 = buf; + char *p2 = 0; + + char const* const errMsg = "Cannot convert data to std::tm."; + + long year = parse10(p1, p2, errMsg); + long month = parse10(p1, p2, errMsg); + long day = parse10(p1, p2, errMsg); + + long hour = 0, minute = 0, second = 0; + if (*p2 != '\0') + { + // there is also the time of day available + hour = parse10(p1, p2, errMsg); + minute = parse10(p1, p2, errMsg); + second = parse10(p1, p2, errMsg); + } + + t.tm_isdst = -1; + t.tm_year = year - 1900; + t.tm_mon = month - 1; + t.tm_mday = day; + t.tm_hour = hour; + t.tm_min = minute; + t.tm_sec = second; + + std::mktime(&t); +} + diff --git a/src/backends/sqlite3/common.h b/src/backends/sqlite3/common.h new file mode 100644 index 0000000000..72aeb97ae6 --- /dev/null +++ b/src/backends/sqlite3/common.h @@ -0,0 +1,87 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_SQLITE3_COMMON_H_INCLUDED +#define SOCI_SQLITE3_COMMON_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include + +namespace soci { namespace details { namespace sqlite3 { + +// helper function for parsing datetime values +void parse_std_tm(char const *buf, std::tm &t); + +// helper for vector operations +template +std::size_t get_vector_size(void *p) +{ + std::vector *v = static_cast *>(p); + return v->size(); +} + +template +void resize_vector(void *p, std::size_t sz) +{ + std::vector *v = static_cast *>(p); + v->resize(sz); +} + +// helper function for parsing integers +template +T string_to_integer(char const * buf) +{ + long long t(0); + int n(0); + int const converted = std::sscanf(buf, "%" LL_FMT_FLAGS "d%n", &t, &n); + if (converted == 1 && static_cast(n) == std::strlen(buf)) + { + // successfully converted to long long + // and no other characters were found in the buffer + + const T max = (std::numeric_limits::max)(); + const T min = (std::numeric_limits::min)(); + if (t <= static_cast(max) && + t >= static_cast(min)) + { + return static_cast(t); + } + } + + throw soci_error("Cannot convert data."); +} + +// helper function for parsing unsigned integers +template +T string_to_unsigned_integer(char const * buf) +{ + unsigned long long t(0); + int n(0); + int const converted = std::sscanf(buf, "%" LL_FMT_FLAGS "u%n", &t, &n); + if (converted == 1 && static_cast(n) == std::strlen(buf)) + { + // successfully converted to unsigned long long + // and no other characters were found in the buffer + + T const max = (std::numeric_limits::max)(); + if (t <= static_cast(max)) + { + return static_cast(t); + } + } + + throw soci_error("Cannot convert data."); +} + +}}} // namespace soci::details::sqlite3 + +#endif // SOCI_SQLITE3_COMMON_H_INCLUDED diff --git a/src/backends/sqlite3/factory.cpp b/src/backends/sqlite3/factory.cpp new file mode 100644 index 0000000000..02691f5975 --- /dev/null +++ b/src/backends/sqlite3/factory.cpp @@ -0,0 +1,42 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, David Courtney +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_SQLITE3_SOURCE +#include "soci-sqlite3.h" +#include + +#ifdef _MSC_VER +#pragma warning(disable:4355) +#endif + +using namespace soci; +using namespace soci::details; + +// concrete factory for Empty concrete strategies +sqlite3_session_backend * sqlite3_backend_factory::make_session( + connection_parameters const & parameters) const +{ + return new sqlite3_session_backend(parameters); +} + +sqlite3_backend_factory const soci::sqlite3; + +extern "C" +{ + +// for dynamic backend loading +SOCI_SQLITE3_DECL backend_factory const * factory_sqlite3() +{ + return &soci::sqlite3; +} + +SOCI_SQLITE3_DECL void register_factory_sqlite3() +{ + soci::dynamic_backends::register_backend("sqlite3", soci::sqlite3); +} + +} // extern "C" diff --git a/src/backends/sqlite3/row-id.cpp b/src/backends/sqlite3/row-id.cpp new file mode 100644 index 0000000000..b0593fbb13 --- /dev/null +++ b/src/backends/sqlite3/row-id.cpp @@ -0,0 +1,26 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, David Courtney +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#include "soci-sqlite3.h" + +#ifdef _MSC_VER +#pragma warning(disable:4355) +#endif + +using namespace soci; +using namespace soci::details; + +sqlite3_rowid_backend::sqlite3_rowid_backend( + sqlite3_session_backend & /* session */) +{ + // ... +} + +sqlite3_rowid_backend::~sqlite3_rowid_backend() +{ + // ... +} diff --git a/src/backends/sqlite3/session.cpp b/src/backends/sqlite3/session.cpp new file mode 100644 index 0000000000..7f953f38f2 --- /dev/null +++ b/src/backends/sqlite3/session.cpp @@ -0,0 +1,161 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, David Courtney +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + + +#include "soci-sqlite3.h" + +#include + +#include +#include + +#ifdef _MSC_VER +#pragma warning(disable:4355) +#endif + +using namespace soci; +using namespace soci::details; +using namespace sqlite_api; + +namespace // anonymous +{ + +// helper function for hardcoded queries +void execude_hardcoded(sqlite_api::sqlite3* conn, char const* const query, char const* const errMsg) +{ + char *zErrMsg = 0; + int const res = sqlite3_exec(conn, query, 0, 0, &zErrMsg); + if (res != SQLITE_OK) + { + std::ostringstream ss; + ss << errMsg << " " << zErrMsg; + sqlite3_free(zErrMsg); + throw soci_error(ss.str()); + } +} + +void check_sqlite_err(sqlite_api::sqlite3* conn, int res, char const* const errMsg) +{ + if (SQLITE_OK != res) + { + const char *zErrMsg = sqlite3_errmsg(conn); + std::ostringstream ss; + ss << errMsg << zErrMsg; + throw soci_error(ss.str()); + } +} + +} // namespace anonymous + + +sqlite3_session_backend::sqlite3_session_backend( + connection_parameters const & parameters) +{ + int timeout = 0; + int connection_flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; + std::string synchronous; + std::string const & connectString = parameters.get_connect_string(); + std::string dbname(connectString); + std::stringstream ssconn(connectString); + while (!ssconn.eof() && ssconn.str().find('=') != std::string::npos) + { + std::string key, val; + std::getline(ssconn, key, '='); + std::getline(ssconn, val, ' '); + + if (val.size()>0 && val[0]=='\"') + { + std::string quotedVal = val.erase(0, 1); + + if (quotedVal[quotedVal.size()-1] == '\"') + { + quotedVal.erase(val.size()-1); + } + else // space inside value string + { + std::getline(ssconn, val, '\"'); + quotedVal = quotedVal + " " + val; + std::string keepspace; + std::getline(ssconn, keepspace, ' '); + } + + val = quotedVal; + } + + if ("dbname" == key || "db" == key) + { + dbname = val; + } + else if ("timeout" == key) + { + std::istringstream converter(val); + converter >> timeout; + } + else if ("synchronous" == key) + { + synchronous = val; + } + else if ("shared_cache" == key && "true" == val) + { + connection_flags |= SQLITE_OPEN_SHAREDCACHE; + } + } + + int res = sqlite3_open_v2(dbname.c_str(), &conn_, connection_flags, NULL); + check_sqlite_err(conn_, res, "Cannot establish connection to the database. "); + + if (!synchronous.empty()) + { + std::string const query("pragma synchronous=" + synchronous); + std::string const errMsg("Query failed: " + query); + execude_hardcoded(conn_, query.c_str(), errMsg.c_str()); + } + + res = sqlite3_busy_timeout(conn_, timeout * 1000); + check_sqlite_err(conn_, res, "Failed to set busy timeout for connection. "); + +} + +sqlite3_session_backend::~sqlite3_session_backend() +{ + clean_up(); +} + +void sqlite3_session_backend::begin() +{ + execude_hardcoded(conn_, "BEGIN", "Cannot begin transaction."); +} + +void sqlite3_session_backend::commit() +{ + execude_hardcoded(conn_, "COMMIT", "Cannot commit transaction."); +} + +void sqlite3_session_backend::rollback() +{ + execude_hardcoded(conn_, "ROLLBACK", "Cannot rollback transaction."); +} + +void sqlite3_session_backend::clean_up() +{ + sqlite3_close(conn_); +} + +sqlite3_statement_backend * sqlite3_session_backend::make_statement_backend() +{ + return new sqlite3_statement_backend(*this); +} + +sqlite3_rowid_backend * sqlite3_session_backend::make_rowid_backend() +{ + return new sqlite3_rowid_backend(*this); +} + +sqlite3_blob_backend * sqlite3_session_backend::make_blob_backend() +{ + return new sqlite3_blob_backend(*this); +} diff --git a/src/backends/sqlite3/soci-sqlite3.h b/src/backends/sqlite3/soci-sqlite3.h new file mode 100644 index 0000000000..53dee2f05e --- /dev/null +++ b/src/backends/sqlite3/soci-sqlite3.h @@ -0,0 +1,279 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, David Courtney +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_SQLITE3_H_INCLUDED +#define SOCI_SQLITE3_H_INCLUDED + +#ifdef _WIN32 +# ifdef SOCI_DLL +# ifdef SOCI_SQLITE3_SOURCE +# define SOCI_SQLITE3_DECL __declspec(dllexport) +# else +# define SOCI_SQLITE3_DECL __declspec(dllimport) +# endif // SOCI_SQLITE3_SOURCE +# endif // SOCI_DLL +#endif // _WIN32 +// +// If SOCI_SQLITE3_DECL isn't defined yet define it now +#ifndef SOCI_SQLITE3_DECL +# define SOCI_SQLITE3_DECL +#endif + +#include +#include +#include "soci-backend.h" + +// Disable flood of nonsense warnings generated for SQLite +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4510 4512 4610) +#endif + +namespace sqlite_api +{ + +#if SQLITE_VERSION_NUMBER < 3003010 +// The sqlite3_destructor_type typedef introduced in 3.3.10 +// http://www.sqlite.org/cvstrac/tktview?tn=2191 +typedef void (*sqlite3_destructor_type)(void*); +#endif + +#include + +} // namespace sqlite_api + +#undef SQLITE_STATIC +#define SQLITE_STATIC ((sqlite_api::sqlite3_destructor_type)0) + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +namespace soci +{ + +struct sqlite3_statement_backend; +struct sqlite3_standard_into_type_backend : details::standard_into_type_backend +{ + sqlite3_standard_into_type_backend(sqlite3_statement_backend &st) + : statement_(st) {} + + virtual void define_by_pos(int &position, + void *data, details::exchange_type type); + + virtual void pre_fetch(); + virtual void post_fetch(bool gotData, bool calledFromFetch, + indicator *ind); + + virtual void clean_up(); + + sqlite3_statement_backend &statement_; + + void *data_; + details::exchange_type type_; + int position_; +}; + +struct sqlite3_vector_into_type_backend : details::vector_into_type_backend +{ + sqlite3_vector_into_type_backend(sqlite3_statement_backend &st) + : statement_(st) {} + + void define_by_pos(int& position, void* data, details::exchange_type type); + + void pre_fetch(); + void post_fetch(bool gotData, indicator* ind); + + void resize(std::size_t sz); + std::size_t size(); + + virtual void clean_up(); + + sqlite3_statement_backend& statement_; + + void *data_; + details::exchange_type type_; + int position_; +}; + +struct sqlite3_standard_use_type_backend : details::standard_use_type_backend +{ + sqlite3_standard_use_type_backend(sqlite3_statement_backend &st) + : statement_(st), buf_(0) {} + + virtual void bind_by_pos(int &position, + void *data, details::exchange_type type, bool readOnly); + virtual void bind_by_name(std::string const &name, + void *data, details::exchange_type type, bool readOnly); + + virtual void pre_use(indicator const *ind); + virtual void post_use(bool gotData, indicator *ind); + + virtual void clean_up(); + + sqlite3_statement_backend &statement_; + + void *data_; + details::exchange_type type_; + int position_; + std::string name_; + char *buf_; +}; + +struct sqlite3_vector_use_type_backend : details::vector_use_type_backend +{ + sqlite3_vector_use_type_backend(sqlite3_statement_backend &st) + : statement_(st) {} + + virtual void bind_by_pos(int &position, + void *data, details::exchange_type type); + virtual void bind_by_name(std::string const &name, + void *data, details::exchange_type type); + + virtual void pre_use(indicator const *ind); + + virtual std::size_t size(); + + virtual void clean_up(); + + sqlite3_statement_backend &statement_; + + void *data_; + details::exchange_type type_; + int position_; + std::string name_; +}; + +struct sqlite3_column +{ + std::string data_; + bool isNull_; + char * blobBuf_; + std::size_t blobSize_; +}; + +typedef std::vector sqlite3_row; +typedef std::vector sqlite3_recordset; + +struct sqlite3_session_backend; +struct sqlite3_statement_backend : details::statement_backend +{ + sqlite3_statement_backend(sqlite3_session_backend &session); + + virtual void alloc(); + virtual void clean_up(); + virtual void prepare(std::string const &query, + details::statement_type eType); + void reset_if_needed(); + + virtual exec_fetch_result execute(int number); + virtual exec_fetch_result fetch(int number); + + virtual long long get_affected_rows(); + virtual int get_number_of_rows(); + + virtual std::string rewrite_for_procedure_call(std::string const &query); + + virtual int prepare_for_describe(); + virtual void describe_column(int colNum, data_type &dtype, + std::string &columnName); + + virtual sqlite3_standard_into_type_backend * make_into_type_backend(); + virtual sqlite3_standard_use_type_backend * make_use_type_backend(); + virtual sqlite3_vector_into_type_backend * make_vector_into_type_backend(); + virtual sqlite3_vector_use_type_backend * make_vector_use_type_backend(); + + sqlite3_session_backend &session_; + sqlite_api::sqlite3_stmt *stmt_; + sqlite3_recordset dataCache_; + sqlite3_recordset useData_; + bool databaseReady_; + bool boundByName_; + bool boundByPos_; + + long long rowsAffectedBulk_; // number of rows affected by the last bulk operation + +private: + exec_fetch_result load_rowset(int totalRows); + exec_fetch_result load_one(); + exec_fetch_result bind_and_execute(int number); +}; + +struct sqlite3_rowid_backend : details::rowid_backend +{ + sqlite3_rowid_backend(sqlite3_session_backend &session); + + ~sqlite3_rowid_backend(); + + unsigned long value_; +}; + +struct sqlite3_blob_backend : details::blob_backend +{ + sqlite3_blob_backend(sqlite3_session_backend &session); + + ~sqlite3_blob_backend(); + + virtual std::size_t get_len(); + virtual std::size_t read(std::size_t offset, char *buf, + std::size_t toRead); + virtual std::size_t write(std::size_t offset, char const *buf, + std::size_t toWrite); + virtual std::size_t append(char const *buf, std::size_t toWrite); + virtual void trim(std::size_t newLen); + + sqlite3_session_backend &session_; + + std::size_t set_data(char const *buf, std::size_t toWrite); + +private: + char *buf_; + size_t len_; +}; + +struct sqlite3_session_backend : details::session_backend +{ + sqlite3_session_backend(connection_parameters const & parameters); + + ~sqlite3_session_backend(); + + virtual void begin(); + virtual void commit(); + virtual void rollback(); + + virtual std::string get_backend_name() const { return "sqlite3"; } + + void clean_up(); + + virtual sqlite3_statement_backend * make_statement_backend(); + virtual sqlite3_rowid_backend * make_rowid_backend(); + virtual sqlite3_blob_backend * make_blob_backend(); + + sqlite_api::sqlite3 *conn_; +}; + +struct sqlite3_backend_factory : backend_factory +{ + sqlite3_backend_factory() {} + virtual sqlite3_session_backend * make_session( + connection_parameters const & parameters) const; +}; + +extern SOCI_SQLITE3_DECL sqlite3_backend_factory const sqlite3; + +extern "C" +{ + +// for dynamic backend loading +SOCI_SQLITE3_DECL backend_factory const * factory_sqlite3(); +SOCI_SQLITE3_DECL void register_factory_sqlite3(); + +} // extern "C" + +} // namespace soci + +#endif // SOCI_SQLITE3_H_INCLUDED diff --git a/src/backends/sqlite3/standard-into-type.cpp b/src/backends/sqlite3/standard-into-type.cpp new file mode 100644 index 0000000000..ce2febe1e4 --- /dev/null +++ b/src/backends/sqlite3/standard-into-type.cpp @@ -0,0 +1,166 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, David Courtney +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#include +#include "soci-sqlite3.h" +#include "rowid.h" +#include "common.h" +#include "blob.h" +// std +#include +#include +#include + +using namespace soci; +using namespace soci::details; +using namespace soci::details::sqlite3; + +void sqlite3_standard_into_type_backend::define_by_pos(int & position, void * data, + exchange_type type) +{ + data_ = data; + type_ = type; + position_ = position++; +} + +void sqlite3_standard_into_type_backend::pre_fetch() +{ + // ... +} + +void sqlite3_standard_into_type_backend::post_fetch(bool gotData, + bool calledFromFetch, + indicator * ind) +{ + if (calledFromFetch == true && gotData == false) + { + // this is a normal end-of-rowset condition, + // no need to do anything (fetch() will return false) + return; + } + + // sqlite columns start at 0 + int const pos = position_ - 1; + + if (gotData) + { + // first, deal with indicators + if (sqlite3_column_type(statement_.stmt_, pos) == SQLITE_NULL) + { + if (ind == NULL) + { + throw soci_error( + "Null value fetched and no indicator defined."); + } + + *ind = i_null; + return; + } + else + { + if (ind != NULL) + { + *ind = i_ok; + } + } + + const char *buf = reinterpret_cast( + sqlite3_column_text(statement_.stmt_,pos)); + + if (buf == NULL) + { + buf = ""; + } + + switch (type_) + { + case x_char: + { + char *dest = static_cast(data_); + *dest = *buf; + } + break; + case x_stdstring: + { + std::string *dest = static_cast(data_); + dest->assign(buf); + } + break; + case x_short: + { + short *dest = static_cast(data_); + long val = std::strtol(buf, NULL, 10); + *dest = static_cast(val); + } + break; + case x_integer: + { + int *dest = static_cast(data_); + long val = std::strtol(buf, NULL, 10); + *dest = static_cast(val); + } + break; + case x_long_long: + { + long long* dest = static_cast(data_); + *dest = std::strtoll(buf, NULL, 10); + } + break; + case x_unsigned_long_long: + { + unsigned long long* dest = static_cast(data_); + *dest = string_to_unsigned_integer(buf); + } + break; + case x_double: + { + double *dest = static_cast(data_); + double val = strtod(buf, NULL); + *dest = static_cast(val); + } + break; + case x_stdtm: + { + // attempt to parse the string and convert to std::tm + std::tm *dest = static_cast(data_); + parse_std_tm(buf, *dest); + } + break; + case x_rowid: + { + // RowID is internally identical to unsigned long + + rowid *rid = static_cast(data_); + sqlite3_rowid_backend *rbe = static_cast(rid->get_backend()); + long long val = std::strtoll(buf, NULL, 10); + rbe->value_ = static_cast(val); + } + break; + case x_blob: + { + blob *b = static_cast(data_); + sqlite3_blob_backend *bbe = + static_cast(b->get_backend()); + + buf = reinterpret_cast(sqlite3_column_blob( + statement_.stmt_, + pos)); + + int len = sqlite3_column_bytes(statement_.stmt_, pos); + bbe->set_data(buf, len); + } + break; + default: + throw soci_error("Into element used with non-supported type."); + } + } +} + +void sqlite3_standard_into_type_backend::clean_up() +{ + // ... +} diff --git a/src/backends/sqlite3/standard-use-type.cpp b/src/backends/sqlite3/standard-use-type.cpp new file mode 100644 index 0000000000..0006fcf641 --- /dev/null +++ b/src/backends/sqlite3/standard-use-type.cpp @@ -0,0 +1,233 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, David Courtney +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#include "soci-sqlite3.h" +#include +#include "rowid.h" +#include "blob.h" +// std +#include +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(disable:4355 4996) +#define snprintf _snprintf // TODO: use soci-platform.h +#endif + +using namespace soci; +using namespace soci::details; + +void sqlite3_standard_use_type_backend::bind_by_pos(int& position, void* data, + exchange_type type, bool /*readOnly*/) +{ + if (statement_.boundByName_) + { + throw soci_error( + "Binding for use elements must be either by position or by name."); + } + + data_ = data; + type_ = type; + position_ = position++; + + statement_.boundByPos_ = true; +} + +void sqlite3_standard_use_type_backend::bind_by_name(std::string const& name, + void* data, exchange_type type, bool /*readOnly*/) +{ + if (statement_.boundByPos_) + { + throw soci_error( + "Binding for use elements must be either by position or by name."); + } + + data_ = data; + type_ = type; + name_ = ":" + name; + + statement_.reset_if_needed(); + position_ = sqlite3_bind_parameter_index(statement_.stmt_, name_.c_str()); + + if (0 == position_) + { + std::ostringstream ss; + ss << "Cannot bind to (by name) " << name_; + throw soci_error(ss.str()); + } + statement_.boundByName_ = true; +} + +void sqlite3_standard_use_type_backend::pre_use(indicator const * ind) +{ + statement_.useData_.resize(1); + int const pos = position_ - 1; + + if (statement_.useData_[0].size() < static_cast(position_)) + { + statement_.useData_[0].resize(position_); + } + + if (ind != NULL && *ind == i_null) + { + statement_.useData_[0][pos].isNull_ = true; + statement_.useData_[0][pos].data_ = ""; + statement_.useData_[0][pos].blobBuf_ = 0; + statement_.useData_[0][pos].blobSize_ = 0; + } + else + { + // allocate and fill the buffer with text-formatted client data + switch (type_) + { + case x_char: + { + buf_ = new char[2]; + buf_[0] = *static_cast(data_); + buf_[1] = '\0'; + } + break; + case x_stdstring: + { + std::string *s = static_cast(data_); + buf_ = new char[s->size() + 1]; + std::strcpy(buf_, s->c_str()); + } + break; + case x_short: + { + std::size_t const bufSize + = std::numeric_limits::digits10 + 3; + buf_ = new char[bufSize]; + snprintf(buf_, bufSize, "%d", + static_cast(*static_cast(data_))); + } + break; + case x_integer: + { + std::size_t const bufSize + = std::numeric_limits::digits10 + 3; + buf_ = new char[bufSize]; + snprintf(buf_, bufSize, "%d", + *static_cast(data_)); + } + break; + case x_long_long: + { + std::size_t const bufSize + = std::numeric_limits::digits10 + 3; + buf_ = new char[bufSize]; + snprintf(buf_, bufSize, "%" LL_FMT_FLAGS "d", + *static_cast(data_)); + } + break; + case x_unsigned_long_long: + { + std::size_t const bufSize + = std::numeric_limits::digits10 + 2; + buf_ = new char[bufSize]; + snprintf(buf_, bufSize, "%" LL_FMT_FLAGS "u", + *static_cast(data_)); + } + break; + case x_double: + { + // no need to overengineer it (KISS)... + + std::size_t const bufSize = 100; + buf_ = new char[bufSize]; + + snprintf(buf_, bufSize, "%.20g", + *static_cast(data_)); + } + break; + case x_stdtm: + { + std::size_t const bufSize = 20; + buf_ = new char[bufSize]; + + std::tm *t = static_cast(data_); + snprintf(buf_, bufSize, "%d-%02d-%02d %02d:%02d:%02d", + t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, + t->tm_hour, t->tm_min, t->tm_sec); + } + break; + case x_rowid: + { + // RowID is internally identical to unsigned long + + rowid *rid = static_cast(data_); + sqlite3_rowid_backend *rbe = +static_cast(rid->get_backend()); + + std::size_t const bufSize + = std::numeric_limits::digits10 + 2; + buf_ = new char[bufSize]; + + snprintf(buf_, bufSize, "%lu", rbe->value_); + } + break; + case x_blob: + { + blob *b = static_cast(data_); + sqlite3_blob_backend *bbe = + static_cast(b->get_backend()); + + std::size_t len = bbe->get_len(); + buf_ = new char[len]; + bbe->read(0, buf_, len); + statement_.useData_[0][pos].blobBuf_ = buf_; + statement_.useData_[0][pos].blobSize_ = len; + } + break; + default: + throw soci_error("Use element used with non-supported type."); + } + + statement_.useData_[0][pos].isNull_ = false; + if (type_ != x_blob) + { + statement_.useData_[0][pos].blobBuf_ = 0; + statement_.useData_[0][pos].blobSize_ = 0; + statement_.useData_[0][pos].data_ = buf_; + } + } +} + +void sqlite3_standard_use_type_backend::post_use( + bool /* gotData */, indicator * /* ind */) +{ + // TODO: Is it possible to have the bound element being overwritten + // by the database? + // If not, then nothing to do here, please remove this comment. + // If yes, then use the value of the readOnly parameter: + // - true: the given object should not be modified and the backend + // should detect if the modification was performed on the + // isolated buffer and throw an exception if the buffer was modified + // (this indicates logic error, because the user used const object + // and executed a query that attempted to modified it) + // - false: the modification should be propagated to the given object. + // ... + + // clean up the working buffer, it might be allocated anew in + // the next run of preUse + clean_up(); +} + +void sqlite3_standard_use_type_backend::clean_up() +{ + if (buf_ != NULL) + { + delete [] buf_; + buf_ = NULL; + } +} diff --git a/src/backends/sqlite3/statement.cpp b/src/backends/sqlite3/statement.cpp new file mode 100644 index 0000000000..9192a4c4d9 --- /dev/null +++ b/src/backends/sqlite3/statement.cpp @@ -0,0 +1,437 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, David Courtney +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +#include "soci-sqlite3.h" +// std +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(disable:4355) +#endif + +using namespace soci; +using namespace soci::details; +using namespace sqlite_api; + +sqlite3_statement_backend::sqlite3_statement_backend( + sqlite3_session_backend &session) + : session_(session) + , stmt_(0) + , dataCache_() + , useData_(0) + , databaseReady_(false) + , boundByName_(false) + , boundByPos_(false) + , rowsAffectedBulk_(-1LL) +{ +} + +void sqlite3_statement_backend::alloc() +{ + // ... +} + +void sqlite3_statement_backend::clean_up() +{ + rowsAffectedBulk_ = -1LL; + + if (stmt_) + { + sqlite3_finalize(stmt_); + stmt_ = 0; + databaseReady_ = false; + } +} + +void sqlite3_statement_backend::prepare(std::string const & query, + statement_type /* eType */) +{ + clean_up(); + + char const* tail = 0; // unused; + int const res = sqlite3_prepare_v2(session_.conn_, + query.c_str(), + static_cast(query.size()), + &stmt_, + &tail); + if (res != SQLITE_OK) + { + char const* zErrMsg = sqlite3_errmsg(session_.conn_); + + std::ostringstream ss; + ss << "sqlite3_statement_backend::prepare: " + << zErrMsg; + throw soci_error(ss.str()); + } + databaseReady_ = true; +} + +// sqlite3_reset needs to be called before a prepared statment can +// be executed a second time. +void sqlite3_statement_backend::reset_if_needed() +{ + if (stmt_ && databaseReady_ == false) + { + int const res = sqlite3_reset(stmt_); + if (SQLITE_OK == res) + { + databaseReady_ = true; + } + } +} + +// This is used by bulk operations +statement_backend::exec_fetch_result +sqlite3_statement_backend::load_rowset(int totalRows) +{ + statement_backend::exec_fetch_result retVal = ef_success; + int numCols = -1; + int i = 0; + + if (!databaseReady_) + { + retVal = ef_no_data; + } + else + { + // make the vector big enough to hold the data we need + dataCache_.resize(totalRows); + + for (i = 0; i < totalRows && databaseReady_; ++i) + { + int const res = sqlite3_step(stmt_); + + if (SQLITE_DONE == res) + { + databaseReady_ = false; + retVal = ef_no_data; + break; + } + else if (SQLITE_ROW == res) + { + // only need to set the number of columns once + if (-1 == numCols) + { + numCols = sqlite3_column_count(stmt_); + for (sqlite3_recordset::iterator it = dataCache_.begin(), + end = dataCache_.end(); it != end; ++it) + { + (*it).resize(numCols); + } + } + for (int c = 0; c < numCols; ++c) + { + char const* buf = + reinterpret_cast(sqlite3_column_text(stmt_, c)); + bool isNull = false; + if (0 == buf) + { + isNull = true; + buf = ""; + } + dataCache_[i][c].data_ = buf; + dataCache_[i][c].isNull_ = isNull; + } + } + else + { + clean_up(); + char const* zErrMsg = sqlite3_errmsg(session_.conn_); + std::ostringstream ss; + ss << "sqlite3_statement_backend::loadRS: " + << zErrMsg; + throw soci_error(ss.str()); + } + } + } + // if we read less than requested then shrink the vector + dataCache_.resize(i); + + return retVal; +} + +// This is used for non-bulk operations +statement_backend::exec_fetch_result +sqlite3_statement_backend::load_one() +{ + statement_backend::exec_fetch_result retVal = ef_success; + + int const res = sqlite3_step(stmt_); + + if (SQLITE_DONE == res) + { + databaseReady_ = false; + retVal = ef_no_data; + } + else if (SQLITE_ROW == res) + { + } + else + { + clean_up(); + + char const* zErrMsg = sqlite3_errmsg(session_.conn_); + + std::ostringstream ss; + ss << "sqlite3_statement_backend::loadOne: " + << zErrMsg; + throw soci_error(ss.str()); + } + + return retVal; +} + +// Execute statements once for every row of useData +statement_backend::exec_fetch_result +sqlite3_statement_backend::bind_and_execute(int number) +{ + statement_backend::exec_fetch_result retVal = ef_no_data; + + long long rowsAffectedBulkTemp = 0; + + int const rows = static_cast(useData_.size()); + for (int row = 0; row < rows; ++row) + { + sqlite3_reset(stmt_); + + int const totalPositions = static_cast(useData_[0].size()); + for (int pos = 1; pos <= totalPositions; ++pos) + { + int bindRes = SQLITE_OK; + const sqlite3_column& curCol = useData_[row][pos-1]; + if (curCol.isNull_) + { + bindRes = sqlite3_bind_null(stmt_, pos); + } + else if (curCol.blobBuf_) + { + bindRes = sqlite3_bind_blob(stmt_, pos, + curCol.blobBuf_, + static_cast(curCol.blobSize_), + SQLITE_STATIC); + } + else + { + bindRes = sqlite3_bind_text(stmt_, pos, + curCol.data_.c_str(), + static_cast(curCol.data_.length()), + SQLITE_STATIC); + } + + if (SQLITE_OK != bindRes) + { + // preserve the number of rows affected so far. + rowsAffectedBulk_ = rowsAffectedBulkTemp; + throw soci_error("Failure to bind on bulk operations"); + } + } + + // Handle the case where there are both into and use elements + // in the same query and one of the into binds to a vector object. + if (1 == rows && number != rows) + { + return load_rowset(number); + } + + retVal = load_one(); //execute each bound line + rowsAffectedBulkTemp += get_affected_rows(); + } + rowsAffectedBulk_ = rowsAffectedBulkTemp; + return retVal; +} + +statement_backend::exec_fetch_result +sqlite3_statement_backend::execute(int number) +{ + if (stmt_ == NULL) + { + throw soci_error("No sqlite statement created"); + } + + sqlite3_reset(stmt_); + databaseReady_ = true; + + statement_backend::exec_fetch_result retVal = ef_no_data; + + if (useData_.empty() == false) + { + retVal = bind_and_execute(number); + } + else + { + if (1 == number) + { + retVal = load_one(); + } + else + { + retVal = load_rowset(number); + } + } + + return retVal; +} + +statement_backend::exec_fetch_result +sqlite3_statement_backend::fetch(int number) +{ + return load_rowset(number); +} + +long long sqlite3_statement_backend::get_affected_rows() +{ + if (rowsAffectedBulk_ >= 0) + { + return rowsAffectedBulk_; + } + return sqlite3_changes(session_.conn_); +} + +int sqlite3_statement_backend::get_number_of_rows() +{ + return static_cast(dataCache_.size()); +} + +std::string sqlite3_statement_backend::rewrite_for_procedure_call( + std::string const &query) +{ + return query; +} + +int sqlite3_statement_backend::prepare_for_describe() +{ + return sqlite3_column_count(stmt_); +} + +void sqlite3_statement_backend::describe_column(int colNum, data_type & type, + std::string & columnName) +{ + columnName = sqlite3_column_name(stmt_, colNum-1); + + // This is a hack, but the sqlite3 type system does not + // have a date or time field. Also it does not reliably + // id other data types. It has a tendency to see everything + // as text. sqlite3_column_decltype returns the text that is + // used in the create table statement + bool typeFound = false; + + char const* declType = sqlite3_column_decltype(stmt_, colNum-1); + + if ( declType == NULL ) + { + static char const* s_char = "char"; + declType = s_char; + } + + std::string dt = declType; + + // do all comparisons in lower case + std::transform(dt.begin(), dt.end(), dt.begin(), tolower); + + if (dt.find("time", 0) != std::string::npos) + { + type = dt_date; + typeFound = true; + } + if (dt.find("date", 0) != std::string::npos) + { + type = dt_date; + typeFound = true; + } + + if (dt.find("int8", 0) != std::string::npos || dt.find("bigint", 0) != std::string::npos) + { + type = dt_long_long; + typeFound = true; + } + else if (dt.find("unsigned big int", 0) != std::string::npos) + { + type = dt_unsigned_long_long; + typeFound = true; + } + else if (dt.find("int", 0) != std::string::npos) + { + type = dt_integer; + typeFound = true; + } + + if (dt.find("float", 0) != std::string::npos || dt.find("double", 0) != std::string::npos) + { + type = dt_double; + typeFound = true; + } + if (dt.find("text", 0) != std::string::npos) + { + type = dt_string; + typeFound = true; + } + if (dt.find("char", 0) != std::string::npos) + { + type = dt_string; + typeFound = true; + } + if (dt.find("boolean", 0) != std::string::npos) + { + type = dt_integer; + typeFound = true; + } + + if (typeFound) + { + return; + } + + // try to get it from the weak ass type system + + // total hack - execute the statment once to get the column types + // then clear so it can be executed again + sqlite3_step(stmt_); + + int const sqlite3_type = sqlite3_column_type(stmt_, colNum-1); + switch (sqlite3_type) + { + case SQLITE_INTEGER: + type = dt_integer; + break; + case SQLITE_FLOAT: + type = dt_double; + break; + case SQLITE_BLOB: + case SQLITE_TEXT: + type = dt_string; + break; + default: + type = dt_string; + break; + } + + sqlite3_reset(stmt_); +} + +sqlite3_standard_into_type_backend * +sqlite3_statement_backend::make_into_type_backend() +{ + return new sqlite3_standard_into_type_backend(*this); +} + +sqlite3_standard_use_type_backend * sqlite3_statement_backend::make_use_type_backend() +{ + return new sqlite3_standard_use_type_backend(*this); +} + +sqlite3_vector_into_type_backend * +sqlite3_statement_backend::make_vector_into_type_backend() +{ + return new sqlite3_vector_into_type_backend(*this); +} + +sqlite3_vector_use_type_backend * +sqlite3_statement_backend::make_vector_use_type_backend() +{ + return new sqlite3_vector_use_type_backend(*this); +} diff --git a/src/backends/sqlite3/test/.gitignore b/src/backends/sqlite3/test/.gitignore new file mode 100644 index 0000000000..35d7969fdb --- /dev/null +++ b/src/backends/sqlite3/test/.gitignore @@ -0,0 +1,2 @@ +test_sqlite3 +test_sqlite3.db diff --git a/src/backends/sqlite3/test/CMakeLists.txt b/src/backends/sqlite3/test/CMakeLists.txt new file mode 100644 index 0000000000..4e21f54b02 --- /dev/null +++ b/src/backends/sqlite3/test/CMakeLists.txt @@ -0,0 +1,15 @@ +############################################################################### +# +# This file is part of CMake configuration for SOCI library +# +# Copyright (C) 2010 Mateusz Loskot +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) +# +############################################################################### + +soci_backend_test( + BACKEND SQLite3 + SOURCE test-sqlite3.cpp + CONNSTR "soci_test.db") \ No newline at end of file diff --git a/src/backends/sqlite3/test/Makefile.basic b/src/backends/sqlite3/test/Makefile.basic new file mode 100644 index 0000000000..c2eb3ed788 --- /dev/null +++ b/src/backends/sqlite3/test/Makefile.basic @@ -0,0 +1,23 @@ +# The following variable is specific to this backend and its correct +# values might depend on your environment - feel free to set it accordingly. + +BOOSTINCLUDEDIR = +SQLITE3INCLUDEDIR = +SQLITE3LIBDIR = -L/usr/lib +SQLITE3LIBS = -lsqlite3 + +# The rest of the Makefile is independent of the target environment. + +COMPILER = g++ +CXXFLAGS = -Wall -pedantic -Wno-long-long +INCLUDEDIRS = -I.. -I../../../core -I../../../core/test ${SQLITE3INCLUDEDIR} ${BOOSTINCLUDEDIR} +LIBDIRS = -L.. -L../../../core ${SQLITE3LIBDIR} +LIBS = -lsoci_core -lsoci_sqlite3 -ldl ${SQLITE3LIBS} + + +test-sqlite3 : test-sqlite3.cpp + ${COMPILER} -o $@ $? ${CXXFLAGS} ${INCLUDEDIRS} ${LIBDIRS} ${LIBS} + + +clean : + rm -f test-sqlite3 diff --git a/src/backends/sqlite3/test/test-sqlite3.cpp b/src/backends/sqlite3/test/test-sqlite3.cpp new file mode 100644 index 0000000000..ae257d9d24 --- /dev/null +++ b/src/backends/sqlite3/test/test-sqlite3.cpp @@ -0,0 +1,395 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, David Courtney +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#include "soci.h" +#include "soci-sqlite3.h" +#include "common-tests.h" +#include +#include +#include +#include +#include +#include +#include + +using namespace soci; +using namespace soci::tests; + +std::string connectString; +backend_factory const &backEnd = *soci::factory_sqlite3(); + +// ROWID test +// In sqlite3 the row id can be called ROWID, _ROWID_ or oid +void test1() +{ + { + session sql(backEnd, connectString); + + try { sql << "drop table test1"; } + catch (soci_error const &) {} // ignore if error + + sql << + "create table test1 (" + " id integer," + " name varchar(100)" + ")"; + + sql << "insert into test1(id, name) values(7, \'John\')"; + + rowid rid(sql); + sql << "select oid from test1 where id = 7", into(rid); + + int id; + std::string name; + + sql << "select id, name from test1 where oid = :rid", + into(id), into(name), use(rid); + + assert(id == 7); + assert(name == "John"); + + sql << "drop table test1"; + } + + std::cout << "test 1 passed" << std::endl; +} + +// BLOB test +struct blob_table_creator : public table_creator_base +{ + blob_table_creator(session & sql) + : table_creator_base(sql) + { + sql << + "create table soci_test (" + " id integer," + " img blob" + ")"; + } +}; + +void test2() +{ + { + session sql(backEnd, connectString); + + blob_table_creator tableCreator(sql); + + char buf[] = "abcdefghijklmnopqrstuvwxyz"; + + sql << "insert into soci_test(id, img) values(7, '')"; + + { + blob b(sql); + + sql << "select img from soci_test where id = 7", into(b); + assert(b.get_len() == 0); + + b.write(0, buf, sizeof(buf)); + assert(b.get_len() == sizeof(buf)); + sql << "update soci_test set img=? where id = 7", use(b); + + b.append(buf, sizeof(buf)); + assert(b.get_len() == 2 * sizeof(buf)); + sql << "insert into soci_test(id, img) values(8, ?)", use(b); + } + { + blob b(sql); + sql << "select img from soci_test where id = 8", into(b); + assert(b.get_len() == 2 * sizeof(buf)); + char buf2[100]; + b.read(0, buf2, 10); + assert(std::strncmp(buf2, "abcdefghij", 10) == 0); + + sql << "select img from soci_test where id = 7", into(b); + assert(b.get_len() == sizeof(buf)); + + } + } + + std::cout << "test 2 passed" << std::endl; +} + +// This test was put in to fix a problem that occurs when there are both +// into and use elements in the same query and one of them (into) binds +// to a vector object. + +struct test3_table_creator : table_creator_base +{ + test3_table_creator(session & sql) : table_creator_base(sql) + { + sql << "create table soci_test( id integer, name varchar, subname varchar);"; + } +}; + +void test3() +{ + { + session sql(backEnd, connectString); + + test3_table_creator tableCreator(sql); + + sql << "insert into soci_test(id,name,subname) values( 1,'john','smith')"; + sql << "insert into soci_test(id,name,subname) values( 2,'george','vals')"; + sql << "insert into soci_test(id,name,subname) values( 3,'ann','smith')"; + sql << "insert into soci_test(id,name,subname) values( 4,'john','grey')"; + sql << "insert into soci_test(id,name,subname) values( 5,'anthony','wall')"; + + { + std::vector v(10); + + statement s(sql.prepare << "Select id from soci_test where name = :name"); + + std::string name = "john"; + + s.exchange(use(name, "name")); + s.exchange(into(v)); + + s.define_and_bind(); + s.execute(true); + + assert(v.size() == 2); + } + } + std::cout << "test 3 passed" << std::endl; +} + + +// Test case from Amnon David 11/1/2007 +// I've noticed that table schemas in SQLite3 can sometimes have typeless +// columns. One (and only?) example is the sqlite_sequence that sqlite +// creates for autoincrement . Attempting to traverse this table caused +// SOCI to crash. I've made the following code change in statement.cpp to +// create a workaround: + +struct test4_table_creator : table_creator_base +{ + test4_table_creator(session & sql) : table_creator_base(sql) + { + sql << "create table soci_test (col INTEGER PRIMARY KEY AUTOINCREMENT, name char)"; + } +}; + +void test4() +{ + { + // we need to have an table that uses autoincrement to test this. + session sql(backEnd, connectString); + + test4_table_creator tableCreator(sql); + + sql << "insert into soci_test(name) values('john')"; + sql << "insert into soci_test(name) values('james')"; + + { + int key; + std::string name; + sql << "select * from soci_test", into(key), into(name); + assert(name == "john"); + + rowset rs = (sql.prepare << "select * from sqlite_sequence"); + rowset::const_iterator it = rs.begin(); + row const& r1 = (*it); + assert(r1.get(0) == "soci_test"); + assert(r1.get(1) == "2"); + } + } + std::cout << "test 4 passed" << std::endl; +} + +struct longlong_table_creator : table_creator_base +{ + longlong_table_creator(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(val number(20))"; + } +}; + +// long long test +void test5() +{ + { + session sql(backEnd, connectString); + + longlong_table_creator tableCreator(sql); + + long long v1 = 1000000000000LL; + assert(v1 / 1000000 == 1000000); + + sql << "insert into soci_test(val) values(:val)", use(v1); + + long long v2 = 0LL; + sql << "select val from soci_test", into(v2); + + assert(v2 == v1); + } + + // vector + { + session sql(backEnd, connectString); + + longlong_table_creator tableCreator(sql); + + std::vector v1; + v1.push_back(1000000000000LL); + v1.push_back(1000000000001LL); + v1.push_back(1000000000002LL); + v1.push_back(1000000000003LL); + v1.push_back(1000000000004LL); + + sql << "insert into soci_test(val) values(:val)", use(v1); + + std::vector v2(10); + sql << "select val from soci_test order by val desc", into(v2); + + assert(v2.size() == 5); + assert(v2[0] == 1000000000004LL); + assert(v2[1] == 1000000000003LL); + assert(v2[2] == 1000000000002LL); + assert(v2[3] == 1000000000001LL); + assert(v2[4] == 1000000000000LL); + } + + std::cout << "test 5 passed" << std::endl; +} + +// DDL Creation objects for common tests +struct table_creator_one : public table_creator_base +{ + table_creator_one(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(id integer, val integer, c char, " + "str varchar(20), sh smallint, ul numeric(20), d float, " + "tm datetime, i1 integer, i2 integer, i3 integer, " + "name varchar(20))"; + } +}; + +struct table_creator_two : public table_creator_base +{ + table_creator_two(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(num_float float, num_int integer," + " name varchar(20), sometime datetime, chr char)"; + } +}; + +struct table_creator_three : public table_creator_base +{ + table_creator_three(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(name varchar(100) not null, " + "phone varchar(15))"; + } +}; + +// Originally, submitted to SQLite3 backend and later moved to common test. +// Test commit b394d039530f124802d06c3b1a969c3117683152 +// Author: Mika Fischer +// Date: Thu Nov 17 13:28:07 2011 +0100 +// Implement get_affected_rows for SQLite3 backend +struct table_creator_for_get_affected_rows : table_creator_base +{ + table_creator_for_get_affected_rows(session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(val integer)"; + } +}; + +// +// Support for SOCI Common Tests +// + +class test_context : public test_context_base +{ +public: + test_context(backend_factory const &backEnd, + std::string const &connectString) + : test_context_base(backEnd, connectString) {} + + table_creator_base* table_creator_1(session& s) const + { + return new table_creator_one(s); + } + + table_creator_base* table_creator_2(session& s) const + { + return new table_creator_two(s); + } + + table_creator_base* table_creator_3(session& s) const + { + return new table_creator_three(s); + } + + table_creator_base* table_creator_4(session& s) const + { + return new table_creator_for_get_affected_rows(s); + } + + std::string to_date_time(std::string const &datdt_string) const + { + return "datetime(\'" + datdt_string + "\')"; + } +}; + +int main(int argc, char** argv) +{ + +#ifdef _MSC_VER + // Redirect errors, unrecoverable problems, and assert() failures to STDERR, + // instead of debug message window. + // This hack is required to run asser()-driven tests by Buildbot. + // NOTE: Comment this 2 lines for debugging with Visual C++ debugger to catch assertions inside. + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); +#endif //_MSC_VER + + if (argc == 2) + { + connectString = argv[1]; + } + else + { + // If no file name is specfied then work in-memory + connectString = ":memory:"; + } + + try + { + test_context tc(backEnd, connectString); + common_tests tests(tc); + tests.run(); + + std::cout << "\nSOCI sqlite3 Tests:\n\n"; + + test1(); + test2(); + test3(); + test4(); + test5(); + + std::cout << "\nOK, all tests passed.\n\n"; + + return EXIT_SUCCESS; + } + catch (soci::soci_error const & e) + { + std::cout << "SOCIERROR: " << e.what() << '\n'; + } + catch (std::exception const & e) + { + std::cout << "EXCEPTION: " << e.what() << '\n'; + } + + return EXIT_FAILURE; +} diff --git a/src/backends/sqlite3/vector-into-type.cpp b/src/backends/sqlite3/vector-into-type.cpp new file mode 100644 index 0000000000..1c85615b04 --- /dev/null +++ b/src/backends/sqlite3/vector-into-type.cpp @@ -0,0 +1,221 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, David Courtney +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#include +#include "soci-sqlite3.h" +#include "common.h" +// std +#include +#include +#include +#include +#include +#include + +using namespace soci; +using namespace soci::details; +using namespace soci::details::sqlite3; + +void sqlite3_vector_into_type_backend::define_by_pos( + int& position, void* data, exchange_type type) +{ + data_ = data; + type_ = type; + position_ = position++; +} + +void sqlite3_vector_into_type_backend::pre_fetch() +{ + // ... +} + +namespace // anonymous +{ + +template +void set_in_vector(void* p, int indx, T const& val) +{ + assert(NULL != p); + + std::vector* dest = static_cast*>(p); + std::vector& v = *dest; + v[indx] = val; +} + +} // namespace anonymous + +void sqlite3_vector_into_type_backend::post_fetch(bool gotData, indicator * ind) +{ + if (!gotData) + { + // no data retrieved + return; + } + + int const endRow = static_cast(statement_.dataCache_.size()); + for (int i = 0; i < endRow; ++i) + { + sqlite3_column const& curCol = + statement_.dataCache_[i][position_-1]; + + if (curCol.isNull_) + { + if (ind == NULL) + { + throw soci_error( + "Null value fetched and no indicator defined."); + } + ind[i] = i_null; + + // no need to convert data if it is null, go to next row + continue; + } + else + { + if (ind != NULL) + { + ind[i] = i_ok; + } + } + + const char * buf = curCol.data_.c_str(); + + // set buf to a null string if a null pointer is returned + if (buf == NULL) + { + buf = ""; + } + + switch (type_) + { + case x_char: + set_in_vector(data_, i, *buf); + break; + case x_stdstring: + set_in_vector(data_, i, buf); + break; + case x_short: + { + short const val = string_to_integer(buf); + set_in_vector(data_, i, val); + } + break; + case x_integer: + { + int const val = string_to_integer(buf); + set_in_vector(data_, i, val); + } + break; + case x_long_long: + { + long long const val = string_to_integer(buf); + set_in_vector(data_, i, val); + } + break; + case x_unsigned_long_long: + { + unsigned long long const val + = string_to_unsigned_integer(buf); + set_in_vector(data_, i, val); + } + break; + case x_double: + { + double const val = strtod(buf, NULL); + set_in_vector(data_, i, val); + } + break; + case x_stdtm: + { + // attempt to parse the string and convert to std::tm + std::tm t; + parse_std_tm(buf, t); + + set_in_vector(data_, i, t); + } + break; + default: + throw soci_error("Into element used with non-supported type."); + } + } +} + +void sqlite3_vector_into_type_backend::resize(std::size_t sz) +{ + switch (type_) + { + // simple cases + case x_char: + resize_vector(data_, sz); + break; + case x_short: + resize_vector(data_, sz); + break; + case x_integer: + resize_vector(data_, sz); + break; + case x_long_long: + resize_vector(data_, sz); + break; + case x_unsigned_long_long: + resize_vector(data_, sz); + break; + case x_double: + resize_vector(data_, sz); + break; + case x_stdstring: + resize_vector(data_, sz); + break; + case x_stdtm: + resize_vector(data_, sz); + break; + default: + throw soci_error("Into vector element used with non-supported type."); + } +} + +std::size_t sqlite3_vector_into_type_backend::size() +{ + std::size_t sz = 0; // dummy initialization to please the compiler + switch (type_) + { + // simple cases + case x_char: + sz = get_vector_size(data_); + break; + case x_short: + sz = get_vector_size(data_); + break; + case x_integer: + sz = get_vector_size(data_); + break; + case x_long_long: + sz = get_vector_size(data_); + break; + case x_unsigned_long_long: + sz = get_vector_size(data_); + break; + case x_double: + sz = get_vector_size(data_); + break; + case x_stdstring: + sz = get_vector_size(data_); + break; + case x_stdtm: + sz = get_vector_size(data_); + break; + default: + throw soci_error("Into vector element used with non-supported type."); + } + + return sz; +} + +void sqlite3_vector_into_type_backend::clean_up() +{ + // ... +} diff --git a/src/backends/sqlite3/vector-use-type.cpp b/src/backends/sqlite3/vector-use-type.cpp new file mode 100644 index 0000000000..67a3534e79 --- /dev/null +++ b/src/backends/sqlite3/vector-use-type.cpp @@ -0,0 +1,260 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, David Courtney +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#include "soci-sqlite3.h" +#include +#include "common.h" +// std +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(disable:4355 4996) +#define snprintf _snprintf // TODO: use soci-platform.h +#endif + +using namespace soci; +using namespace soci::details; +using namespace soci::details::sqlite3; + +void sqlite3_vector_use_type_backend::bind_by_pos(int & position, + void * data, + exchange_type type) +{ + if (statement_.boundByName_) + { + throw soci_error( + "Binding for use elements must be either by position or by name."); + } + + data_ = data; + type_ = type; + position_ = position++; + + statement_.boundByPos_ = true; +} + +void sqlite3_vector_use_type_backend::bind_by_name(std::string const & name, + void * data, + exchange_type type) +{ + if (statement_.boundByPos_) + { + throw soci_error( + "Binding for use elements must be either by position or by name."); + } + + data_ = data; + type_ = type; + name_ = ":" + name; + + statement_.reset_if_needed(); + position_ = sqlite3_bind_parameter_index(statement_.stmt_, name_.c_str()); + + if (0 == position_) + { + std::ostringstream ss; + ss << "Cannot bind (by name) to " << name_; + throw soci_error(ss.str()); + } + statement_.boundByName_ = true; +} + +void sqlite3_vector_use_type_backend::pre_use(indicator const * ind) +{ + std::size_t const vsize = size(); + + // make sure that useData can hold enough rows + if (statement_.useData_.size() != vsize) + { + statement_.useData_.resize(vsize); + } + + int const pos = position_ - 1; + + for (size_t i = 0; i != vsize; ++i) + { + char *buf = 0; + + // make sure that each row can accomodate the number of columns + if (statement_.useData_[i].size() < static_cast(position_)) + { + statement_.useData_[i].resize(position_); + } + + // the data in vector can be either i_ok or i_null + if (ind != NULL && ind[i] == i_null) + { + statement_.useData_[i][pos].isNull_ = true; + statement_.useData_[i][pos].data_ = ""; + statement_.useData_[i][pos].blobBuf_ = 0; + statement_.useData_[i][pos].blobSize_ = 0; + } + else + { + // allocate and fill the buffer with text-formatted client data + switch (type_) + { + case x_char: + { + std::vector *pv + = static_cast *>(data_); + std::vector &v = *pv; + + buf = new char[2]; + buf[0] = v[i]; + buf[1] = '\0'; + } + break; + case x_stdstring: + { + std::vector *pv + = static_cast *>(data_); + std::vector &v = *pv; + + buf = new char[v[i].size() + 1]; + std::strcpy(buf, v[i].c_str()); + } + break; + case x_short: + { + std::vector *pv + = static_cast *>(data_); + std::vector &v = *pv; + + std::size_t const bufSize + = std::numeric_limits::digits10 + 3; + buf = new char[bufSize]; + snprintf(buf, bufSize, "%d", static_cast(v[i])); + } + break; + case x_integer: + { + std::vector *pv + = static_cast *>(data_); + std::vector &v = *pv; + + std::size_t const bufSize + = std::numeric_limits::digits10 + 3; + buf = new char[bufSize]; + snprintf(buf, bufSize, "%d", v[i]); + } + break; + case x_long_long: + { + std::vector *pv + = static_cast *>(data_); + std::vector &v = *pv; + + std::size_t const bufSize + = std::numeric_limits::digits10 + 3; + buf = new char[bufSize]; + snprintf(buf, bufSize, "%" LL_FMT_FLAGS "d", v[i]); + } + break; + case x_unsigned_long_long: + { + std::vector* pv + = static_cast*>(data_); + std::vector& v = *pv; + + std::size_t const bufSize + = std::numeric_limits::digits10 + 2; + buf = new char[bufSize]; + snprintf(buf, bufSize, "%" LL_FMT_FLAGS "u", v[i]); + } + break; + case x_double: + { + // no need to overengineer it (KISS)... + + std::vector *pv + = static_cast *>(data_); + std::vector &v = *pv; + + std::size_t const bufSize = 100; + buf = new char[bufSize]; + + snprintf(buf, bufSize, "%.20g", v[i]); + } + break; + case x_stdtm: + { + std::vector *pv + = static_cast *>(data_); + std::vector &v = *pv; + + std::size_t const bufSize = 20; + buf = new char[bufSize]; + + snprintf(buf, bufSize, "%d-%02d-%02d %02d:%02d:%02d", + v[i].tm_year + 1900, v[i].tm_mon + 1, v[i].tm_mday, + v[i].tm_hour, v[i].tm_min, v[i].tm_sec); + } + break; + default: + throw soci_error( + "Use vector element used with non-supported type."); + } + + statement_.useData_[i][pos].isNull_ = false; + statement_.useData_[i][pos].data_ = buf; + statement_.useData_[i][pos].blobBuf_ = 0; + statement_.useData_[i][pos].blobSize_ = 0; + } + + if (buf) + { + delete [] buf; + } + } +} + +std::size_t sqlite3_vector_use_type_backend::size() +{ + std::size_t sz = 0; // dummy initialization to please the compiler + switch (type_) + { + // simple cases + case x_char: + sz = get_vector_size(data_); + break; + case x_short: + sz = get_vector_size(data_); + break; + case x_integer: + sz = get_vector_size(data_); + break; + case x_long_long: + sz = get_vector_size(data_); + break; + case x_unsigned_long_long: + sz = get_vector_size(data_); + break; + case x_double: + sz = get_vector_size(data_); + break; + case x_stdstring: + sz = get_vector_size(data_); + break; + case x_stdtm: + sz = get_vector_size(data_); + break; + default: + throw soci_error("Use vector element used with non-supported type."); + } + + return sz; +} + +void sqlite3_vector_use_type_backend::clean_up() +{ + // ... +} diff --git a/src/cmake/.gitignore b/src/cmake/.gitignore new file mode 100644 index 0000000000..61caff875d --- /dev/null +++ b/src/cmake/.gitignore @@ -0,0 +1,21 @@ +*~ +*.kdev[0-9] +*.swp +aclocal.m4 +autom4te.cache +confdefs.h +config.guess +config.log +config.status +config.sub +configure +depcomp +install-sh +libtool +ltmain.sh +m4 +missing +Makefile +Makefile.in +tmp + diff --git a/src/cmake/CMakeLists.txt b/src/cmake/CMakeLists.txt new file mode 100644 index 0000000000..317236e6a0 --- /dev/null +++ b/src/cmake/CMakeLists.txt @@ -0,0 +1,13 @@ +############################################################################### +# +# This file is part of CMake configuration for SOCI library +# +# Copyright (C) 2009 Mateusz Loskot +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) +# +############################################################################### + +# install the cmake modules +file(GLOB SOCI_CMAKE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/*.cmake") diff --git a/src/cmake/SociBackend.cmake b/src/cmake/SociBackend.cmake new file mode 100644 index 0000000000..5551086e3b --- /dev/null +++ b/src/cmake/SociBackend.cmake @@ -0,0 +1,349 @@ +################################################################################ +# SociBackend.cmake - part of CMake configuration of SOCI library +################################################################################ +# Copyright (C) 2010 Mateusz Loskot +# +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) +################################################################################ +# Macros in this module: +# +# soci_backend +# - defines project of a database backend for SOCI library +# +# soci_backend_test +# - defines test project of a database backend for SOCI library +################################################################################ + +# Defines project of a database backend for SOCI library +# +# soci_backend(backendname +# HEADERS header1 header2 +# DEPENDS dependency1 dependency2 +# DESCRIPTION description +# AUTHORS author1 author2 +# MAINTAINERS maintainer1 maintainer2) +# +macro(soci_backend NAME) + parse_arguments(THIS_BACKEND + "HEADERS;DEPENDS;DESCRIPTION;AUTHORS;MAINTAINERS;" + "" + ${ARGN}) + + colormsg(HIGREEN "${NAME} - ${THIS_BACKEND_DESCRIPTION}") + + # Backend name variants utils + string(TOLOWER "${PROJECT_NAME}" PROJECTNAMEL) + string(TOLOWER "${NAME}" NAMEL) + string(TOUPPER "${NAME}" NAMEU) + + # Backend option available to user + set(THIS_BACKEND_OPTION SOCI_${NAMEU}) + option(${THIS_BACKEND_OPTION} + "Attempt to build ${PROJECT_NAME} backend for ${NAME}" ON) + + # Determine required dependencies + set(THIS_BACKEND_DEPENDS_INCLUDE_DIRS) + set(THIS_BACKEND_DEPENDS_LIBRARIES) + set(THIS_BACKEND_DEPENDS_DEFS) + set(DEPENDS_NOT_FOUND) + + # CMake 2.8+ syntax only: + #foreach(dep IN LISTS THIS_BACKEND_DEPENDS) + foreach(dep ${THIS_BACKEND_DEPENDS}) + + soci_check_package_found(${dep} DEPEND_FOUND) + if(NOT DEPEND_FOUND) + list(APPEND DEPENDS_NOT_FOUND ${dep}) + else() + string(TOUPPER "${dep}" DEPU) + list(APPEND THIS_BACKEND_DEPENDS_INCLUDE_DIRS ${${DEPU}_INCLUDE_DIR}) + list(APPEND THIS_BACKEND_DEPENDS_INCLUDE_DIRS ${${DEPU}_INCLUDE_DIRS}) + list(APPEND THIS_BACKEND_DEPENDS_LIBRARIES ${${DEPU}_LIBRARIES}) + list(APPEND THIS_BACKEND_DEPENDS_DEFS -DHAVE_${DEPU}=1) + endif() + endforeach() + + list(LENGTH DEPENDS_NOT_FOUND NOT_FOUND_COUNT) + + if (NOT_FOUND_COUNT GREATER 0) + + colormsg(_RED_ "WARNING:") + colormsg(RED "Some required dependencies of ${NAME} backend not found:") + + if (${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION} LESS 2.8) + foreach(dep ${DEPENDS_NOT_FOUND}) + colormsg(RED " ${dep}") + endforeach() + else() + foreach(dep IN LISTS DEPENDS_NOT_FOUND) + colormsg(RED " ${dep}") + endforeach() + endif() + + # TODO: Abort or warn compilation may fail? --mloskot + colormsg(RED "Skipping") + + set(${THIS_BACKEND_OPTION} OFF) + + else(NOT_FOUND_COUNT GREATER 0) + + if(${THIS_BACKEND_OPTION}) + + # Backend-specific include directories + list(APPEND THIS_BACKEND_DEPENDS_INCLUDE_DIRS ${SOCI_SOURCE_DIR}/core) + set_directory_properties(PROPERTIES INCLUDE_DIRECTORIES + "${THIS_BACKEND_DEPENDS_INCLUDE_DIRS}") + + # Backend-specific preprocessor definitions + add_definitions(${THIS_BACKEND_DEPENDS_DEFS}) + + # Backend installable headers and sources + if (NOT THIS_BACKEND_HEADERS) + file(GLOB THIS_BACKEND_HEADERS *.h) + endif() + file(GLOB THIS_BACKEND_SOURCES *.cpp) + set(THIS_BACKEND_HEADERS_VAR SOCI_${NAMEU}_HEADERS) + set(${THIS_BACKEND_HEADERS_VAR} ${THIS_BACKEND_HEADERS}) + + # Group source files for IDE source explorers (e.g. Visual Studio) + source_group("Header Files" FILES ${THIS_BACKEND_HEADERS}) + source_group("Source Files" FILES ${THIS_BACKEND_SOURCES}) + source_group("CMake Files" FILES CMakeLists.txt) + + # Backend target + set(THIS_BACKEND_TARGET ${PROJECTNAMEL}_${NAMEL}) + set(THIS_BACKEND_TARGET_VAR SOCI_${NAMEU}_TARGET) + set(${THIS_BACKEND_TARGET_VAR} ${THIS_BACKEND_TARGET}) + + soci_target_output_name(${THIS_BACKEND_TARGET} ${THIS_BACKEND_TARGET_VAR}_OUTPUT_NAME) + + set(THIS_BACKEND_TARGET_OUTPUT_NAME ${${THIS_BACKEND_TARGET_VAR}_OUTPUT_NAME}) + set(THIS_BACKEND_TARGET_OUTPUT_NAME_VAR ${THIS_BACKEND_TARGET_VAR}_OUTPUT_NAME) + + # TODO: Extract as macros: soci_shared_lib_target and soci_static_lib_target --mloskot + + # Shared library target + if (SOCI_SHARED) + add_library(${THIS_BACKEND_TARGET} + SHARED + ${THIS_BACKEND_SOURCES} + ${THIS_BACKEND_HEADERS}) + + target_link_libraries(${THIS_BACKEND_TARGET} + ${SOCI_CORE_TARGET} + ${THIS_BACKEND_DEPENDS_LIBRARIES}) + + if(WIN32) + set_target_properties(${THIS_BACKEND_TARGET} + PROPERTIES + OUTPUT_NAME ${THIS_BACKEND_TARGET_OUTPUT_NAME} + DEFINE_SYMBOL SOCI_DLL) + else() + set_target_properties(${THIS_BACKEND_TARGET} + PROPERTIES + SOVERSION ${${PROJECT_NAME}_SOVERSION} + INSTALL_NAME_DIR ${CMAKE_INSTALL_PREFIX}/lib) + endif() + + set_target_properties(${THIS_BACKEND_TARGET} + PROPERTIES + VERSION ${${PROJECT_NAME}_VERSION} + CLEAN_DIRECT_OUTPUT 1) + endif() + + # Static library target + if (SOCI_STATIC) + set(THIS_BACKEND_TARGET_STATIC ${THIS_BACKEND_TARGET}_static) + + add_library(${THIS_BACKEND_TARGET_STATIC} + STATIC + ${THIS_BACKEND_SOURCES} + ${THIS_BACKEND_HEADERS}) + + set_target_properties(${THIS_BACKEND_TARGET_STATIC} + PROPERTIES + OUTPUT_NAME ${THIS_BACKEND_TARGET_OUTPUT_NAME} + PREFIX "lib" + CLEAN_DIRECT_OUTPUT 1) + endif() + + # Backend installation + install(FILES ${THIS_BACKEND_HEADERS} + DESTINATION + ${INCLUDEDIR}/${PROJECTNAMEL}/${NAMEL}) + + if (SOCI_SHARED) + install(TARGETS ${THIS_BACKEND_TARGET} + RUNTIME DESTINATION ${BINDIR} + LIBRARY DESTINATION ${LIBDIR} + ARCHIVE DESTINATION ${LIBDIR}) + endif() + + if (SOCI_SHARED) + install(TARGETS ${THIS_BACKEND_TARGET_STATIC} + RUNTIME DESTINATION ${BINDIR} + LIBRARY DESTINATION ${LIBDIR} + ARCHIVE DESTINATION ${LIBDIR}) + endif() + + else() + colormsg(HIRED "${NAME}" RED "backend disabled, since") + endif() + + endif(NOT_FOUND_COUNT GREATER 0) + + boost_report_value(${THIS_BACKEND_OPTION}) + + if(${THIS_BACKEND_OPTION}) + boost_report_value(${THIS_BACKEND_TARGET_VAR}) + boost_report_value(${THIS_BACKEND_TARGET_OUTPUT_NAME_VAR}) + boost_report_value(${THIS_BACKEND_HEADERS_VAR}) + + soci_report_directory_property(COMPILE_DEFINITIONS) + endif() + + # LOG + #message("soci_backend:") + #message("NAME: ${NAME}") + #message("${THIS_BACKEND_OPTION} = ${SOCI_BACKEND_SQLITE3}") + #message("DEPENDS: ${THIS_BACKEND_DEPENDS}") + #message("DESCRIPTION: ${THIS_BACKEND_DESCRIPTION}") + #message("AUTHORS: ${THIS_BACKEND_AUTHORS}") + #message("MAINTAINERS: ${THIS_BACKEND_MAINTAINERS}") + #message("HEADERS: ${THIS_BACKEND_HEADERS}") + #message("SOURCES: ${THIS_BACKEND_SOURCES}") + #message("DEPENDS_LIBRARIES: ${THIS_BACKEND_DEPENDS_LIBRARIES}") + #message("DEPENDS_INCLUDE_DIRS: ${THIS_BACKEND_DEPENDS_INCLUDE_DIRS}") +endmacro() + +# Generates .vcxproj.user for target of each test. +# +# soci_backend_test_create_vcxproj_user( +# PostgreSQLTest +# "host=localhost dbname=soci_test user=mloskot") +# +function(soci_backend_test_create_vcxproj_user TARGET_NAME TEST_CMD_ARGS) + if(MSVC) + set(SYSTEM_NAME $ENV{USERDOMAIN}) + set(USER_NAME $ENV{USERNAME}) + set(SOCI_TEST_CMD_ARGS ${TEST_CMD_ARGS}) + + if(MSVC_VERSION EQUAL 1600) + configure_file( + ${SOCI_SOURCE_DIR}/cmake/resources/vs2010-test-cmd-args.vcxproj.user.in + ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}.vcxproj.user + @ONLY) + endif() + endif() +endfunction(soci_backend_test_create_vcxproj_user) + +# Defines test project of a database backend for SOCI library +# +# soci_backend_test(BACKEND mybackend SOURCE mytest1.cpp +# NAME mytest1 +# CONNSTR "my test connection" +# DEPENDS library1 library2) +# +macro(soci_backend_test) + parse_arguments(THIS_TEST + "BACKEND;SOURCE;CONNSTR;NAME;DEPENDS;" + "" + ${ARGN}) + + # Test backend name + string(TOUPPER "${THIS_TEST_BACKEND}" BACKENDU) + string(TOLOWER "${THIS_TEST_BACKEND}" BACKENDL) + + if(SOCI_TESTS AND SOCI_${BACKENDU} AND NOT SOCI_${BACKENDU}_DO_NOT_TEST) + + # Test name + if(THIS_TEST_NAME) + string(TOUPPER "${THIS_TEST_NAME}" NAMEU) + set(TEST_FULL_NAME SOCI_${BACKENDU}_TEST_${NAMEU}) + else() + set(TEST_FULL_NAME SOCI_${BACKENDU}_TEST) + endif() + + set(TEST_CONNSTR_VAR ${TEST_FULL_NAME}_CONNSTR) + set(${TEST_CONNSTR_VAR} "" + CACHE STRING "Connection string for ${BACKENDU} test") + + if(NOT ${TEST_CONNSTR_VAR} AND THIS_TEST_CONNSTR) + set(${TEST_CONNSTR_VAR} ${THIS_TEST_CONNSTR}) + endif() + boost_report_value(${TEST_CONNSTR_VAR}) + + include_directories(${SOCI_SOURCE_DIR}/core/test) + include_directories(${SOCI_SOURCE_DIR}/backends/${BACKENDL}) + + # TODO: Find more generic way of adding Boost to core and backend tests only. + # Ideally, from within Boost.cmake. + set(SOCI_TEST_DEPENDENCIES) + if(Boost_FOUND) + include_directories(${Boost_INCLUDE_DIRS}) + if(Boost_DATE_TIME_FOUND) + set(SOCI_TEST_DEPENDENCIES ${Boost_DATE_TIME_LIBRARY}) + add_definitions(-DHAVE_BOOST_DATE_TIME=1) + endif() + endif() + + string(TOLOWER "${TEST_FULL_NAME}" TEST_TARGET) + + set(TEST_HEADERS ${PROJECT_SOURCE_DIR}/core/test/common-tests.h) + + # Shared libraries test + add_executable(${TEST_TARGET} ${TEST_HEADERS} ${THIS_TEST_SOURCE}) + + target_link_libraries(${TEST_TARGET} + ${SOCI_CORE_TARGET} + ${SOCI_${BACKENDU}_TARGET} + ${${BACKENDU}_LIBRARIES} + ${SOCI_TEST_DEPENDENCIES}) + + add_test(${TEST_TARGET} + ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TEST_TARGET} + ${${TEST_CONNSTR_VAR}}) + + soci_backend_test_create_vcxproj_user(${TEST_TARGET} "\"${${TEST_CONNSTR_VAR}}\"") + + # Static libraries test + if(SOCI_STATIC) + set(TEST_TARGET_STATIC ${TEST_TARGET}_static) + + add_executable(${TEST_TARGET_STATIC} ${TEST_HEADERS} ${THIS_TEST_SOURCE}) + + target_link_libraries(${TEST_TARGET_STATIC} + ${SOCI_CORE_TARGET_STATIC} + ${SOCI_${BACKENDU}_TARGET}_static + ${${BACKENDU}_LIBRARIES} + ${SOCI_CORE_STATIC_DEPENDENCIES} + ${SOCI_TEST_DEPENDENCIES}) + + add_test(${TEST_TARGET_STATIC} + ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TEST_TARGET_STATIC} + ${${TEST_CONNSTR_VAR}}) + + soci_backend_test_create_vcxproj_user(${TEST_TARGET_STATIC} "\"${${TEST_CONNSTR_VAR}}\"") + endif(SOCI_STATIC) + + # Ask make check to try to build tests first before executing them + add_dependencies(check ${TEST_TARGET} ${TEST_TARGET_STATIC}) + + # Group source files for IDE source explorers (e.g. Visual Studio) + source_group("Header Files" FILES ${TEST_HEADERS}) + source_group("Source Files" FILES ${THIS_TEST_SOURCE}) + source_group("CMake Files" FILES CMakeLists.txt) + + endif() + + # LOG + #message("NAME=${NAME}") + #message("THIS_TEST_NAME=${THIS_TEST_NAME}") + #message("THIS_TEST_BACKEND=${THIS_TEST_BACKEND}") + #message("THIS_TEST_CONNSTR=${THIS_TEST_CONNSTR}") + #message("THIS_TEST_SOURCE=${THIS_TEST_SOURCE}") + #message("THIS_TEST_OPTION=${THIS_TEST_OPTION}") + +endmacro() diff --git a/src/cmake/SociConfig.cmake b/src/cmake/SociConfig.cmake new file mode 100644 index 0000000000..fd301b221d --- /dev/null +++ b/src/cmake/SociConfig.cmake @@ -0,0 +1,53 @@ +################################################################################ +# SociConfig.cmake - CMake build configuration of SOCI library +################################################################################ +# Copyright (C) 2010 Mateusz Loskot +# +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) +################################################################################ + +# +# Force compilation flags and set desired warnings level +# + +if (MSVC) + if (MSVC80 OR MSVC90 OR MSVC10) + add_definitions(-D_CRT_SECURE_NO_DEPRECATE) + add_definitions(-D_CRT_SECURE_NO_WARNINGS) + add_definitions(-D_CRT_NONSTDC_NO_WARNING) + add_definitions(-D_SCL_SECURE_NO_WARNINGS) + endif() + + if(CMAKE_CXX_FLAGS MATCHES "/W[0-4]") + string(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4") + endif() + +else() + + set(SOCI_GCC_CLANG_COMMON_FLAGS + "-pedantic -ansi -Wall -Wpointer-arith -Wcast-align -Wcast-qual -Wfloat-equal -Wredundant-decls -Wno-long-long") + + if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) + + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC ${SOCI_GCC_CLANG_COMMON_FLAGS}") + if (CMAKE_COMPILER_IS_GNUCXX) + if (CMAKE_SYSTEM_NAME MATCHES "FreeBSD") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++98") + else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++98") + endif() + endif() + + elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" OR "${CMAKE_CXX_COMPILER}" MATCHES "clang") + + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${SOCI_GCC_CLANG_COMMON_FLAGS}") + + else() + message(FATAL_ERROR "CMake is unable to recognize compilation toolset to build SOCI for you!") + endif() + +endif() diff --git a/src/cmake/SociDependencies.cmake b/src/cmake/SociDependencies.cmake new file mode 100644 index 0000000000..3c12771a7d --- /dev/null +++ b/src/cmake/SociDependencies.cmake @@ -0,0 +1,82 @@ +################################################################################ +# SociDependencies.cmake - part of CMake configuration of SOCI library +# +# Based on BoostExternals.cmake from CMake configuration for Boost +################################################################################ +# Copyright (C) 2010 Mateusz Loskot +# Copyright (C) 2009 Troy Straszheim +# +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) +################################################################################ +# Macros in this module: +# +# soci_backend - defines a database backend for SOCI library +# +################################################################################ + +# +# List of SOCI dependncies +# +set(SOCI_BACKENDS_DB_DEPENDENCIES + MySQL + ODBC + Oracle + PostgreSQL + SQLite3 + Firebird + DB2) + +set(SOCI_BACKENDS_ALL_DEPENDENCIES + Boost + ${SOCI_BACKENDS_DB_DEPENDENCIES}) + +# +# Perform checks +# +colormsg(_HIBLUE_ "Looking for SOCI dependencies:") + +macro(boost_external_report NAME) + + set(VARNAME ${NAME}) + set(SUCCESS ${${VARNAME}_FOUND}) + + set(VARNAMES ${ARGV}) + list(REMOVE_AT VARNAMES 0) + + # Test both, given original name and uppercase version too + if(NOT SUCCESS) + string(TOUPPER ${NAME} VARNAME) + set(SUCCESS ${${VARNAME}_FOUND}) + if(NOT SUCCESS) + colormsg(_RED_ "WARNING:") + colormsg(RED "${NAME} not found, some libraries or features will be disabled.") + colormsg(RED "See the documentation for ${NAME} or manually set these variables:") + endif() + endif() + + foreach(variable ${VARNAMES}) + boost_report_value(${VARNAME}_${variable}) + endforeach() +endmacro() + +# +# Some externals default to OFF +# +option(WITH_VALGRIND "Run tests under valgrind" OFF) + +# +# Detect available dependencies +# +foreach(external ${SOCI_BACKENDS_ALL_DEPENDENCIES}) + string(TOUPPER "${external}" EXTERNAL) + option(WITH_${EXTERNAL} "Attempt to find and configure ${external}" ON) + if(WITH_${EXTERNAL}) + colormsg(HICYAN "${external}:") + include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/dependencies/${external}.cmake) + else() + set(${EXTERNAL}_FOUND FALSE CACHE BOOL "${external} found" FORCE) + colormsg(HIRED "${external}:" RED "disabled, since WITH_${EXTERNAL}=OFF") + endif() +endforeach() diff --git a/src/cmake/SociSystemInfo.cmake b/src/cmake/SociSystemInfo.cmake new file mode 100644 index 0000000000..35ad28b5c2 --- /dev/null +++ b/src/cmake/SociSystemInfo.cmake @@ -0,0 +1,82 @@ +################################################################################ +# SociSystemInfo.cmake - part of CMake configuration of SOCI library +# +# Based on idea taken from http://code.google.com/p/softart/ project +################################################################################ +# Copyright (C) 2010 Mateusz Loskot +# +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) +################################################################################ +# The following variables are defined: +# SOCI_COMPILER_NAME - name of compiler toolset, follows Boost toolset naming. +# SOCI_PLATFORM_NAME - target platform name: x64, x86 or win32 +# +# Based on the Pre-defined Compiler Macros +# http://sourceforge.net/p/predef/wiki/Compilers/ +################################################################################ + +set(SOCI_COMPILER_NAME) +set(SOCI_PLATFORM_NAME) + +if(MINGW OR UNIX) + exec_program(${CMAKE_C_COMPILER} ARGS -dumpversion OUTPUT_VARIABLE GCC_VERSION) + string(REPLACE "." "" GCC_VERSION_STR_FULL ${GCC_VERSION}) + string(REGEX MATCH "[0-9]+\\.[0-9]+" GCC_VERSION_MAJOR_MINOR ${GCC_VERSION}) +endif() + +if(WIN32) + if(MSVC) + if(MSVC_VERSION EQUAL 1200) + set(SOCI_COMPILER_NAME "msvc-6.0") + endif() + if(MSVC_VERSION EQUAL 1300) + set(SOCI_COMPILER_NAME "msvc-7.0") + endif() + if(MSVC_VERSION EQUAL 1310) + set(SOCI_COMPILER_NAME "msvc-7.1") # Visual Studio 2003 + endif() + if(MSVC_VERSION EQUAL 1400) + set(SOCI_COMPILER_NAME "msvc-8.0") # Visual Studio 2005 + endif() + if(MSVC_VERSION EQUAL 1500) + set(SOCI_COMPILER_NAME "msvc-9.0") # Visual Studio 2008 + endif() + if(MSVC_VERSION EQUAL 1600) + set(SOCI_COMPILER_NAME "msvc-10.0") # Visual Studio 2010 + endif() + if(MSVC_VERSION EQUAL 1700) + set(SOCI_COMPILER_NAME "msvc-11.0") # Visual Studio 2012 + endif() + endif(MSVC) + + if(MINGW) + set(SOCI_COMPILER_NAME "mingw-${GCC_VERSION}") + endif( MINGW ) + + if(CMAKE_GENERATOR MATCHES "Win64") + set(SOCI_PLATFORM_NAME "x64") + else() + set(SOCI_PLATFORM_NAME "win32") + endif() +endif(WIN32) + +if(UNIX) + set(SOCI_COMPILER_NAME "gcc-${GCC_VERSION}") + if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64") + set(SOCI_PLATFORM_NAME "x64") + else() + set(SOCI_PLATFORM_NAME "x86") + endif() +endif(UNIX) + +if(NOT SOCI_COMPILER_NAME) + colormsg(_RED_ "WARNING:") + colormsg(RED "Could not determine compiler toolset name to set SOCI_COMPILER_NAME variable.") +endif() + +if(NOT SOCI_PLATFORM_NAME) + colormsg(_RED_ "WARNING:") + colormsg(RED "Could not determine platform name to set SOCI_PLATFORM_NAME variable.") +endif() diff --git a/src/cmake/SociUtilities.cmake b/src/cmake/SociUtilities.cmake new file mode 100644 index 0000000000..05e91532ff --- /dev/null +++ b/src/cmake/SociUtilities.cmake @@ -0,0 +1,416 @@ +################################################################################ +# SociUtilities.cmake - part of CMake configuration of SOCI library +# +# Based on BoostUtilities.cmake from CMake configuration for Boost +################################################################################ +# Copyright (C) 2007 Douglas Gregor +# Copyright (C) 2007 Troy Straszheim +# Copyright (C) 2010 Mateusz Loskot +# +# Distributed under the Boost Software License, Version 1.0. +# See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt +################################################################################ +# Macros in this module: +# +# list_contains: Determine whether a string value is in a list. +# +# car: Return the first element in a list +# +# cdr: Return all but the first element in a list +# +# parse_arguments: Parse keyword arguments for use in other macros. +# +# soci_check_package_found: Test varname-FOUND for case-insensitive varname +# +################################################################################ + +# This utility macro determines whether a particular string value +# occurs within a list of strings: +# +# list_contains(result string_to_find arg1 arg2 arg3 ... argn) +# +# This macro sets the variable named by result equal to TRUE if +# string_to_find is found anywhere in the following arguments. +macro(list_contains var value) + set(${var}) + foreach (value2 ${ARGN}) + if (${value} STREQUAL ${value2}) + set(${var} TRUE) + endif (${value} STREQUAL ${value2}) + endforeach (value2) +endmacro(list_contains) + +# This utility macro extracts the first argument from the list of +# arguments given, and places it into the variable named var. +# +# car(var arg1 arg2 ...) +macro(car var) + set(${var} ${ARGV1}) +endmacro(car) + +# This utility macro extracts all of the arguments given except the +# first, and places them into the variable named var. +# +# car(var arg1 arg2 ...) +macro(cdr var junk) + set(${var} ${ARGN}) +endmacro(cdr) + +# The parse_arguments macro will take the arguments of another macro and +# define several variables. The first argument to parse_arguments is a +# prefix to put on all variables it creates. The second argument is a +# list of names, and the third argument is a list of options. Both of +# these lists should be quoted. The rest of parse_arguments are +# arguments from another macro to be parsed. +# +# parse_arguments(prefix arg_names options arg1 arg2...) +# +# For each item in options, parse_arguments will create a variable with +# that name, prefixed with prefix_. So, for example, if prefix is +# MY_MACRO and options is OPTION1;OPTION2, then parse_arguments will +# create the variables MY_MACRO_OPTION1 and MY_MACRO_OPTION2. These +# variables will be set to true if the option exists in the command line +# or false otherwise. +# +# For each item in arg_names, parse_arguments will create a variable +# with that name, prefixed with prefix_. Each variable will be filled +# with the arguments that occur after the given arg_name is encountered +# up to the next arg_name or the end of the arguments. All options are +# removed from these lists. parse_arguments also creates a +# prefix_DEFAULT_ARGS variable containing the list of all arguments up +# to the first arg_name encountered. +macro(parse_arguments prefix arg_names option_names) + set(DEFAULT_ARGS) + foreach(arg_name ${arg_names}) + set(${prefix}_${arg_name}) + endforeach(arg_name) + foreach(option ${option_names}) + set(${prefix}_${option} FALSE) + endforeach(option) + + set(current_arg_name DEFAULT_ARGS) + set(current_arg_list) + foreach(arg ${ARGN}) + list_contains(is_arg_name ${arg} ${arg_names}) + if (is_arg_name) + set(${prefix}_${current_arg_name} ${current_arg_list}) + set(current_arg_name ${arg}) + set(current_arg_list) + else (is_arg_name) + list_contains(is_option ${arg} ${option_names}) + if (is_option) + set(${prefix}_${arg} TRUE) + else (is_option) + set(current_arg_list ${current_arg_list} ${arg}) + endif (is_option) + endif (is_arg_name) + endforeach(arg) + set(${prefix}_${current_arg_name} ${current_arg_list}) +endmacro(parse_arguments) + +# Perform a reverse topological sort on the given LIST. +# +# topological_sort(my_list "MY_" "_EDGES") +# +# LIST is the name of a variable containing a list of elements to be +# sorted in reverse topological order. Each element in the list has a +# set of outgoing edges (for example, those other list elements that +# it depends on). In the resulting reverse topological ordering +# (written back into the variable named LIST), an element will come +# later in the list than any of the elements that can be reached by +# following its outgoing edges and the outgoing edges of any vertices +# they target, recursively. Thus, if the edges represent dependencies +# on build targets, for example, the reverse topological ordering is +# the order in which one would build those targets. +# +# For each element E in this list, the edges for E are contained in +# the variable named ${PREFIX}${E}${SUFFIX}, where E is the +# upper-cased version of the element in the list. If no such variable +# exists, then it is assumed that there are no edges. For example, if +# my_list contains a, b, and c, one could provide a dependency graph +# using the following variables: +# +# MY_A_EDGES b +# MY_B_EDGES +# MY_C_EDGES a b +# +# With the involcation of topological_sort shown above and these +# variables, the resulting reverse topological ordering will be b, a, +# c. +function(topological_sort LIST PREFIX SUFFIX) + # Clear the stack and output variable + set(VERTICES "${${LIST}}") + set(STACK) + set(${LIST}) + + # Loop over all of the vertices, starting the topological sort from + # each one. + foreach(VERTEX ${VERTICES}) + string(TOUPPER ${VERTEX} UPPER_VERTEX) + + # If we haven't already processed this vertex, start a depth-first + # search from where. + if (NOT FOUND_${UPPER_VERTEX}) + # Push this vertex onto the stack with all of its outgoing edges + string(REPLACE ";" " " NEW_ELEMENT + "${VERTEX};${${PREFIX}${UPPER_VERTEX}${SUFFIX}}") + list(APPEND STACK ${NEW_ELEMENT}) + + # We've now seen this vertex + set(FOUND_${UPPER_VERTEX} TRUE) + + # While the depth-first search stack is not empty + list(LENGTH STACK STACK_LENGTH) + while(STACK_LENGTH GREATER 0) + # Remove the vertex and its remaining out-edges from the top + # of the stack + list(GET STACK -1 OUT_EDGES) + list(REMOVE_AT STACK -1) + + # Get the source vertex and the list of out-edges + separate_arguments(OUT_EDGES) + list(GET OUT_EDGES 0 SOURCE) + list(REMOVE_AT OUT_EDGES 0) + + # While there are still out-edges remaining + list(LENGTH OUT_EDGES OUT_DEGREE) + while (OUT_DEGREE GREATER 0) + # Pull off the first outgoing edge + list(GET OUT_EDGES 0 TARGET) + list(REMOVE_AT OUT_EDGES 0) + + string(TOUPPER ${TARGET} UPPER_TARGET) + if (NOT FOUND_${UPPER_TARGET}) + # We have not seen the target before, so we will traverse + # its outgoing edges before coming back to our + # source. This is the key to the depth-first traversal. + + # We've now seen this vertex + set(FOUND_${UPPER_TARGET} TRUE) + + # Push the remaining edges for the current vertex onto the + # stack + string(REPLACE ";" " " NEW_ELEMENT + "${SOURCE};${OUT_EDGES}") + list(APPEND STACK ${NEW_ELEMENT}) + + # Setup the new source and outgoing edges + set(SOURCE ${TARGET}) + string(TOUPPER ${SOURCE} UPPER_SOURCE) + set(OUT_EDGES + ${${PREFIX}${UPPER_SOURCE}${SUFFIX}}) + endif(NOT FOUND_${UPPER_TARGET}) + + list(LENGTH OUT_EDGES OUT_DEGREE) + endwhile (OUT_DEGREE GREATER 0) + + # We have finished all of the outgoing edges for + # SOURCE; add it to the resulting list. + list(APPEND ${LIST} ${SOURCE}) + + # Check the length of the stack + list(LENGTH STACK STACK_LENGTH) + endwhile(STACK_LENGTH GREATER 0) + endif (NOT FOUND_${UPPER_VERTEX}) + endforeach(VERTEX) + + set(${LIST} ${${LIST}} PARENT_SCOPE) +endfunction(topological_sort) + +# Small little hack that tweaks a component name (as used for CPack) +# to make sure to avoid certain names that cause problems. Sets the +# variable named varname to the "sanitized" name. +# +# FIXME: This is a complete hack. We probably need to fix the CPack +# generators (NSIS in particular) to get rid of the need for this. +macro(fix_cpack_component_name varname name) + if (${name} STREQUAL "foreach") + set(${varname} "boost_foreach") + else() + set(${varname} ${name}) + endif() +endmacro() + + +# +# A big shout out to the cmake gurus @ compiz +# +function (colormsg) + string (ASCII 27 _escape) + set(WHITE "29") + set(GRAY "30") + set(RED "31") + set(GREEN "32") + set(YELLOW "33") + set(BLUE "34") + set(MAG "35") + set(CYAN "36") + + foreach (color WHITE GRAY RED GREEN YELLOW BLUE MAG CYAN) + set(HI${color} "1\;${${color}}") + set(LO${color} "2\;${${color}}") + set(_${color}_ "4\;${${color}}") + set(_HI${color}_ "1\;4\;${${color}}") + set(_LO${color}_ "2\;4\;${${color}}") + endforeach() + + set(str "") + set(coloron FALSE) + foreach(arg ${ARGV}) + if (NOT ${${arg}} STREQUAL "") + if (CMAKE_COLOR_MAKEFILE) + set(str "${str}${_escape}[${${arg}}m") + set(coloron TRUE) + endif() + else() + set(str "${str}${arg}") + if (coloron) + set(str "${str}${_escape}[0m") + set(coloron FALSE) + endif() + set(str "${str} ") + endif() + endforeach() + message(STATUS ${str}) +endfunction() + +# colormsg("Colors:" +# WHITE "white" GRAY "gray" GREEN "green" +# RED "red" YELLOW "yellow" BLUE "blue" MAG "mag" CYAN "cyan" +# _WHITE_ "white" _GRAY_ "gray" _GREEN_ "green" +# _RED_ "red" _YELLOW_ "yellow" _BLUE_ "blue" _MAG_ "mag" _CYAN_ "cyan" +# _HIWHITE_ "white" _HIGRAY_ "gray" _HIGREEN_ "green" +# _HIRED_ "red" _HIYELLOW_ "yellow" _HIBLUE_ "blue" _HIMAG_ "mag" _HICYAN_ "cyan" +# HIWHITE "white" HIGRAY "gray" HIGREEN "green" +# HIRED "red" HIYELLOW "yellow" HIBLUE "blue" HIMAG "mag" HICYAN "cyan" +# "right?") + +# +# pretty-prints the value of a variable so that the +# equals signs align +# +function(boost_report_value NAME) + string(LENGTH "${NAME}" varlen) + # LOG + #message(STATUS "boost_report_value: NAME=${NAME} (${varlen})") + #message(STATUS "boost_report_value: \${NAME}=${${NAME}}") + math(EXPR padding_len 40-${varlen}) + string(SUBSTRING " " + 0 ${padding_len} varpadding) + colormsg("${NAME}${varpadding} = ${${NAME}}") +endfunction() + +function(trace NAME) + if(BOOST_CMAKE_TRACE) + string(LENGTH "${NAME}" varlen) + math(EXPR padding_len 40-${varlen}) + string(SUBSTRING "........................................" + 0 ${padding_len} varpadding) + message("${NAME} ${varpadding} ${${NAME}}") + endif() +endfunction() + +# +# pretty-prints the value of a variable so that the +# equals signs align +# +function(boost_report_pretty PRETTYNAME VARNAME) + string(LENGTH "${PRETTYNAME}" varlen) + math(EXPR padding_len 30-${varlen}) + string(SUBSTRING " " + 0 ${padding_len} varpadding) + message(STATUS "${PRETTYNAME}${varpadding} = ${${VARNAME}}") +endfunction() + +# +# assert that ARG is actually a library target +# +macro(dependency_check ARG) + trace(ARG) + if (NOT "${ARG}" STREQUAL "") + get_target_property(deptype ${ARG} TYPE) + if(NOT deptype MATCHES ".*_LIBRARY$") + set(DEPENDENCY_OKAY FALSE) + list(APPEND DEPENDENCY_FAILURES ${ARG}) + endif() + endif() +endmacro() + +# +# Tests package-FOUND for varname in three cases as given, lowercase and +# uppercase. +# +macro(soci_check_package_found NAME SUCCESS) + + set(${SUCCESS} FALSE) + set(VARNAME ${NAME}) + set(VARNAME_SUCCESS ${${VARNAME}_FOUND}) + + # Test both, given original name and uppercase version too + if(VARNAME_SUCCESS) + set(${SUCCESS} TRUE) + else() + string(TOUPPER ${NAME} VARNAME) + set(VARNAME_SUCCESS ${${VARNAME}_FOUND}) + if(VARNAME_SUCCESS) + set(${SUCCESS} TRUE) + endif() + endif() +endmacro() + +# +# Pretty-print of given property of current directory. +# +macro(soci_report_directory_property PROPNAME) + get_directory_property(${PROPNAME} ${PROPNAME}) + boost_report_value(${PROPNAME}) +endmacro() + +# +# Scans the current directory and returns a list of subdirectories. +# Author: Robert Fleming +# Source: http://www.cmake.org/pipermail/cmake/2008-February/020114.html +# +# Third parameter is 1 if you want relative paths returned. +# Usage: list_subdirectories(the_list_is_returned_here /path/to/project TRUE) +# +macro(list_subdirectories retval curdir return_relative) + file(GLOB sub-dir RELATIVE ${curdir} *) + set(list_of_dirs "") + foreach(dir ${sub-dir}) + if(IS_DIRECTORY ${curdir}/${dir}) + if (${return_relative}) + set(list_of_dirs ${list_of_dirs} ${dir}) + else() + set(list_of_dirs ${list_of_dirs} ${curdir}/${dir}) + endif() + endif() + endforeach() + set(${retval} ${list_of_dirs}) +endmacro() + +# +# Generates output name for given target depending on platform and version. +# For instance, on Windows, libraries get ABI version suffix soci_coreXY.{dll|lib}. +# +function(soci_target_output_name TARGET_NAME OUTPUT_NAME) + if(NOT DEFINED TARGET_NAME) + message(SEND_ERROR "Error, the variable TARGET_NAME is not defined!") + endif() + + if(NOT DEFINED ${PROJECT_NAME}_VERSION) + message(SEND_ERROR "Error, the variable ${${PROJECT_NAME}_VERSION} is not defined!") + endif() + + # On Windows, ABI version is specified using binary file name suffix. + # On Unix, suffix is empty and SOVERSION is used instead. + if (WIN32) + string(LENGTH "${${PROJECT_NAME}_ABI_VERSION}" abilen) + if(abilen GREATER 0) + set(SUFFIX "_${${PROJECT_NAME}_ABI_VERSION}") + endif() + endif() + + set(${OUTPUT_NAME} ${TARGET_NAME}${SUFFIX} PARENT_SCOPE) +endfunction() diff --git a/src/cmake/SociVersion.cmake b/src/cmake/SociVersion.cmake new file mode 100644 index 0000000000..e6758502d5 --- /dev/null +++ b/src/cmake/SociVersion.cmake @@ -0,0 +1,57 @@ +################################################################################ +# SociVersion.cmake - part of CMake configuration of SOCI library +################################################################################ +# Copyright (C) 2010 Mateusz Loskot +# +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) +################################################################################ +# Macros in this module: +# +# soci_version - defines version information for SOCI library +# +################################################################################ + +# Defines version information for SOCI library +# +# soci_version(MAJOR major_version MINOR minor_version PATCH patch_level) +# +# MAJOR.MINOR version is used to set SOVERSION +# +macro(soci_version) + parse_arguments(THIS_VERSION "MAJOR;MINOR;PATCH;" + "" + ${ARGN}) + + # Set version components + set(${PROJECT_NAME}_VERSION_MAJOR ${THIS_VERSION_MAJOR}) + set(${PROJECT_NAME}_VERSION_MINOR ${THIS_VERSION_MINOR}) + set(${PROJECT_NAME}_VERSION_PATCH ${THIS_VERSION_PATCH}) + + # Set VERSION string + set(${PROJECT_NAME}_VERSION + "${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_VERSION_MINOR}.${${PROJECT_NAME}_VERSION_PATCH}") + + # Set SOVERSION based on major and minor + set(${PROJECT_NAME}_SOVERSION + "${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_VERSION_MINOR}") + + # Set ABI version string used to name binary output and, by SOCI loader, to find binaries. + # On Windows, ABI version is specified using binary file name suffix. + # On Unix, suffix ix empty and SOVERSION is used instead. + if (UNIX) + set(${PROJECT_NAME}_ABI_VERSION ${${PROJECT_NAME}_SOVERSION}) + elseif(WIN32) + set(${PROJECT_NAME}_ABI_VERSION + "${${PROJECT_NAME}_VERSION_MAJOR}_${${PROJECT_NAME}_VERSION_MINOR}") + else() + message(FATAL_ERROR "Ambiguous target platform with unknown ABI version scheme. Giving up.") + endif() + + boost_report_value(${PROJECT_NAME}_VERSION) + boost_report_value(${PROJECT_NAME}_ABI_VERSION) + + add_definitions(-DSOCI_ABI_VERSION="${${PROJECT_NAME}_ABI_VERSION}") + +endmacro() diff --git a/src/cmake/dependencies/Boost.cmake b/src/cmake/dependencies/Boost.cmake new file mode 100644 index 0000000000..c64af9eee3 --- /dev/null +++ b/src/cmake/dependencies/Boost.cmake @@ -0,0 +1,14 @@ +set(Boost_FIND_QUIETLY TRUE) + +set(Boost_USE_STATIC_LIBS ON) +set(Boost_USE_MULTITHREADED ON) +find_package(Boost 1.33.1 COMPONENTS date_time) + +if (NOT Boost_date_time_FOUND) + find_package(Boost 1.33.1) +endif() + +set(Boost_RELEASE_VERSION + "${Boost_MAJOR_VERSION}.${Boost_MINOR_VERSION}.${Boost_SUBMINOR_VERSION}") + +boost_external_report(Boost RELEASE_VERSION INCLUDE_DIR LIBRARIES) diff --git a/src/cmake/dependencies/DB2.cmake b/src/cmake/dependencies/DB2.cmake new file mode 100644 index 0000000000..063f1a17d8 --- /dev/null +++ b/src/cmake/dependencies/DB2.cmake @@ -0,0 +1,5 @@ +set(DB2_FIND_QUIETLY TRUE) + +find_package(DB2) + +boost_external_report(DB2 INCLUDE_DIR LIBRARIES) diff --git a/src/cmake/dependencies/Firebird.cmake b/src/cmake/dependencies/Firebird.cmake new file mode 100644 index 0000000000..73c8f598e1 --- /dev/null +++ b/src/cmake/dependencies/Firebird.cmake @@ -0,0 +1,6 @@ +set(Firebird_FIND_QUIETLY TRUE) + +find_package(Firebird) + +boost_external_report(Firebird INCLUDE_DIR LIBRARIES VERSION) + diff --git a/src/cmake/dependencies/MySQL.cmake b/src/cmake/dependencies/MySQL.cmake new file mode 100644 index 0000000000..9cac7a2241 --- /dev/null +++ b/src/cmake/dependencies/MySQL.cmake @@ -0,0 +1,10 @@ +set(MySQL_FIND_QUIETLY TRUE) + +find_package(MySQL) + +boost_external_report(MySQL INCLUDE_DIR LIBRARIES) + +#if(MYSQL_FOUND) +# include_directories(${MYSQL_INCLUDE_DIR}) +# add_definitions(-DHAVE_MYSQL) +#endif() \ No newline at end of file diff --git a/src/cmake/dependencies/ODBC.cmake b/src/cmake/dependencies/ODBC.cmake new file mode 100644 index 0000000000..88b85a1c59 --- /dev/null +++ b/src/cmake/dependencies/ODBC.cmake @@ -0,0 +1,5 @@ +set(ODBC_FIND_QUIETLY TRUE) + +find_package(ODBC) + +boost_external_report(ODBC INCLUDE_DIR LIBRARIES) diff --git a/src/cmake/dependencies/Oracle.cmake b/src/cmake/dependencies/Oracle.cmake new file mode 100644 index 0000000000..c66452040e --- /dev/null +++ b/src/cmake/dependencies/Oracle.cmake @@ -0,0 +1,5 @@ +set(ORACLE_FIND_QUIETLY TRUE) + +find_package(Oracle) + +boost_external_report(Oracle INCLUDE_DIR LIBRARIES) diff --git a/src/cmake/dependencies/PostgreSQL.cmake b/src/cmake/dependencies/PostgreSQL.cmake new file mode 100644 index 0000000000..4a4feeec84 --- /dev/null +++ b/src/cmake/dependencies/PostgreSQL.cmake @@ -0,0 +1,5 @@ +set(PostgreSQL_FIND_QUIETLY TRUE) + +find_package(PostgreSQL) + +boost_external_report(PostgreSQL INCLUDE_DIR LIBRARIES VERSION) \ No newline at end of file diff --git a/src/cmake/dependencies/SQLite3.cmake b/src/cmake/dependencies/SQLite3.cmake new file mode 100644 index 0000000000..0daa9a593a --- /dev/null +++ b/src/cmake/dependencies/SQLite3.cmake @@ -0,0 +1,5 @@ +set(SQLITE3_FIND_QUIETLY TRUE) + +find_package(SQLite3) + +boost_external_report(SQLite3 INCLUDE_DIR LIBRARIES) diff --git a/src/cmake/dependencies/Threads.cmake b/src/cmake/dependencies/Threads.cmake new file mode 100644 index 0000000000..953bb9757d --- /dev/null +++ b/src/cmake/dependencies/Threads.cmake @@ -0,0 +1,5 @@ +set(Threads_FIND_QUIETLY TRUE) + +find_package(Threads) +message(STATUS "X: ${Threads_FOUND}") +boost_external_report(Threads LIBRARIES) diff --git a/src/cmake/modules/FindDB2.cmake b/src/cmake/modules/FindDB2.cmake new file mode 100644 index 0000000000..ba8b8165a2 --- /dev/null +++ b/src/cmake/modules/FindDB2.cmake @@ -0,0 +1,103 @@ +############################################################################### +# CMake module to search for DB2 client library +# +# On success, the macro sets the following variables: +# DB2_FOUND = if the library found +# DB2_LIBRARY = full path to the library +# DB2_LIBRARIES = full path to the library +# DB2_INCLUDE_DIR = where to find the library headers +# +# Copyright (c) 2013 Denis Chapligin +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# +############################################################################### + +if(UNIX) + set(DB2_INSTALL_PATHS + /opt/ibm/db2/V10.1 + /opt/ibm/db2/V9.7 + /opt/ibm/db2/V9.5 + /opt/ibm/db2/V9.1) + + if(CMAKE_SIZEOF_VOID_P EQUAL 4) + set(DB2_LIBDIRS "lib32" "lib") + else() + set(DB2_LIBDIRS "lib64") + endif() + + set(DB2_FIND_INCLUDE_PATHS) + set(DB2_FIND_LIB_PATHS) + foreach(db2_install_path ${DB2_INSTALL_PATHS}) + if (IS_DIRECTORY ${db2_install_path}/include) + set(DB2_FIND_INCLUDE_PATHS + ${DB2_FIND_INCLUDE_PATHS} + ${db2_install_path}/include) + endif() + foreach(db2_libdir ${DB2_LIBDIRS}) + if (IS_DIRECTORY ${db2_install_path}/${db2_libdir}) + set(DB2_FIND_LIB_PATHS + ${DB2_FIND_LIB_PATHS} + ${db2_install_path}/${db2_libdir}) + endif() + endforeach(db2_libdir) + endforeach(db2_install_path) +elseif(WIN32) + if (CMAKE_CL_64) # 64-bit build, DB2 64-bit installed + set(DB2_FIND_INCLUDE_PATHS $ENV{ProgramW6432}/IBM/SQLLIB/include) + set(DB2_FIND_LIB_PATHS $ENV{ProgramW6432}/IBM/SQLLIB/lib) + else() # 32-bit build, DB2 64-bit or DB2 32-bit installed + + if(EXISTS "$ENV{ProgramW6432}/IBM/SQLLIB/lib") + # On 64-bit Windows with DB2 64-bit installed: + # LIB environment points to {DB2}/IBM/SQLLIB/lib with64-bit db2api.lib, + # this flag prevents checking paths in LIB, so Win32 version can be detected + set(DB2_FIND_LIB_NO_LIB NO_SYSTEM_ENVIRONMENT_PATH) + + endif() + + set(DB2_FIND_INCLUDE_PATHS + $ENV{ProgramW6432}/IBM/SQLLIB/include + $ENV{ProgramFiles}/IBM/SQLLIB/include) + set(DB2_FIND_LIB_PATHS + $ENV{ProgramFiles}/IBM/SQLLIB/lib + $ENV{ProgramFiles}/IBM/SQLLIB/lib/win32 + $ENV{ProgramW6432}/IBM/SQLLIB/lib/win32) + endif() +endif() + +find_path(DB2_INCLUDE_DIR sqlcli1.h + $ENV{DB2_INCLUDE_DIR} + $ENV{DB2_DIR}/include + ${DB2_FIND_INCLUDE_PATHS}) + +find_library(DB2_LIBRARY + NAMES db2 db2api + PATHS + ${DB2_FIND_LIB_PATHS} + ${DB2_FIND_LIB_NO_LIB}) + +if(DB2_LIBRARY) + get_filename_component(DB2_LIBRARY_DIR ${DB2_LIBRARY} PATH) +endif() + +if(DB2_INCLUDE_DIR AND DB2_LIBRARY_DIR) + set(DB2_FOUND TRUE) + + include_directories(${DB2_INCLUDE_DIR}) + link_directories(${DB2_LIBRARY_DIR}) + +endif() + +set(DB2_LIBRARIES ${DB2_LIBRARY}) + +# Handle the QUIETLY and REQUIRED arguments and set DB2_FOUND to TRUE +# if all listed variables are TRUE +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(DB2 + DEFAULT_MSG + DB2_INCLUDE_DIR + DB2_LIBRARIES) + +mark_as_advanced(DB2_INCLUDE_DIR DB2_LIBRARIES) diff --git a/src/cmake/modules/FindDL.cmake b/src/cmake/modules/FindDL.cmake new file mode 100644 index 0000000000..3f7f889f3a --- /dev/null +++ b/src/cmake/modules/FindDL.cmake @@ -0,0 +1,21 @@ +if(DL_INCLUDE_DIR) + set(DL_FIND_QUIETLY TRUE) +endif() + +find_path(DL_INCLUDE_DIR dlfcn.h) +find_library(DL_LIBRARY NAMES dl) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(DL DEFAULT_MSG DL_LIBRARY DL_INCLUDE_DIR) + +if(NOT DL_FOUND) + # if dlopen can be found without linking in dl then, + # dlopen is part of libc, so don't need to link extra libs. + include(CheckFunctionExists) + check_function_exists(dlopen DL_FOUND) + set(DL_LIBRARY "") +endif() + +set(DL_LIBRARIES ${DL_LIBRARY}) + +mark_as_advanced(DL_LIBRARY DL_INCLUDE_DIR) diff --git a/src/cmake/modules/FindFirebird.cmake b/src/cmake/modules/FindFirebird.cmake new file mode 100644 index 0000000000..4fa279df29 --- /dev/null +++ b/src/cmake/modules/FindFirebird.cmake @@ -0,0 +1,33 @@ +############################################################## +# Copyright (c) 2008 Daniel Pfeifer # +# # +# Distributed under the Boost Software License, Version 1.0. # +############################################################## + +# This module defines +# FIREBIRD_INCLUDE_DIR - where to find ibase.h +# FIREBIRD_LIBRARIES - the libraries to link against to use FIREBIRD +# FIREBIRD_FOUND - true if FIREBIRD was found + +find_path(FIREBIRD_INCLUDE_DIR ibase.h + /usr/include + $ENV{ProgramFiles}/Firebird/*/include +) + +find_library(FIREBIRD_LIBRARIES + NAMES + fbclient + fbclient_ms + PATHS + /usr/lib + $ENV{ProgramFiles}/Firebird/*/lib +) + +# fbembed ? + +include(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(Firebird + DEFAULT_MSG FIREBIRD_LIBRARIES FIREBIRD_INCLUDE_DIR) + +mark_as_advanced(FIREBIRD_INCLUDE_DIR FIREBIRD_LIBRARIES) + diff --git a/src/cmake/modules/FindMySQL.cmake b/src/cmake/modules/FindMySQL.cmake new file mode 100644 index 0000000000..f9b3908503 --- /dev/null +++ b/src/cmake/modules/FindMySQL.cmake @@ -0,0 +1,139 @@ +# - Try to find MySQL / MySQL Embedded library +# Find the MySQL includes and client library +# This module defines +# MYSQL_INCLUDE_DIR, where to find mysql.h +# MYSQL_LIBRARIES, the libraries needed to use MySQL. +# MYSQL_LIB_DIR, path to the MYSQL_LIBRARIES +# MYSQL_EMBEDDED_LIBRARIES, the libraries needed to use MySQL Embedded. +# MYSQL_EMBEDDED_LIB_DIR, path to the MYSQL_EMBEDDED_LIBRARIES +# MYSQL_FOUND, If false, do not try to use MySQL. +# MYSQL_EMBEDDED_FOUND, If false, do not try to use MySQL Embedded. + +# Copyright (c) 2006-2008, Jarosław Staniek +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +include(CheckCXXSourceCompiles) + +if(WIN32) + find_path(MYSQL_INCLUDE_DIR mysql.h + PATHS + $ENV{MYSQL_INCLUDE_DIR} + $ENV{MYSQL_DIR}/include + $ENV{ProgramFiles}/MySQL/*/include + $ENV{SystemDrive}/MySQL/*/include + ) +else(WIN32) + find_path(MYSQL_INCLUDE_DIR mysql.h + PATHS + $ENV{MYSQL_INCLUDE_DIR} + $ENV{MYSQL_DIR}/include + /usr/local/mysql/include + /opt/mysql/mysql/include + PATH_SUFFIXES + mysql + ) +endif(WIN32) + +if(WIN32) + if (${CMAKE_BUILD_TYPE}) + string(TOLOWER ${CMAKE_BUILD_TYPE} CMAKE_BUILD_TYPE_TOLOWER) + endif() + + # path suffix for debug/release mode + # binary_dist: mysql binary distribution + # build_dist: custom build + if(CMAKE_BUILD_TYPE_TOLOWER MATCHES "debug") + set(binary_dist debug) + set(build_dist Debug) + else(CMAKE_BUILD_TYPE_TOLOWER MATCHES "debug") + ADD_DEFINITIONS(-DDBUG_OFF) + set(binary_dist opt) + set(build_dist Release) + endif(CMAKE_BUILD_TYPE_TOLOWER MATCHES "debug") + +# find_library(MYSQL_LIBRARIES NAMES mysqlclient + find_library(MYSQL_LIBRARIES NAMES libmysql + PATHS + $ENV{MYSQL_DIR}/lib/${binary_dist} + $ENV{MYSQL_DIR}/libmysql/${build_dist} + $ENV{MYSQL_DIR}/client/${build_dist} + $ENV{ProgramFiles}/MySQL/*/lib/${binary_dist} + $ENV{SystemDrive}/MySQL/*/lib/${binary_dist} + ) +else(WIN32) +# find_library(MYSQL_LIBRARIES NAMES mysqlclient + find_library(MYSQL_LIBRARIES NAMES libmysql + PATHS + $ENV{MYSQL_DIR}/libmysql_r/.libs + $ENV{MYSQL_DIR}/lib + $ENV{MYSQL_DIR}/lib/mysql + /usr/local/mysql/lib + /opt/mysql/mysql/lib + PATH_SUFFIXES + mysql + ) +endif(WIN32) + +if(WIN32) + set(MYSQL_LIB_PATHS + $ENV{MYSQL_DIR}/lib/opt + $ENV{MYSQL_DIR}/client/release + $ENV{ProgramFiles}/MySQL/*/lib/opt + $ENV{SystemDrive}/MySQL/*/lib/opt + ) + find_library(MYSQL_LIBRARIES NAMES mysqlclient + PATHS + ${MYSQL_LIB_PATHS} + ) +else(WIN32) + set(MYSQL_LIB_PATHS + $ENV{MYSQL_DIR}/libmysql_r/.libs + $ENV{MYSQL_DIR}/lib + $ENV{MYSQL_DIR}/lib/mysql + /usr/local/mysql/lib + /opt/mysql/mysql/lib + PATH_SUFFIXES + mysql + ) + find_library(MYSQL_LIBRARIES NAMES mysqlclient + PATHS + ${MYSQL_LIB_PATHS} + ) +endif(WIN32) + +find_library(MYSQL_EMBEDDED_LIBRARIES NAMES mysqld + PATHS + ${MYSQL_LIB_PATHS} +) + +if(MYSQL_LIBRARIES) + get_filename_component(MYSQL_LIB_DIR ${MYSQL_LIBRARIES} PATH) +endif(MYSQL_LIBRARIES) + +if(MYSQL_EMBEDDED_LIBRARIES) + get_filename_component(MYSQL_EMBEDDED_LIB_DIR ${MYSQL_EMBEDDED_LIBRARIES} PATH) +endif(MYSQL_EMBEDDED_LIBRARIES) + +set( CMAKE_REQUIRED_INCLUDES ${MYSQL_INCLUDE_DIR} ) +set( CMAKE_REQUIRED_LIBRARIES ${MYSQL_EMBEDDED_LIBRARIES} ) +check_cxx_source_compiles( "#include \nint main() { int i = MYSQL_OPT_USE_EMBEDDED_CONNECTION; }" HAVE_MYSQL_OPT_EMBEDDED_CONNECTION ) + +if(MYSQL_INCLUDE_DIR AND MYSQL_LIBRARIES) + set(MYSQL_FOUND TRUE) + message(STATUS "Found MySQL: ${MYSQL_INCLUDE_DIR}, ${MYSQL_LIBRARIES}") +else(MYSQL_INCLUDE_DIR AND MYSQL_LIBRARIES) + set(MYSQL_FOUND FALSE) + message(STATUS "MySQL not found.") +endif(MYSQL_INCLUDE_DIR AND MYSQL_LIBRARIES) + +if(MYSQL_INCLUDE_DIR AND MYSQL_EMBEDDED_LIBRARIES AND HAVE_MYSQL_OPT_EMBEDDED_CONNECTION) + set(MYSQL_EMBEDDED_FOUND TRUE) + message(STATUS "Found MySQL Embedded: ${MYSQL_INCLUDE_DIR}, ${MYSQL_EMBEDDED_LIBRARIES}") +else(MYSQL_INCLUDE_DIR AND MYSQL_EMBEDDED_LIBRARIES AND HAVE_MYSQL_OPT_EMBEDDED_CONNECTION) + set(MYSQL_EMBEDDED_FOUND FALSE) + message(STATUS "MySQL Embedded not found.") +endif(MYSQL_INCLUDE_DIR AND MYSQL_EMBEDDED_LIBRARIES AND HAVE_MYSQL_OPT_EMBEDDED_CONNECTION) + +mark_as_advanced(MYSQL_INCLUDE_DIR MYSQL_LIBRARIES MYSQL_EMBEDDED_LIBRARIES) diff --git a/src/cmake/modules/FindODBC.cmake b/src/cmake/modules/FindODBC.cmake new file mode 100644 index 0000000000..85ecdcfe9d --- /dev/null +++ b/src/cmake/modules/FindODBC.cmake @@ -0,0 +1,60 @@ +# +# Find the ODBC driver manager includes and library. +# +# ODBC is an open standard for connecting to different databases in a +# semi-vendor-independent fashion. First you install the ODBC driver +# manager. Then you need a driver for each separate database you want +# to connect to (unless a generic one works). VTK includes neither +# the driver manager nor the vendor-specific drivers: you have to find +# those yourself. +# +# This module defines +# ODBC_INCLUDE_DIR, where to find sql.h +# ODBC_LIBRARIES, the libraries to link against to use ODBC +# ODBC_FOUND. If false, you cannot build anything that requires MySQL. + +# also defined, but not for general use is +# ODBC_LIBRARY, where to find the ODBC driver manager library. + +set(ODBC_FOUND FALSE) + +find_path(ODBC_INCLUDE_DIR sql.h + /usr/include + /usr/include/odbc + /usr/local/include + /usr/local/include/odbc + /usr/local/odbc/include + "C:/Program Files (x86)/Windows Kits/8.0/include/um" + "C:/Program Files (x86)/Microsoft SDKs/Windows/v7.0A/Include" + "C:/Program Files/ODBC/include" + "C:/Program Files/Microsoft SDKs/Windows/v7.0/include" + "C:/Program Files/Microsoft SDKs/Windows/v6.0a/include" + "C:/ODBC/include" + DOC "Specify the directory containing sql.h." +) + +find_library(ODBC_LIBRARY + NAMES iodbc odbc odbcinst odbc32 + PATHS + /usr/lib + /usr/lib/odbc + /usr/local/lib + /usr/local/lib/odbc + /usr/local/odbc/lib + "C:/Program Files (x86)/Windows Kits/8.0/Lib/win8/um/x86/" + "C:/Program Files (x86)/Microsoft SDKs/Windows/v7.0A/Lib" + "C:/Program Files/ODBC/lib" + "C:/ODBC/lib/debug" + DOC "Specify the ODBC driver manager library here." +) + +if(ODBC_LIBRARY) + if(ODBC_INCLUDE_DIR) + set( ODBC_FOUND 1 ) + endif() +endif() + +set(ODBC_LIBRARIES ${ODBC_LIBRARY}) + +mark_as_advanced(ODBC_FOUND ODBC_LIBRARY ODBC_EXTRA_LIBRARIES ODBC_INCLUDE_DIR) + diff --git a/src/cmake/modules/FindOracle.cmake b/src/cmake/modules/FindOracle.cmake new file mode 100644 index 0000000000..e407967d23 --- /dev/null +++ b/src/cmake/modules/FindOracle.cmake @@ -0,0 +1,75 @@ +############################################################################### +# +# CMake module to search for Oracle client library (OCI) +# +# On success, the macro sets the following variables: +# ORACLE_FOUND = if the library found +# ORACLE_LIBRARY = full path to the library +# ORACLE_LIBRARIES = full path to the library +# ORACLE_INCLUDE_DIR = where to find the library headers also defined, +# but not for general use are +# ORACLE_VERSION = version of library which was found, e.g. "1.2.5" +# +# Copyright (c) 2009-2013 Mateusz Loskot +# +# Developed with inspiration from Petr Vanek +# who wrote similar macro for TOra - http://torasql.com/ +# +# Module source: http://github.com/mloskot/workshop/tree/master/cmake/ +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# +############################################################################### + +# If ORACLE_HOME not defined, assume Oracle libraries not available +if(DEFINED ENV{ORACLE_HOME}) + + set(ORACLE_HOME $ENV{ORACLE_HOME}) + message(STATUS "ORACLE_HOME=${ORACLE_HOME}") + + find_path(ORACLE_INCLUDE_DIR + NAMES oci.h + PATHS + ${ORACLE_HOME}/rdbms/public + ${ORACLE_HOME}/include + ${ORACLE_HOME}/sdk/include # Oracle SDK + ${ORACLE_HOME}/OCI/include) # Oracle XE on Windows + + set(ORACLE_OCI_NAMES clntsh libclntsh oci) # Dirty trick might help on OSX, see issues/89 + set(ORACLE_OCCI_NAMES libocci occi oraocci10 oraocci11) + set(ORACLE_NNZ_NAMES nnz10 libnnz10 nnz11 libnnz11 nnz12 libnnz12 ociw32) + + set(ORACLE_LIB_DIR + ${ORACLE_HOME} + ${ORACLE_HOME}/lib + ${ORACLE_HOME}/sdk/lib # Oracle SDK + ${ORACLE_HOME}/sdk/lib/msvc + ${ORACLE_HOME}/OCI/lib/msvc) # Oracle XE on Windows + + find_library(ORACLE_OCI_LIBRARY + NAMES ${ORACLE_OCI_NAMES} PATHS ${ORACLE_LIB_DIR}) + find_library(ORACLE_OCCI_LIBRARY + NAMES ${ORACLE_OCCI_NAMES} PATHS ${ORACLE_LIB_DIR}) + find_library(ORACLE_NNZ_LIBRARY + NAMES ${ORACLE_NNZ_NAMES} PATHS ${ORACLE_LIB_DIR}) + + set(ORACLE_LIBRARY + ${ORACLE_OCI_LIBRARY} + ${ORACLE_OCCI_LIBRARY} + ${ORACLE_NNZ_LIBRARY}) + + if(NOT WIN32) + set(ORACLE_LIBRARY ${ORACLE_LIBRARY} ${ORACLE_CLNTSH_LIBRARY}) + endif(NOT WIN32) + + set(ORACLE_LIBRARIES ${ORACLE_LIBRARY}) + +endif(DEFINED ENV{ORACLE_HOME}) + +# Handle the QUIETLY and REQUIRED arguments and set ORACLE_FOUND to TRUE +# if all listed variables are TRUE +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(ORACLE DEFAULT_MSG ORACLE_LIBRARY ORACLE_INCLUDE_DIR) + +mark_as_advanced(ORACLE_INCLUDE_DIR ORACLE_LIBRARY) diff --git a/src/cmake/modules/FindPostgreSQL.cmake b/src/cmake/modules/FindPostgreSQL.cmake new file mode 100644 index 0000000000..8178418ccb --- /dev/null +++ b/src/cmake/modules/FindPostgreSQL.cmake @@ -0,0 +1,83 @@ +# - Find PostgreSQL +# Find the PostgreSQL includes and client library +# This module defines +# POSTGRESQL_INCLUDE_DIR, where to find libpq-fe.h +# POSTGRESQL_LIBRARIES, libraries needed to use PostgreSQL +# POSTGRESQL_VERSION, if found, version of PostgreSQL +# POSTGRESQL_FOUND, if false, do not try to use PostgreSQL +# +# Copyright (c) 2010, Mateusz Loskot, +# Copyright (c) 2006, Jaroslaw Staniek, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +find_program(PG_CONFIG NAMES pg_config + PATHS + /usr/bin + /usr/local/bin + $ENV{ProgramFiles}/PostgreSQL/*/bin + $ENV{SystemDrive}/PostgreSQL/*/bin + DOC "Path to pg_config utility") + +if(PG_CONFIG) + exec_program(${PG_CONFIG} + ARGS "--version" + OUTPUT_VARIABLE PG_CONFIG_VERSION) + + if(${PG_CONFIG_VERSION} MATCHES "^[A-Za-z]+[ ](.*)$") + string(REGEX REPLACE "^[A-Za-z]+[ ](.*)$" "\\1" POSTGRESQL_VERSION "${PG_CONFIG_VERSION}") + endif() + + exec_program(${PG_CONFIG} + ARGS "--includedir" + OUTPUT_VARIABLE PG_CONFIG_INCLUDEDIR) + + exec_program(${PG_CONFIG} + ARGS "--libdir" + OUTPUT_VARIABLE PG_CONFIG_LIBDIR) +else() + set(POSTGRESQL_VERSION "unknown") +endif() + +find_path(POSTGRESQL_INCLUDE_DIR libpq-fe.h + ${PG_CONFIG_INCLUDEDIR} + /usr/include/server + /usr/include/pgsql/server + /usr/local/include/pgsql/server + /usr/include/postgresql + /usr/include/postgresql/server + /usr/include/postgresql/*/server + $ENV{ProgramFiles}/PostgreSQL/*/include + $ENV{SystemDrive}/PostgreSQL/*/include) + +find_library(POSTGRESQL_LIBRARIES NAMES pq libpq + PATHS + ${PG_CONFIG_LIBDIR} + /usr/lib + /usr/local/lib + /usr/lib/postgresql + /usr/lib64 + /usr/local/lib64 + /usr/lib64/postgresql + $ENV{ProgramFiles}/PostgreSQL/*/lib + $ENV{SystemDrive}/PostgreSQL/*/lib + $ENV{ProgramFiles}/PostgreSQL/*/lib/ms + $ENV{SystemDrive}/PostgreSQL/*/lib/ms) + +if(POSTGRESQL_INCLUDE_DIR AND POSTGRESQL_LIBRARIES) + set(POSTGRESQL_FOUND TRUE) +else() + set(POSTGRESQL_FOUND FALSE) +endif() + +# Handle the QUIETLY and REQUIRED arguments and set POSTGRESQL_FOUND to TRUE +# if all listed variables are TRUE +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(PostgreSQL + DEFAULT_MSG + POSTGRESQL_INCLUDE_DIR + POSTGRESQL_LIBRARIES + POSTGRESQL_VERSION) + +mark_as_advanced(POSTGRESQL_INCLUDE_DIR POSTGRESQL_LIBRARIES) diff --git a/src/cmake/modules/FindSQLite3.cmake b/src/cmake/modules/FindSQLite3.cmake new file mode 100644 index 0000000000..2efb4d4b60 --- /dev/null +++ b/src/cmake/modules/FindSQLite3.cmake @@ -0,0 +1,62 @@ +############################################################################### +# CMake module to search for SQLite 3 library +# +# On success, the macro sets the following variables: +# SQLITE3_FOUND = if the library found +# SQLITE3_LIBRARY = full path to the library +# SQLITE3_LIBRARIES = full path to the library +# SSQLITE3_INCLUDE_DIR = where to find the library headers +# +# Copyright (c) 2009 Mateusz Loskot +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# +############################################################################### + +find_path(SQLITE3_INCLUDE_DIR + NAMES sqlite3.h + PATH_PREFIXES sqlite sqlite3 + PATHS + /usr/include + /usr/local/include + $ENV{LIB_DIR}/include + $ENV{LIB_DIR}/include/sqlite + $ENV{LIB_DIR}/include/sqlite3 + $ENV{ProgramFiles}/SQLite/*/include + $ENV{ProgramFiles}/SQLite3/*/include + $ENV{SystemDrive}/SQLite/*/include + $ENV{SystemDrive}/SQLite3/*/include + $ENV{SQLITE_ROOT}/include + ${SQLITE_ROOT_DIR}/include + $ENV{OSGEO4W_ROOT}/include) + +set(SQLITE3_NAMES sqlite3_i sqlite3) +find_library(SQLITE3_LIBRARY + NAMES ${SQLITE3_NAMES} + PATHS + /usr/lib + /usr/local/lib + $ENV{LIB_DIR}/lib + $ENV{ProgramFiles}/SQLite/*/lib + $ENV{ProgramFiles}/SQLite3/*/lib + $ENV{SystemDrive}/SQLite/*/lib + $ENV{SystemDrive}/SQLite3/*/lib + $ENV{SQLITE_ROOT}/lib + ${SQLITE_ROOT_DIR}/lib + $ENV{OSGEO4W_ROOT}/lib) + +set(SQLITE3_LIBRARIES + ${SQLITE3_LIBRARIES} + ${SQLITE3_LIBRARY}) + +#message(STATUS ${SQLITE3_LIBRARY}) +# Handle the QUIETLY and REQUIRED arguments and set SQLITE3_FOUND to TRUE +# if all listed variables are TRUE +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(SQLITE3 + DEFAULT_MSG + SQLITE3_LIBRARIES + SQLITE3_INCLUDE_DIR) + +mark_as_advanced(SQLITE3_LIBRARY SQLITE3_INCLUDE_DIR SQLITE3_LIBRARIES) diff --git a/src/cmake/modules/FindSoci.cmake b/src/cmake/modules/FindSoci.cmake new file mode 100644 index 0000000000..f49b874289 --- /dev/null +++ b/src/cmake/modules/FindSoci.cmake @@ -0,0 +1,98 @@ +############################################################################### +# CMake module to search for SOCI library +# +# WARNING: This module is experimental work in progress. +# +# This module defines: +# SOCI_INCLUDE_DIRS = include dirs to be used when using the soci library +# SOCI_LIBRARY = full path to the soci library +# SOCI_VERSION = the soci version found (not yet. soci does not provide that info.) +# SOCI_FOUND = true if soci was found +# +# This module respects: +# LIB_SUFFIX = (64|32|"") Specifies the suffix for the lib directory +# +# For each component you specify in find_package(), the following variables are set. +# +# SOCI_${COMPONENT}_PLUGIN = full path to the soci plugin +# SOCI_${COMPONENT}_FOUND +# +# Copyright (c) 2011 Michael Jansen +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# +############################################################################### +# +### Global Configuration Section +# +SET(_SOCI_ALL_PLUGINS mysql odbc postgresql sqlite3) +SET(_SOCI_REQUIRED_VARS SOCI_INCLUDE_DIR SOCI_LIBRARY) + +# +### FIRST STEP: Find the soci headers. +# +FIND_PATH( + SOCI_INCLUDE_DIR soci.h + PATH "/usr/local" + PATH_SUFFIXES "" "soci" + DOC "Soci (http://soci.sourceforge.net) include directory") +MARK_AS_ADVANCED(SOCI_INCLUDE_DIR) + +SET(SOCI_INCLUDE_DIRS ${SOCI_INCLUDE_DIR}) + +# +### SECOND STEP: Find the soci core library. Respect LIB_SUFFIX +# +FIND_LIBRARY( + SOCI_LIBRARY + NAMES soci_core + HINTS ${SOCI_INCLUDE_DIR}/.. + PATH_SUFFIXES lib${LIB_SUFFIX}) +MARK_AS_ADVANCED(SOCI_LIBRARY) + +GET_FILENAME_COMPONENT(SOCI_LIBRARY_DIR ${SOCI_LIBRARY} PATH) +MARK_AS_ADVANCED(SOCI_LIBRARY_DIR) + +# +### THIRD STEP: Find all installed plugins if the library was found +# +IF(SOCI_INCLUDE_DIR AND SOCI_LIBRARY) + + MESSAGE(STATUS "Soci found: Looking for plugins") + FOREACH(plugin IN LISTS _SOCI_ALL_PLUGINS) + + FIND_LIBRARY( + SOCI_${plugin}_PLUGIN + NAMES soci_${plugin} + HINTS ${SOCI_INCLUDE_DIR}/.. + PATH_SUFFIXES lib${LIB_SUFFIX}) + MARK_AS_ADVANCED(SOCI_${plugin}_PLUGIN) + + IF(SOCI_${plugin}_PLUGIN) + MESSAGE(STATUS " * Plugin ${plugin} found ${SOCI_${plugin}_PLUGIN}.") + SET(SOCI_${plugin}_FOUND True) + ELSE() + MESSAGE(STATUS " * Plugin ${plugin} not found.") + SET(SOCI_${plugin}_FOUND False) + ENDIF() + + ENDFOREACH() + + # + ### FOURTH CHECK: Check if the required components were all found + # + FOREACH(component ${Soci_FIND_COMPONENTS}) + IF(NOT SOCI_${component}_FOUND) + MESSAGE(SEND_ERROR "Required component ${component} not found.") + ENDIF() + ENDFOREACH() + +ENDIF() + +# +### ADHERE TO STANDARDS +# +include(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(Soci DEFAULT_MSG ${_SOCI_REQUIRED_VARS}) + diff --git a/src/cmake/resources/vs2010-test-cmd-args.vcxproj.user.in b/src/cmake/resources/vs2010-test-cmd-args.vcxproj.user.in new file mode 100644 index 0000000000..f41ec9b27b --- /dev/null +++ b/src/cmake/resources/vs2010-test-cmd-args.vcxproj.user.in @@ -0,0 +1,7 @@ + + + + @SOCI_TEST_CMD_ARGS@ + WindowsLocalDebugger + + diff --git a/src/core/.gitignore b/src/core/.gitignore new file mode 100644 index 0000000000..64ec3b6829 --- /dev/null +++ b/src/core/.gitignore @@ -0,0 +1,5 @@ +*.o +*.la +*.lo +.deps +.libs diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt new file mode 100644 index 0000000000..043f42a7ce --- /dev/null +++ b/src/core/CMakeLists.txt @@ -0,0 +1,154 @@ +############################################################################### +# +# This file is part of CMake configuration for SOCI library +# +# Copyright (C) 2009-2010 Mateusz Loskot +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) +# +############################################################################### + +colormsg(_HIBLUE_ "Configuring SOCI core library:") + +# Core dependencies +set(SOCI_CORE_DEPENDENCIES) + +include(FindThreads) + +if(Threads_FOUND OR CMAKE_USE_WIN32_THREADS_INIT OR CMAKE_THREAD_LIBS_INIT) + list(APPEND SOCI_CORE_DEPENDENCIES ${CMAKE_THREAD_LIBS_INIT}) +else() + message(FATAL_ERROR "No thread library found") +endif() + +if(NOT MSVC) + set(DL_FIND_QUIETLY TRUE) + find_package(DL) + if(DL_FOUND) + list(APPEND SOCI_CORE_DEPENDENCIES ${DL_LIBRARY}) + set_directory_properties(PROPERTIES INCLUDE_DIRECTORIES ${DL_INCLUDE_DIR}) + add_definitions(-DHAVE_DL=1) + endif() +endif() + +if(Boost_FOUND) + + get_property(SOCI_COMPILE_DEFINITIONS + DIRECTORY ${CMAKE_SOURCE_DIR} + PROPERTY COMPILE_DEFINITIONS) + + list(APPEND SOCI_COMPILE_DEFINITIONS "HAVE_BOOST=1") + + if(Boost_DATE_TIME_FOUND) + list(APPEND SOCI_CORE_DEPENDENCIES ${Boost_DATE_TIME_LIBRARY}) + list(APPEND SOCI_COMPILE_DEFINITIONS "HAVE_BOOST_DATE_TIME=1") + endif() + + set_property(DIRECTORY ${CMAKE_SOURCE_DIR} + PROPERTY COMPILE_DEFINITIONS ${SOCI_COMPILE_DEFINITIONS}) + + set_directory_properties(PROPERTIES INCLUDE_DIRECTORIES ${Boost_INCLUDE_DIRS}) + + set_property(DIRECTORY ${CMAKE_SOURCE_DIR} + PROPERTY INCLUDE_DIRECTORIES ${Boost_INCLUDE_DIRS}) + +endif() + +# Core source files +file(GLOB SOCI_CORE_HEADERS *.h) +file(GLOB SOCI_CORE_SOURCES *.cpp) + +# Group source files for IDE source explorers (e.g. Visual Studio) +source_group("Header Files" FILES ${SOCI_CORE_HEADERS}) +source_group("Source Files" FILES ${SOCI_CORE_SOURCES}) +source_group("CMake Files" FILES CMakeLists.txt) + +# Core targets configuration +string(TOLOWER "${PROJECT_NAME}" PROJECTNAMEL) +set(SOCI_CORE_TARGET ${PROJECTNAMEL}_core) + +soci_target_output_name(${SOCI_CORE_TARGET} SOCI_CORE_TARGET_OUTPUT_NAME) + +# Configure SOCI backend loader to also use default install directory. +set_directory_properties(PROPERTIES INCLUDE_DIRECTORIES ${CMAKE_CURRENT_BINARY_DIR}) +set_property(DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY INCLUDE_DIRECTORIES ${CMAKE_CURRENT_BINARY_DIR}) +configure_file(soci_backends_config.h.in ${CMAKE_CURRENT_BINARY_DIR}/soci_backends_config.h) + +# +# Core shared library +# +if (SOCI_SHARED) + add_library(${SOCI_CORE_TARGET} SHARED ${SOCI_CORE_HEADERS} ${SOCI_CORE_SOURCES}) + + target_link_libraries(${SOCI_CORE_TARGET} ${SOCI_CORE_DEPENDENCIES}) + + if(WIN32) + set_target_properties(${SOCI_CORE_TARGET} + PROPERTIES + DEFINE_SYMBOL SOCI_DLL + OUTPUT_NAME "${SOCI_CORE_TARGET_OUTPUT_NAME}" + VERSION ${SOCI_VERSION} + CLEAN_DIRECT_OUTPUT 1) + else() + set_target_properties(${SOCI_CORE_TARGET} + PROPERTIES + VERSION ${SOCI_VERSION} + SOVERSION ${SOCI_SOVERSION} + INSTALL_NAME_DIR ${CMAKE_INSTALL_PREFIX}/lib + CLEAN_DIRECT_OUTPUT 1) + endif() +endif() + +add_definitions(-DSOCI_LIB_PREFIX="${CMAKE_SHARED_LIBRARY_PREFIX}soci_" + -DSOCI_LIB_SUFFIX="${CMAKE_SHARED_LIBRARY_SUFFIX}") + +# +# Core static library +# +if (SOCI_STATIC) + set(SOCI_CORE_TARGET_STATIC ${SOCI_CORE_TARGET}_static) + + add_library(${SOCI_CORE_TARGET_STATIC} STATIC ${SOCI_CORE_HEADERS} ${SOCI_CORE_SOURCES}) + + set_target_properties(${SOCI_CORE_TARGET_STATIC} + PROPERTIES + OUTPUT_NAME ${SOCI_CORE_TARGET_OUTPUT_NAME} + PREFIX "lib" + CLEAN_DIRECT_OUTPUT 1) +endif() + +# +# Core installation +# +install(FILES ${SOCI_CORE_HEADERS} DESTINATION ${INCLUDEDIR}/${PROJECTNAMEL}) + +if (SOCI_SHARED) + install(TARGETS ${SOCI_CORE_TARGET} + RUNTIME DESTINATION ${BINDIR} + LIBRARY DESTINATION ${LIBDIR} + ARCHIVE DESTINATION ${LIBDIR}) +endif() + +if (SOCI_STATIC) + install(TARGETS ${SOCI_CORE_TARGET_STATIC} + RUNTIME DESTINATION ${BINDIR} + LIBRARY DESTINATION ${LIBDIR} + ARCHIVE DESTINATION ${LIBDIR}) +endif() + +# +# Core configuration summary +# +boost_report_value(SOCI_CORE_TARGET) +boost_report_value(SOCI_CORE_TARGET_OUTPUT_NAME) +boost_report_value(SOCI_CORE_DEPENDENCIES) +boost_report_value(WITH_BOOST) +soci_report_directory_property(COMPILE_DEFINITIONS) + +# Export core target name to make it visible by backends +set(SOCI_CORE_TARGET ${SOCI_CORE_TARGET} PARENT_SCOPE) +if (SOCI_STATIC) + set(SOCI_CORE_TARGET_STATIC ${SOCI_CORE_TARGET_STATIC} PARENT_SCOPE) +endif() +set(SOCI_CORE_STATIC_DEPENDENCIES ${SOCI_CORE_DEPENDENCIES} PARENT_SCOPE) diff --git a/src/core/Makefile.basic b/src/core/Makefile.basic new file mode 100644 index 0000000000..f24fa2f1ab --- /dev/null +++ b/src/core/Makefile.basic @@ -0,0 +1,79 @@ +COMPILER = g++ +CXXFLAGS = -Wall -pedantic -Wno-long-long +INCLUDEDIRS = + +BACKENDLOADERDEFS = -DSOCI_LIB_PREFIX=\"libsoci_\" -DSOCI_LIB_SUFFIX=\".so\" + +OBJS = session.o statement.o row.o values.o \ + into-type.o use-type.o \ + blob.o rowid.o procedure.o ref-counted-prepare-info.o ref-counted-statement.o \ + once-temp-type.o prepare-temp-type.o error.o transaction.o backend-loader.o \ + connection-pool.o soci-simple.o + + +libsoci_core.a : ${OBJS} + ar rv $@ $? + rm *.o + +shared : ${OBJS} + ${COMPILER} -fPIC -c $? ${CXXFLAGS} ${INCLUDEDIRS} + ${COMPILER} -shared -o libsoci_core.so ${OBJS} + rm *.o + +session.o : session.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +statement.o : statement.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +row.o : row.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +values.o : values.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +into-type.o : into-type.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +use-type.o : use-type.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +blob.o : blob.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +error.o : error.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +rowid.o : rowid.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +procedure.o : procedure.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +ref-counted-prepare-info.o : ref-counted-prepare-info.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +ref-counted-statement.o : ref-counted-statement.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +once-temp-type.o : once-temp-type.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +prepare-temp-type.o : prepare-temp-type.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +transaction.o : transaction.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +backend-loader.o : backend-loader.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${BACKENDLOADERDEFS} ${INCLUDEDIRS} + +connection-pool.o : connection-pool.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + +soci-simple.o : soci-simple.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + + +clean : + rm -f libsoci_core.a libsoci_core.so diff --git a/src/core/backend-loader.cpp b/src/core/backend-loader.cpp new file mode 100644 index 0000000000..ad228cdf31 --- /dev/null +++ b/src/core/backend-loader.cpp @@ -0,0 +1,347 @@ +// +// Copyright (C) 2008 Maciej Sobczak with contributions from Artyom Tonkikh +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_SOURCE +#include "backend-loader.h" +#include "error.h" +#include +#include +#include +#include +#include +#ifndef _MSC_VER +#include +#endif + +#include "soci_backends_config.h" + +using namespace soci; +using namespace soci::dynamic_backends; + +#ifdef _WIN32 + +#include + +typedef CRITICAL_SECTION soci_mutex_t; +typedef HMODULE soci_handler_t; + +#define LOCK(x) EnterCriticalSection(x) +#define UNLOCK(x) LeaveCriticalSection(x) +#define MUTEX_INIT(x) InitializeCriticalSection(x) +#define MUTEX_DEST(x) DeleteCriticalSection(x) +#ifdef _UNICODE +#define DLOPEN(x) LoadLibraryA(x) +#else +#define DLOPEN(x) LoadLibrary(x) +#endif +#define DLCLOSE(x) FreeLibrary(x) +#define DLSYM(x, y) GetProcAddress(x, y) + +#ifdef SOCI_ABI_VERSION +#define LIBNAME(x) (SOCI_LIB_PREFIX + x + "_" SOCI_ABI_VERSION SOCI_LIB_SUFFIX) +#else +#define LIBNAME(x) (SOCI_LIB_PREFIX + x + SOCI_LIB_SUFFIX) +#endif // SOCI_ABI_VERSION + +#else + +#include +#include + +typedef pthread_mutex_t soci_mutex_t; +typedef void * soci_handler_t; + +#define LOCK(x) pthread_mutex_lock(x) +#define UNLOCK(x) pthread_mutex_unlock(x) +#define MUTEX_INIT(x) pthread_mutex_init(x, NULL) +#define MUTEX_DEST(x) pthread_mutex_destroy(x) +#define DLOPEN(x) dlopen(x, RTLD_LAZY) +#define DLCLOSE(x) dlclose(x) +#define DLSYM(x, y) dlsym(x, y) + +#ifdef SOCI_ABI_VERSION + +#ifdef __APPLE__ +#define LIBNAME(x) (SOCI_LIB_PREFIX + x + "." SOCI_ABI_VERSION SOCI_LIB_SUFFIX) +#else +#define LIBNAME(x) (SOCI_LIB_PREFIX + x + SOCI_LIB_SUFFIX "." SOCI_ABI_VERSION) +#endif + +#else +#define LIBNAME(x) (SOCI_LIB_PREFIX + x + SOCI_LIB_SUFFIX) +#endif // SOCI_ABI_VERSION + +#endif // _WIN32 + + +namespace // unnamed +{ + +struct info +{ + soci_handler_t handler_; + backend_factory const * factory_; + info() : handler_(0), factory_(0) {} +}; + +typedef std::map factory_map; +factory_map factories_; + +std::vector search_paths_; + +soci_mutex_t mutex_; + +std::vector get_default_paths() +{ + std::vector paths; + + // TODO: may be problem with finding getenv in std namespace in Visual C++ --mloskot + char const* const penv = std::getenv("SOCI_BACKENDS_PATH"); + if (0 == penv) + { + paths.push_back("."); + paths.push_back(DEFAULT_BACKENDS_PATH); + return paths; + } + + std::string const env = penv; + if (env.empty()) + { + paths.push_back("."); + paths.push_back(DEFAULT_BACKENDS_PATH); + return paths; + } + + std::string::size_type searchFrom = 0; + while (searchFrom != env.size()) + { + std::string::size_type const found = env.find(":", searchFrom); + if (found == searchFrom) + { + ++searchFrom; + } + else if (std::string::npos != found) + { + std::string const path(env.substr(searchFrom, found - searchFrom)); + paths.push_back(path); + + searchFrom = found + 1; + } + else // found == npos + { + std::string const path = env.substr(searchFrom); + paths.push_back(path); + + searchFrom = env.size(); + } + } + + return paths; +} + +// used to automatically initialize the global state +struct static_state_mgr +{ + static_state_mgr() + { + MUTEX_INIT(&mutex_); + + search_paths_ = get_default_paths(); + } + + ~static_state_mgr() + { + unload_all(); + + MUTEX_DEST(&mutex_); + } +} static_state_mgr_; + +class scoped_lock +{ +public: + scoped_lock(soci_mutex_t * m) : mptr(m) { LOCK(m); }; + ~scoped_lock() { UNLOCK(mptr); }; +private: + soci_mutex_t * mptr; +}; + +// non-synchronized helper for the other functions +void do_unload(std::string const & name) +{ + factory_map::iterator i = factories_.find(name); + + if (i != factories_.end()) + { + soci_handler_t h = i->second.handler_; + if (h != NULL) + { + DLCLOSE(h); + } + + factories_.erase(i); + } +} + +// non-synchronized helper +void do_register_backend(std::string const & name, std::string const & shared_object) +{ + // The rules for backend search are as follows: + // - if the shared_object is given, + // it names the library file and the search paths are not used + // - otherwise (shared_object not provided or empty): + // - file named libsoci_NAME.so.SOVERSION is searched in the list of search paths + + soci_handler_t h = 0; + if (shared_object.empty() == false) + { + h = DLOPEN(shared_object.c_str()); + } + else + { + // try system paths + h = DLOPEN(LIBNAME(name).c_str()); + if (0 == h) + { + // try all search paths + for (std::size_t i = 0; i != search_paths_.size(); ++i) + { + std::string const fullFileName(search_paths_[i] + "/" + LIBNAME(name)); + h = DLOPEN(fullFileName.c_str()); + if (0 != h) + { + // already found + break; + } + } + } + } + + if (0 == h) + { + throw soci_error("Failed to find shared library for backend " + name); + } + + std::string symbol = "factory_" + name; + + typedef backend_factory const * bfc_ptr; + typedef bfc_ptr (*get_t)(void); + get_t entry; + entry = reinterpret_cast( + reinterpret_cast(DLSYM(h, symbol.c_str()))); + + if (0 == entry) + { + DLCLOSE(h); + throw soci_error("Failed to resolve dynamic symbol: " + symbol); + } + + // unload the existing handler if it's already loaded + + do_unload(name); + + backend_factory const* f = entry(); + + info new_entry; + new_entry.factory_ = f; + new_entry.handler_ = h; + + factories_[name] = new_entry; +} + +} // unnamed namespace + +backend_factory const& dynamic_backends::get(std::string const& name) +{ + scoped_lock lock(&mutex_); + + factory_map::iterator i = factories_.find(name); + + if (i != factories_.end()) + { + return *(i->second.factory_); + } + + // no backend found with this name, try to register it first + + do_register_backend(name, std::string()); + + // second attempt, must succeed (the backend is already loaded) + + i = factories_.find(name); + + assert(i != factories_.end()); + + return *(i->second.factory_); +} + +SOCI_DECL std::vector& search_paths() +{ + return search_paths_; +} + +SOCI_DECL void dynamic_backends::register_backend( + std::string const& name, std::string const& shared_object) +{ + scoped_lock lock(&mutex_); + + do_register_backend(name, shared_object); +} + +SOCI_DECL void dynamic_backends::register_backend( + std::string const& name, backend_factory const& factory) +{ + scoped_lock lock(&mutex_); + + // unload the existing handler if it's already loaded + + do_unload(name); + + info new_entry; + new_entry.factory_ = &factory; + + factories_[name] = new_entry; +} + +SOCI_DECL std::vector dynamic_backends::list_all() +{ + scoped_lock lock(&mutex_); + + std::vector ret; + ret.reserve(factories_.size()); + + for (factory_map::iterator i = factories_.begin(); i != factories_.end(); ++i) + { + std::string const& name = i->first; + ret.push_back(name); + } + + return ret; +} + +SOCI_DECL void dynamic_backends::unload(std::string const& name) +{ + scoped_lock lock(&mutex_); + + do_unload(name); +} + +SOCI_DECL void dynamic_backends::unload_all() +{ + scoped_lock lock(&mutex_); + + for (factory_map::iterator i = factories_.begin(); i != factories_.end(); ++i) + { + soci_handler_t h = i->second.handler_; + if (0 != h) + { + DLCLOSE(h); + } + } + + factories_.clear(); +} diff --git a/src/core/backend-loader.h b/src/core/backend-loader.h new file mode 100644 index 0000000000..e4453abc63 --- /dev/null +++ b/src/core/backend-loader.h @@ -0,0 +1,37 @@ +// +// Copyright (C) 2008 Maciej Sobczak with contributions from Artyom Tonkikh +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_BACKEND_LOADER_H_INCLUDED +#define SOCI_BACKEND_LOADER_H_INCLUDED + +#include "soci-backend.h" +// std +#include +#include + +namespace soci +{ + +namespace dynamic_backends +{ + +// used internally by session +backend_factory const & get(std::string const & name); + +// provided for advanced user-level management +SOCI_DECL std::vector & search_paths(); +SOCI_DECL void register_backend(std::string const & name, std::string const & shared_object = std::string()); +SOCI_DECL void register_backend(std::string const & name, backend_factory const & factory); +SOCI_DECL std::vector list_all(); +SOCI_DECL void unload(std::string const & name); +SOCI_DECL void unload_all(); + +} // namespace dynamic_backends + +} // namespace soci + +#endif // SOCI_BACKEND_LOADER_H_INCLUDED diff --git a/src/core/blob-exchange.h b/src/core/blob-exchange.h new file mode 100644 index 0000000000..03f2258ba0 --- /dev/null +++ b/src/core/blob-exchange.h @@ -0,0 +1,58 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_BLOB_EXCHANGE_H_INCLUDED +#define SOCI_BLOB_EXCHANGE_H_INCLUDED + +#include "blob.h" +#include "into-type.h" +#include "use-type.h" +// std +#include + +namespace soci +{ + +namespace details +{ + +template <> +class into_type : public standard_into_type +{ +public: + into_type(blob & b) : standard_into_type(&b, x_blob) {} + into_type(blob & b, indicator & ind) + : standard_into_type(&b, x_blob, ind) {} +}; + +template <> +class use_type : public standard_use_type +{ +public: + use_type(blob & b, std::string const & name = std::string()) + : standard_use_type(&b, x_blob, false, name) {} + use_type(blob const & b, std::string const & name = std::string()) + : standard_use_type(const_cast(&b), x_blob, true, name) {} + use_type(blob & b, indicator & ind, + std::string const & name = std::string()) + : standard_use_type(&b, x_blob, ind, false, name) {} + use_type(blob const & b, indicator & ind, + std::string const & name = std::string()) + : standard_use_type(const_cast(&b), x_blob, ind, true, name) {} +}; + +template <> +struct exchange_traits +{ + typedef basic_type_tag type_family; +}; + +} // namespace details + +} // namespace soci + +#endif // SOCI_BLOB_EXCHANGE_H_INCLUDED diff --git a/src/core/blob.cpp b/src/core/blob.cpp new file mode 100644 index 0000000000..91c779d21f --- /dev/null +++ b/src/core/blob.cpp @@ -0,0 +1,50 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_SOURCE +#include "blob.h" +#include "session.h" + +#include + +using namespace soci; + +blob::blob(session & s) +{ + backEnd_ = s.make_blob_backend(); +} + +blob::~blob() +{ + delete backEnd_; +} + +std::size_t blob::get_len() +{ + return backEnd_->get_len(); +} + +std::size_t blob::read(std::size_t offset, char *buf, std::size_t toRead) +{ + return backEnd_->read(offset, buf, toRead); +} + +std::size_t blob::write( + std::size_t offset, char const * buf, std::size_t toWrite) +{ + return backEnd_->write(offset, buf, toWrite); +} + +std::size_t blob::append(char const * buf, std::size_t toWrite) +{ + return backEnd_->append(buf, toWrite); +} + +void blob::trim(std::size_t newLen) +{ + backEnd_->trim(newLen); +} diff --git a/src/core/blob.h b/src/core/blob.h new file mode 100644 index 0000000000..16159ff6ad --- /dev/null +++ b/src/core/blob.h @@ -0,0 +1,47 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_BLOB_H_INCLUDED +#define SOCI_BLOB_H_INCLUDED + +#include "soci-config.h" +// std +#include + +namespace soci +{ +// basic blob operations + +class session; + +namespace details +{ +class blob_backend; +} // namespace details + +class SOCI_DECL blob +{ +public: + explicit blob(session & s); + ~blob(); + + std::size_t get_len(); + std::size_t read(std::size_t offset, char * buf, std::size_t toRead); + std::size_t write(std::size_t offset, char const * buf, + std::size_t toWrite); + std::size_t append(char const * buf, std::size_t toWrite); + void trim(std::size_t newLen); + + details::blob_backend * get_backend() { return backEnd_; } + +private: + details::blob_backend * backEnd_; +}; + +} // namespace soci + +#endif diff --git a/src/core/boost-fusion.h b/src/core/boost-fusion.h new file mode 100644 index 0000000000..5d18c8cc71 --- /dev/null +++ b/src/core/boost-fusion.h @@ -0,0 +1,99 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_BOOST_FUSION_H_INCLUDED +#define SOCI_BOOST_FUSION_H_INCLUDED + +#ifndef SOCI_MAX_FUSION_SEQUENCE_LENGTH +#define SOCI_MAX_FUSION_SEQUENCE_LENGTH 10 +#endif + +#include "values.h" +#include "type-conversion-traits.h" +// boost +#include +#include +#include +#include +#include +#include +#include +#include + +namespace soci +{ +namespace detail +{ + +template +struct type_conversion; + +#define SOCI_READ_FROM_BASE(z, k, data) \ + >> boost::fusion::at_c(out) +/**/ + +#define SOCI_READ_TO_BASE(z, k, data) \ + << boost::fusion::at_c(in) +/**/ + +#define SOCI_TYPE_CONVERSION_FUSION(z, k, data) \ + template \ + struct type_conversion \ + { \ + typedef values base_type; \ + \ + static void from_base(base_type const & in, indicator /*ind*/, Seq & out) \ + { \ + in \ + BOOST_PP_REPEAT(k, SOCI_READ_FROM_BASE, BOOST_PP_EMPTY) \ + ; \ + } \ + \ + static void to_base(Seq & in, base_type & out, indicator & /*ind*/) \ + { \ + out \ + BOOST_PP_REPEAT(k, SOCI_READ_TO_BASE, BOOST_PP_EMPTY) \ + ; \ + } \ + }; +/**/ + +BOOST_PP_REPEAT_FROM_TO(1, BOOST_PP_ADD(SOCI_MAX_FUSION_SEQUENCE_LENGTH, 1), SOCI_TYPE_CONVERSION_FUSION, BOOST_PP_EMPTY) + +#undef SOCI_TYPE_CONVERSION_FUSION +#undef SOCI_READ_FROM_BASE +#undef SOCI_READ_TO_BASE + +} // namespace detail + +template +struct type_conversion + >::type > +{ + typedef values base_type; + +private: + typedef typename boost::fusion::result_of::size::type size; + typedef detail::type_conversion converter; + +public: + static void from_base(base_type const & in, indicator ind, T& out) + { + converter::from_base( in, ind, out ); + } + + static void to_base(T& in, base_type & out, indicator & ind) + { + converter::to_base( in, out, ind ); + } +}; + +} // namespace soci + +#endif // SOCI_BOOST_FUSION_H_INCLUDED diff --git a/src/core/boost-gregorian-date.h b/src/core/boost-gregorian-date.h new file mode 100644 index 0000000000..cfa3c81d23 --- /dev/null +++ b/src/core/boost-gregorian-date.h @@ -0,0 +1,47 @@ +// +// Copyright (C) 2008 Maciej Sobczak +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_BOOST_GREGORIAN_DATE_H_INCLUDED +#define SOCI_BOOST_GREGORIAN_DATE_H_INCLUDED + +#include "type-conversion-traits.h" +// boost +#include +#include +// std +#include + +namespace soci +{ + +template<> +struct type_conversion +{ + typedef std::tm base_type; + + static void from_base( + base_type const & in, indicator ind, boost::gregorian::date & out) + { + if (ind == i_null) + { + throw soci_error("Null value not allowed for this type"); + } + + out = boost::gregorian::date_from_tm(in); + } + + static void to_base( + boost::gregorian::date const & in, base_type & out, indicator & ind) + { + out = boost::gregorian::to_tm(in); + ind = i_ok; + } +}; + +} // namespace soci + +#endif // SOCI_BOOST_GREGORIAN_DATE_H_INCLUDED diff --git a/src/core/boost-optional.h b/src/core/boost-optional.h new file mode 100644 index 0000000000..2f6699a35e --- /dev/null +++ b/src/core/boost-optional.h @@ -0,0 +1,55 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_BOOST_OPTIONAL_H_INCLUDED +#define SOCI_BOOST_OPTIONAL_H_INCLUDED + +#include "type-conversion-traits.h" +// boost +#include + +namespace soci +{ + +// simple fall-back for boost::optional +template +struct type_conversion > +{ + typedef typename type_conversion::base_type base_type; + + static void from_base(base_type const & in, indicator ind, + boost::optional & out) + { + if (ind == i_null) + { + out.reset(); + } + else + { + T tmp; + type_conversion::from_base(in, ind, tmp); + out = tmp; + } + } + + static void to_base(boost::optional const & in, + base_type & out, indicator & ind) + { + if (in.is_initialized()) + { + type_conversion::to_base(in.get(), out, ind); + } + else + { + ind = i_null; + } + } +}; + +} // namespace soci + +#endif // SOCI_BOOST_OPTIONAL_H_INCLUDED diff --git a/src/core/boost-tuple.h b/src/core/boost-tuple.h new file mode 100644 index 0000000000..7cf8240f66 --- /dev/null +++ b/src/core/boost-tuple.h @@ -0,0 +1,324 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_BOOST_TUPLE_H_INCLUDED +#define SOCI_BOOST_TUPLE_H_INCLUDED + +#include "values.h" +#include "type-conversion-traits.h" +// boost +#include + +#if defined(BOOST_VERSION) && BOOST_VERSION < 103500 + +namespace soci +{ + +template +struct type_conversion > +{ + typedef values base_type; + + static void from_base(base_type const & in, indicator ind, + boost::tuple & out) + { + in + >> boost::tuples::get<0>(out); + } + + static void to_base(boost::tuple & in, + base_type & out, indicator & ind) + { + out + << boost::tuples::get<0>(in); + } +}; + +template +struct type_conversion > +{ + typedef values base_type; + + static void from_base(base_type const & in, indicator ind, + boost::tuple & out) + { + in + >> boost::tuples::get<0>(out) + >> boost::tuples::get<1>(out); + } + + static void to_base(boost::tuple & in, + base_type & out, indicator & ind) + { + out + << boost::tuples::get<0>(in) + << boost::tuples::get<1>(in); + } +}; + +template +struct type_conversion > +{ + typedef values base_type; + + static void from_base(base_type const & in, indicator ind, + boost::tuple & out) + { + in + >> boost::tuples::get<0>(out) + >> boost::tuples::get<1>(out) + >> boost::tuples::get<2>(out); + } + + static void to_base(boost::tuple & in, + base_type & out, indicator & ind) + { + out + << boost::tuples::get<0>(in) + << boost::tuples::get<1>(in) + << boost::tuples::get<2>(in); + } +}; + +template +struct type_conversion > +{ + typedef values base_type; + + static void from_base(base_type const & in, indicator ind, + boost::tuple & out) + { + in + >> boost::tuples::get<0>(out) + >> boost::tuples::get<1>(out) + >> boost::tuples::get<2>(out) + >> boost::tuples::get<3>(out); + } + + static void to_base(boost::tuple & in, + base_type & out, indicator & ind) + { + out + << boost::tuples::get<0>(in) + << boost::tuples::get<1>(in) + << boost::tuples::get<2>(in) + << boost::tuples::get<3>(in); + } +}; + +template +struct type_conversion > +{ + typedef values base_type; + + static void from_base(base_type const & in, indicator ind, + boost::tuple & out) + { + in + >> boost::tuples::get<0>(out) + >> boost::tuples::get<1>(out) + >> boost::tuples::get<2>(out) + >> boost::tuples::get<3>(out) + >> boost::tuples::get<4>(out); + } + + static void to_base(boost::tuple & in, + base_type & out, indicator & ind) + { + out + << boost::tuples::get<0>(in) + << boost::tuples::get<1>(in) + << boost::tuples::get<2>(in) + << boost::tuples::get<3>(in) + << boost::tuples::get<4>(in); + } +}; + +template +struct type_conversion > +{ + typedef values base_type; + + static void from_base(base_type const & in, indicator ind, + boost::tuple & out) + { + in + >> boost::tuples::get<0>(out) + >> boost::tuples::get<1>(out) + >> boost::tuples::get<2>(out) + >> boost::tuples::get<3>(out) + >> boost::tuples::get<4>(out) + >> boost::tuples::get<5>(out); + } + + static void to_base(boost::tuple & in, + base_type & out, indicator & ind) + { + out + << boost::tuples::get<0>(in) + << boost::tuples::get<1>(in) + << boost::tuples::get<2>(in) + << boost::tuples::get<3>(in) + << boost::tuples::get<4>(in) + << boost::tuples::get<5>(in); + } +}; + +template +struct type_conversion > +{ + typedef values base_type; + + static void from_base(base_type const & in, indicator ind, + boost::tuple & out) + { + in + >> boost::tuples::get<0>(out) + >> boost::tuples::get<1>(out) + >> boost::tuples::get<2>(out) + >> boost::tuples::get<3>(out) + >> boost::tuples::get<4>(out) + >> boost::tuples::get<5>(out) + >> boost::tuples::get<6>(out); + } + + static void to_base(boost::tuple & in, + base_type & out, indicator & ind) + { + out + << boost::tuples::get<0>(in) + << boost::tuples::get<1>(in) + << boost::tuples::get<2>(in) + << boost::tuples::get<3>(in) + << boost::tuples::get<4>(in) + << boost::tuples::get<5>(in) + << boost::tuples::get<6>(in); + } +}; + +template +struct type_conversion > +{ + typedef values base_type; + + static void from_base(base_type const & in, indicator ind, + boost::tuple & out) + { + in + >> boost::tuples::get<0>(out) + >> boost::tuples::get<1>(out) + >> boost::tuples::get<2>(out) + >> boost::tuples::get<3>(out) + >> boost::tuples::get<4>(out) + >> boost::tuples::get<5>(out) + >> boost::tuples::get<6>(out) + >> boost::tuples::get<7>(out); + } + + static void to_base(boost::tuple & in, + base_type & out, indicator & ind) + { + out + << boost::tuples::get<0>(in) + << boost::tuples::get<1>(in) + << boost::tuples::get<2>(in) + << boost::tuples::get<3>(in) + << boost::tuples::get<4>(in) + << boost::tuples::get<5>(in) + << boost::tuples::get<6>(in) + << boost::tuples::get<7>(in); + } +}; + +template +struct type_conversion > +{ + typedef values base_type; + + static void from_base(base_type const & in, indicator ind, + boost::tuple & out) + { + in + >> boost::tuples::get<0>(out) + >> boost::tuples::get<1>(out) + >> boost::tuples::get<2>(out) + >> boost::tuples::get<3>(out) + >> boost::tuples::get<4>(out) + >> boost::tuples::get<5>(out) + >> boost::tuples::get<6>(out) + >> boost::tuples::get<7>(out) + >> boost::tuples::get<8>(out); + } + + static void to_base(boost::tuple & in, + base_type & out, indicator & ind) + { + out + << boost::tuples::get<0>(in) + << boost::tuples::get<1>(in) + << boost::tuples::get<2>(in) + << boost::tuples::get<3>(in) + << boost::tuples::get<4>(in) + << boost::tuples::get<5>(in) + << boost::tuples::get<6>(in) + << boost::tuples::get<7>(in) + << boost::tuples::get<8>(in); + } +}; + +template +struct type_conversion > +{ + typedef values base_type; + + static void from_base(base_type const & in, indicator ind, + boost::tuple & out) + { + in + >> boost::tuples::get<0>(out) + >> boost::tuples::get<1>(out) + >> boost::tuples::get<2>(out) + >> boost::tuples::get<3>(out) + >> boost::tuples::get<4>(out) + >> boost::tuples::get<5>(out) + >> boost::tuples::get<6>(out) + >> boost::tuples::get<7>(out) + >> boost::tuples::get<8>(out) + >> boost::tuples::get<9>(out); + } + + static void to_base( + boost::tuple & in, + base_type & out, indicator & ind) + { + out + << boost::tuples::get<0>(in) + << boost::tuples::get<1>(in) + << boost::tuples::get<2>(in) + << boost::tuples::get<3>(in) + << boost::tuples::get<4>(in) + << boost::tuples::get<5>(in) + << boost::tuples::get<6>(in) + << boost::tuples::get<7>(in) + << boost::tuples::get<8>(in) + << boost::tuples::get<9>(in); + } +}; + +} // namespace soci + +#else // BOOST_VERSION >= 103500 +# include "boost-fusion.h" +# include +#endif + +#endif // SOCI_BOOST_TUPLE_H_INCLUDED diff --git a/src/core/connection-parameters.cpp b/src/core/connection-parameters.cpp new file mode 100644 index 0000000000..30d3e2fa4a --- /dev/null +++ b/src/core/connection-parameters.cpp @@ -0,0 +1,62 @@ +// +// Copyright (C) 2013 Vadim Zeitlin +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_SOURCE +#include "connection-parameters.h" +#include "soci-backend.h" +#include "backend-loader.h" + +using namespace soci; + +namespace // anonymous +{ + +void parseConnectString(std::string const & connectString, + std::string & backendName, + std::string & connectionParameters) +{ + std::string const protocolSeparator = "://"; + + std::string::size_type const p = connectString.find(protocolSeparator); + if (p == std::string::npos) + { + throw soci_error("No backend name found in " + connectString); + } + + backendName = connectString.substr(0, p); + connectionParameters = connectString.substr(p + protocolSeparator.size()); +} + +} // namespace anonymous + +connection_parameters::connection_parameters() + : factory_(NULL) +{ +} + +connection_parameters::connection_parameters(backend_factory const & factory, + std::string const & connectString) + : factory_(&factory), connectString_(connectString) +{ +} + +connection_parameters::connection_parameters(std::string const & backendName, + std::string const & connectString) + : factory_(&dynamic_backends::get(backendName)), connectString_(connectString) +{ +} + +connection_parameters::connection_parameters(std::string const & fullConnectString) +{ + std::string backendName; + std::string connectString; + + parseConnectString(fullConnectString, backendName, connectString); + + factory_ = &dynamic_backends::get(backendName); + connectString_ = connectString; +} diff --git a/src/core/connection-parameters.h b/src/core/connection-parameters.h new file mode 100644 index 0000000000..86483a33a2 --- /dev/null +++ b/src/core/connection-parameters.h @@ -0,0 +1,68 @@ +// +// Copyright (C) 2013 Vadim Zeitlin +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_CONNECTION_PARAMETERS_H_INCLUDED +#define SOCI_CONNECTION_PARAMETERS_H_INCLUDED + +#include "soci-config.h" + +#include +#include + +namespace soci +{ + +class backend_factory; + +// Simple container for the information used when opening a session. +class SOCI_DECL connection_parameters +{ +public: + connection_parameters(); + connection_parameters(backend_factory const & factory, std::string const & connectString); + connection_parameters(std::string const & backendName, std::string const & connectString); + explicit connection_parameters(std::string const & fullConnectString); + + // Default copy ctor, assignment operator and dtor are all OK for us. + + + // Retrieve the backend and the connection strings specified in the ctor. + backend_factory const * get_factory() const { return factory_; } + std::string const & get_connect_string() const { return connectString_; } + + // Set the value of the given option, overwriting any previous value. + void set_option(const char * name, std::string const & value) + { + options_[name] = value; + } + + // Return true if the option with the given name was found and fill the + // provided parameter with its value. + bool get_option(const char * name, std::string & value) const + { + Options::const_iterator const it = options_.find(name); + if (it == options_.end()) + return false; + + value = it->second; + + return true; + } + +private: + // The backend and connection string specified in our ctor. + backend_factory const * factory_; + std::string connectString_; + + // We store all the values as strings for simplicity. + typedef std::map Options; + Options options_; +}; + +} // namespace soci + +#endif // SOCI_CONNECTION_PARAMETERS_H_INCLUDED diff --git a/src/core/connection-pool.cpp b/src/core/connection-pool.cpp new file mode 100644 index 0000000000..265e21dad3 --- /dev/null +++ b/src/core/connection-pool.cpp @@ -0,0 +1,339 @@ +// +// Copyright (C) 2008 Maciej Sobczak +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_SOURCE +#include "connection-pool.h" +#include "error.h" +#include "session.h" +#include +#include + +#ifndef _WIN32 +// POSIX implementation + +#include +#include +#include + +using namespace soci; + +struct connection_pool::connection_pool_impl +{ + bool find_free(std::size_t & pos) + { + for (std::size_t i = 0; i != sessions_.size(); ++i) + { + if (sessions_[i].first) + { + pos = i; + return true; + } + } + + return false; + } + + // by convention, first == true means the entry is free (not used) + std::vector > sessions_; + pthread_mutex_t mtx_; + pthread_cond_t cond_; +}; + +connection_pool::connection_pool(std::size_t size) +{ + if (size == 0) + { + throw soci_error("Invalid pool size"); + } + + pimpl_ = new connection_pool_impl(); + pimpl_->sessions_.resize(size); + for (std::size_t i = 0; i != size; ++i) + { + pimpl_->sessions_[i] = std::make_pair(true, new session()); + } + + int cc = pthread_mutex_init(&(pimpl_->mtx_), NULL); + if (cc != 0) + { + throw soci_error("Synchronization error"); + } + + cc = pthread_cond_init(&(pimpl_->cond_), NULL); + if (cc != 0) + { + throw soci_error("Synchronization error"); + } +} + +connection_pool::~connection_pool() +{ + for (std::size_t i = 0; i != pimpl_->sessions_.size(); ++i) + { + delete pimpl_->sessions_[i].second; + } + + pthread_mutex_destroy(&(pimpl_->mtx_)); + pthread_cond_destroy(&(pimpl_->cond_)); + + delete pimpl_; +} + +session & connection_pool::at(std::size_t pos) +{ + if (pos >= pimpl_->sessions_.size()) + { + throw soci_error("Invalid pool position"); + } + + return *(pimpl_->sessions_[pos].second); +} + +std::size_t connection_pool::lease() +{ + std::size_t pos; + + // no timeout + bool const success = try_lease(pos, -1); + assert(success); + + return pos; +} + +bool connection_pool::try_lease(std::size_t & pos, int timeout) +{ + struct timespec tm; + if (timeout >= 0) + { + // timeout is relative in milliseconds + + struct timeval tmv; + gettimeofday(&tmv, NULL); + + tm.tv_sec = tmv.tv_sec + timeout / 1000; + tm.tv_nsec = tmv.tv_usec * 1000 + (timeout % 1000) * 1000 * 1000; + + if (tm.tv_nsec >= 1000 * 1000 * 1000) + { + ++tm.tv_sec; + tm.tv_nsec -= 1000 * 1000 * 1000; + } + } + + int cc = pthread_mutex_lock(&(pimpl_->mtx_)); + if (cc != 0) + { + throw soci_error("Synchronization error"); + } + + while (pimpl_->find_free(pos) == false) + { + if (timeout < 0) + { + // no timeout, allow unlimited blocking + cc = pthread_cond_wait(&(pimpl_->cond_), &(pimpl_->mtx_)); + } + else + { + // wait with timeout + cc = pthread_cond_timedwait( + &(pimpl_->cond_), &(pimpl_->mtx_), &tm); + } + + if (cc == ETIMEDOUT) + { + break; + } + } + + if (cc == 0) + { + pimpl_->sessions_[pos].first = false; + } + + pthread_mutex_unlock(&(pimpl_->mtx_)); + + return cc == 0; +} + +void connection_pool::give_back(std::size_t pos) +{ + if (pos >= pimpl_->sessions_.size()) + { + throw soci_error("Invalid pool position"); + } + + int cc = pthread_mutex_lock(&(pimpl_->mtx_)); + if (cc != 0) + { + throw soci_error("Synchronization error"); + } + + if (pimpl_->sessions_[pos].first) + { + pthread_mutex_unlock(&(pimpl_->mtx_)); + throw soci_error("Cannot release pool entry (already free)"); + } + + pimpl_->sessions_[pos].first = true; + + pthread_mutex_unlock(&(pimpl_->mtx_)); + + pthread_cond_signal(&(pimpl_->cond_)); +} + +#else +// Windows implementation + +#include + +using namespace soci; + +struct connection_pool::connection_pool_impl +{ + bool find_free(std::size_t & pos) + { + for (std::size_t i = 0; i != sessions_.size(); ++i) + { + if (sessions_[i].first) + { + pos = i; + return true; + } + } + + return false; + } + + // by convention, first == true means the entry is free (not used) + std::vector > sessions_; + + CRITICAL_SECTION mtx_; + HANDLE sem_; +}; + +connection_pool::connection_pool(std::size_t size) +{ + if (size == 0) + { + throw soci_error("Invalid pool size"); + } + + pimpl_ = new connection_pool_impl(); + pimpl_->sessions_.resize(size); + for (std::size_t i = 0; i != size; ++i) + { + pimpl_->sessions_[i] = std::make_pair(true, new session()); + } + + InitializeCriticalSection(&(pimpl_->mtx_)); + + // initially all entries are available + HANDLE s = CreateSemaphore(NULL, + static_cast(size), static_cast(size), NULL); + if (s == NULL) + { + throw soci_error("Synchronization error"); + } + + pimpl_->sem_ = s; +} + +connection_pool::~connection_pool() +{ + for (std::size_t i = 0; i != pimpl_->sessions_.size(); ++i) + { + delete pimpl_->sessions_[i].second; + } + + DeleteCriticalSection(&(pimpl_->mtx_)); + CloseHandle(pimpl_->sem_); + + delete pimpl_; +} + +session & connection_pool::at(std::size_t pos) +{ + if (pos >= pimpl_->sessions_.size()) + { + throw soci_error("Invalid pool position"); + } + + return *(pimpl_->sessions_[pos].second); +} + +std::size_t connection_pool::lease() +{ + std::size_t pos; + + // no timeout + bool const success = try_lease(pos, -1); + assert(success); + if (!success) + { + // TODO: anything to report? --mloskot + } + + return pos; +} + +bool connection_pool::try_lease(std::size_t & pos, int timeout) +{ + DWORD cc = WaitForSingleObject(pimpl_->sem_, + timeout >= 0 ? static_cast(timeout) : INFINITE); + if (cc == WAIT_OBJECT_0) + { + // semaphore acquired, there is (at least) one free entry + + EnterCriticalSection(&(pimpl_->mtx_)); + + bool const success = pimpl_->find_free(pos); + assert(success); + if (!success) + { + // TODO: anything to report? --mloskot + } + + pimpl_->sessions_[pos].first = false; + + LeaveCriticalSection(&(pimpl_->mtx_)); + + return true; + } + else if (cc == WAIT_TIMEOUT) + { + return false; + } + else + { + throw soci_error("Synchronization error"); + } +} + +void connection_pool::give_back(std::size_t pos) +{ + if (pos >= pimpl_->sessions_.size()) + { + throw soci_error("Invalid pool position"); + } + + EnterCriticalSection(&(pimpl_->mtx_)); + + if (pimpl_->sessions_[pos].first) + { + LeaveCriticalSection(&(pimpl_->mtx_)); + throw soci_error("Cannot release pool entry (already free)"); + } + + pimpl_->sessions_[pos].first = true; + + LeaveCriticalSection(&(pimpl_->mtx_)); + + ReleaseSemaphore(pimpl_->sem_, 1, NULL); +} + +#endif // _WIN32 diff --git a/src/core/connection-pool.h b/src/core/connection-pool.h new file mode 100644 index 0000000000..b20c5cf7fe --- /dev/null +++ b/src/core/connection-pool.h @@ -0,0 +1,39 @@ +// +// Copyright (C) 2008 Maciej Sobczak +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_CONNECTION_POOL_H_INCLUDED +#define SOCI_CONNECTION_POOL_H_INCLUDED + +#include "soci-config.h" +// std +#include + +namespace soci +{ + +class session; + +class SOCI_DECL connection_pool +{ +public: + explicit connection_pool(std::size_t size); + ~connection_pool(); + + session & at(std::size_t pos); + + std::size_t lease(); + bool try_lease(std::size_t & pos, int timeout); + void give_back(std::size_t pos); + +private: + struct connection_pool_impl; + connection_pool_impl * pimpl_; +}; + +} + +#endif // SOCI_CONNECTION_POOL_H_INCLUDED diff --git a/src/core/error.cpp b/src/core/error.cpp new file mode 100644 index 0000000000..b16e2ca280 --- /dev/null +++ b/src/core/error.cpp @@ -0,0 +1,17 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_SOURCE + +#include "error.h" + +using namespace soci; + +soci_error::soci_error(std::string const & msg) + : std::runtime_error(msg) +{ +} diff --git a/src/core/error.h b/src/core/error.h new file mode 100644 index 0000000000..3e69375773 --- /dev/null +++ b/src/core/error.h @@ -0,0 +1,27 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_ERROR_H_INCLUDED +#define SOCI_ERROR_H_INCLUDED + +#include "soci-config.h" +// std +#include +#include + +namespace soci +{ + +class SOCI_DECL soci_error : public std::runtime_error +{ +public: + explicit soci_error(std::string const & msg); +}; + +} // namespace soci + +#endif // SOCI_ERROR_H_INCLUDED diff --git a/src/core/exchange-traits.h b/src/core/exchange-traits.h new file mode 100644 index 0000000000..9f21838ead --- /dev/null +++ b/src/core/exchange-traits.h @@ -0,0 +1,138 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_EXCHANGE_TRAITS_H_INCLUDED +#define SOCI_EXCHANGE_TRAITS_H_INCLUDED + +#include "type-conversion-traits.h" +#include "soci-backend.h" +// std +#include +#include +#include + +namespace soci +{ + +namespace details +{ + +struct basic_type_tag {}; +struct user_type_tag {}; + +template +struct exchange_traits +{ + // this is used for tag-dispatch between implementations for basic types + // and user-defined types + typedef user_type_tag type_family; + + enum // anonymous + { + x_type = + exchange_traits + < + typename type_conversion::base_type + >::x_type + }; +}; + +template <> +struct exchange_traits +{ + typedef basic_type_tag type_family; + enum { x_type = x_short }; +}; + +template <> +struct exchange_traits : exchange_traits +{ +}; + +template <> +struct exchange_traits +{ + typedef basic_type_tag type_family; + enum { x_type = x_integer }; +}; + +template <> +struct exchange_traits : exchange_traits +{ +}; + +template <> +struct exchange_traits +{ + typedef basic_type_tag type_family; + enum { x_type = x_char }; +}; + +template <> +struct exchange_traits +{ + typedef basic_type_tag type_family; + enum { x_type = x_long_long }; +}; + +template <> +struct exchange_traits +{ + typedef basic_type_tag type_family; + enum { x_type = x_unsigned_long_long }; +}; + +// long must be mapped either to x_integer or x_long_long: +template struct long_traits_helper; +template<> struct long_traits_helper<4> { enum { x_type = x_integer }; }; +template<> struct long_traits_helper<8> { enum { x_type = x_long_long }; }; + +template <> +struct exchange_traits +{ + typedef basic_type_tag type_family; + enum { x_type = long_traits_helper::x_type }; +}; + +template <> +struct exchange_traits : exchange_traits +{ +}; + +template <> +struct exchange_traits +{ + typedef basic_type_tag type_family; + enum { x_type = x_double }; +}; + +template <> +struct exchange_traits +{ + typedef basic_type_tag type_family; + enum { x_type = x_stdstring }; +}; + +template <> +struct exchange_traits +{ + typedef basic_type_tag type_family; + enum { x_type = x_stdtm }; +}; + +template +struct exchange_traits > +{ + typedef typename exchange_traits::type_family type_family; + enum { x_type = exchange_traits::x_type }; +}; + +} // namespace details + +} // namespace soci + +#endif // SOCI_EXCHANGE_TRAITS_H_INCLUDED diff --git a/src/core/into-type.cpp b/src/core/into-type.cpp new file mode 100644 index 0000000000..15fb1a1dd1 --- /dev/null +++ b/src/core/into-type.cpp @@ -0,0 +1,105 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_SOURCE +#include "into-type.h" +#include "statement.h" + +using namespace soci; +using namespace soci::details; + +standard_into_type::~standard_into_type() +{ + delete backEnd_; +} + +void standard_into_type::define(statement_impl & st, int & position) +{ + backEnd_ = st.make_into_type_backend(); + backEnd_->define_by_pos(position, data_, type_); +} + +void standard_into_type::pre_fetch() +{ + backEnd_->pre_fetch(); +} + +void standard_into_type::post_fetch(bool gotData, bool calledFromFetch) +{ + backEnd_->post_fetch(gotData, calledFromFetch, ind_); + + if (gotData) + { + convert_from_base(); + } +} + +void standard_into_type::clean_up() +{ + // backEnd_ might be NULL if IntoType was used + if (backEnd_ != NULL) + { + backEnd_->clean_up(); + } +} + +vector_into_type::~vector_into_type() +{ + delete backEnd_; +} + +void vector_into_type::define(statement_impl & st, int & position) +{ + backEnd_ = st.make_vector_into_type_backend(); + backEnd_->define_by_pos(position, data_, type_); +} + +void vector_into_type::pre_fetch() +{ + backEnd_->pre_fetch(); +} + +void vector_into_type::post_fetch(bool gotData, bool /* calledFromFetch */) +{ + if (indVec_ != NULL && indVec_->empty() == false) + { + assert(indVec_->empty() == false); + backEnd_->post_fetch(gotData, &(*indVec_)[0]); + } + else + { + backEnd_->post_fetch(gotData, NULL); + } + + if (gotData) + { + convert_from_base(); + } +} + +void vector_into_type::resize(std::size_t sz) +{ + if (indVec_ != NULL) + { + indVec_->resize(sz); + } + + backEnd_->resize(sz); +} + +std::size_t vector_into_type::size() const +{ + return backEnd_->size(); +} + +void vector_into_type::clean_up() +{ + if (backEnd_ != NULL) + { + backEnd_->clean_up(); + } +} diff --git a/src/core/into-type.h b/src/core/into-type.h new file mode 100644 index 0000000000..990b7937c3 --- /dev/null +++ b/src/core/into-type.h @@ -0,0 +1,164 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_INTO_TYPE_H_INCLUDED +#define SOCI_INTO_TYPE_H_INCLUDED + +#include "soci-backend.h" +#include "type-ptr.h" +#include "exchange-traits.h" +// std +#include +#include + +namespace soci +{ + +class session; + +namespace details +{ + +class prepare_temp_type; +class standard_into_type_backend; +class vector_into_type_backend; +class statement_impl; + +// this is intended to be a base class for all classes that deal with +// defining output data +class into_type_base +{ +public: + virtual ~into_type_base() {} + + virtual void define(statement_impl & st, int & position) = 0; + virtual void pre_fetch() = 0; + virtual void post_fetch(bool gotData, bool calledFromFetch) = 0; + virtual void clean_up() = 0; + + virtual std::size_t size() const = 0; // returns the number of elements + virtual void resize(std::size_t /* sz */) {} // used for vectors only +}; + +typedef type_ptr into_type_ptr; + +// standard types + +class SOCI_DECL standard_into_type : public into_type_base +{ +public: + standard_into_type(void * data, exchange_type type) + : data_(data), type_(type), ind_(NULL), backEnd_(NULL) {} + standard_into_type(void * data, exchange_type type, indicator & ind) + : data_(data), type_(type), ind_(&ind), backEnd_(NULL) {} + + virtual ~standard_into_type(); + +protected: + virtual void post_fetch(bool gotData, bool calledFromFetch); + +private: + virtual void define(statement_impl & st, int & position); + virtual void pre_fetch(); + virtual void clean_up(); + + virtual std::size_t size() const { return 1; } + + // conversion hook (from base type to arbitrary user type) + virtual void convert_from_base() {} + + void * data_; + exchange_type type_; + indicator * ind_; + + standard_into_type_backend * backEnd_; +}; + +// into type base class for vectors +class SOCI_DECL vector_into_type : public into_type_base +{ +public: + vector_into_type(void * data, exchange_type type) + : data_(data), type_(type), indVec_(NULL), backEnd_(NULL) {} + + vector_into_type(void * data, exchange_type type, + std::vector & ind) + : data_(data), type_(type), indVec_(&ind), backEnd_(NULL) {} + + ~vector_into_type(); + +protected: + virtual void post_fetch(bool gotData, bool calledFromFetch); + +private: + virtual void define(statement_impl & st, int & position); + virtual void pre_fetch(); + virtual void clean_up(); + virtual void resize(std::size_t sz); + virtual std::size_t size() const; + + void * data_; + exchange_type type_; + std::vector * indVec_; + + vector_into_type_backend * backEnd_; + + virtual void convert_from_base() {} +}; + +// implementation for the basic types (those which are supported by the library +// out of the box without user-provided conversions) + +template +class into_type : public standard_into_type +{ +public: + into_type(T & t) + : standard_into_type(&t, + static_cast(exchange_traits::x_type)) {} + into_type(T & t, indicator & ind) + : standard_into_type(&t, + static_cast(exchange_traits::x_type), ind) {} +}; + +template +class into_type > : public vector_into_type +{ +public: + into_type(std::vector & v) + : vector_into_type(&v, + static_cast(exchange_traits::x_type)) {} + into_type(std::vector & v, std::vector & ind) + : vector_into_type(&v, + static_cast(exchange_traits::x_type), ind) {} +}; + +// helper dispatchers for basic types + +template +into_type_ptr do_into(T & t, basic_type_tag) +{ + return into_type_ptr(new into_type(t)); +} + +template +into_type_ptr do_into(T & t, indicator & ind, basic_type_tag) +{ + return into_type_ptr(new into_type(t, ind)); +} + +template +into_type_ptr do_into(T & t, std::vector & ind, basic_type_tag) +{ + return into_type_ptr(new into_type(t, ind)); +} + +} // namespace details + +} // namespace soci + +#endif // SOCI_INTO_TYPE_H_INCLUDED diff --git a/src/core/into.h b/src/core/into.h new file mode 100644 index 0000000000..242f512219 --- /dev/null +++ b/src/core/into.h @@ -0,0 +1,55 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_INTO_H_INCLUDED +#define SOCI_INTO_H_INCLUDED + +#include "into-type.h" +#include "exchange-traits.h" +#include "type-conversion.h" +// std +#include +#include + +namespace soci +{ + +// the into function is a helper for defining output variables +// these helpers work with both basic and user-defined types thanks to +// the tag-dispatching, as defined in exchange_traits template + +template +details::into_type_ptr into(T & t) +{ + return details::do_into(t, + typename details::exchange_traits::type_family()); +} + +template +details::into_type_ptr into(T & t, indicator & ind) +{ + return details::do_into(t, ind, + typename details::exchange_traits::type_family()); +} + +template +details::into_type_ptr into(T & t, std::vector & ind) +{ + return details::do_into(t, ind, + typename details::exchange_traits::type_family()); +} + +// for char buffer with run-time size information +template +details::into_type_ptr into(T & t, std::size_t bufSize) +{ + return details::into_type_ptr(new details::into_type(t, bufSize)); +} + +} // namespace soci + +#endif // SOCI_INTO_H_INCLUDED diff --git a/src/core/once-temp-type.cpp b/src/core/once-temp-type.cpp new file mode 100644 index 0000000000..91dfab5925 --- /dev/null +++ b/src/core/once-temp-type.cpp @@ -0,0 +1,53 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_SOURCE +#include "once-temp-type.h" +#include "ref-counted-statement.h" +#include "session.h" + +using namespace soci; +using namespace soci::details; + +once_temp_type::once_temp_type(session & s) + : rcst_(new ref_counted_statement(s)) +{ + // this is the beginning of new query + s.get_query_stream().str(""); +} + +once_temp_type::once_temp_type(once_temp_type const & o) + :rcst_(o.rcst_) +{ + rcst_->inc_ref(); +} + +once_temp_type & once_temp_type::operator=(once_temp_type const & o) +{ + o.rcst_->inc_ref(); + rcst_->dec_ref(); + rcst_ = o.rcst_; + + return *this; +} + +once_temp_type::~once_temp_type() SOCI_ONCE_TEMP_TYPE_NOEXCEPT +{ + rcst_->dec_ref(); +} + +once_temp_type & once_temp_type::operator,(into_type_ptr const & i) +{ + rcst_->exchange(i); + return *this; +} + +once_temp_type & once_temp_type::operator,(use_type_ptr const & u) +{ + rcst_->exchange(u); + return *this; +} diff --git a/src/core/once-temp-type.h b/src/core/once-temp-type.h new file mode 100644 index 0000000000..abcc2de85b --- /dev/null +++ b/src/core/once-temp-type.h @@ -0,0 +1,108 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_ONCE_TEMP_TYPE_H_INCLUDED +#define SOCI_ONCE_TEMP_TYPE_H_INCLUDED + +#include "ref-counted-statement.h" +#include "prepare-temp-type.h" + +#if __cplusplus >= 201103L +#define SOCI_ONCE_TEMP_TYPE_NOEXCEPT noexcept(false) +#else +#define SOCI_ONCE_TEMP_TYPE_NOEXCEPT +#endif + +namespace soci +{ + +class session; + +namespace details +{ + +class ref_counted_statement; + +// this needs to be lightweight and copyable +class SOCI_DECL once_temp_type +{ +public: + + once_temp_type(session & s); + once_temp_type(once_temp_type const & o); + once_temp_type & operator=(once_temp_type const & o); + + ~once_temp_type() SOCI_ONCE_TEMP_TYPE_NOEXCEPT; + + template + once_temp_type & operator<<(T const & t) + { + rcst_->accumulate(t); + return *this; + } + + once_temp_type & operator,(into_type_ptr const &); + once_temp_type & operator,(use_type_ptr const &); + +private: + ref_counted_statement * rcst_; +}; + +// this needs to be lightweight and copyable +class once_type +{ +public: + once_type() : session_(NULL) {} + once_type(session * s) : session_(s) {} + + void set_session(session * s) + { + session_ = s; + } + + template + once_temp_type operator<<(T const & t) + { + once_temp_type o(*session_); + o << t; + return o; + } + +private: + session * session_; +}; + + +// this needs to be lightweight and copyable +class prepare_type +{ +public: + prepare_type() : session_(NULL) {} + prepare_type(session * s) : session_(s) {} + + void set_session(session * s) + { + session_ = s; + } + + template + prepare_temp_type operator<<(T const & t) + { + prepare_temp_type p(*session_); + p << t; + return p; + } + +private: + session * session_; +}; + +} // namespace details + +} // namespace soci + +#endif diff --git a/src/core/prepare-temp-type.cpp b/src/core/prepare-temp-type.cpp new file mode 100644 index 0000000000..4631bb38f8 --- /dev/null +++ b/src/core/prepare-temp-type.cpp @@ -0,0 +1,53 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_SOURCE +#include "prepare-temp-type.h" +#include "ref-counted-prepare-info.h" +#include "session.h" + +using namespace soci; +using namespace soci::details; + +prepare_temp_type::prepare_temp_type(session & s) + : rcpi_(new ref_counted_prepare_info(s)) +{ + // this is the beginning of new query + s.get_query_stream().str(""); +} + +prepare_temp_type::prepare_temp_type(prepare_temp_type const & o) + :rcpi_(o.rcpi_) +{ + rcpi_->inc_ref(); +} + +prepare_temp_type & prepare_temp_type::operator=(prepare_temp_type const & o) +{ + o.rcpi_->inc_ref(); + rcpi_->dec_ref(); + rcpi_ = o.rcpi_; + + return *this; +} + +prepare_temp_type::~prepare_temp_type() +{ + rcpi_->dec_ref(); +} + +prepare_temp_type & prepare_temp_type::operator,(into_type_ptr const & i) +{ + rcpi_->exchange(i); + return *this; +} + +prepare_temp_type & prepare_temp_type::operator,(use_type_ptr const & u) +{ + rcpi_->exchange(u); + return *this; +} diff --git a/src/core/prepare-temp-type.h b/src/core/prepare-temp-type.h new file mode 100644 index 0000000000..c3d8a0172d --- /dev/null +++ b/src/core/prepare-temp-type.h @@ -0,0 +1,51 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_PREPARE_TEMP_TYPE_INCLUDED +#define SOCI_PREPARE_TEMP_TYPE_INCLUDED + +#include "into-type.h" +#include "use-type.h" +#include "ref-counted-prepare-info.h" + +namespace soci +{ + +namespace details +{ + +// this needs to be lightweight and copyable +class SOCI_DECL prepare_temp_type +{ +public: + prepare_temp_type(session &); + prepare_temp_type(prepare_temp_type const &); + prepare_temp_type & operator=(prepare_temp_type const &); + + ~prepare_temp_type(); + + template + prepare_temp_type & operator<<(T const & t) + { + rcpi_->accumulate(t); + return *this; + } + + prepare_temp_type & operator,(into_type_ptr const & i); + prepare_temp_type & operator,(use_type_ptr const & u); + + ref_counted_prepare_info * get_prepare_info() const { return rcpi_; } + +private: + ref_counted_prepare_info * rcpi_; +}; + +} // namespace details + +} // namespace soci + +#endif diff --git a/src/core/procedure.cpp b/src/core/procedure.cpp new file mode 100644 index 0000000000..59f8d7a92d --- /dev/null +++ b/src/core/procedure.cpp @@ -0,0 +1,33 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_SOURCE +#include "procedure.h" +#include "statement.h" +#include "prepare-temp-type.h" + +using namespace soci; +using namespace soci::details; + +procedure_impl::procedure_impl(prepare_temp_type const & prep) + : statement_impl(prep.get_prepare_info()->session_), + refCount_(1) +{ + ref_counted_prepare_info * prepInfo = prep.get_prepare_info(); + + // take all bind/define info + intos_.swap(prepInfo->intos_); + uses_.swap(prepInfo->uses_); + + // allocate handle + alloc(); + + // prepare the statement + prepare(rewrite_for_procedure_call(prepInfo->get_query())); + + define_and_bind(); +} diff --git a/src/core/procedure.h b/src/core/procedure.h new file mode 100644 index 0000000000..92a4396e40 --- /dev/null +++ b/src/core/procedure.h @@ -0,0 +1,89 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_PROCEDURE_H_INCLUDED +#define SOCI_PROCEDURE_H_INCLUDED + +#include "statement.h" + +namespace soci +{ + +namespace details +{ + +class SOCI_DECL procedure_impl : public statement_impl +{ +public: + procedure_impl(session & s) : statement_impl(s), refCount_(1) {} + procedure_impl(prepare_temp_type const & prep); + + void inc_ref() { ++refCount_; } + void dec_ref() + { + if (--refCount_ == 0) + { + delete this; + } + } + +private: + int refCount_; +}; + +} // namespace details + +class SOCI_DECL procedure +{ +public: + // this is a conversion constructor + procedure(details::prepare_temp_type const & prep) + : impl_(new details::procedure_impl(prep)) {} + + ~procedure() { impl_->dec_ref(); } + + // copy is supported here + procedure(procedure const & other) + : impl_(other.impl_) + { + impl_->inc_ref(); + } + void operator=(procedure const & other) + { + other.impl_->inc_ref(); + impl_->dec_ref(); + impl_ = other.impl_; + } + + // forwarders to procedure_impl + // (or rather to its base interface from statement_impl) + + bool execute(bool withDataExchange = false) + { + gotData_ = impl_->execute(withDataExchange); + return gotData_; + } + + bool fetch() + { + gotData_ = impl_->fetch(); + return gotData_; + } + + bool got_data() const + { + return gotData_; + } + +private: + details::procedure_impl * impl_; + bool gotData_; +}; + +} // namespace soci + +#endif diff --git a/src/core/query_transformation.h b/src/core/query_transformation.h new file mode 100644 index 0000000000..0e184b0e04 --- /dev/null +++ b/src/core/query_transformation.h @@ -0,0 +1,59 @@ +// +// Copyright (C) 2013 Mateusz Loskot +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_QUERY_TRANSFORMATION_H_INCLUDED +#define SOCI_QUERY_TRANSFORMATION_H_INCLUDED + +#include "soci-config.h" +#include +#include + +namespace soci +{ + +namespace details +{ + +// Query transformation is a mechanism that enables user to apply +// any string-to-string transformation to SQL statement just +// before it is executed. +// Transformation procedure is specified by user, +// be it a function or an arbitrary type as long as it +// defines operator() with the appropriate signature: +// unary function takes any type converible-to std::string +// and returns std::string. + +class query_transformation_function + : public std::unary_function +{ +public: + virtual ~query_transformation_function() {} + virtual result_type operator()(argument_type a) const = 0; +}; + +template +class query_transformation : public query_transformation_function +{ +public: + query_transformation(T callback) + : callback_(callback) + {} + + result_type operator()(argument_type query) const + { + return callback_(query); + } + +private: + T callback_; +}; + +} // namespace details + +} // namespace soci + +#endif // SOCI_QUERY_TRANSFORMATION_H_INCLUDED diff --git a/src/core/ref-counted-prepare-info.cpp b/src/core/ref-counted-prepare-info.cpp new file mode 100644 index 0000000000..a655e16ea3 --- /dev/null +++ b/src/core/ref-counted-prepare-info.cpp @@ -0,0 +1,46 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_SOURCE +#include "ref-counted-prepare-info.h" +#include "session.h" + +using namespace soci; +using namespace soci::details; + +void ref_counted_prepare_info::exchange(into_type_ptr const & i) +{ + intos_.push_back(i.get()); + i.release(); +} + +void ref_counted_prepare_info::exchange(use_type_ptr const & u) +{ + uses_.push_back(u.get()); + u.release(); +} + +void ref_counted_prepare_info::final_action() +{ + // deallocate all bind and define objects + for (std::size_t i = intos_.size(); i > 0; --i) + { + delete intos_[i - 1]; + intos_.resize(i - 1); + } + + for (std::size_t i = uses_.size(); i > 0; --i) + { + delete uses_[i - 1]; + uses_.resize(i - 1); + } +} + +std::string ref_counted_prepare_info::get_query() const +{ + return session_.get_query(); +} diff --git a/src/core/ref-counted-prepare-info.h b/src/core/ref-counted-prepare-info.h new file mode 100644 index 0000000000..5339bf3027 --- /dev/null +++ b/src/core/ref-counted-prepare-info.h @@ -0,0 +1,59 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_REF_COUNTED_PREPARE_INFO_INCLUDED +#define SOCI_REF_COUNTED_PREPARE_INFO_INCLUDED + +#include "ref-counted-statement.h" +// std +#include +#include + +namespace soci +{ + +class session; + +namespace details +{ + +class procedure_impl; +class statement_impl; +class into_type_base; + +// this class conveys only the statement text and the bind/define info +// it exists only to be passed to statement's constructor +class ref_counted_prepare_info : public ref_counted_statement_base +{ +public: + ref_counted_prepare_info(session& s) + : ref_counted_statement_base(s) + , session_(s) + {} + + void exchange(into_type_ptr const& i); + void exchange(use_type_ptr const& u); + + void final_action(); + +private: + friend class statement_impl; + friend class procedure_impl; + + session& session_; + + std::vector intos_; + std::vector uses_; + + std::string get_query() const; +}; + +} // namespace details + +} // namespace soci + +#endif diff --git a/src/core/ref-counted-statement.cpp b/src/core/ref-counted-statement.cpp new file mode 100644 index 0000000000..ec7b4ddd49 --- /dev/null +++ b/src/core/ref-counted-statement.cpp @@ -0,0 +1,44 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_SOURCE +#include "ref-counted-statement.h" +#include "session.h" + +using namespace soci; +using namespace soci::details; + +ref_counted_statement_base::ref_counted_statement_base(session& s) + : refCount_(1) + , session_(s) +{ +} + +void ref_counted_statement::final_action() +{ + try + { + st_.alloc(); + st_.prepare(session_.get_query(), st_one_time_query); + st_.define_and_bind(); + + const bool gotData = st_.execute(true); + session_.set_got_data(gotData); + } + catch (...) + { + st_.clean_up(); + throw; + } + + st_.clean_up(); +} + +std::ostringstream& ref_counted_statement_base::get_query_stream() +{ + return session_.get_query_stream(); +} diff --git a/src/core/ref-counted-statement.h b/src/core/ref-counted-statement.h new file mode 100644 index 0000000000..78374d0653 --- /dev/null +++ b/src/core/ref-counted-statement.h @@ -0,0 +1,91 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_REF_COUNTED_STATEMENT_H_INCLUDED +#define SOCI_REF_COUNTED_STATEMENT_H_INCLUDED + +#include "statement.h" +#include "into-type.h" +#include "use-type.h" +// std +#include + +namespace soci +{ + +namespace details +{ + +// this class is a base for both "once" and "prepare" statements +class SOCI_DECL ref_counted_statement_base +{ +public: + ref_counted_statement_base(session& s); + + virtual ~ref_counted_statement_base() {} + + virtual void final_action() = 0; + + void inc_ref() { ++refCount_; } + void dec_ref() + { + if (--refCount_ == 0) + { + try + { + final_action(); + } + catch (...) + { + delete this; + throw; + } + + delete this; + } + } + + template + void accumulate(T const & t) { get_query_stream() << t; } + +protected: + // this function allows to break the circular dependenc + // between session and this class + std::ostringstream & get_query_stream(); + + int refCount_; + + session & session_; + +private: + // noncopyable + ref_counted_statement_base(ref_counted_statement_base const&); + ref_counted_statement_base& operator=(ref_counted_statement_base const&); +}; + +// this class is supposed to be a vehicle for the "once" statements +// it executes the whole statement in its destructor +class ref_counted_statement : public ref_counted_statement_base +{ +public: + ref_counted_statement(session & s) + : ref_counted_statement_base(s), st_(s) {} + + void exchange(into_type_ptr const & i) { st_.exchange(i); } + void exchange(use_type_ptr const & u) { st_.exchange(u); } + + virtual void final_action(); + +private: + statement st_; +}; + +} // namespace details + +} // namespace soci + +#endif diff --git a/src/core/row-exchange.h b/src/core/row-exchange.h new file mode 100644 index 0000000000..5f4763674e --- /dev/null +++ b/src/core/row-exchange.h @@ -0,0 +1,77 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_INTO_ROW_H_INCLUDED +#define SOCI_INTO_ROW_H_INCLUDED + +#include "into-type.h" +#include "exchange-traits.h" +#include "row.h" +#include "statement.h" +// std +#include + +namespace soci +{ + +namespace details +{ + +// Support selecting into a row for dynamic queries + +template <> +class into_type + : public into_type_base // bypass the standard_into_type +{ +public: + into_type(row & r) : r_(r) {} + into_type(row & r, indicator &) : r_(r) {} + +private: + // special handling for Row + virtual void define(statement_impl & st, int & /* position */) + { + st.set_row(&r_); + + // actual row description is performed + // as part of the statement execute + } + + virtual void pre_fetch() {} + virtual void post_fetch(bool gotData, bool /* calledFromFetch */) + { + r_.reset_get_counter(); + + if (gotData) + { + // this is used only to re-dispatch to derived class, if any + // (the derived class might be generated automatically by + // user conversions) + convert_from_base(); + } + } + + virtual void clean_up() {} + + virtual std::size_t size() const { return 1; } + + virtual void convert_from_base() {} + + row & r_; +}; + +template <> +struct exchange_traits +{ + typedef basic_type_tag type_family; +}; + +} // namespace details + +} // namespace soci + +#endif diff --git a/src/core/row.cpp b/src/core/row.cpp new file mode 100644 index 0000000000..eb2f10bcd1 --- /dev/null +++ b/src/core/row.cpp @@ -0,0 +1,113 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_SOURCE +#include "row.h" + +#include +#include +#include +#include + +using namespace soci; +using namespace details; + +row::row() + : uppercaseColumnNames_(false) + , currentPos_(0) +{} + +row::~row() +{ + clean_up(); +} + +void row::uppercase_column_names(bool forceToUpper) +{ + uppercaseColumnNames_ = forceToUpper; +} + +void row::add_properties(column_properties const &cp) +{ + columns_.push_back(cp); + + std::string columnName; + std::string const & originalName = cp.get_name(); + if (uppercaseColumnNames_) + { + for (std::size_t i = 0; i != originalName.size(); ++i) + { + columnName.push_back(static_cast(std::toupper(originalName[i]))); + } + + // rewrite the column name in the column_properties object + // as well to retain consistent views + + columns_[columns_.size() - 1].set_name(columnName); + } + else + { + columnName = originalName; + } + + index_[columnName] = columns_.size() - 1; +} + +std::size_t row::size() const +{ + return holders_.size(); +} + +void row::clean_up() +{ + std::size_t const hsize = holders_.size(); + for (std::size_t i = 0; i != hsize; ++i) + { + delete holders_[i]; + delete indicators_[i]; + } + + columns_.clear(); + holders_.clear(); + indicators_.clear(); + index_.clear(); +} + +indicator row::get_indicator(std::size_t pos) const +{ + assert(indicators_.size() >= static_cast(pos + 1)); + return *indicators_[pos]; +} + +indicator row::get_indicator(std::string const &name) const +{ + return get_indicator(find_column(name)); +} + +column_properties const & row::get_properties(std::size_t pos) const +{ + assert(columns_.size() >= pos + 1); + return columns_[pos]; +} + +column_properties const & row::get_properties(std::string const &name) const +{ + return get_properties(find_column(name)); +} + +std::size_t row::find_column(std::string const &name) const +{ + std::map::const_iterator it = index_.find(name); + if (it == index_.end()) + { + std::ostringstream msg; + msg << "Column '" << name << "' not found"; + throw soci_error(msg.str()); + } + + return it->second; +} diff --git a/src/core/row.h b/src/core/row.h new file mode 100644 index 0000000000..f1aa90525f --- /dev/null +++ b/src/core/row.h @@ -0,0 +1,147 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_ROW_H_INCLUDED +#define SOCI_ROW_H_INCLUDED + +#include "type-holder.h" +#include "soci-backend.h" +#include "type-conversion.h" +// std +#include +#include +#include +#include +#include + +namespace soci +{ + +class SOCI_DECL column_properties +{ + // use getters/setters in case we want to make some + // of the getters lazy in the future +public: + + std::string get_name() const { return name_; } + data_type get_data_type() const { return dataType_; } + + void set_name(std::string const& name) { name_ = name; } + void set_data_type(data_type dataType) { dataType_ = dataType; } + +private: + std::string name_; + data_type dataType_; +}; + +class SOCI_DECL row +{ +public: + row(); + ~row(); + + void uppercase_column_names(bool forceToUpper); + void add_properties(column_properties const& cp); + std::size_t size() const; + void clean_up(); + + indicator get_indicator(std::size_t pos) const; + indicator get_indicator(std::string const& name) const; + + template + inline void add_holder(T* t, indicator* ind) + { + holders_.push_back(new details::type_holder(t)); + indicators_.push_back(ind); + } + + column_properties const& get_properties(std::size_t pos) const; + column_properties const& get_properties(std::string const& name) const; + + template + T get(std::size_t pos) const + { + assert(holders_.size() >= pos + 1); + + typedef typename type_conversion::base_type base_type; + base_type const& baseVal = holders_[pos]->get(); + + T ret; + type_conversion::from_base(baseVal, *indicators_[pos], ret); + return ret; + } + + template + T get(std::size_t pos, T const &nullValue) const + { + assert(holders_.size() >= pos + 1); + + if (i_null == *indicators_[pos]) + { + return nullValue; + } + + return get(pos); + } + + template + T get(std::string const &name) const + { + std::size_t const pos = find_column(name); + return get(pos); + } + + template + T get(std::string const &name, T const &nullValue) const + { + std::size_t const pos = find_column(name); + + if (i_null == *indicators_[pos]) + { + return nullValue; + } + + return get(pos); + } + + template + row const& operator>>(T& value) const + { + value = get(currentPos_); + ++currentPos_; + return *this; + } + + void skip(std::size_t num = 1) const + { + currentPos_ += num; + } + + void reset_get_counter() const + { + currentPos_ = 0; + } + +private: + // copy not supported + row(row const &); + void operator=(row const &); + + std::size_t find_column(std::string const& name) const; + + std::vector columns_; + std::vector holders_; + std::vector indicators_; + std::map index_; + + bool uppercaseColumnNames_; + mutable std::size_t currentPos_; +}; + +} // namespace soci + +#endif // SOCI_ROW_H_INCLUDED diff --git a/src/core/rowid-exchange.h b/src/core/rowid-exchange.h new file mode 100644 index 0000000000..614f6ade02 --- /dev/null +++ b/src/core/rowid-exchange.h @@ -0,0 +1,59 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_ROWID_EXCHANGE_H_INCLUDED +#define SOCI_ROWID_EXCHANGE_H_INCLUDED + +#include "rowid.h" +#include "into-type.h" +#include "use-type.h" +#include "exchange-traits.h" +// std +#include + +namespace soci +{ + +namespace details +{ + +template <> +class use_type : public standard_use_type +{ +public: + use_type(rowid & rid, std::string const & name = std::string()) + : standard_use_type(&rid, x_rowid, false, name) {} + use_type(rowid const & rid, std::string const & name = std::string()) + : standard_use_type(const_cast(&rid), x_rowid, true, name) {} + use_type(rowid & rid, indicator & ind, + std::string const & name = std::string()) + : standard_use_type(&rid, x_rowid, ind, false, name) {} + use_type(rowid const & rid, indicator & ind, + std::string const & name = std::string()) + : standard_use_type(const_cast(&rid), x_rowid, ind, true, name) {} +}; + +template <> +class into_type : public standard_into_type +{ +public: + into_type(rowid & rid) : standard_into_type(&rid, x_rowid) {} + into_type(rowid & rid, indicator & ind) + :standard_into_type(&rid, x_rowid, ind) {} +}; + +template <> +struct exchange_traits +{ + typedef basic_type_tag type_family; +}; + +} // namespace details + +} // namespace soci + +#endif // SOCI_ROWID_EXCHANGE_H_INCLUDED diff --git a/src/core/rowid.cpp b/src/core/rowid.cpp new file mode 100644 index 0000000000..5f64244a99 --- /dev/null +++ b/src/core/rowid.cpp @@ -0,0 +1,23 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_SOURCE +#include "rowid.h" +#include "session.h" + +using namespace soci; +using namespace soci::details; + +rowid::rowid(session & s) +{ + backEnd_ = s.make_rowid_backend(); +} + +rowid::~rowid() +{ + delete backEnd_; +} diff --git a/src/core/rowid.h b/src/core/rowid.h new file mode 100644 index 0000000000..78685cdc46 --- /dev/null +++ b/src/core/rowid.h @@ -0,0 +1,41 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_ROWID_H_INCLUDED +#define SOCI_ROWID_H_INCLUDED + +#include "soci-config.h" + +namespace soci +{ + +class session; + +namespace details +{ + +class rowid_backend; + +} // namespace details + +// ROWID support + +class SOCI_DECL rowid +{ +public: + explicit rowid(session & s); + ~rowid(); + + details::rowid_backend * get_backend() { return backEnd_; } + +private: + details::rowid_backend *backEnd_; +}; + +} // namespace soci + +#endif diff --git a/src/core/rowset.h b/src/core/rowset.h new file mode 100644 index 0000000000..a2b7926f86 --- /dev/null +++ b/src/core/rowset.h @@ -0,0 +1,242 @@ +// +// Copyright (C) 2006-2008 Mateusz Loskot +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_ROWSET_H_INCLUDED +#define SOCI_ROWSET_H_INCLUDED + +#include "statement.h" +// std +#include +#include + +namespace soci +{ + +// +// rowset iterator of input category. +// +template +class rowset_iterator +{ +public: + + // Standard iterator traits + + typedef std::input_iterator_tag iterator_category; + typedef T value_type; + typedef T * pointer; + typedef T & reference; + typedef ptrdiff_t difference_type; + + // Constructors + + rowset_iterator() + : st_(0), define_(0) + {} + + rowset_iterator(statement & st, T & define) + : st_(&st), define_(&define) + { + assert(0 != st_); + assert(0 != define_); + assert(0 != st_->get_backend()); + + // Fetch first row to properly initialize iterator + ++(*this); + } + + // Access operators + + reference operator*() const + { + return (*define_); + } + + pointer operator->() const + { + return &(operator*()); + } + + // Iteration operators + + rowset_iterator & operator++() + { + // Fetch next row from dataset + + if (st_->fetch() == false) + { + // Set iterator to non-derefencable state (pass-the-end) + st_ = 0; + define_ = 0; + } + + return (*this); + } + + rowset_iterator operator++(int) + { + rowset_iterator tmp(*this); + ++(*this); + return tmp; + } + + // Comparison operators + + bool operator==(rowset_iterator const & rhs) const + { + return (st_== rhs.st_ && define_ == rhs.define_); + } + + bool operator!=(rowset_iterator const & rhs) const + { + return ((*this == rhs) == false); + } + +private: + + statement * st_; + T * define_; + +}; // class rowset_iterator + +namespace details +{ + +// +// Implementation of rowset +// +template +class rowset_impl +{ +public: + + typedef rowset_iterator iterator; + + rowset_impl(details::prepare_temp_type const & prep) + : refs_(1), st_(new statement(prep)), define_(new T()) + { + assert(0 != st_.get()); + assert(0 != define_.get()); + + st_->exchange_for_rowset(into(*define_)); + st_->execute(); + } + + void incRef() + { + ++refs_; + } + + void decRef() + { + if (--refs_ == 0) + { + delete this; + } + } + + iterator begin() const + { + // No ownership transfer occurs here + return iterator(*st_, *define_); + } + + iterator end() const + { + return iterator(); + } + +private: + + unsigned int refs_; + + const std::auto_ptr st_; + const std::auto_ptr define_; + + // Non-copyable + rowset_impl(rowset_impl const &); + rowset_impl & operator=(rowset_impl const &); + +}; // class rowset_impl + +} // namespace details + + +// +// rowset is a thin wrapper on statement and provides access to STL-like input iterator. +// The rowset_iterator can be used to easily loop through statement results and +// use STL algorithms accepting input iterators. +// +template +class rowset +{ +public: + + typedef T value_type; + typedef rowset_iterator iterator; + typedef rowset_iterator const_iterator; + + // this is a conversion constructor + rowset(details::prepare_temp_type const& prep) + : pimpl_(new details::rowset_impl(prep)) + { + assert(0 != pimpl_); + } + + rowset(rowset const & other) + : pimpl_(other.pimpl_) + { + assert(0 != pimpl_); + + pimpl_->incRef(); + } + + ~rowset() + { + assert(0 != pimpl_); + + pimpl_->decRef(); + } + + rowset& operator=(rowset const& rhs) + { + assert(0 != pimpl_); + assert(0 != rhs.pimpl_); + + if (&rhs != this) + { + rhs.pimpl_->incRef(); + pimpl_->decRef(); + pimpl_ = rhs.pimpl_; + } + return *this; + } + + const_iterator begin() const + { + assert(0 != pimpl_); + + return pimpl_->begin(); + } + + const_iterator end() const + { + assert(0 != pimpl_); + + return pimpl_->end(); + } + +private: + + // Pointer to implementation - the body + details::rowset_impl* pimpl_; + +}; // class rowset + +} // namespace soci + +#endif // SOCI_ROWSET_H_INCLUDED diff --git a/src/core/session.cpp b/src/core/session.cpp new file mode 100644 index 0000000000..27568bfee4 --- /dev/null +++ b/src/core/session.cpp @@ -0,0 +1,392 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_SOURCE +#include "session.h" +#include "connection-parameters.h" +#include "connection-pool.h" +#include "soci-backend.h" +#include "query_transformation.h" + +#ifdef _MSC_VER +#pragma warning(disable:4355) +#endif + +using namespace soci; +using namespace soci::details; + +namespace // anonymous +{ + +void ensureConnected(session_backend * backEnd) +{ + if (backEnd == NULL) + { + throw soci_error("Session is not connected."); + } +} + +} // namespace anonymous + +session::session() + : once(this), prepare(this), query_transformation_(NULL), logStream_(NULL), + uppercaseColumnNames_(false), backEnd_(NULL), + isFromPool_(false), pool_(NULL) +{ +} + +session::session(connection_parameters const & parameters) + : once(this), prepare(this), query_transformation_(NULL), logStream_(NULL), + lastConnectParameters_(parameters), + uppercaseColumnNames_(false), backEnd_(NULL), + isFromPool_(false), pool_(NULL) +{ + open(lastConnectParameters_); +} + +session::session(backend_factory const & factory, + std::string const & connectString) + : once(this), prepare(this), query_transformation_(NULL), logStream_(NULL), + lastConnectParameters_(factory, connectString), + uppercaseColumnNames_(false), backEnd_(NULL), + isFromPool_(false), pool_(NULL) +{ + open(lastConnectParameters_); +} + +session::session(std::string const & backendName, + std::string const & connectString) + : once(this), prepare(this), query_transformation_(NULL), logStream_(NULL), + lastConnectParameters_(backendName, connectString), + uppercaseColumnNames_(false), backEnd_(NULL), + isFromPool_(false), pool_(NULL) +{ + open(lastConnectParameters_); +} + +session::session(std::string const & connectString) + : once(this), prepare(this), query_transformation_(NULL), logStream_(NULL), + lastConnectParameters_(connectString), + uppercaseColumnNames_(false), backEnd_(NULL), + isFromPool_(false), pool_(NULL) +{ + open(lastConnectParameters_); +} + +session::session(connection_pool & pool) + : query_transformation_(NULL), logStream_(NULL), isFromPool_(true), pool_(&pool) +{ + poolPosition_ = pool.lease(); + session & pooledSession = pool.at(poolPosition_); + + once.set_session(&pooledSession); + prepare.set_session(&pooledSession); + backEnd_ = pooledSession.get_backend(); +} + +session::~session() +{ + if (isFromPool_) + { + pool_->give_back(poolPosition_); + } + else + { + delete query_transformation_; + delete backEnd_; + } +} + +void session::open(connection_parameters const & parameters) +{ + if (isFromPool_) + { + pool_->at(poolPosition_).open(parameters); + } + else + { + if (backEnd_ != NULL) + { + throw soci_error("Cannot open already connected session."); + } + + backend_factory const * const factory = parameters.get_factory(); + if (factory == NULL) + { + throw soci_error("Cannot connect without a valid backend."); + } + + backEnd_ = factory->make_session(parameters); + lastConnectParameters_ = parameters; + } +} + +void session::open(backend_factory const & factory, + std::string const & connectString) +{ + open(connection_parameters(factory, connectString)); +} + +void session::open(std::string const & backendName, + std::string const & connectString) +{ + open(connection_parameters(backendName, connectString)); +} + +void session::open(std::string const & connectString) +{ + open(connection_parameters(connectString)); +} + +void session::close() +{ + if (isFromPool_) + { + pool_->at(poolPosition_).close(); + backEnd_ = NULL; + } + else + { + delete backEnd_; + backEnd_ = NULL; + } +} + +void session::reconnect() +{ + if (isFromPool_) + { + pool_->at(poolPosition_).reconnect(); + backEnd_ = pool_->at(poolPosition_).get_backend(); + } + else + { + backend_factory const * const lastFactory = lastConnectParameters_.get_factory(); + if (lastFactory == NULL) + { + throw soci_error("Cannot reconnect without previous connection."); + } + + if (backEnd_ != NULL) + { + close(); + } + + backEnd_ = lastFactory->make_session(lastConnectParameters_); + } +} + +void session::begin() +{ + ensureConnected(backEnd_); + + backEnd_->begin(); +} + +void session::commit() +{ + ensureConnected(backEnd_); + + backEnd_->commit(); +} + +void session::rollback() +{ + ensureConnected(backEnd_); + + backEnd_->rollback(); +} + +std::ostringstream & session::get_query_stream() +{ + if (isFromPool_) + { + return pool_->at(poolPosition_).get_query_stream(); + } + else + { + return query_stream_; + } +} + +std::string session::get_query() const +{ + if (isFromPool_) + { + return pool_->at(poolPosition_).get_query(); + } + else + { + // preserve logical constness of get_query, + // stream used as read-only here, + session* pthis = const_cast(this); + + // sole place where any user-defined query transformation is applied + if (query_transformation_) + { + return (*query_transformation_)(pthis->get_query_stream().str()); + } + return pthis->get_query_stream().str(); + } +} + +void session::set_query_transformation_( + std::auto_ptr qtf) +{ + if (isFromPool_) + { + pool_->at(poolPosition_).set_query_transformation_(qtf); + } + else + { + delete query_transformation_; + query_transformation_= qtf.release(); + } +} + +void session::set_log_stream(std::ostream * s) +{ + if (isFromPool_) + { + pool_->at(poolPosition_).set_log_stream(s); + } + else + { + logStream_ = s; + } +} + +std::ostream * session::get_log_stream() const +{ + if (isFromPool_) + { + return pool_->at(poolPosition_).get_log_stream(); + } + else + { + return logStream_; + } +} + +void session::log_query(std::string const & query) +{ + if (isFromPool_) + { + pool_->at(poolPosition_).log_query(query); + } + else + { + if (logStream_ != NULL) + { + *logStream_ << query << '\n'; + } + + lastQuery_ = query; + } +} + +std::string session::get_last_query() const +{ + if (isFromPool_) + { + return pool_->at(poolPosition_).get_last_query(); + } + else + { + return lastQuery_; + } +} + +void session::set_got_data(bool gotData) +{ + if (isFromPool_) + { + pool_->at(poolPosition_).set_got_data(gotData); + } + else + { + gotData_ = gotData; + } +} + +bool session::got_data() const +{ + if (isFromPool_) + { + return pool_->at(poolPosition_).got_data(); + } + else + { + return gotData_; + } +} + +void session::uppercase_column_names(bool forceToUpper) +{ + if (isFromPool_) + { + pool_->at(poolPosition_).uppercase_column_names(forceToUpper); + } + else + { + uppercaseColumnNames_ = forceToUpper; + } +} + +bool session::get_uppercase_column_names() const +{ + if (isFromPool_) + { + return pool_->at(poolPosition_).get_uppercase_column_names(); + } + else + { + return uppercaseColumnNames_; + } +} + +bool session::get_next_sequence_value(std::string const & sequence, long & value) +{ + ensureConnected(backEnd_); + + return backEnd_->get_next_sequence_value(*this, sequence, value); +} + +bool session::get_last_insert_id(std::string const & sequence, long & value) +{ + ensureConnected(backEnd_); + + return backEnd_->get_last_insert_id(*this, sequence, value); +} + +std::string session::get_backend_name() const +{ + ensureConnected(backEnd_); + + return backEnd_->get_backend_name(); +} + +statement_backend * session::make_statement_backend() +{ + ensureConnected(backEnd_); + + return backEnd_->make_statement_backend(); +} + +rowid_backend * session::make_rowid_backend() +{ + ensureConnected(backEnd_); + + return backEnd_->make_rowid_backend(); +} + +blob_backend * session::make_blob_backend() +{ + ensureConnected(backEnd_); + + return backEnd_->make_blob_backend(); +} diff --git a/src/core/session.h b/src/core/session.h new file mode 100644 index 0000000000..ff521b0a30 --- /dev/null +++ b/src/core/session.h @@ -0,0 +1,150 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_SESSION_H_INCLUDED +#define SOCI_SESSION_H_INCLUDED + +#include "once-temp-type.h" +#include "query_transformation.h" +#include "connection-parameters.h" + +// std +#include +#include +#include +#include +#include + +namespace soci +{ +class values; +class backend_factory; + +namespace details +{ + +class session_backend; +class statement_backend; +class rowid_backend; +class blob_backend; + +} // namespace details + +class connection_pool; + +class SOCI_DECL session +{ +private: + + void set_query_transformation_(std::auto_ptr qtf); + +public: + session(); + explicit session(connection_parameters const & parameters); + session(backend_factory const & factory, std::string const & connectString); + session(std::string const & backendName, std::string const & connectString); + explicit session(std::string const & connectString); + explicit session(connection_pool & pool); + + ~session(); + + void open(connection_parameters const & parameters); + void open(backend_factory const & factory, std::string const & connectString); + void open(std::string const & backendName, std::string const & connectString); + void open(std::string const & connectString); + void close(); + void reconnect(); + + void begin(); + void commit(); + void rollback(); + + // once and prepare are for syntax sugar only + details::once_type once; + details::prepare_type prepare; + + // even more sugar + template + details::once_temp_type operator<<(T const & t) { return once << t; } + + std::ostringstream & get_query_stream(); + std::string get_query() const; + + template + void set_query_transformation(T callback) + { + std::auto_ptr qtf(new details::query_transformation(callback)); + set_query_transformation_(qtf); + + assert(qtf.get() == NULL); + } + + // support for basic logging + void set_log_stream(std::ostream * s); + std::ostream * get_log_stream() const; + + void log_query(std::string const & query); + std::string get_last_query() const; + + void set_got_data(bool gotData); + bool got_data() const; + + void uppercase_column_names(bool forceToUpper); + + bool get_uppercase_column_names() const; + + // Functions for dealing with sequence/auto-increment values. + + // If true is returned, value is filled with the next value from the given + // sequence. Otherwise either the sequence is invalid (doesn't exist) or + // the current backend doesn't support sequences. If you use sequences for + // automatically generating primary key values, you should use + // get_last_insert_id() after the insertion in this case. + bool get_next_sequence_value(std::string const & sequence, long & value); + + // If true is returned, value is filled with the last auto-generated value + // for this table (although some backends ignore the table argument and + // return the last value auto-generated in this session). + bool get_last_insert_id(std::string const & table, long & value); + + + // for diagnostics and advanced users + // (downcast it to expected back-end session class) + details::session_backend * get_backend() { return backEnd_; } + + std::string get_backend_name() const; + + details::statement_backend * make_statement_backend(); + details::rowid_backend * make_rowid_backend(); + details::blob_backend * make_blob_backend(); + +private: + session(session const &); + session& operator=(session const &); + + std::ostringstream query_stream_; + details::query_transformation_function* query_transformation_; + + std::ostream * logStream_; + std::string lastQuery_; + + connection_parameters lastConnectParameters_; + + bool uppercaseColumnNames_; + + details::session_backend * backEnd_; + + bool gotData_; + + bool isFromPool_; + std::size_t poolPosition_; + connection_pool * pool_; +}; + +} // namespace soci + +#endif // SOCI_SESSION_H_INCLUDED diff --git a/src/core/soci-backend.h b/src/core/soci-backend.h new file mode 100644 index 0000000000..75edee4ad2 --- /dev/null +++ b/src/core/soci-backend.h @@ -0,0 +1,277 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_BACKEND_H_INCLUDED +#define SOCI_BACKEND_H_INCLUDED + +#include "soci-config.h" +#include "error.h" +// std +#include +#include +#include + +namespace soci +{ + +// data types, as seen by the user +enum data_type +{ + dt_string, dt_date, dt_double, dt_integer, dt_long_long, dt_unsigned_long_long +}; + +// the enum type for indicator variables +enum indicator { i_ok, i_null, i_truncated }; + +class session; + +namespace details +{ + +// data types, as used to describe exchange format +enum exchange_type +{ + x_char, + x_stdstring, + x_short, + x_integer, + x_long_long, + x_unsigned_long_long, + x_double, + x_stdtm, + x_statement, + x_rowid, + x_blob +}; + +// type of statement (used for optimizing statement preparation) +enum statement_type +{ + st_one_time_query, + st_repeatable_query +}; + +// polymorphic into type backend + +class standard_into_type_backend +{ +public: + standard_into_type_backend() {} + virtual ~standard_into_type_backend() {} + + virtual void define_by_pos(int& position, void* data, exchange_type type) = 0; + + virtual void pre_fetch() = 0; + virtual void post_fetch(bool gotData, bool calledFromFetch, indicator* ind) = 0; + + virtual void clean_up() = 0; + +private: + // noncopyable + standard_into_type_backend(standard_into_type_backend const&); + standard_into_type_backend& operator=(standard_into_type_backend const&); +}; + +class vector_into_type_backend +{ +public: + + vector_into_type_backend() {} + virtual ~vector_into_type_backend() {} + + virtual void define_by_pos(int& position, void* data, exchange_type type) = 0; + + virtual void pre_fetch() = 0; + virtual void post_fetch(bool gotData, indicator* ind) = 0; + + virtual void resize(std::size_t sz) = 0; + virtual std::size_t size() = 0; + + virtual void clean_up() = 0; + +private: + // noncopyable + vector_into_type_backend(vector_into_type_backend const&); + vector_into_type_backend& operator=(vector_into_type_backend const&); +}; + +// polymorphic use type backend + +class standard_use_type_backend +{ +public: + standard_use_type_backend() {} + virtual ~standard_use_type_backend() {} + + virtual void bind_by_pos(int& position, void* data, + exchange_type type, bool readOnly) = 0; + virtual void bind_by_name(std::string const& name, + void* data, exchange_type type, bool readOnly) = 0; + + virtual void pre_use(indicator const* ind) = 0; + virtual void post_use(bool gotData, indicator * ind) = 0; + + virtual void clean_up() = 0; + +private: + // noncopyable + standard_use_type_backend(standard_use_type_backend const&); + standard_use_type_backend& operator=(standard_use_type_backend const&); +}; + +class vector_use_type_backend +{ +public: + vector_use_type_backend() {} + virtual ~vector_use_type_backend() {} + + virtual void bind_by_pos(int& position, void* data, exchange_type type) = 0; + virtual void bind_by_name(std::string const& name, + void* data, exchange_type type) = 0; + + virtual void pre_use(indicator const* ind) = 0; + + virtual std::size_t size() = 0; + + virtual void clean_up() = 0; + +private: + // noncopyable + vector_use_type_backend(vector_use_type_backend const&); + vector_use_type_backend& operator=(vector_use_type_backend const&); +}; + +// polymorphic statement backend + +class statement_backend +{ +public: + statement_backend() {} + virtual ~statement_backend() {} + + virtual void alloc() = 0; + virtual void clean_up() = 0; + + virtual void prepare(std::string const& query, statement_type eType) = 0; + + enum exec_fetch_result + { + ef_success, + ef_no_data + }; + + virtual exec_fetch_result execute(int number) = 0; + virtual exec_fetch_result fetch(int number) = 0; + + virtual long long get_affected_rows() = 0; + virtual int get_number_of_rows() = 0; + + virtual std::string rewrite_for_procedure_call(std::string const& query) = 0; + + virtual int prepare_for_describe() = 0; + virtual void describe_column(int colNum, data_type& dtype, + std::string& column_name) = 0; + + virtual standard_into_type_backend* make_into_type_backend() = 0; + virtual standard_use_type_backend* make_use_type_backend() = 0; + virtual vector_into_type_backend* make_vector_into_type_backend() = 0; + virtual vector_use_type_backend* make_vector_use_type_backend() = 0; + +private: + // noncopyable + statement_backend(statement_backend const&); + statement_backend& operator=(statement_backend const&); +}; + +// polymorphic RowID backend + +class rowid_backend +{ +public: + virtual ~rowid_backend() {} +}; + +// polymorphic blob backend + +class blob_backend +{ +public: + blob_backend() {} + virtual ~blob_backend() {} + + virtual std::size_t get_len() = 0; + virtual std::size_t read(std::size_t offset, char* buf, + std::size_t toRead) = 0; + virtual std::size_t write(std::size_t offset, char const* buf, + std::size_t toWrite) = 0; + virtual std::size_t append(char const* buf, std::size_t toWrite) = 0; + virtual void trim(std::size_t newLen) = 0; + +private: + // noncopyable + blob_backend(blob_backend const&); + blob_backend& operator=(blob_backend const&); +}; + +// polymorphic session backend + +class session_backend +{ +public: + session_backend() {} + virtual ~session_backend() {} + + virtual void begin() = 0; + virtual void commit() = 0; + virtual void rollback() = 0; + + // At least one of these functions is usually not implemented for any given + // backend as RDBMS support either sequences or auto-generated values, so + // we don't declare them as pure virtuals to avoid having to define trivial + // versions of them in the derived classes. However every backend should + // define at least one of them to allow the code using auto-generated values + // to work. + virtual bool get_next_sequence_value(session&, std::string const&, long&) + { + return false; + } + virtual bool get_last_insert_id(session&, std::string const&, long&) + { + return false; + } + + virtual std::string get_backend_name() const = 0; + + virtual statement_backend* make_statement_backend() = 0; + virtual rowid_backend* make_rowid_backend() = 0; + virtual blob_backend* make_blob_backend() = 0; + +private: + // noncopyable + session_backend(session_backend const&); + session_backend& operator=(session_backend const&); +}; + +} // namespace details + +// simple base class for the session back-end factory + +class connection_parameters; + +class SOCI_DECL backend_factory +{ +public: + backend_factory() {} + virtual ~backend_factory() {} + + virtual details::session_backend* make_session( + connection_parameters const& parameters) const = 0; +}; + +} // namespace soci + +#endif // SOCI_BACKEND_H_INCLUDED diff --git a/src/core/soci-config.h b/src/core/soci-config.h new file mode 100644 index 0000000000..3cae58c9f0 --- /dev/null +++ b/src/core/soci-config.h @@ -0,0 +1,38 @@ +// +// Copyright (C) 2006-2008 Mateusz Loskot +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_CONFIG_H_INCLUDED +#define SOCI_CONFIG_H_INCLUDED + +// +// On Windows platform, define SOCI_DECL depending on +// static or dynamic (SOCI_DLL) linkage. +// +// For details, see +// http://www.boost.org/more/separate_compilation.html +// + +#ifdef _WIN32 +# ifdef SOCI_DLL +# ifdef SOCI_SOURCE +# define SOCI_DECL __declspec(dllexport) +# else +# define SOCI_DECL __declspec(dllimport) +# endif // SOCI_SOURCE +# endif // SOCI_DLL +#endif // _WIN32 +// +// If SOCI_DECL isn't defined yet define it now +#ifndef SOCI_DECL +# define SOCI_DECL +#endif + +#ifdef _MSC_VER +#pragma warning(disable:4251 4275) +#endif // _MSC_VER + +#endif // SOCI_CONFIG_H_INCLUDED diff --git a/src/core/soci-platform.h b/src/core/soci-platform.h new file mode 100644 index 0000000000..a6c1bb4f56 --- /dev/null +++ b/src/core/soci-platform.h @@ -0,0 +1,57 @@ +// +// Copyright (C) 2006-2008 Mateusz Loskot +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_PLATFORM_H_INCLUDED +#define SOCI_PLATFORM_H_INCLUDED + +#if defined(_MSC_VER) || defined(__MINGW32__) +#define LL_FMT_FLAGS "I64" +#else +#define LL_FMT_FLAGS "ll" +#endif + +// Portability hacks for Microsoft Visual C++ compiler +#ifdef _MSC_VER +#include + +// Define if you have the vsnprintf variants. +#if _MSC_VER < 1500 +# define HAVE_VSNPRINTF 1 +# define vsnprintf _vsnprintf +#endif + +// Define if you have the snprintf variants. +#define HAVE_SNPRINTF 1 +#define snprintf _snprintf + +// Define if you have the strtoll and strtoull variants. +#if _MSC_VER >= 1300 +# define HAVE_STRTOLL 1 +# define HAVE_STRTOULL 1 + +#if _MSC_VER < 1800 +namespace std { + inline long long strtoll(char const* str, char** str_end, int base) + { + return _strtoi64(str, str_end, base); + } + + inline unsigned long long strtoull(char const* str, char** str_end, int base) + { + return _strtoui64(str, str_end, base); + } +} +#endif + +#else +# undef HAVE_STRTOLL +# undef HAVE_STRTOULL +# error "Visual C++ versions prior 1300 don't support _strtoi64 and _strtoui64" +#endif // _MSC_VER >= 1300 +#endif // _MSC_VER + +#endif // SOCI_PLATFORM_H_INCLUDED diff --git a/src/core/soci-simple.cpp b/src/core/soci-simple.cpp new file mode 100644 index 0000000000..f73a18dd72 --- /dev/null +++ b/src/core/soci-simple.cpp @@ -0,0 +1,1842 @@ +// +// Copyright (C) 2008 Maciej Sobczak +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_SOURCE + +#include "soci-simple.h" +#include "soci.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace soci; + +namespace // unnamed +{ + +struct session_wrapper +{ + session sql; + + bool is_ok; + std::string error_message; +}; + +} // namespace unnamed + + +SOCI_DECL session_handle soci_create_session(char const * connection_string) +{ + session_wrapper * wrapper = NULL; + try + { + wrapper = new session_wrapper(); + } + catch (...) + { + return NULL; + } + + try + { + wrapper->sql.open(connection_string); + wrapper->is_ok = true; + } + catch (std::exception const & e) + { + wrapper->is_ok = false; + wrapper->error_message = e.what(); + } + + return wrapper; +} + +SOCI_DECL void soci_destroy_session(session_handle s) +{ + session_wrapper * wrapper = static_cast(s); + delete wrapper; +} + +SOCI_DECL void soci_begin(session_handle s) +{ + session_wrapper * wrapper = static_cast(s); + try + { + wrapper->sql.begin(); + wrapper->is_ok = true; + } + catch (std::exception const & e) + { + wrapper->is_ok = false; + wrapper->error_message = e.what(); + } +} + +SOCI_DECL void soci_commit(session_handle s) +{ + session_wrapper * wrapper = static_cast(s); + try + { + wrapper->sql.commit(); + wrapper->is_ok = true; + } + catch (std::exception const & e) + { + wrapper->is_ok = false; + wrapper->error_message = e.what(); + } +} + +SOCI_DECL void soci_rollback(session_handle s) +{ + session_wrapper * wrapper = static_cast(s); + try + { + wrapper->sql.rollback(); + wrapper->is_ok = true; + } + catch (std::exception const & e) + { + wrapper->is_ok = false; + wrapper->error_message = e.what(); + } +} + +// this will not be needed until dynamic row is exposed +// SOCI_DECL void soci_uppercase_column_names(session_handle s, bool forceToUpper) +// { +// session_wrapper * wrapper = static_cast(s); +// wrapper->sql.uppercase_column_names(forceToUpper); +// wrapper->is_ok = true; +// } + +SOCI_DECL int soci_session_state(session_handle s) +{ + session_wrapper * wrapper = static_cast(s); + + return wrapper->is_ok ? 1 : 0; +} + +SOCI_DECL char const * soci_session_error_message(session_handle s) +{ + session_wrapper * wrapper = static_cast(s); + + return wrapper->error_message.c_str(); +} + + +// statement + + +namespace // unnamed +{ + +struct statement_wrapper +{ + statement_wrapper(session & sql) + : st(sql), statement_state(clean), into_kind(empty), use_kind(empty), + next_position(0), is_ok(true) {} + + statement st; + + enum state { clean, defining, executing } statement_state; + enum kind { empty, single, bulk } into_kind, use_kind; + + // into elements + int next_position; + std::vector into_types; // for both single and bulk + std::vector into_indicators; + std::map into_strings; + std::map into_ints; + std::map into_longlongs; + std::map into_doubles; + std::map into_dates; + + std::vector > into_indicators_v; + std::map > into_strings_v; + std::map > into_ints_v; + std::map > into_longlongs_v; + std::map > into_doubles_v; + std::map > into_dates_v; + + // use elements + std::map use_indicators; + std::map use_strings; + std::map use_ints; + std::map use_longlongs; + std::map use_doubles; + std::map use_dates; + + std::map > use_indicators_v; + std::map > use_strings_v; + std::map > use_ints_v; + std::map > use_longlongs_v; + std::map > use_doubles_v; + std::map > use_dates_v; + + // format is: "YYYY MM DD hh mm ss" + char date_formatted[20]; + + bool is_ok; + std::string error_message; +}; + +// helper for checking if the attempt was made to add more into/use elements +// after the statement was set for execution +bool cannot_add_elements(statement_wrapper & wrapper, statement_wrapper::kind k, bool into) +{ + if (wrapper.statement_state == statement_wrapper::executing) + { + wrapper.is_ok = false; + wrapper.error_message = "Cannot add more data items."; + return true; + } + + if (into) + { + if (k == statement_wrapper::single && wrapper.into_kind == statement_wrapper::bulk) + { + wrapper.is_ok = false; + wrapper.error_message = "Cannot add single into data items."; + return true; + } + if (k == statement_wrapper::bulk && wrapper.into_kind == statement_wrapper::single) + { + wrapper.is_ok = false; + wrapper.error_message = "Cannot add vector into data items."; + return true; + } + } + else + { + // trying to add use elements + if (k == statement_wrapper::single && wrapper.use_kind == statement_wrapper::bulk) + { + wrapper.is_ok = false; + wrapper.error_message = "Cannot add single use data items."; + return true; + } + if (k == statement_wrapper::bulk && wrapper.use_kind == statement_wrapper::single) + { + wrapper.is_ok = false; + wrapper.error_message = "Cannot add vector use data items."; + return true; + } + } + + wrapper.is_ok = true; + return false; +} + +// helper for checking if the expected into element exists on the given position +bool position_check_failed(statement_wrapper & wrapper, statement_wrapper::kind k, + int position, data_type expected_type, char const * type_name) +{ + if (position < 0 || position >= wrapper.next_position) + { + wrapper.is_ok = false; + wrapper.error_message = "Invalid position."; + return true; + } + + if (wrapper.into_types[position] != expected_type) + { + wrapper.is_ok = false; + wrapper.error_message = "No into "; + if (k == statement_wrapper::bulk) + { + wrapper.error_message += "vector "; + } + wrapper.error_message += type_name; + wrapper.error_message += " element at this position."; + return true; + } + + wrapper.is_ok = true; + return false; +} + +// helper for checking if the into element on the given position +// is not null +bool not_null_check_failed(statement_wrapper & wrapper, int position) +{ + if (wrapper.into_indicators[position] == i_null) + { + wrapper.is_ok = false; + wrapper.error_message = "Element is null."; + return true; + } + + wrapper.is_ok = true; + return false; +} + +// overloaded version for vectors +bool not_null_check_failed(statement_wrapper & wrapper, int position, int index) +{ + if (wrapper.into_indicators_v[position][index] == i_null) + { + wrapper.is_ok = false; + wrapper.error_message = "Element is null."; + return true; + } + + wrapper.is_ok = true; + return false; +} + +// helper for checking the index value +template +bool index_check_failed(std::vector const & v, + statement_wrapper & wrapper, int index) +{ + if (index < 0 || index >= static_cast(v.size())) + { + wrapper.is_ok = false; + wrapper.error_message = "Invalid index."; + return true; + } + + wrapper.is_ok = true; + return false; +} + +// helper for checking the uniqueness of the use element's name +bool name_unique_check_failed(statement_wrapper & wrapper, + statement_wrapper::kind k, char const * name) +{ + bool is_unique; + if (k == statement_wrapper::single) + { + typedef std::map::const_iterator iterator; + iterator const it = wrapper.use_indicators.find(name); + is_unique = it == wrapper.use_indicators.end(); + } + else + { + // vector version + + typedef std::map + < + std::string, + std::vector + >::const_iterator iterator; + + iterator const it = wrapper.use_indicators_v.find(name); + is_unique = it == wrapper.use_indicators_v.end(); + } + + if (is_unique) + { + wrapper.is_ok = true; + return false; + } + else + { + wrapper.is_ok = false; + wrapper.error_message = "Name of use element should be unique."; + return true; + } +} + +// helper for checking if the use element with the given name exists +bool name_exists_check_failed(statement_wrapper & wrapper, + char const * name, data_type expected_type, + statement_wrapper::kind k, char const * type_name) +{ + bool name_exists = false; + if (k == statement_wrapper::single) + { + switch (expected_type) + { + case dt_string: + { + typedef std::map + < + std::string, + std::string + >::const_iterator iterator; + iterator const it = wrapper.use_strings.find(name); + name_exists = (it != wrapper.use_strings.end()); + } + break; + case dt_integer: + { + typedef std::map::const_iterator iterator; + iterator const it = wrapper.use_ints.find(name); + name_exists = (it != wrapper.use_ints.end()); + } + break; + case dt_long_long: + { + typedef std::map::const_iterator + iterator; + iterator const it = wrapper.use_longlongs.find(name); + name_exists = (it != wrapper.use_longlongs.end()); + } + break; + case dt_double: + { + typedef std::map::const_iterator iterator; + iterator const it = wrapper.use_doubles.find(name); + name_exists = (it != wrapper.use_doubles.end()); + } + break; + case dt_date: + { + typedef std::map::const_iterator iterator; + iterator const it = wrapper.use_dates.find(name); + name_exists = (it != wrapper.use_dates.end()); + } + break; + default: + assert(false); + } + } + else + { + // vector version + + switch (expected_type) + { + case dt_string: + { + typedef std::map + < + std::string, + std::vector + >::const_iterator iterator; + iterator const it = wrapper.use_strings_v.find(name); + name_exists = (it != wrapper.use_strings_v.end()); + } + break; + case dt_integer: + { + typedef std::map + < + std::string, + std::vector + >::const_iterator iterator; + iterator const it = wrapper.use_ints_v.find(name); + name_exists = (it != wrapper.use_ints_v.end()); + } + break; + case dt_long_long: + { + typedef std::map + < + std::string, + std::vector + >::const_iterator iterator; + iterator const it = wrapper.use_longlongs_v.find(name); + name_exists = (it != wrapper.use_longlongs_v.end()); + } + break; + case dt_double: + { + typedef std::map >::const_iterator iterator; + iterator const it = wrapper.use_doubles_v.find(name); + name_exists = (it != wrapper.use_doubles_v.end()); + } + break; + case dt_date: + { + typedef std::map >::const_iterator iterator; + iterator const it = wrapper.use_dates_v.find(name); + name_exists = (it != wrapper.use_dates_v.end()); + } + break; + default: + assert(false); + } + } + + if (name_exists) + { + wrapper.is_ok = true; + return false; + } + else + { + wrapper.is_ok = false; + wrapper.error_message = "No use "; + wrapper.error_message += type_name; + wrapper.error_message += " element with this name."; + return true; + } +} + +// helper function for resizing all vectors in the map +template +void resize_in_map(std::map > & m, int new_size) +{ + typedef typename std::map >::iterator iterator; + iterator it = m.begin(); + iterator const end = m.end(); + for ( ; it != end; ++it) + { + std::vector & v = it->second; + v.resize(new_size); + } +} + +// helper for formatting date values +char const * format_date(statement_wrapper & wrapper, std::tm const & d) +{ + std::sprintf(wrapper.date_formatted, "%d %d %d %d %d %d", + d.tm_year + 1900, d.tm_mon + 1, d.tm_mday, + d.tm_hour, d.tm_min, d.tm_sec); + + return wrapper.date_formatted; +} + +bool string_to_date(char const * val, std::tm & /* out */ dt, + statement_wrapper & wrapper) +{ + // format is: "YYYY MM DD hh mm ss" + int year; + int month; + int day; + int hour; + int minute; + int second; + int const converted = std::sscanf(val, "%d %d %d %d %d %d", + &year, &month, &day, &hour, &minute, &second); + if (converted != 6) + { + wrapper.is_ok = false; + wrapper.error_message = "Cannot convert date."; + return false; + } + + wrapper.is_ok = true; + + dt.tm_year = year - 1900; + dt.tm_mon = month - 1; + dt.tm_mday = day; + dt.tm_hour = hour; + dt.tm_min = minute; + dt.tm_sec = second; + +return true; +} + +} // namespace unnamed + + +SOCI_DECL statement_handle soci_create_statement(session_handle s) +{ + session_wrapper * session_w = static_cast(s); + try + { + statement_wrapper * statement_w = new statement_wrapper(session_w->sql); + return statement_w; + } + catch (std::exception const & e) + { + session_w->is_ok = false; + session_w->error_message = e.what(); + return NULL; + } +} + +SOCI_DECL void soci_destroy_statement(statement_handle st) +{ + statement_wrapper * wrapper = static_cast(st); + delete wrapper; +} + +SOCI_DECL int soci_into_string(statement_handle st) +{ + statement_wrapper * wrapper = static_cast(st); + + if (cannot_add_elements(*wrapper, statement_wrapper::single, true)) + { + return -1; + } + + wrapper->statement_state = statement_wrapper::defining; + wrapper->into_kind = statement_wrapper::single; + + wrapper->into_types.push_back(dt_string); + wrapper->into_indicators.push_back(i_ok); + wrapper->into_strings[wrapper->next_position]; // create new entry + return wrapper->next_position++; +} + +SOCI_DECL int soci_into_int(statement_handle st) +{ + statement_wrapper * wrapper = static_cast(st); + + if (cannot_add_elements(*wrapper, statement_wrapper::single, true)) + { + return -1; + } + + wrapper->statement_state = statement_wrapper::defining; + wrapper->into_kind = statement_wrapper::single; + + wrapper->into_types.push_back(dt_integer); + wrapper->into_indicators.push_back(i_ok); + wrapper->into_ints[wrapper->next_position]; // create new entry + return wrapper->next_position++; +} + +SOCI_DECL int soci_into_long_long(statement_handle st) +{ + statement_wrapper * wrapper = static_cast(st); + + if (cannot_add_elements(*wrapper, statement_wrapper::single, true)) + { + return -1; + } + + wrapper->statement_state = statement_wrapper::defining; + wrapper->into_kind = statement_wrapper::single; + + wrapper->into_types.push_back(dt_long_long); + wrapper->into_indicators.push_back(i_ok); + wrapper->into_longlongs[wrapper->next_position]; // create new entry + return wrapper->next_position++; +} + +SOCI_DECL int soci_into_double(statement_handle st) +{ + statement_wrapper * wrapper = static_cast(st); + + if (cannot_add_elements(*wrapper, statement_wrapper::single, true)) + { + return -1; + } + + wrapper->statement_state = statement_wrapper::defining; + wrapper->into_kind = statement_wrapper::single; + + wrapper->into_types.push_back(dt_double); + wrapper->into_indicators.push_back(i_ok); + wrapper->into_doubles[wrapper->next_position]; // create new entry + return wrapper->next_position++; +} + +SOCI_DECL int soci_into_date(statement_handle st) +{ + statement_wrapper * wrapper = static_cast(st); + + if (cannot_add_elements(*wrapper, statement_wrapper::single, true)) + { + return -1; + } + + wrapper->statement_state = statement_wrapper::defining; + wrapper->into_kind = statement_wrapper::single; + + wrapper->into_types.push_back(dt_date); + wrapper->into_indicators.push_back(i_ok); + wrapper->into_dates[wrapper->next_position]; // create new entry + return wrapper->next_position++; +} + +SOCI_DECL int soci_into_string_v(statement_handle st) +{ + statement_wrapper * wrapper = static_cast(st); + + if (cannot_add_elements(*wrapper, statement_wrapper::bulk, true)) + { + return -1; + } + + wrapper->statement_state = statement_wrapper::defining; + wrapper->into_kind = statement_wrapper::bulk; + + wrapper->into_types.push_back(dt_string); + wrapper->into_indicators_v.push_back(std::vector()); + wrapper->into_strings_v[wrapper->next_position]; + return wrapper->next_position++; +} + +SOCI_DECL int soci_into_int_v(statement_handle st) +{ + statement_wrapper * wrapper = static_cast(st); + + if (cannot_add_elements(*wrapper, statement_wrapper::bulk, true)) + { + return -1; + } + + wrapper->statement_state = statement_wrapper::defining; + wrapper->into_kind = statement_wrapper::bulk; + + wrapper->into_types.push_back(dt_integer); + wrapper->into_indicators_v.push_back(std::vector()); + wrapper->into_ints_v[wrapper->next_position]; + return wrapper->next_position++; +} + +SOCI_DECL int soci_into_long_long_v(statement_handle st) +{ + statement_wrapper * wrapper = static_cast(st); + + if (cannot_add_elements(*wrapper, statement_wrapper::bulk, true)) + { + return -1; + } + + wrapper->statement_state = statement_wrapper::defining; + wrapper->into_kind = statement_wrapper::bulk; + + wrapper->into_types.push_back(dt_long_long); + wrapper->into_indicators_v.push_back(std::vector()); + wrapper->into_longlongs_v[wrapper->next_position]; + return wrapper->next_position++; +} + +SOCI_DECL int soci_into_double_v(statement_handle st) +{ + statement_wrapper * wrapper = static_cast(st); + + if (cannot_add_elements(*wrapper, statement_wrapper::bulk, true)) + { + return -1; + } + + wrapper->statement_state = statement_wrapper::defining; + wrapper->into_kind = statement_wrapper::bulk; + + wrapper->into_types.push_back(dt_double); + wrapper->into_indicators_v.push_back(std::vector()); + wrapper->into_doubles_v[wrapper->next_position]; + return wrapper->next_position++; +} + +SOCI_DECL int soci_into_date_v(statement_handle st) +{ + statement_wrapper * wrapper = static_cast(st); + + if (cannot_add_elements(*wrapper, statement_wrapper::bulk, true)) + { + return -1; + } + + wrapper->statement_state = statement_wrapper::defining; + wrapper->into_kind = statement_wrapper::bulk; + + wrapper->into_types.push_back(dt_date); + wrapper->into_indicators_v.push_back(std::vector()); + wrapper->into_dates_v[wrapper->next_position]; + return wrapper->next_position++; +} + +SOCI_DECL int soci_get_into_state(statement_handle st, int position) +{ + statement_wrapper * wrapper = static_cast(st); + + if (position < 0 || position >= wrapper->next_position) + { + wrapper->is_ok = false; + wrapper->error_message = "Invalid position."; + return 0; + } + + wrapper->is_ok = true; + return wrapper->into_indicators[position] == i_ok ? 1 : 0; +} + +SOCI_DECL char const * soci_get_into_string(statement_handle st, int position) +{ + statement_wrapper * wrapper = static_cast(st); + + if (position_check_failed(*wrapper, + statement_wrapper::single, position, dt_string, "string") || + not_null_check_failed(*wrapper, position)) + { + return ""; + } + + return wrapper->into_strings[position].c_str(); +} + +SOCI_DECL int soci_get_into_int(statement_handle st, int position) +{ + statement_wrapper * wrapper = static_cast(st); + + if (position_check_failed(*wrapper, + statement_wrapper::single, position, dt_integer, "int") || + not_null_check_failed(*wrapper, position)) + { + return 0; + } + + return wrapper->into_ints[position]; +} + +SOCI_DECL long long soci_get_into_long_long(statement_handle st, int position) +{ + statement_wrapper * wrapper = static_cast(st); + + if (position_check_failed(*wrapper, + statement_wrapper::single, position, dt_long_long, "long long") || + not_null_check_failed(*wrapper, position)) + { + return 0LL; + } + + return wrapper->into_longlongs[position]; +} + +SOCI_DECL double soci_get_into_double(statement_handle st, int position) +{ + statement_wrapper * wrapper = static_cast(st); + + if (position_check_failed(*wrapper, + statement_wrapper::single, position, dt_double, "double") || + not_null_check_failed(*wrapper, position)) + { + return 0.0; + } + + return wrapper->into_doubles[position]; +} + +SOCI_DECL char const * soci_get_into_date(statement_handle st, int position) +{ + statement_wrapper * wrapper = static_cast(st); + + if (position_check_failed(*wrapper, + statement_wrapper::single, position, dt_date, "date") || + not_null_check_failed(*wrapper, position)) + { + return ""; + } + + // format is: "YYYY MM DD hh mm ss" + std::tm const & d = wrapper->into_dates[position]; + return format_date(*wrapper, d); +} + +SOCI_DECL int soci_into_get_size_v(statement_handle st) +{ + statement_wrapper * wrapper = static_cast(st); + + if (wrapper->into_kind != statement_wrapper::bulk) + { + wrapper->is_ok = false; + wrapper->error_message = "No vector into elements."; + return -1; + } + + return static_cast(wrapper->into_indicators_v[0].size()); +} + +SOCI_DECL void soci_into_resize_v(statement_handle st, int new_size) +{ + statement_wrapper * wrapper = static_cast(st); + + if (new_size <= 0) + { + wrapper->is_ok = false; + wrapper->error_message = "Invalid size."; + return; + } + + if (wrapper->into_kind != statement_wrapper::bulk) + { + wrapper->is_ok = false; + wrapper->error_message = "No vector into elements."; + return; + } + + for (int i = 0; i != wrapper->next_position; ++i) + { + wrapper->into_indicators_v[i].resize(new_size); + + switch (wrapper->into_types[i]) + { + case dt_string: + wrapper->into_strings_v[i].resize(new_size); + break; + case dt_integer: + wrapper->into_ints_v[i].resize(new_size); + break; + case dt_long_long: + wrapper->into_longlongs_v[i].resize(new_size); + break; + case dt_double: + wrapper->into_doubles_v[i].resize(new_size); + break; + case dt_date: + wrapper->into_dates_v[i].resize(new_size); + break; + default: + assert(false); + } + } + + wrapper->is_ok = true; +} + +SOCI_DECL int soci_get_into_state_v(statement_handle st, int position, int index) +{ + statement_wrapper * wrapper = static_cast(st); + + if (position < 0 || position >= wrapper->next_position) + { + wrapper->is_ok = false; + wrapper->error_message = "Invalid position."; + return 0; + } + + std::vector const & v = wrapper->into_indicators_v[position]; + if (index_check_failed(v, *wrapper, index)) + { + return 0; + } + + return v[index] == i_ok ? 1 : 0; +} + +SOCI_DECL char const * soci_get_into_string_v(statement_handle st, int position, int index) +{ + statement_wrapper * wrapper = static_cast(st); + + if (position_check_failed(*wrapper, + statement_wrapper::bulk, position, dt_string, "string")) + { + return ""; + } + + std::vector const & v = wrapper->into_strings_v[position]; + if (index_check_failed(v, *wrapper, index) || + not_null_check_failed(*wrapper, position, index)) + { + return ""; + } + + return v[index].c_str(); +} + +SOCI_DECL int soci_get_into_int_v(statement_handle st, int position, int index) +{ + statement_wrapper * wrapper = static_cast(st); + + if (position_check_failed(*wrapper, + statement_wrapper::bulk, position, dt_integer, "int")) + { + return 0; + } + + std::vector const & v = wrapper->into_ints_v[position]; + if (index_check_failed(v, *wrapper, index) || + not_null_check_failed(*wrapper, position, index)) + { + return 0; + } + + return v[index]; +} + +SOCI_DECL long long soci_get_into_long_long_v(statement_handle st, int position, int index) +{ + statement_wrapper * wrapper = static_cast(st); + + if (position_check_failed(*wrapper, + statement_wrapper::bulk, position, dt_long_long, "long long")) + { + return 0; + } + + std::vector const & v = wrapper->into_longlongs_v[position]; + if (index_check_failed(v, *wrapper, index) || + not_null_check_failed(*wrapper, position, index)) + { + return 0; + } + + return v[index]; +} + +SOCI_DECL double soci_get_into_double_v(statement_handle st, int position, int index) +{ + statement_wrapper * wrapper = static_cast(st); + + if (position_check_failed(*wrapper, + statement_wrapper::bulk, position, dt_double, "double")) + { + return 0.0; + } + + std::vector const & v = wrapper->into_doubles_v[position]; + if (index_check_failed(v, *wrapper, index) || + not_null_check_failed(*wrapper, position, index)) + { + return 0.0; + } + + return v[index]; +} + +SOCI_DECL char const * soci_get_into_date_v(statement_handle st, int position, int index) +{ + statement_wrapper * wrapper = static_cast(st); + + if (position_check_failed(*wrapper, + statement_wrapper::bulk, position, dt_date, "date")) + { + return ""; + } + + std::vector const & v = wrapper->into_dates_v[position]; + if (index_check_failed(v, *wrapper, index) || + not_null_check_failed(*wrapper, position, index)) + { + return ""; + } + + return format_date(*wrapper, v[index]); +} + +SOCI_DECL void soci_use_string(statement_handle st, char const * name) +{ + statement_wrapper * wrapper = static_cast(st); + + if (cannot_add_elements(*wrapper, statement_wrapper::single, false) || + name_unique_check_failed(*wrapper, statement_wrapper::single, name)) + { + return; + } + + wrapper->statement_state = statement_wrapper::defining; + wrapper->use_kind = statement_wrapper::single; + + wrapper->use_indicators[name] = i_ok; // create new entry + wrapper->use_strings[name]; // create new entry +} + +SOCI_DECL void soci_use_int(statement_handle st, char const * name) +{ + statement_wrapper * wrapper = static_cast(st); + + if (cannot_add_elements(*wrapper, statement_wrapper::single, false) || + name_unique_check_failed(*wrapper, statement_wrapper::single, name)) + { + return; + } + + wrapper->statement_state = statement_wrapper::defining; + wrapper->use_kind = statement_wrapper::single; + + wrapper->use_indicators[name] = i_ok; // create new entry + wrapper->use_ints[name]; // create new entry +} + +SOCI_DECL void soci_use_long_long(statement_handle st, char const * name) +{ + statement_wrapper * wrapper = static_cast(st); + + if (cannot_add_elements(*wrapper, statement_wrapper::single, false) || + name_unique_check_failed(*wrapper, statement_wrapper::single, name)) + { + return; + } + + wrapper->statement_state = statement_wrapper::defining; + wrapper->use_kind = statement_wrapper::single; + + wrapper->use_indicators[name] = i_ok; // create new entry + wrapper->use_longlongs[name]; // create new entry +} + +SOCI_DECL void soci_use_double(statement_handle st, char const * name) +{ + statement_wrapper * wrapper = static_cast(st); + + if (cannot_add_elements(*wrapper, statement_wrapper::single, false) || + name_unique_check_failed(*wrapper, statement_wrapper::single, name)) + { + return; + } + + wrapper->statement_state = statement_wrapper::defining; + wrapper->use_kind = statement_wrapper::single; + + wrapper->use_indicators[name] = i_ok; // create new entry + wrapper->use_doubles[name]; // create new entry +} + +SOCI_DECL void soci_use_date(statement_handle st, char const * name) +{ + statement_wrapper * wrapper = static_cast(st); + + if (cannot_add_elements(*wrapper, statement_wrapper::single, false) || + name_unique_check_failed(*wrapper, statement_wrapper::single, name)) + { + return; + } + + wrapper->statement_state = statement_wrapper::defining; + wrapper->use_kind = statement_wrapper::single; + + wrapper->use_indicators[name] = i_ok; // create new entry + wrapper->use_dates[name]; // create new entry +} + +SOCI_DECL void soci_use_string_v(statement_handle st, char const * name) +{ + statement_wrapper * wrapper = static_cast(st); + + if (cannot_add_elements(*wrapper, statement_wrapper::bulk, false) || + name_unique_check_failed(*wrapper, statement_wrapper::bulk, name)) + { + return; + } + + wrapper->statement_state = statement_wrapper::defining; + wrapper->use_kind = statement_wrapper::bulk; + + wrapper->use_indicators_v[name]; // create new entry + wrapper->use_strings_v[name]; // create new entry +} + +SOCI_DECL void soci_use_int_v(statement_handle st, char const * name) +{ + statement_wrapper * wrapper = static_cast(st); + + if (cannot_add_elements(*wrapper, statement_wrapper::bulk, false) || + name_unique_check_failed(*wrapper, statement_wrapper::bulk, name)) + { + return; + } + + wrapper->statement_state = statement_wrapper::defining; + wrapper->use_kind = statement_wrapper::bulk; + + wrapper->use_indicators_v[name]; // create new entry + wrapper->use_ints_v[name]; // create new entry +} + +SOCI_DECL void soci_use_long_long_v(statement_handle st, char const * name) +{ + statement_wrapper * wrapper = static_cast(st); + + if (cannot_add_elements(*wrapper, statement_wrapper::bulk, false) || + name_unique_check_failed(*wrapper, statement_wrapper::bulk, name)) + { + return; + } + + wrapper->statement_state = statement_wrapper::defining; + wrapper->use_kind = statement_wrapper::bulk; + + wrapper->use_indicators_v[name]; // create new entry + wrapper->use_longlongs_v[name]; // create new entry +} + +SOCI_DECL void soci_use_double_v(statement_handle st, char const * name) +{ + statement_wrapper * wrapper = static_cast(st); + + if (cannot_add_elements(*wrapper, statement_wrapper::bulk, false) || + name_unique_check_failed(*wrapper, statement_wrapper::bulk, name)) + { + return; + } + + wrapper->statement_state = statement_wrapper::defining; + wrapper->use_kind = statement_wrapper::bulk; + + wrapper->use_indicators_v[name]; // create new entry + wrapper->use_doubles_v[name]; // create new entry +} + +SOCI_DECL void soci_use_date_v(statement_handle st, char const * name) +{ + statement_wrapper * wrapper = static_cast(st); + + if (cannot_add_elements(*wrapper, statement_wrapper::bulk, false) || + name_unique_check_failed(*wrapper, statement_wrapper::bulk, name)) + { + return; + } + + wrapper->statement_state = statement_wrapper::defining; + wrapper->use_kind = statement_wrapper::bulk; + + wrapper->use_indicators_v[name]; // create new entry + wrapper->use_dates_v[name]; // create new entry +} + +SOCI_DECL void soci_set_use_state(statement_handle st, char const * name, int state) +{ + statement_wrapper * wrapper = static_cast(st); + + typedef std::map::const_iterator iterator; + iterator const it = wrapper->use_indicators.find(name); + if (it == wrapper->use_indicators.end()) + { + wrapper->is_ok = false; + wrapper->error_message = "Invalid name."; + return; + } + + wrapper->is_ok = true; + wrapper->use_indicators[name] = (state != 0 ? i_ok : i_null); +} + +SOCI_DECL void soci_set_use_string(statement_handle st, char const * name, char const * val) +{ + statement_wrapper * wrapper = static_cast(st); + + if (name_exists_check_failed(*wrapper, + name, dt_string, statement_wrapper::single, "string")) + { + return; + } + + wrapper->use_indicators[name] = i_ok; + wrapper->use_strings[name] = val; +} + +SOCI_DECL void soci_set_use_int(statement_handle st, char const * name, int val) +{ + statement_wrapper * wrapper = static_cast(st); + + if (name_exists_check_failed(*wrapper, + name, dt_integer, statement_wrapper::single, "int")) + { + return; + } + + wrapper->use_indicators[name] = i_ok; + wrapper->use_ints[name] = val; +} + +SOCI_DECL void soci_set_use_long_long(statement_handle st, char const * name, long long val) +{ + statement_wrapper * wrapper = static_cast(st); + + if (name_exists_check_failed(*wrapper, + name, dt_long_long, statement_wrapper::single, "long long")) + { + return; + } + + wrapper->use_indicators[name] = i_ok; + wrapper->use_longlongs[name] = val; +} + +SOCI_DECL void soci_set_use_double(statement_handle st, char const * name, double val) +{ + statement_wrapper * wrapper = static_cast(st); + + if (name_exists_check_failed(*wrapper, + name, dt_double, statement_wrapper::single, "double")) + { + return; + } + + wrapper->use_indicators[name] = i_ok; + wrapper->use_doubles[name] = val; +} + +SOCI_DECL void soci_set_use_date(statement_handle st, char const * name, char const * val) +{ + statement_wrapper * wrapper = static_cast(st); + + if (name_exists_check_failed(*wrapper, + name, dt_date, statement_wrapper::single, "date")) + { + return; + } + + std::tm dt; + bool const converted = string_to_date(val, dt, *wrapper); + if (converted == false) + { + return; + } + + wrapper->use_indicators[name] = i_ok; + wrapper->use_dates[name] = dt; +} + +SOCI_DECL int soci_use_get_size_v(statement_handle st) +{ + statement_wrapper * wrapper = static_cast(st); + + if (wrapper->use_kind != statement_wrapper::bulk) + { + wrapper->is_ok = false; + wrapper->error_message = "No vector use elements."; + return -1; + } + + typedef std::map >::const_iterator iterator; + iterator const any_element = wrapper->use_indicators_v.begin(); + assert(any_element != wrapper->use_indicators_v.end()); + + return static_cast(any_element->second.size()); +} + +SOCI_DECL void soci_use_resize_v(statement_handle st, int new_size) +{ + statement_wrapper * wrapper = static_cast(st); + + if (new_size <= 0) + { + wrapper->is_ok = false; + wrapper->error_message = "Invalid size."; + return; + } + + if (wrapper->use_kind != statement_wrapper::bulk) + { + wrapper->is_ok = false; + wrapper->error_message = "No vector use elements."; + return; + } + + resize_in_map(wrapper->use_indicators_v, new_size); + resize_in_map(wrapper->use_strings_v, new_size); + resize_in_map(wrapper->use_ints_v, new_size); + resize_in_map(wrapper->use_longlongs_v, new_size); + resize_in_map(wrapper->use_doubles_v, new_size); + resize_in_map(wrapper->use_dates_v, new_size); + + wrapper->is_ok = true; +} + +SOCI_DECL void soci_set_use_state_v(statement_handle st, + char const * name, int index, int state) +{ + statement_wrapper * wrapper = static_cast(st); + + typedef std::map >::iterator iterator; + iterator const it = wrapper->use_indicators_v.find(name); + if (it == wrapper->use_indicators_v.end()) + { + wrapper->is_ok = false; + wrapper->error_message = "Invalid name."; + return; + } + + std::vector & v = it->second; + if (index_check_failed(v, *wrapper, index)) + { + return; + } + + v[index] = (state != 0 ? i_ok : i_null); +} + +SOCI_DECL void soci_set_use_string_v(statement_handle st, + char const * name, int index, char const * val) +{ + statement_wrapper * wrapper = static_cast(st); + + if (name_exists_check_failed(*wrapper, + name, dt_string, statement_wrapper::bulk, "vector string")) + { + return; + } + + std::vector & v = wrapper->use_strings_v[name]; + if (index_check_failed(v, *wrapper, index)) + { + return; + } + + wrapper->use_indicators_v[name][index] = i_ok; + v[index] = val; +} + +SOCI_DECL void soci_set_use_int_v(statement_handle st, + char const * name, int index, int val) +{ + statement_wrapper * wrapper = static_cast(st); + + if (name_exists_check_failed(*wrapper, + name, dt_integer, statement_wrapper::bulk, "vector int")) + { + return; + } + + std::vector & v = wrapper->use_ints_v[name]; + if (index_check_failed(v, *wrapper, index)) + { + return; + } + + wrapper->use_indicators_v[name][index] = i_ok; + v[index] = val; +} + +SOCI_DECL void soci_set_use_long_long_v(statement_handle st, + char const * name, int index, long long val) +{ + statement_wrapper * wrapper = static_cast(st); + + if (name_exists_check_failed(*wrapper, + name, dt_long_long, statement_wrapper::bulk, "vector long long")) + { + return; + } + + std::vector & v = wrapper->use_longlongs_v[name]; + if (index_check_failed(v, *wrapper, index)) + { + return; + } + + wrapper->use_indicators_v[name][index] = i_ok; + v[index] = val; +} + +SOCI_DECL void soci_set_use_double_v(statement_handle st, + char const * name, int index, double val) +{ + statement_wrapper * wrapper = static_cast(st); + + if (name_exists_check_failed(*wrapper, + name, dt_double, statement_wrapper::bulk, "vector double")) + { + return; + } + + std::vector & v = wrapper->use_doubles_v[name]; + if (index_check_failed(v, *wrapper, index)) + { + return; + } + + wrapper->use_indicators_v[name][index] = i_ok; + v[index] = val; +} + +SOCI_DECL void soci_set_use_date_v(statement_handle st, + char const * name, int index, char const * val) +{ + statement_wrapper * wrapper = static_cast(st); + + if (name_exists_check_failed(*wrapper, + name, dt_date, statement_wrapper::bulk, "vector date")) + { + return; + } + + std::vector & v = wrapper->use_dates_v[name]; + if (index_check_failed(v, *wrapper, index)) + { + return; + } + + std::tm dt; + bool const converted = string_to_date(val, dt, *wrapper); + if (converted == false) + { + return; + } + + wrapper->use_indicators_v[name][index] = i_ok; + v[index] = dt; +} + +SOCI_DECL int soci_get_use_state(statement_handle st, char const * name) +{ + statement_wrapper * wrapper = static_cast(st); + + typedef std::map::const_iterator iterator; + iterator const it = wrapper->use_indicators.find(name); + if (it == wrapper->use_indicators.end()) + { + wrapper->is_ok = false; + wrapper->error_message = "Invalid name."; + return 0; + } + + wrapper->is_ok = true; + return wrapper->use_indicators[name] == i_ok ? 1 : 0; +} + +SOCI_DECL char const * soci_get_use_string(statement_handle st, char const * name) +{ + statement_wrapper * wrapper = static_cast(st); + + if (name_exists_check_failed(*wrapper, + name, dt_string, statement_wrapper::bulk, "string")) + { + return ""; + } + + return wrapper->use_strings[name].c_str(); +} + +SOCI_DECL int soci_get_use_int(statement_handle st, char const * name) +{ + statement_wrapper * wrapper = static_cast(st); + + if (name_exists_check_failed(*wrapper, + name, dt_integer, statement_wrapper::bulk, "int")) + { + return 0; + } + + return wrapper->use_ints[name]; +} + +SOCI_DECL long long soci_get_use_long_long(statement_handle st, char const * name) +{ + statement_wrapper * wrapper = static_cast(st); + + if (name_exists_check_failed(*wrapper, + name, dt_long_long, statement_wrapper::bulk, "long long")) + { + return 0LL; + } + + return wrapper->use_longlongs[name]; +} + +SOCI_DECL double soci_get_use_double(statement_handle st, char const * name) +{ + statement_wrapper * wrapper = static_cast(st); + + if (name_exists_check_failed(*wrapper, + name, dt_double, statement_wrapper::bulk, "double")) + { + return 0.0; + } + + return wrapper->use_doubles[name]; +} + +SOCI_DECL char const * soci_get_use_date(statement_handle st, char const * name) +{ + statement_wrapper * wrapper = static_cast(st); + + if (name_exists_check_failed(*wrapper, + name, dt_date, statement_wrapper::bulk, "date")) + { + return ""; + } + + // format is: "YYYY MM DD hh mm ss" + std::tm const & d = wrapper->use_dates[name]; + std::sprintf(wrapper->date_formatted, "%d %d %d %d %d %d", + d.tm_year + 1900, d.tm_mon + 1, d.tm_mday, + d.tm_hour, d.tm_min, d.tm_sec); + + return wrapper->date_formatted; +} + +SOCI_DECL void soci_prepare(statement_handle st, char const * query) +{ + statement_wrapper * wrapper = static_cast(st); + + try + { + wrapper->statement_state = statement_wrapper::executing; + + // bind all into elements + + int const into_elements = static_cast(wrapper->into_types.size()); + if (wrapper->into_kind == statement_wrapper::single) + { + for (int i = 0; i != into_elements; ++i) + { + switch (wrapper->into_types[i]) + { + case dt_string: + wrapper->st.exchange( + into(wrapper->into_strings[i], wrapper->into_indicators[i])); + break; + case dt_integer: + wrapper->st.exchange( + into(wrapper->into_ints[i], wrapper->into_indicators[i])); + break; + case dt_long_long: + wrapper->st.exchange( + into(wrapper->into_longlongs[i], wrapper->into_indicators[i])); + break; + case dt_double: + wrapper->st.exchange( + into(wrapper->into_doubles[i], wrapper->into_indicators[i])); + break; + case dt_date: + wrapper->st.exchange( + into(wrapper->into_dates[i], wrapper->into_indicators[i])); + break; + default: + assert(false); + } + } + } + else + { + // vector elements + for (int i = 0; i != into_elements; ++i) + { + switch (wrapper->into_types[i]) + { + case dt_string: + wrapper->st.exchange( + into(wrapper->into_strings_v[i], wrapper->into_indicators_v[i])); + break; + case dt_integer: + wrapper->st.exchange( + into(wrapper->into_ints_v[i], wrapper->into_indicators_v[i])); + break; + case dt_long_long: + wrapper->st.exchange( + into(wrapper->into_longlongs_v[i], wrapper->into_indicators_v[i])); + break; + case dt_double: + wrapper->st.exchange( + into(wrapper->into_doubles_v[i], wrapper->into_indicators_v[i])); + break; + case dt_date: + wrapper->st.exchange( + into(wrapper->into_dates_v[i], wrapper->into_indicators_v[i])); + break; + default: + assert(false); + } + } + } + + // bind all use elements + { + // strings + typedef std::map::iterator iterator; + iterator uit = wrapper->use_strings.begin(); + iterator const uend = wrapper->use_strings.end(); + for ( ; uit != uend; ++uit) + { + std::string const & use_name = uit->first; + std::string & use_string = uit->second; + indicator & use_ind = wrapper->use_indicators[use_name]; + wrapper->st.exchange(use(use_string, use_ind, use_name)); + } + } + { + // ints + typedef std::map::iterator iterator; + iterator uit = wrapper->use_ints.begin(); + iterator const uend = wrapper->use_ints.end(); + for ( ; uit != uend; ++uit) + { + std::string const & use_name = uit->first; + int & use_int = uit->second; + indicator & use_ind = wrapper->use_indicators[use_name]; + wrapper->st.exchange(use(use_int, use_ind, use_name)); + } + } + { + // longlongs + typedef std::map::iterator iterator; + iterator uit = wrapper->use_longlongs.begin(); + iterator const uend = wrapper->use_longlongs.end(); + for ( ; uit != uend; ++uit) + { + std::string const & use_name = uit->first; + long long & use_longlong = uit->second; + indicator & use_ind = wrapper->use_indicators[use_name]; + wrapper->st.exchange(use(use_longlong, use_ind, use_name)); + } + } + { + // doubles + typedef std::map::iterator iterator; + iterator uit = wrapper->use_doubles.begin(); + iterator const uend = wrapper->use_doubles.end(); + for ( ; uit != uend; ++uit) + { + std::string const & use_name = uit->first; + double & use_double = uit->second; + indicator & use_ind = wrapper->use_indicators[use_name]; + wrapper->st.exchange(use(use_double, use_ind, use_name)); + } + } + { + // dates + typedef std::map::iterator iterator; + iterator uit = wrapper->use_dates.begin(); + iterator const uend = wrapper->use_dates.end(); + for ( ; uit != uend; ++uit) + { + std::string const & use_name = uit->first; + std::tm & use_date = uit->second; + indicator & use_ind = wrapper->use_indicators[use_name]; + wrapper->st.exchange(use(use_date, use_ind, use_name)); + } + } + + // bind all use vecctor elements + { + // strings + typedef std::map >::iterator iterator; + iterator uit = wrapper->use_strings_v.begin(); + iterator const uend = wrapper->use_strings_v.end(); + for ( ; uit != uend; ++uit) + { + std::string const & use_name = uit->first; + std::vector & use_string = uit->second; + std::vector & use_ind = + wrapper->use_indicators_v[use_name]; + wrapper->st.exchange(use(use_string, use_ind, use_name)); + } + } + { + // ints + typedef std::map >::iterator iterator; + iterator uit = wrapper->use_ints_v.begin(); + iterator const uend = wrapper->use_ints_v.end(); + for ( ; uit != uend; ++uit) + { + std::string const & use_name = uit->first; + std::vector & use_int = uit->second; + std::vector & use_ind = + wrapper->use_indicators_v[use_name]; + wrapper->st.exchange(use(use_int, use_ind, use_name)); + } + } + { + // longlongs + typedef std::map >::iterator iterator; + iterator uit = wrapper->use_longlongs_v.begin(); + iterator const uend = wrapper->use_longlongs_v.end(); + for ( ; uit != uend; ++uit) + { + std::string const & use_name = uit->first; + std::vector & use_longlong = uit->second; + std::vector & use_ind = + wrapper->use_indicators_v[use_name]; + wrapper->st.exchange(use(use_longlong, use_ind, use_name)); + } + } + { + // doubles + typedef std::map >::iterator iterator; + iterator uit = wrapper->use_doubles_v.begin(); + iterator const uend = wrapper->use_doubles_v.end(); + for ( ; uit != uend; ++uit) + { + std::string const & use_name = uit->first; + std::vector & use_double = uit->second; + std::vector & use_ind = + wrapper->use_indicators_v[use_name]; + wrapper->st.exchange(use(use_double, use_ind, use_name)); + } + } + { + // dates + typedef std::map >::iterator iterator; + iterator uit = wrapper->use_dates_v.begin(); + iterator const uend = wrapper->use_dates_v.end(); + for ( ; uit != uend; ++uit) + { + std::string const & use_name = uit->first; + std::vector & use_date = uit->second; + std::vector & use_ind = + wrapper->use_indicators_v[use_name]; + wrapper->st.exchange(use(use_date, use_ind, use_name)); + } + } + + wrapper->st.alloc(); + wrapper->st.prepare(query); + wrapper->st.define_and_bind(); + + wrapper->is_ok = true; + } + catch (std::exception const & e) + { + wrapper->is_ok = false; + wrapper->error_message = e.what(); + } +} + +SOCI_DECL int soci_execute(statement_handle st, int withDataExchange) +{ + statement_wrapper * wrapper = static_cast(st); + + try + { + bool const gotData = wrapper->st.execute(withDataExchange != 0); + + wrapper->is_ok = true; + + return gotData ? 1 : 0; + } + catch (std::exception const & e) + { + wrapper->is_ok = false; + wrapper->error_message = e.what(); + + return 0; + } +} + +SOCI_DECL long long soci_get_affected_rows(statement_handle st) +{ + statement_wrapper * wrapper = static_cast(st); + + return wrapper->st.get_affected_rows(); +} + +SOCI_DECL int soci_fetch(statement_handle st) +{ + statement_wrapper * wrapper = static_cast(st); + + try + { + bool const gotData = wrapper->st.fetch(); + + wrapper->is_ok = true; + + return gotData ? 1 : 0; + } + catch (std::exception const & e) + { + wrapper->is_ok = false; + wrapper->error_message = e.what(); + + return 0; + } +} + +SOCI_DECL int soci_got_data(statement_handle st) +{ + statement_wrapper * wrapper = static_cast(st); + + return wrapper->st.got_data() ? 1 : 0; +} + +SOCI_DECL int soci_statement_state(statement_handle st) +{ + statement_wrapper * wrapper = static_cast(st); + + return wrapper->is_ok ? 1 : 0; +} + +SOCI_DECL char const * soci_statement_error_message(statement_handle st) +{ + statement_wrapper * wrapper = static_cast(st); + + return wrapper->error_message.c_str(); +} diff --git a/src/core/soci-simple.h b/src/core/soci-simple.h new file mode 100644 index 0000000000..57e5101cd0 --- /dev/null +++ b/src/core/soci-simple.h @@ -0,0 +1,137 @@ +// +// Copyright (C) 2008 Maciej Sobczak +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_SIMPLE_H_INCLUDED +#define SOCI_SIMPLE_H_INCLUDED + +#include "soci-config.h" + +#ifdef __cplusplus +extern "C" +{ +#endif // __cplusplus + +// session + +typedef void * session_handle; +SOCI_DECL session_handle soci_create_session(char const * connectionString); +SOCI_DECL void soci_destroy_session(session_handle s); + +SOCI_DECL void soci_begin(session_handle s); +SOCI_DECL void soci_commit(session_handle s); +SOCI_DECL void soci_rollback(session_handle s); + +SOCI_DECL int soci_session_state(session_handle s); +SOCI_DECL char const * soci_session_error_message(session_handle s); + +// statement + +typedef void * statement_handle; +SOCI_DECL statement_handle soci_create_statement(session_handle s); +SOCI_DECL void soci_destroy_statement(statement_handle st); + +// positional bind of into elments (the functions return the position for convenience) +SOCI_DECL int soci_into_string (statement_handle st); +SOCI_DECL int soci_into_int (statement_handle st); +SOCI_DECL int soci_into_long_long(statement_handle st); +SOCI_DECL int soci_into_double (statement_handle st); +SOCI_DECL int soci_into_date (statement_handle st); + +// vector versions +SOCI_DECL int soci_into_string_v (statement_handle st); +SOCI_DECL int soci_into_int_v (statement_handle st); +SOCI_DECL int soci_into_long_long_v(statement_handle st); +SOCI_DECL int soci_into_double_v (statement_handle st); +SOCI_DECL int soci_into_date_v (statement_handle st); + +// positional read of into elements +SOCI_DECL int soci_get_into_state (statement_handle st, int position); +SOCI_DECL char const * soci_get_into_string (statement_handle st, int position); +SOCI_DECL int soci_get_into_int (statement_handle st, int position); +SOCI_DECL long long soci_get_into_long_long(statement_handle st, int position); +SOCI_DECL double soci_get_into_double (statement_handle st, int position); +SOCI_DECL char const * soci_get_into_date (statement_handle st, int position); + +// positional (re)size of vectors +SOCI_DECL int soci_into_get_size_v(statement_handle st); +SOCI_DECL void soci_into_resize_v (statement_handle st, int new_size); + +// positional read of vectors +SOCI_DECL int soci_get_into_state_v (statement_handle st, int position, int index); +SOCI_DECL char const * soci_get_into_string_v (statement_handle st, int position, int index); +SOCI_DECL int soci_get_into_int_v (statement_handle st, int position, int index); +SOCI_DECL long long soci_get_into_long_long_v(statement_handle st, int position, int index); +SOCI_DECL double soci_get_into_double_v (statement_handle st, int position, int index); +SOCI_DECL char const * soci_get_into_date_v (statement_handle st, int position, int index); + + +// named bind of use elements +SOCI_DECL void soci_use_string (statement_handle st, char const * name); +SOCI_DECL void soci_use_int (statement_handle st, char const * name); +SOCI_DECL void soci_use_long_long(statement_handle st, char const * name); +SOCI_DECL void soci_use_double (statement_handle st, char const * name); +SOCI_DECL void soci_use_date (statement_handle st, char const * name); + +// vector versions +SOCI_DECL void soci_use_string_v (statement_handle st, char const * name); +SOCI_DECL void soci_use_int_v (statement_handle st, char const * name); +SOCI_DECL void soci_use_long_long_v(statement_handle st, char const * name); +SOCI_DECL void soci_use_double_v (statement_handle st, char const * name); +SOCI_DECL void soci_use_date_v (statement_handle st, char const * name); + + +// named write of use elements +SOCI_DECL void soci_set_use_state (statement_handle st, char const * name, int state); +SOCI_DECL void soci_set_use_string (statement_handle st, char const * name, char const * val); +SOCI_DECL void soci_set_use_int (statement_handle st, char const * name, int val); +SOCI_DECL void soci_set_use_long_long(statement_handle st, char const * name, long long val); +SOCI_DECL void soci_set_use_double (statement_handle st, char const * name, double val); +SOCI_DECL void soci_set_use_date (statement_handle st, char const * name, char const * val); + +// positional (re)size of vectors +SOCI_DECL int soci_use_get_size_v(statement_handle st); +SOCI_DECL void soci_use_resize_v (statement_handle st, int new_size); + +// named write of use vectors +SOCI_DECL void soci_set_use_state_v(statement_handle st, + char const * name, int index, int state); +SOCI_DECL void soci_set_use_string_v(statement_handle st, + char const * name, int index, char const * val); +SOCI_DECL void soci_set_use_int_v(statement_handle st, + char const * name, int index, int val); +SOCI_DECL void soci_set_use_long_long_v(statement_handle st, + char const * name, int index, long long val); +SOCI_DECL void soci_set_use_double_v(statement_handle st, + char const * name, int index, double val); +SOCI_DECL void soci_set_use_date_v(statement_handle st, + char const * name, int index, char const * val); + + +// named read of use elements (for modifiable use values) +SOCI_DECL int soci_get_use_state (statement_handle st, char const * name); +SOCI_DECL char const * soci_get_use_string (statement_handle st, char const * name); +SOCI_DECL int soci_get_use_int (statement_handle st, char const * name); +SOCI_DECL long long soci_get_use_long_long(statement_handle st, char const * name); +SOCI_DECL double soci_get_use_double (statement_handle st, char const * name); +SOCI_DECL char const * soci_get_use_date (statement_handle st, char const * name); + + +// statement preparation and execution +SOCI_DECL void soci_prepare(statement_handle st, char const * query); +SOCI_DECL int soci_execute(statement_handle st, int withDataExchange); +SOCI_DECL long long soci_get_affected_rows(statement_handle st); +SOCI_DECL int soci_fetch(statement_handle st); +SOCI_DECL int soci_got_data(statement_handle st); + +SOCI_DECL int soci_statement_state(statement_handle s); +SOCI_DECL char const * soci_statement_error_message(statement_handle s); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif // SOCI_SIMPLE_H_INCLUDED diff --git a/src/core/soci.h b/src/core/soci.h new file mode 100644 index 0000000000..423282edca --- /dev/null +++ b/src/core/soci.h @@ -0,0 +1,61 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_H_INCLUDED +#define SOCI_H_INCLUDED + +#ifdef _MSC_VER +#pragma warning(disable:4251 4512 4511) +#endif + +// namespace soci +#include "backend-loader.h" +#include "blob.h" +#include "blob-exchange.h" +#include "connection-pool.h" +#include "error.h" +#include "exchange-traits.h" +#include "into.h" +#include "into-type.h" +#include "once-temp-type.h" +#include "prepare-temp-type.h" +#include "procedure.h" +#include "ref-counted-prepare-info.h" +#include "ref-counted-statement.h" +#include "row.h" +#include "row-exchange.h" +#include "rowid.h" +#include "rowid-exchange.h" +#include "rowset.h" +#include "session.h" +#include "soci-backend.h" +#include "soci-config.h" +#include "soci-platform.h" +#include "statement.h" +#include "transaction.h" +#include "type-conversion.h" +#include "type-conversion-traits.h" +#include "type-holder.h" +#include "type-ptr.h" +#include "unsigned-types.h" +#include "use.h" +#include "use-type.h" +#include "values.h" +#include "values-exchange.h" + +// namespace boost +#ifdef SOCI_USE_BOOST +#include +#if defined(BOOST_VERSION) && BOOST_VERSION >= 103500 +#include "boost-fusion.h" +#endif // BOOST_VERSION +#include "boost-optional.h" +#include "boost-tuple.h" +#include "boost-gregorian-date.h" +#endif // SOCI_USE_BOOST + +#endif // SOCI_H_INCLUDED diff --git a/src/core/soci_backends_config.h.in b/src/core/soci_backends_config.h.in new file mode 100644 index 0000000000..9550611a18 --- /dev/null +++ b/src/core/soci_backends_config.h.in @@ -0,0 +1,12 @@ +// +// Copyright (C) 2011 Alex Ott +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +#ifndef SOCI_BACKENDS_CONFIG_H +#define SOCI_BACKENDS_CONFIG_H + +#define DEFAULT_BACKENDS_PATH "@CMAKE_INSTALL_PREFIX@/@LIBDIR@" + +#endif // SOCI_BACKENDS_CONFIG_H diff --git a/src/core/statement.cpp b/src/core/statement.cpp new file mode 100644 index 0000000000..57ff40a7bf --- /dev/null +++ b/src/core/statement.cpp @@ -0,0 +1,742 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_SOURCE +#include "statement.h" +#include "session.h" +#include "into-type.h" +#include "use-type.h" +#include "values.h" +#include +#include + +#ifdef _MSC_VER +#pragma warning(disable:4355) +#endif + +using namespace soci; +using namespace soci::details; + +void statement::exchange(into_type_ptr const & i) +{ + impl_->exchange(i); +} + +void statement::exchange(use_type_ptr const & u) +{ + impl_->exchange(u); +} + +statement_impl::statement_impl(session & s) + : session_(s), refCount_(1), row_(0), + fetchSize_(1), initialFetchSize_(1), + alreadyDescribed_(false) +{ + backEnd_ = s.make_statement_backend(); +} + +statement_impl::statement_impl(prepare_temp_type const & prep) + : session_(prep.get_prepare_info()->session_), + refCount_(1), row_(0), fetchSize_(1), alreadyDescribed_(false) +{ + backEnd_ = session_.make_statement_backend(); + + ref_counted_prepare_info * prepInfo = prep.get_prepare_info(); + + // take all bind/define info + intos_.swap(prepInfo->intos_); + uses_.swap(prepInfo->uses_); + + // allocate handle + alloc(); + + // prepare the statement + query_ = prepInfo->get_query(); + try + { + prepare(query_); + } + catch(...) + { + clean_up(); + throw; + } + + define_and_bind(); +} + +statement_impl::~statement_impl() +{ + clean_up(); +} + +void statement_impl::alloc() +{ + backEnd_->alloc(); +} + +void statement_impl::bind(values & values) +{ + std::size_t cnt = 0; + + try + { + for (std::vector::iterator it = + values.uses_.begin(); it != values.uses_.end(); ++it) + { + // only bind those variables which are: + // - either named and actually referenced in the statement, + // - or positional + + std::string const& useName = (*it)->get_name(); + if (useName.empty()) + { + // positional use element + + int position = static_cast(uses_.size()); + (*it)->bind(*this, position); + uses_.push_back(*it); + indicators_.push_back(values.indicators_[cnt]); + } + else + { + // named use element - check if it is used + std::string const placeholder = ":" + useName; + + std::size_t pos = query_.find(placeholder); + while (pos != std::string::npos) + { + // Retrieve next char after placeholder + // make sure we do not go out of range on the string + const char nextChar = (pos + placeholder.size()) < query_.size() ? + query_[pos + placeholder.size()] : '\0'; + + if (std::isalnum(nextChar)) + { + // We got a partial match only, + // keep looking for the placeholder + pos = query_.find(placeholder, pos + placeholder.size()); + } + else + { + int position = static_cast(uses_.size()); + (*it)->bind(*this, position); + uses_.push_back(*it); + indicators_.push_back(values.indicators_[cnt]); + // Ok we found it, done + break; + } + } + // In case we couldn't find the placeholder + if (pos == std::string::npos) + { + values.add_unused(*it, values.indicators_[cnt]); + } + } + + cnt++; + } + } + catch (...) + { + for (std::size_t i = ++cnt; i != values.uses_.size(); ++i) + { + values.add_unused(values.uses_[i], values.indicators_[i]); + } + throw; + } +} + +void statement_impl::exchange(into_type_ptr const & i) +{ + intos_.push_back(i.get()); + i.release(); +} + +void statement_impl::exchange_for_row(into_type_ptr const & i) +{ + intosForRow_.push_back(i.get()); + i.release(); +} + +void statement_impl::exchange_for_rowset(into_type_ptr const & i) +{ + if (intos_.empty() == false) + { + throw soci_error("Explicit into elements not allowed with rowset."); + } + + into_type_base* p = i.get(); + intos_.push_back(p); + i.release(); + + int definePosition = 1; + p->define(*this, definePosition); + definePositionForRow_ = definePosition; +} + +void statement_impl::exchange(use_type_ptr const & u) +{ + uses_.push_back(u.get()); + u.release(); +} + +void statement_impl::clean_up() +{ + // deallocate all bind and define objects + std::size_t const isize = intos_.size(); + for (std::size_t i = isize; i != 0; --i) + { + intos_[i - 1]->clean_up(); + delete intos_[i - 1]; + intos_.resize(i - 1); + } + + std::size_t const ifrsize = intosForRow_.size(); + for (std::size_t i = ifrsize; i != 0; --i) + { + intosForRow_[i - 1]->clean_up(); + delete intosForRow_[i - 1]; + intosForRow_.resize(i - 1); + } + + std::size_t const usize = uses_.size(); + for (std::size_t i = usize; i != 0; --i) + { + uses_[i - 1]->clean_up(); + delete uses_[i - 1]; + uses_.resize(i - 1); + } + + std::size_t const indsize = indicators_.size(); + for (std::size_t i = 0; i != indsize; ++i) + { + delete indicators_[i]; + indicators_[i] = NULL; + } + + if (backEnd_ != NULL) + { + backEnd_->clean_up(); + delete backEnd_; + backEnd_ = NULL; + } +} + +void statement_impl::prepare(std::string const & query, + statement_type eType) +{ + query_ = query; + session_.log_query(query); + + backEnd_->prepare(query, eType); +} + +void statement_impl::define_and_bind() +{ + int definePosition = 1; + std::size_t const isize = intos_.size(); + for (std::size_t i = 0; i != isize; ++i) + { + intos_[i]->define(*this, definePosition); + } + + // if there are some implicite into elements + // injected by the row description process, + // they should be defined in the later phase, + // starting at the position where the above loop finished + definePositionForRow_ = definePosition; + + int bindPosition = 1; + std::size_t const usize = uses_.size(); + for (std::size_t i = 0; i != usize; ++i) + { + uses_[i]->bind(*this, bindPosition); + } +} + +void statement_impl::define_for_row() +{ + std::size_t const isize = intosForRow_.size(); + for (std::size_t i = 0; i != isize; ++i) + { + intosForRow_[i]->define(*this, definePositionForRow_); + } +} + +void statement_impl::undefine_and_bind() +{ + std::size_t const isize = intos_.size(); + for (std::size_t i = isize; i != 0; --i) + { + intos_[i - 1]->clean_up(); + } + + std::size_t const ifrsize = intosForRow_.size(); + for (std::size_t i = ifrsize; i != 0; --i) + { + intosForRow_[i - 1]->clean_up(); + } + + std::size_t const usize = uses_.size(); + for (std::size_t i = usize; i != 0; --i) + { + uses_[i - 1]->clean_up(); + } +} + +bool statement_impl::execute(bool withDataExchange) +{ + initialFetchSize_ = intos_size(); + + if (intos_.empty() == false && initialFetchSize_ == 0) + { + // this can happen only with into-vectors elements + // and is not allowed when calling execute + throw soci_error("Vectors of size 0 are not allowed."); + } + + fetchSize_ = initialFetchSize_; + + // pre-use should be executed before inspecting the sizes of use + // elements, as they can be resized in type conversion routines + + pre_use(); + + std::size_t const bindSize = uses_size(); + + if (bindSize > 1 && fetchSize_ > 1) + { + throw soci_error( + "Bulk insert/update and bulk select not allowed in same query"); + } + + // looks like a hack and it is - row description should happen + // *after* the use elements were completely prepared + // and *before* the into elements are touched, so that the row + // description process can inject more into elements for + // implicit data exchange + if (row_ != NULL && alreadyDescribed_ == false) + { + describe(); + define_for_row(); + } + + int num = 0; + if (withDataExchange) + { + num = 1; + + pre_fetch(); + + if (static_cast(fetchSize_) > num) + { + num = static_cast(fetchSize_); + } + if (static_cast(bindSize) > num) + { + num = static_cast(bindSize); + } + } + + statement_backend::exec_fetch_result res = backEnd_->execute(num); + + bool gotData = false; + + if (res == statement_backend::ef_success) + { + // the "success" means that the statement executed correctly + // and for select statement this also means that some rows were read + + if (num > 0) + { + gotData = true; + + // ensure into vectors have correct size + resize_intos(static_cast(num)); + } + } + else // res == ef_no_data + { + // the "no data" means that the end-of-rowset condition was hit + // but still some rows might have been read (the last bunch of rows) + // it can also mean that the statement did not produce any results + + gotData = fetchSize_ > 1 ? resize_intos() : false; + } + + if (num > 0) + { + post_fetch(gotData, false); + } + + post_use(gotData); + + session_.set_got_data(gotData); + return gotData; +} + +long long statement_impl::get_affected_rows() +{ + return backEnd_->get_affected_rows(); +} + +bool statement_impl::fetch() +{ + if (fetchSize_ == 0) + { + truncate_intos(); + session_.set_got_data(false); + return false; + } + + bool gotData = false; + + // vectors might have been resized between fetches + std::size_t const newFetchSize = intos_size(); + if (newFetchSize > initialFetchSize_) + { + // this is not allowed, because most likely caused reallocation + // of the vector - this would require complete re-bind + + throw soci_error( + "Increasing the size of the output vector is not supported."); + } + else if (newFetchSize == 0) + { + session_.set_got_data(false); + return false; + } + else + { + // the output vector was downsized or remains the same as before + fetchSize_ = newFetchSize; + } + + statement_backend::exec_fetch_result const res = backEnd_->fetch(static_cast(fetchSize_)); + if (res == statement_backend::ef_success) + { + // the "success" means that some number of rows was read + // and that it is not yet the end-of-rowset (there are more rows) + + gotData = true; + + // ensure into vectors have correct size + resize_intos(fetchSize_); + } + else // res == ef_no_data + { + // end-of-rowset condition + + if (fetchSize_ > 1) + { + // but still the last bunch of rows might have been read + gotData = resize_intos(); + fetchSize_ = 0; + } + else + { + truncate_intos(); + gotData = false; + } + } + + post_fetch(gotData, true); + session_.set_got_data(gotData); + return gotData; +} + +std::size_t statement_impl::intos_size() +{ + // this function does not need to take into account intosForRow_ elements, + // since their sizes are always 1 (which is the same and the primary + // into(row) element, which has injected them) + + std::size_t intos_size = 0; + std::size_t const isize = intos_.size(); + for (std::size_t i = 0; i != isize; ++i) + { + if (i==0) + { + intos_size = intos_[i]->size(); + } + else if (intos_size != intos_[i]->size()) + { + std::ostringstream msg; + msg << "Bind variable size mismatch (into[" + << static_cast(i) << "] has size " + << static_cast(intos_[i]->size()) + << ", into[0] has size " + << static_cast(intos_size); + throw soci_error(msg.str()); + } + } + return intos_size; +} + +std::size_t statement_impl::uses_size() +{ + std::size_t usesSize = 0; + std::size_t const usize = uses_.size(); + for (std::size_t i = 0; i != usize; ++i) + { + if (i==0) + { + usesSize = uses_[i]->size(); + if (usesSize == 0) + { + // this can happen only for vectors + throw soci_error("Vectors of size 0 are not allowed."); + } + } + else if (usesSize != uses_[i]->size()) + { + std::ostringstream msg; + msg << "Bind variable size mismatch (use[" + << static_cast(i) << "] has size " + << static_cast(uses_[i]->size()) + << ", use[0] has size " + << static_cast(usesSize); + throw soci_error(msg.str()); + } + } + return usesSize; +} + +bool statement_impl::resize_intos(std::size_t upperBound) +{ + // this function does not need to take into account the intosForRow_ + // elements, since they are never used for bulk operations + + int rows = backEnd_->get_number_of_rows(); + if (rows < 0) + { + rows = 0; + } + if (upperBound != 0 && upperBound < static_cast(rows)) + { + rows = static_cast(upperBound); + } + + std::size_t const isize = intos_.size(); + for (std::size_t i = 0; i != isize; ++i) + { + intos_[i]->resize((std::size_t)rows); + } + + return rows > 0 ? true : false; +} + +void statement_impl::truncate_intos() +{ + std::size_t const isize = intos_.size(); + for (std::size_t i = 0; i != isize; ++i) + { + intos_[i]->resize(0); + } +} + +void statement_impl::pre_fetch() +{ + std::size_t const isize = intos_.size(); + for (std::size_t i = 0; i != isize; ++i) + { + intos_[i]->pre_fetch(); + } + + std::size_t const ifrsize = intosForRow_.size(); + for (std::size_t i = 0; i != ifrsize; ++i) + { + intosForRow_[i]->pre_fetch(); + } +} + +void statement_impl::pre_use() +{ + std::size_t const usize = uses_.size(); + for (std::size_t i = 0; i != usize; ++i) + { + uses_[i]->pre_use(); + } +} + +void statement_impl::post_fetch(bool gotData, bool calledFromFetch) +{ + // first iterate over intosForRow_ elements, since the Row element + // (which is among the intos_ elements) might depend on the + // values of those implicitly injected elements + + std::size_t const ifrsize = intosForRow_.size(); + for (std::size_t i = 0; i != ifrsize; ++i) + { + intosForRow_[i]->post_fetch(gotData, calledFromFetch); + } + + std::size_t const isize = intos_.size(); + for (std::size_t i = 0; i != isize; ++i) + { + intos_[i]->post_fetch(gotData, calledFromFetch); + } +} + +void statement_impl::post_use(bool gotData) +{ + // iterate in reverse order here in case the first item + // is an UseType (since it depends on the other UseTypes) + for (std::size_t i = uses_.size(); i != 0; --i) + { + uses_[i-1]->post_use(gotData); + } +} + +namespace soci +{ +namespace details +{ + +// Map data_types to stock types for dynamic result set support + +template<> +void statement_impl::bind_into() +{ + into_row(); +} + +template<> +void statement_impl::bind_into() +{ + into_row(); +} + +template<> +void statement_impl::bind_into() +{ + into_row(); +} + +template<> +void statement_impl::bind_into() +{ + into_row(); +} + +template<> +void statement_impl::bind_into() +{ + into_row(); +} + +template<> +void statement_impl::bind_into() +{ + into_row(); +} + +void statement_impl::describe() +{ + row_->clean_up(); + + int const numcols = backEnd_->prepare_for_describe(); + for (int i = 1; i <= numcols; ++i) + { + data_type dtype; + std::string columnName; + + backEnd_->describe_column(i, dtype, columnName); + + column_properties props; + props.set_name(columnName); + props.set_data_type(dtype); + + switch (dtype) + { + case dt_string: + bind_into(); + break; + case dt_double: + bind_into(); + break; + case dt_integer: + bind_into(); + break; + case dt_long_long: + bind_into(); + break; + case dt_unsigned_long_long: + bind_into(); + break; + case dt_date: + bind_into(); + break; + default: + std::ostringstream msg; + msg << "db column type " << dtype + <<" not supported for dynamic selects"<add_properties(props); + } + + alreadyDescribed_ = true; +} + +} // namespace details +} // namespace soci + +void statement_impl::set_row(row * r) +{ + if (row_ != NULL) + { + throw soci_error( + "Only one Row element allowed in a single statement."); + } + + row_ = r; + row_->uppercase_column_names(session_.get_uppercase_column_names()); +} + +std::string statement_impl::rewrite_for_procedure_call(std::string const & query) +{ + return backEnd_->rewrite_for_procedure_call(query); +} + +void statement_impl::inc_ref() +{ + ++refCount_; +} + +void statement_impl::dec_ref() +{ + if (--refCount_ == 0) + { + delete this; + } +} + +standard_into_type_backend * +statement_impl::make_into_type_backend() +{ + return backEnd_->make_into_type_backend(); +} + +standard_use_type_backend * +statement_impl::make_use_type_backend() +{ + return backEnd_->make_use_type_backend(); +} + +vector_into_type_backend * +statement_impl::make_vector_into_type_backend() +{ + return backEnd_->make_vector_into_type_backend(); +} + +vector_use_type_backend * +statement_impl::make_vector_use_type_backend() +{ + return backEnd_->make_vector_use_type_backend(); +} diff --git a/src/core/statement.h b/src/core/statement.h new file mode 100644 index 0000000000..e639a84e4a --- /dev/null +++ b/src/core/statement.h @@ -0,0 +1,271 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_STATEMENT_H_INCLUDED +#define SOCI_STATEMENT_H_INCLUDED + +#include "into-type.h" +#include "into.h" +#include "use-type.h" +#include "soci-backend.h" +#include "row.h" +// std +#include +#include +#include +#include + +namespace soci +{ + +class session; +class values; + +namespace details +{ + +class into_type_base; +class use_type_base; +class prepare_temp_type; + +class SOCI_DECL statement_impl +{ +public: + explicit statement_impl(session & s); + explicit statement_impl(prepare_temp_type const & prep); + ~statement_impl(); + + void alloc(); + void bind(values & v); + void exchange(into_type_ptr const & i); + void exchange(use_type_ptr const & u); + void clean_up(); + + void prepare(std::string const & query, + statement_type eType = st_repeatable_query); + void define_and_bind(); + void undefine_and_bind(); + bool execute(bool withDataExchange = false); + long long get_affected_rows(); + bool fetch(); + void describe(); + void set_row(row * r); + void exchange_for_rowset(into_type_ptr const & i); + + // for diagnostics and advanced users + // (downcast it to expected back-end statement class) + statement_backend * get_backend() { return backEnd_; } + + standard_into_type_backend * make_into_type_backend(); + standard_use_type_backend * make_use_type_backend(); + vector_into_type_backend * make_vector_into_type_backend(); + vector_use_type_backend * make_vector_use_type_backend(); + + void inc_ref(); + void dec_ref(); + + session & session_; + + std::string rewrite_for_procedure_call(std::string const & query); + +protected: + std::vector intos_; + std::vector uses_; + std::vector indicators_; + +private: + + int refCount_; + + row * row_; + std::size_t fetchSize_; + std::size_t initialFetchSize_; + std::string query_; + std::map namedUses_; + + std::vector intosForRow_; + int definePositionForRow_; + + void exchange_for_row(into_type_ptr const & i); + void define_for_row(); + + template + void into_row() + { + T * t = new T(); + indicator * ind = new indicator(i_ok); + row_->add_holder(t, ind); + exchange_for_row(into(*t, *ind)); + } + + template + void bind_into(); + + bool alreadyDescribed_; + + std::size_t intos_size(); + std::size_t uses_size(); + void pre_fetch(); + void pre_use(); + void post_fetch(bool gotData, bool calledFromFetch); + void post_use(bool gotData); + bool resize_intos(std::size_t upperBound = 0); + void truncate_intos(); + + soci::details::statement_backend * backEnd_; + + // The type is noncopyable. + statement_impl(statement_impl const &); + statement_impl& operator=(statement_impl const &); + +}; + +} // namespace details + +// Statement is a handle class for statement_impl +// (this provides copyability to otherwise non-copyable type) +class SOCI_DECL statement +{ +public: + statement(session & s) + : impl_(new details::statement_impl(s)) {} + statement(details::prepare_temp_type const & prep) + : impl_(new details::statement_impl(prep)) {} + ~statement() { impl_->dec_ref(); } + + // copy is supported for this handle class + statement(statement const & other) + : impl_(other.impl_) + { + impl_->inc_ref(); + } + + void operator=(statement const & other) + { + other.impl_->inc_ref(); + impl_->dec_ref(); + impl_ = other.impl_; + } + + void alloc() { impl_->alloc(); } + void bind(values & v) { impl_->bind(v); } + void exchange(details::into_type_ptr const & i); + void exchange(details::use_type_ptr const & u); + void clean_up() { impl_->clean_up(); } + + void prepare(std::string const & query, + details::statement_type eType = details::st_repeatable_query) + { + impl_->prepare(query, eType); + } + + void define_and_bind() { impl_->define_and_bind(); } + void undefine_and_bind() { impl_->undefine_and_bind(); } + bool execute(bool withDataExchange = false) + { + gotData_ = impl_->execute(withDataExchange); + return gotData_; + } + + long long get_affected_rows() + { + return impl_->get_affected_rows(); + } + + bool fetch() + { + gotData_ = impl_->fetch(); + return gotData_; + } + + bool got_data() const { return gotData_; } + + void describe() { impl_->describe(); } + void set_row(row * r) { impl_->set_row(r); } + void exchange_for_rowset(details::into_type_ptr const & i) + { + impl_->exchange_for_rowset(i); + } + + // for diagnostics and advanced users + // (downcast it to expected back-end statement class) + details::statement_backend * get_backend() + { + return impl_->get_backend(); + } + + details::standard_into_type_backend * make_into_type_backend() + { + return impl_->make_into_type_backend(); + } + + details::standard_use_type_backend * make_use_type_backend() + { + return impl_->make_use_type_backend(); + } + + details::vector_into_type_backend * make_vector_into_type_backend() + { + return impl_->make_vector_into_type_backend(); + } + + details::vector_use_type_backend * make_vector_use_type_backend() + { + return impl_->make_vector_use_type_backend(); + } + + std::string rewrite_for_procedure_call(std::string const & query) + { + return impl_->rewrite_for_procedure_call(query); + } + +private: + details::statement_impl * impl_; + bool gotData_; +}; + +namespace details +{ +// exchange_traits for statement + +template <> +struct exchange_traits +{ + typedef basic_type_tag type_family; + enum { x_type = x_statement }; +}; + +// into and use types for Statement (for nested statements and cursors) + +template <> +class into_type : public standard_into_type +{ +public: + into_type(statement & s) : standard_into_type(&s, x_statement) {} + into_type(statement & s, indicator & ind) + : standard_into_type(&s, x_statement, ind) {} +}; + +template <> +class use_type : public standard_use_type +{ +public: + use_type(statement & s, std::string const & name = std::string()) + : standard_use_type(&s, x_statement, false, name) {} + use_type(statement & s, indicator & ind, + std::string const & name = std::string()) + : standard_use_type(&s, x_statement, ind, false, name) {} + + // Note: there is no const version of use for statement, + // because most likely it would not make much sense anyway. +}; + +} // namespace details + +} // namespace soci + +#endif // SOCI_STATEMENT_H_INCLUDED diff --git a/src/core/test/common-tests.h b/src/core/test/common-tests.h new file mode 100644 index 0000000000..37fc4fbe0d --- /dev/null +++ b/src/core/test/common-tests.h @@ -0,0 +1,3980 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_COMMON_TESTS_H_INCLUDED +#define SOCI_COMMON_TESTS_H_INCLUDED + +#include "soci.h" +#include "soci-config.h" + +#ifdef HAVE_BOOST +// explicitly pull conversions for Boost's optional, tuple and fusion: +#include +#include +#include +#include +#if defined(BOOST_VERSION) && BOOST_VERSION >= 103500 +#include +#endif // BOOST_VERSION +#endif // HAVE_BOOST + +#include +#include +#include +#include +#include +#include + +// Objects used later in tests 14,15 +struct PhonebookEntry +{ + std::string name; + std::string phone; +}; + +struct PhonebookEntry2 : public PhonebookEntry +{ +}; + +class PhonebookEntry3 +{ +public: + void setName(std::string const & n) { name_ = n; } + std::string getName() const { return name_; } + + void setPhone(std::string const & p) { phone_ = p; } + std::string getPhone() const { return phone_; } + +public: + std::string name_; + std::string phone_; +}; + +// user-defined object for test26 and test28 +class MyInt +{ +public: + MyInt() {} + MyInt(int i) : i_(i) {} + void set(int i) { i_ = i; } + int get() const { return i_; } +private: + int i_; +}; + +namespace soci +{ + +// basic type conversion for user-defined type with single base value +template<> struct type_conversion +{ + typedef int base_type; + + static void from_base(int i, indicator ind, MyInt &mi) + { + if (ind == i_ok) + { + mi.set(i); + } + } + + static void to_base(MyInt const &mi, int &i, indicator &ind) + { + i = mi.get(); + ind = i_ok; + } +}; + +// basic type conversion on many values (ORM) +template<> struct type_conversion +{ + typedef soci::values base_type; + + static void from_base(values const &v, indicator /* ind */, PhonebookEntry &pe) + { + // here we ignore the possibility the the whole object might be NULL + pe.name = v.get("NAME"); + pe.phone = v.get("PHONE", ""); + } + + static void to_base(PhonebookEntry const &pe, values &v, indicator &ind) + { + v.set("NAME", pe.name); + v.set("PHONE", pe.phone, pe.phone.empty() ? i_null : i_ok); + ind = i_ok; + } +}; + +// type conversion which directly calls values::get_indicator() +template<> struct type_conversion +{ + typedef soci::values base_type; + + static void from_base(values const &v, indicator /* ind */, PhonebookEntry2 &pe) + { + // here we ignore the possibility the the whole object might be NULL + + pe.name = v.get("NAME"); + indicator ind = v.get_indicator("PHONE"); //another way to test for null + pe.phone = ind == i_null ? "" : v.get("PHONE"); + } + + static void to_base(PhonebookEntry2 const &pe, values &v, indicator &ind) + { + v.set("NAME", pe.name); + v.set("PHONE", pe.phone, pe.phone.empty() ? i_null : i_ok); + ind = i_ok; + } +}; + +template<> struct type_conversion +{ + typedef soci::values base_type; + + static void from_base(values const &v, indicator /* ind */, PhonebookEntry3 &pe) + { + // here we ignore the possibility the the whole object might be NULL + + pe.setName(v.get("NAME")); + pe.setPhone(v.get("PHONE", "")); + } + + static void to_base(PhonebookEntry3 const &pe, values &v, indicator &ind) + { + v.set("NAME", pe.getName()); + v.set("PHONE", pe.getPhone(), pe.getPhone().empty() ? i_null : i_ok); + ind = i_ok; + } +}; + +} // namespace soci + +namespace soci +{ +namespace tests +{ + +// ensure connection is checked, no crash occurs + +#define SOCI_TEST_ENSURE_CONNECTED(sql, method) { \ + std::string msg; \ + try { \ + (sql.method)(); \ + assert(!"exception expected"); \ + } catch (soci_error const &e) { msg = e.what(); } \ + assert(msg.empty() == false); } (void)sql + +#define SOCI_TEST_ENSURE_CONNECTED2(sql, method) { \ + std::string msg; \ + try { std::string seq; long v(0); \ + (sql.method)(seq, v); \ + assert(!"exception expected"); \ + } catch (soci_error const &e) { msg = e.what(); } \ + assert(msg.empty() == false); } (void)sql + +inline bool equal_approx(double const a, double const b) +{ + // The formula taken from CATCH test framework + // https://github.com/philsquared/Catch/ + // Thanks to Richard Harris for his help refining this formula + double const epsilon(std::numeric_limits::epsilon() * 100); + double const scale(1.0); + return std::fabs(a - b) < epsilon * (scale + (std::max)(std::fabs(a), std::fabs(b))); +} + +// TODO: improve cleanup capabilities by subtypes, soci_test name may be omitted --mloskot +// i.e. optional ctor param accepting custom table name +class table_creator_base +{ +public: + table_creator_base(session& sql) + : msession(sql) { drop(); } + + virtual ~table_creator_base() { drop();} +private: + void drop() + { + try + { + msession << "drop table soci_test"; + } + catch (soci_error const& e) + { + //std::cerr << e.what() << std::endl; + e.what(); + } + } + session& msession; +}; + +class procedure_creator_base +{ +public: + procedure_creator_base(session& sql) + : msession(sql) { drop(); } + + virtual ~procedure_creator_base() { drop();} +private: + void drop() + { + try { msession << "drop procedure soci_test"; } catch (soci_error&) {} + } + session& msession; +}; + +class function_creator_base +{ +public: + function_creator_base(session& sql) + : msession(sql) { drop(); } + + virtual ~function_creator_base() { drop();} + +protected: + virtual std::string dropstatement() + { + return "drop function soci_test"; + } + +private: + void drop() + { + try { msession << dropstatement(); } catch (soci_error&) {} + } + session& msession; +}; + +class test_context_base +{ +public: + test_context_base(backend_factory const &backEnd, + std::string const &connectString) + : backEndFactory_(backEnd), + connectString_(connectString) {} + + backend_factory const & get_backend_factory() const + { + return backEndFactory_; + } + + std::string get_connect_string() const + { + return connectString_; + } + + virtual std::string to_date_time(std::string const &dateTime) const = 0; + + virtual table_creator_base* table_creator_1(session&) const = 0; + virtual table_creator_base* table_creator_2(session&) const = 0; + virtual table_creator_base* table_creator_3(session&) const = 0; + virtual table_creator_base* table_creator_4(session&) const = 0; + + virtual ~test_context_base() {} // quiet the compiler + +private: + backend_factory const &backEndFactory_; + std::string const connectString_; +}; + +class common_tests +{ +public: + common_tests(test_context_base const &tc) + : tc_(tc), + backEndFactory_(tc.get_backend_factory()), + connectString_(tc.get_connect_string()) + {} + + void run(bool dbSupportsTransactions = true) + { + std::cout<<"\nSOCI Common Tests:\n\n"; + + test0(); + test1(); + test2(); + test3(); + test4(); + test5(); + test6(); + test7(); + test8(); + test9(); + + if (dbSupportsTransactions) + { + test10(); + } + else + { + std::cout<<"skipping test 10 (database doesn't support transactions)\n"; + } + + test11(); + test12(); + test13(); + test14(); + test15(); + test16(); + test17(); + test18(); + test19(); + test20(); + test21(); + test22(); + test23(); + test24(); + test25(); + test26(); + test27(); + test28(); + test29(); + test30(); + test31(); + test_get_affected_rows(); + test_query_transformation(); + test_query_transformation_with_connection_pool(); + test_pull5(); + test_issue67(); + test_prepared_insert_with_orm_type(); + test_issue154(); + test_placeholder_partial_matching_with_orm_type(); + } + +private: + test_context_base const & tc_; + backend_factory const &backEndFactory_; + std::string const connectString_; + +typedef std::auto_ptr auto_table_creator; + +void test0() +{ + { + soci::session sql; // no connection + SOCI_TEST_ENSURE_CONNECTED(sql, begin); + SOCI_TEST_ENSURE_CONNECTED(sql, commit); + SOCI_TEST_ENSURE_CONNECTED(sql, rollback); + SOCI_TEST_ENSURE_CONNECTED(sql, get_backend_name); + SOCI_TEST_ENSURE_CONNECTED(sql, make_statement_backend); + SOCI_TEST_ENSURE_CONNECTED(sql, make_rowid_backend); + SOCI_TEST_ENSURE_CONNECTED(sql, make_blob_backend); + SOCI_TEST_ENSURE_CONNECTED2(sql, get_next_sequence_value); + SOCI_TEST_ENSURE_CONNECTED2(sql, get_last_insert_id); + } + std::cout << "test 0 passed\n"; +} +void test1() +{ + session sql(backEndFactory_, connectString_); + + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + std::string msg; + try + { + // expected error + sql << "drop table soci_test_nosuchtable"; + assert(false); + } + catch (soci_error const &e) + { + msg = e.what(); + } + assert(msg.empty() == false); + + sql << "insert into soci_test (id) values (" << 123 << ")"; + int id; + sql << "select id from soci_test", into(id); + assert(id == 123); + + std::cout << "test 1 passed\n"; +} + +// "into" tests, type conversions, etc. +void test2() +{ + { + session sql(backEndFactory_, connectString_); + + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + char c('a'); + sql << "insert into soci_test(c) values(:c)", use(c); + sql << "select c from soci_test", into(c); + assert(c == 'a'); + } + + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + std::string helloSOCI("Hello, SOCI!"); + sql << "insert into soci_test(str) values(:s)", use(helloSOCI); + std::string str; + sql << "select str from soci_test", into(str); + assert(str == "Hello, SOCI!"); + } + + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + short three(3); + sql << "insert into soci_test(sh) values(:id)", use(three); + short sh(0); + sql << "select sh from soci_test", into(sh); + assert(sh == 3); + } + + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + int five(5); + sql << "insert into soci_test(id) values(:id)", use(five); + int i(0); + sql << "select id from soci_test", into(i); + assert(i == 5); + } + + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + unsigned long seven(7); + sql << "insert into soci_test(ul) values(:ul)", use(seven); + unsigned long ul(0); + sql << "select ul from soci_test", into(ul); + assert(ul == 7); + } + + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + double pi(3.14159265); + sql << "insert into soci_test(d) values(:d)", use(pi); + double d(0.0); + sql << "select d from soci_test", into(d); + assert(equal_approx(d, 3.14159265)); + } + + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + std::tm nov15; + nov15.tm_year = 105; + nov15.tm_mon = 10; + nov15.tm_mday = 15; + nov15.tm_hour = 0; + nov15.tm_min = 0; + nov15.tm_sec = 0; + + sql << "insert into soci_test(tm) values(:tm)", use(nov15); + + std::tm t; + sql << "select tm from soci_test", into(t); + assert(t.tm_year == 105); + assert(t.tm_mon == 10); + assert(t.tm_mday == 15); + assert(t.tm_hour == 0); + assert(t.tm_min == 0); + assert(t.tm_sec == 0); + } + + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + std::tm nov15; + nov15.tm_year = 105; + nov15.tm_mon = 10; + nov15.tm_mday = 15; + nov15.tm_hour = 22; + nov15.tm_min = 14; + nov15.tm_sec = 17; + + sql << "insert into soci_test(tm) values(:tm)", use(nov15); + + std::tm t; + sql << "select tm from soci_test", into(t); + assert(t.tm_year == 105); + assert(t.tm_mon == 10); + assert(t.tm_mday == 15); + assert(t.tm_hour == 22); + assert(t.tm_min == 14); + assert(t.tm_sec == 17); + } + + // test indicators + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + int id(1); + std::string str("Hello"); + sql << "insert into soci_test(id, str) values(:id, :str)", + use(id), use(str); + + int i; + indicator ind; + sql << "select id from soci_test", into(i, ind); + assert(ind == i_ok); + } + + // more indicator tests, NULL values + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + sql << "insert into soci_test(id,tm) values(NULL,NULL)"; + int i; + indicator ind; + sql << "select id from soci_test", into(i, ind); + assert(ind == i_null); + + // additional test for NULL with std::tm + std::tm t; + sql << "select tm from soci_test", into(t, ind); + assert(ind == i_null); + + try + { + // expect error + sql << "select id from soci_test", into(i); + assert(false); + } + catch (soci_error const &e) + { + std::string error = e.what(); + assert(error == + "Null value fetched and no indicator defined."); + } + + sql << "select id from soci_test where id = 1000", into(i, ind); + assert(sql.got_data() == false); + + // no data expected + sql << "select id from soci_test where id = 1000", into(i); + assert(sql.got_data() == false); + + // no data expected, test correct behaviour with use + int id = 1000; + sql << "select id from soci_test where id = :id", use(id), into(i); + assert(sql.got_data() == false); + } + } + + std::cout << "test 2 passed" << std::endl; +} + +// repeated fetch and bulk fetch +void test3() +{ + { + session sql(backEndFactory_, connectString_); + + // repeated fetch and bulk fetch of char + { + // create and populate the test table + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + char c; + for (c = 'a'; c <= 'z'; ++c) + { + sql << "insert into soci_test(c) values(\'" << c << "\')"; + } + + int count; + sql << "select count(*) from soci_test", into(count); + assert(count == 'z' - 'a' + 1); + + { + char c2 = 'a'; + + statement st = (sql.prepare << + "select c from soci_test order by c", into(c)); + + st.execute(); + while (st.fetch()) + { + assert(c == c2); + ++c2; + } + assert(c2 == 'a' + count); + } + { + char c2 = 'a'; + + std::vector vec(10); + statement st = (sql.prepare << + "select c from soci_test order by c", into(vec)); + st.execute(); + while (st.fetch()) + { + for (std::size_t i = 0; i != vec.size(); ++i) + { + assert(c2 == vec[i]); + ++c2; + } + + vec.resize(10); + } + assert(c2 == 'a' + count); + } + + { + // verify an exception is thrown when empty vector is used + std::vector vec; + try + { + sql << "select c from soci_test", into(vec); + assert(false); + } + catch (soci_error const &e) + { + std::string msg = e.what(); + assert(msg == "Vectors of size 0 are not allowed."); + } + } + + } + + // repeated fetch and bulk fetch of std::string + { + // create and populate the test table + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + int const rowsToTest = 10; + for (int i = 0; i != rowsToTest; ++i) + { + std::ostringstream ss; + ss << "Hello_" << i; + + sql << "insert into soci_test(str) values(\'" + << ss.str() << "\')"; + } + + int count; + sql << "select count(*) from soci_test", into(count); + assert(count == rowsToTest); + + { + int i = 0; + std::string s; + statement st = (sql.prepare << + "select str from soci_test order by str", into(s)); + + st.execute(); + while (st.fetch()) + { + std::ostringstream ss; + ss << "Hello_" << i; + assert(s == ss.str()); + ++i; + } + assert(i == rowsToTest); + } + { + int i = 0; + + std::vector vec(4); + statement st = (sql.prepare << + "select str from soci_test order by str", into(vec)); + st.execute(); + while (st.fetch()) + { + for (std::size_t j = 0; j != vec.size(); ++j) + { + std::ostringstream ss; + ss << "Hello_" << i; + assert(ss.str() == vec[j]); + ++i; + } + + vec.resize(4); + } + assert(i == rowsToTest); + } + } + + // repeated fetch and bulk fetch of short + { + // create and populate the test table + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + short const rowsToTest = 100; + short sh; + for (sh = 0; sh != rowsToTest; ++sh) + { + sql << "insert into soci_test(sh) values(" << sh << ")"; + } + + int count; + sql << "select count(*) from soci_test", into(count); + assert(count == rowsToTest); + + { + short sh2 = 0; + + statement st = (sql.prepare << + "select sh from soci_test order by sh", into(sh)); + + st.execute(); + while (st.fetch()) + { + assert(sh == sh2); + ++sh2; + } + assert(sh2 == rowsToTest); + } + { + short sh2 = 0; + + std::vector vec(8); + statement st = (sql.prepare << + "select sh from soci_test order by sh", into(vec)); + st.execute(); + while (st.fetch()) + { + for (std::size_t i = 0; i != vec.size(); ++i) + { + assert(sh2 == vec[i]); + ++sh2; + } + + vec.resize(8); + } + assert(sh2 == rowsToTest); + } + } + + // repeated fetch and bulk fetch of int (4-bytes) + { + // create and populate the test table + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + int const rowsToTest = 100; + int i; + for (i = 0; i != rowsToTest; ++i) + { + sql << "insert into soci_test(id) values(" << i << ")"; + } + + int count; + sql << "select count(*) from soci_test", into(count); + assert(count == rowsToTest); + + { + int i2 = 0; + + statement st = (sql.prepare << + "select id from soci_test order by id", into(i)); + + st.execute(); + while (st.fetch()) + { + assert(i == i2); + ++i2; + } + assert(i2 == rowsToTest); + } + { + // additional test with the use element + + int i2 = 0; + int cond = 0; // this condition is always true + + statement st = (sql.prepare << + "select id from soci_test where id >= :cond order by id", + use(cond), into(i)); + + st.execute(); + while (st.fetch()) + { + assert(i == i2); + ++i2; + } + assert(i2 == rowsToTest); + } + { + int i2 = 0; + + std::vector vec(8); + statement st = (sql.prepare << + "select id from soci_test order by id", into(vec)); + st.execute(); + while (st.fetch()) + { + for (std::size_t i = 0; i != vec.size(); ++i) + { + assert(i2 == vec[i]); + ++i2; + } + + vec.resize(8); + } + assert(i2 == rowsToTest); + } + } + + // repeated fetch and bulk fetch of unsigned int (4-bytes) + { + // create and populate the test table + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + unsigned int const rowsToTest = 100; + unsigned int ul; + for (ul = 0; ul != rowsToTest; ++ul) + { + sql << "insert into soci_test(ul) values(" << ul << ")"; + } + + int count; + sql << "select count(*) from soci_test", into(count); + assert(count == static_cast(rowsToTest)); + + { + unsigned int ul2 = 0; + + statement st = (sql.prepare << + "select ul from soci_test order by ul", into(ul)); + + st.execute(); + while (st.fetch()) + { + assert(ul == ul2); + ++ul2; + } + assert(ul2 == rowsToTest); + } + { + unsigned int ul2 = 0; + + std::vector vec(8); + statement st = (sql.prepare << + "select ul from soci_test order by ul", into(vec)); + st.execute(); + while (st.fetch()) + { + for (std::size_t i = 0; i != vec.size(); ++i) + { + assert(ul2 == vec[i]); + ++ul2; + } + + vec.resize(8); + } + assert(ul2 == rowsToTest); + } + } + + // repeated fetch and bulk fetch of double + { + // create and populate the test table + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + int const rowsToTest = 100; + double d = 0.0; + for (int i = 0; i != rowsToTest; ++i) + { + sql << "insert into soci_test(d) values(" << d << ")"; + d += 0.6; + } + + int count; + sql << "select count(*) from soci_test", into(count); + assert(count == rowsToTest); + + { + double d2 = 0.0; + int i = 0; + + statement st = (sql.prepare << + "select d from soci_test order by d", into(d)); + + st.execute(); + while (st.fetch()) + { + assert(equal_approx(d, d2)); + d2 += 0.6; + ++i; + } + assert(i == rowsToTest); + } + { + double d2 = 0.0; + int i = 0; + + std::vector vec(8); + statement st = (sql.prepare << + "select d from soci_test order by d", into(vec)); + st.execute(); + while (st.fetch()) + { + for (std::size_t j = 0; j != vec.size(); ++j) + { + assert(equal_approx(d2, vec[j])); + d2 += 0.6; + ++i; + } + + vec.resize(8); + } + assert(i == rowsToTest); + } + } + + // repeated fetch and bulk fetch of std::tm + { + // create and populate the test table + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + int const rowsToTest = 8; + for (int i = 0; i != rowsToTest; ++i) + { + std::ostringstream ss; + ss << 2000 + i << "-0" << 1 + i << '-' << 20 - i << ' ' + << 15 + i << ':' << 50 - i << ':' << 40 + i; + + sql << "insert into soci_test(id, tm) values(" << i + << ", " << tc_.to_date_time(ss.str()) << ")"; + } + + int count; + sql << "select count(*) from soci_test", into(count); + assert(count == rowsToTest); + + { + std::tm t; + int i = 0; + + statement st = (sql.prepare << + "select tm from soci_test order by id", into(t)); + + st.execute(); + while (st.fetch()) + { + assert(t.tm_year + 1900 == 2000 + i); + assert(t.tm_mon + 1 == 1 + i); + assert(t.tm_mday == 20 - i); + assert(t.tm_hour == 15 + i); + assert(t.tm_min == 50 - i); + assert(t.tm_sec == 40 + i); + + ++i; + } + assert(i == rowsToTest); + } + { + int i = 0; + + std::vector vec(3); + statement st = (sql.prepare << + "select tm from soci_test order by id", into(vec)); + st.execute(); + while (st.fetch()) + { + for (std::size_t j = 0; j != vec.size(); ++j) + { + assert(vec[j].tm_year + 1900 == 2000 + i); + assert(vec[j].tm_mon + 1 == 1 + i); + assert(vec[j].tm_mday == 20 - i); + assert(vec[j].tm_hour == 15 + i); + assert(vec[j].tm_min == 50 - i); + assert(vec[j].tm_sec == 40 + i); + + ++i; + } + + vec.resize(3); + } + assert(i == rowsToTest); + } + } + } + + std::cout << "test 3 passed" << std::endl; +} + +// test for indicators (repeated fetch and bulk) +void test4() +{ + session sql(backEndFactory_, connectString_); + + // create and populate the test table + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + { + sql << "insert into soci_test(id, val) values(1, 10)"; + sql << "insert into soci_test(id, val) values(2, 11)"; + sql << "insert into soci_test(id, val) values(3, NULL)"; + sql << "insert into soci_test(id, val) values(4, NULL)"; + sql << "insert into soci_test(id, val) values(5, 12)"; + + { + int val; + indicator ind; + + statement st = (sql.prepare << + "select val from soci_test order by id", into(val, ind)); + + st.execute(); + bool gotData = st.fetch(); + assert(gotData); + assert(ind == i_ok); + assert(val == 10); + gotData = st.fetch(); + assert(gotData); + assert(ind == i_ok); + assert(val == 11); + gotData = st.fetch(); + assert(gotData); + assert(ind == i_null); + gotData = st.fetch(); + assert(gotData); + assert(ind == i_null); + gotData = st.fetch(); + assert(gotData); + assert(ind == i_ok); + assert(val == 12); + gotData = st.fetch(); + assert(gotData == false); + } + { + std::vector vals(3); + std::vector inds(3); + + statement st = (sql.prepare << + "select val from soci_test order by id", into(vals, inds)); + + st.execute(); + bool gotData = st.fetch(); + assert(gotData); + assert(vals.size() == 3); + assert(inds.size() == 3); + assert(inds[0] == i_ok); + assert(vals[0] == 10); + assert(inds[1] == i_ok); + assert(vals[1] == 11); + assert(inds[2] == i_null); + gotData = st.fetch(); + assert(gotData); + assert(vals.size() == 2); + assert(inds[0] == i_null); + assert(inds[1] == i_ok); + assert(vals[1] == 12); + gotData = st.fetch(); + assert(gotData == false); + } + + // additional test for "no data" condition + { + std::vector vals(3); + std::vector inds(3); + + statement st = (sql.prepare << + "select val from soci_test where 0 = 1", into(vals, inds)); + + bool gotData = st.execute(true); + assert(gotData == false); + + // for convenience, vectors should be truncated + assert(vals.empty()); + assert(inds.empty()); + + // for even more convenience, fetch should not fail + // but just report end of rowset + // (and vectors should be truncated) + + vals.resize(1); + inds.resize(1); + + gotData = st.fetch(); + assert(gotData == false); + assert(vals.empty()); + assert(inds.empty()); + } + + // additional test for "no data" without prepared statement + { + std::vector vals(3); + std::vector inds(3); + + sql << "select val from soci_test where 0 = 1", + into(vals, inds); + + // vectors should be truncated + assert(vals.empty()); + assert(inds.empty()); + } + } + + std::cout << "test 4 passed" << std::endl; +} + +// test for different sizes of data vector and indicators vector +// (library should force ind. vector to have same size as data vector) +void test5() +{ + session sql(backEndFactory_, connectString_); + + // create and populate the test table + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + { + sql << "insert into soci_test(id, val) values(1, 10)"; + sql << "insert into soci_test(id, val) values(2, 11)"; + sql << "insert into soci_test(id, val) values(3, NULL)"; + sql << "insert into soci_test(id, val) values(4, NULL)"; + sql << "insert into soci_test(id, val) values(5, 12)"; + + { + std::vector vals(4); + std::vector inds; + + statement st = (sql.prepare << + "select val from soci_test order by id", into(vals, inds)); + + st.execute(); + st.fetch(); + assert(vals.size() == 4); + assert(inds.size() == 4); + vals.resize(3); + st.fetch(); + assert(vals.size() == 1); + assert(inds.size() == 1); + } + } + + std::cout << "test 5 passed" << std::endl; +} + +// "use" tests, type conversions, etc. +void test6() +{ +// Note: this functionality is not available with older PostgreSQL +#ifndef SOCI_POSTGRESQL_NOPARAMS + { + session sql(backEndFactory_, connectString_); + + // test for char + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + char c('a'); + sql << "insert into soci_test(c) values(:c)", use(c); + + c = 'b'; + sql << "select c from soci_test", into(c); + assert(c == 'a'); + + } + + // test for std::string + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + std::string s = "Hello SOCI!"; + sql << "insert into soci_test(str) values(:s)", use(s); + + std::string str; + sql << "select str from soci_test", into(str); + + assert(str == "Hello SOCI!"); + } + + // test for short + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + short s = 123; + sql << "insert into soci_test(id) values(:id)", use(s); + + short s2 = 0; + sql << "select id from soci_test", into(s2); + + assert(s2 == 123); + } + + // test for int + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + int i = -12345678; + sql << "insert into soci_test(id) values(:i)", use(i); + + int i2 = 0; + sql << "select id from soci_test", into(i2); + + assert(i2 == -12345678); + } + + // test for unsigned long + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + unsigned long ul = 4000000000ul; + sql << "insert into soci_test(ul) values(:num)", use(ul); + + unsigned long ul2 = 0; + sql << "select ul from soci_test", into(ul2); + + assert(ul2 == 4000000000ul); + } + + // test for double + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + double d = 3.14159265; + sql << "insert into soci_test(d) values(:d)", use(d); + + double d2 = 0; + sql << "select d from soci_test", into(d2); + + assert(equal_approx(d2, d)); + } + + // test for std::tm + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + std::tm t; + t.tm_year = 105; + t.tm_mon = 10; + t.tm_mday = 19; + t.tm_hour = 21; + t.tm_min = 39; + t.tm_sec = 57; + sql << "insert into soci_test(tm) values(:t)", use(t); + + std::tm t2; + t2.tm_year = 0; + t2.tm_mon = 0; + t2.tm_mday = 0; + t2.tm_hour = 0; + t2.tm_min = 0; + t2.tm_sec = 0; + + sql << "select tm from soci_test", into(t2); + + assert(t.tm_year == 105); + assert(t.tm_mon == 10); + assert(t.tm_mday == 19); + assert(t.tm_hour == 21); + assert(t.tm_min == 39); + assert(t.tm_sec == 57); + } + + // test for repeated use + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + int i; + statement st = (sql.prepare + << "insert into soci_test(id) values(:id)", use(i)); + + i = 5; + st.execute(true); + i = 6; + st.execute(true); + i = 7; + st.execute(true); + + std::vector v(5); + sql << "select id from soci_test order by id", into(v); + + assert(v.size() == 3); + assert(v[0] == 5); + assert(v[1] == 6); + assert(v[2] == 7); + } + + // tests for use of const objects + + // test for char + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + char const c('a'); + sql << "insert into soci_test(c) values(:c)", use(c); + + char c2 = 'b'; + sql << "select c from soci_test", into(c2); + assert(c2 == 'a'); + + } + + // test for std::string + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + std::string const s = "Hello const SOCI!"; + sql << "insert into soci_test(str) values(:s)", use(s); + + std::string str; + sql << "select str from soci_test", into(str); + + assert(str == "Hello const SOCI!"); + } + + // test for short + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + short const s = 123; + sql << "insert into soci_test(id) values(:id)", use(s); + + short s2 = 0; + sql << "select id from soci_test", into(s2); + + assert(s2 == 123); + } + + // test for int + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + int const i = -12345678; + sql << "insert into soci_test(id) values(:i)", use(i); + + int i2 = 0; + sql << "select id from soci_test", into(i2); + + assert(i2 == -12345678); + } + + // test for unsigned long + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + unsigned long const ul = 4000000000ul; + sql << "insert into soci_test(ul) values(:num)", use(ul); + + unsigned long ul2 = 0; + sql << "select ul from soci_test", into(ul2); + + assert(ul2 == 4000000000ul); + } + + // test for double + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + double const d = 3.14159265; + sql << "insert into soci_test(d) values(:d)", use(d); + + double d2 = 0; + sql << "select d from soci_test", into(d2); + + assert(equal_approx(d2, d)); + } + + // test for std::tm + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + std::tm t; + t.tm_year = 105; + t.tm_mon = 10; + t.tm_mday = 19; + t.tm_hour = 21; + t.tm_min = 39; + t.tm_sec = 57; + std::tm const & ct = t; + sql << "insert into soci_test(tm) values(:t)", use(ct); + + std::tm t2; + t2.tm_year = 0; + t2.tm_mon = 0; + t2.tm_mday = 0; + t2.tm_hour = 0; + t2.tm_min = 0; + t2.tm_sec = 0; + + sql << "select tm from soci_test", into(t2); + + assert(t.tm_year == 105); + assert(t.tm_mon == 10); + assert(t.tm_mday == 19); + assert(t.tm_hour == 21); + assert(t.tm_min == 39); + assert(t.tm_sec == 57); + } + } + + std::cout << "test 6 passed" << std::endl; +#endif // SOCI_POSTGRESQL_NOPARAMS +} + +// test for multiple use (and into) elements +void test7() +{ + { + session sql(backEndFactory_, connectString_); + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + { + int i1 = 5; + int i2 = 6; + int i3 = 7; + +#ifndef SOCI_POSTGRESQL_NOPARAMS + + sql << "insert into soci_test(i1, i2, i3) values(:i1, :i2, :i3)", + use(i1), use(i2), use(i3); + +#else + // Older PostgreSQL does not support use elements. + + sql << "insert into soci_test(i1, i2, i3) values(5, 6, 7)"; + +#endif // SOCI_POSTGRESQL_NOPARAMS + + i1 = 0; + i2 = 0; + i3 = 0; + sql << "select i1, i2, i3 from soci_test", + into(i1), into(i2), into(i3); + + assert(i1 == 5); + assert(i2 == 6); + assert(i3 == 7); + + // same for vectors + sql << "delete from soci_test"; + + i1 = 0; + i2 = 0; + i3 = 0; + +#ifndef SOCI_POSTGRESQL_NOPARAMS + + statement st = (sql.prepare + << "insert into soci_test(i1, i2, i3) values(:i1, :i2, :i3)", + use(i1), use(i2), use(i3)); + + i1 = 1; + i2 = 2; + i3 = 3; + st.execute(true); + i1 = 4; + i2 = 5; + i3 = 6; + st.execute(true); + i1 = 7; + i2 = 8; + i3 = 9; + st.execute(true); + +#else + // Older PostgreSQL does not support use elements. + + sql << "insert into soci_test(i1, i2, i3) values(1, 2, 3)"; + sql << "insert into soci_test(i1, i2, i3) values(4, 5, 6)"; + sql << "insert into soci_test(i1, i2, i3) values(7, 8, 9)"; + +#endif // SOCI_POSTGRESQL_NOPARAMS + + std::vector v1(5); + std::vector v2(5); + std::vector v3(5); + + sql << "select i1, i2, i3 from soci_test order by i1", + into(v1), into(v2), into(v3); + + assert(v1.size() == 3); + assert(v2.size() == 3); + assert(v3.size() == 3); + assert(v1[0] == 1); + assert(v1[1] == 4); + assert(v1[2] == 7); + assert(v2[0] == 2); + assert(v2[1] == 5); + assert(v2[2] == 8); + assert(v3[0] == 3); + assert(v3[1] == 6); + assert(v3[2] == 9); + } + } + + std::cout << "test 7 passed" << std::endl; +} + +// use vector elements +void test8() +{ +// Not supported with older PostgreSQL +#ifndef SOCI_POSTGRESQL_NOPARAMS + + { + session sql(backEndFactory_, connectString_); + + // test for char + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + std::vector v; + v.push_back('a'); + v.push_back('b'); + v.push_back('c'); + v.push_back('d'); + + sql << "insert into soci_test(c) values(:c)", use(v); + + std::vector v2(4); + + sql << "select c from soci_test order by c", into(v2); + assert(v2.size() == 4); + assert(v2[0] == 'a'); + assert(v2[1] == 'b'); + assert(v2[2] == 'c'); + assert(v2[3] == 'd'); + } + + // test for std::string + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + std::vector v; + v.push_back("ala"); + v.push_back("ma"); + v.push_back("kota"); + + sql << "insert into soci_test(str) values(:s)", use(v); + + std::vector v2(4); + + sql << "select str from soci_test order by str", into(v2); + assert(v2.size() == 3); + assert(v2[0] == "ala"); + assert(v2[1] == "kota"); + assert(v2[2] == "ma"); + } + + // test for short + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + std::vector v; + v.push_back(-5); + v.push_back(6); + v.push_back(7); + v.push_back(123); + + sql << "insert into soci_test(sh) values(:sh)", use(v); + + std::vector v2(4); + + sql << "select sh from soci_test order by sh", into(v2); + assert(v2.size() == 4); + assert(v2[0] == -5); + assert(v2[1] == 6); + assert(v2[2] == 7); + assert(v2[3] == 123); + } + + // test for int + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + std::vector v; + v.push_back(-2000000000); + v.push_back(0); + v.push_back(1); + v.push_back(2000000000); + + sql << "insert into soci_test(id) values(:i)", use(v); + + std::vector v2(4); + + sql << "select id from soci_test order by id", into(v2); + assert(v2.size() == 4); + assert(v2[0] == -2000000000); + assert(v2[1] == 0); + assert(v2[2] == 1); + assert(v2[3] == 2000000000); + } + + // test for unsigned int + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + std::vector v; + v.push_back(0); + v.push_back(1); + v.push_back(123); + v.push_back(1000); + + sql << "insert into soci_test(ul) values(:ul)", use(v); + + std::vector v2(4); + + sql << "select ul from soci_test order by ul", into(v2); + assert(v2.size() == 4); + assert(v2[0] == 0); + assert(v2[1] == 1); + assert(v2[2] == 123); + assert(v2[3] == 1000); + } + + // test for double + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + std::vector v; + v.push_back(0); + v.push_back(-0.0001); + v.push_back(0.0001); + v.push_back(3.1415926); + + sql << "insert into soci_test(d) values(:d)", use(v); + + std::vector v2(4); + + sql << "select d from soci_test order by d", into(v2); + assert(v2.size() == 4); + assert(equal_approx(v2[0],-0.0001)); + assert(equal_approx(v2[1], 0)); + assert(equal_approx(v2[2], 0.0001)); + assert(equal_approx(v2[3], 3.1415926)); + } + + // test for std::tm + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + std::vector v; + std::tm t; + t.tm_year = 105; + t.tm_mon = 10; + t.tm_mday = 26; + t.tm_hour = 22; + t.tm_min = 45; + t.tm_sec = 17; + + v.push_back(t); + + t.tm_sec = 37; + v.push_back(t); + + t.tm_mday = 25; + v.push_back(t); + + sql << "insert into soci_test(tm) values(:t)", use(v); + + std::vector v2(4); + + sql << "select tm from soci_test order by tm", into(v2); + assert(v2.size() == 3); + assert(v2[0].tm_year == 105); + assert(v2[0].tm_mon == 10); + assert(v2[0].tm_mday == 25); + assert(v2[0].tm_hour == 22); + assert(v2[0].tm_min == 45); + assert(v2[0].tm_sec == 37); + assert(v2[1].tm_year == 105); + assert(v2[1].tm_mon == 10); + assert(v2[1].tm_mday == 26); + assert(v2[1].tm_hour == 22); + assert(v2[1].tm_min == 45); + assert(v2[1].tm_sec == 17); + assert(v2[2].tm_year == 105); + assert(v2[2].tm_mon == 10); + assert(v2[2].tm_mday == 26); + assert(v2[2].tm_hour == 22); + assert(v2[2].tm_min == 45); + assert(v2[2].tm_sec == 37); + } + + // additional test for int (use const vector) + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + std::vector v; + v.push_back(-2000000000); + v.push_back(0); + v.push_back(1); + v.push_back(2000000000); + + std::vector const & cv = v; + + sql << "insert into soci_test(id) values(:i)", use(cv); + + std::vector v2(4); + + sql << "select id from soci_test order by id", into(v2); + assert(v2.size() == 4); + assert(v2[0] == -2000000000); + assert(v2[1] == 0); + assert(v2[2] == 1); + assert(v2[3] == 2000000000); + } + } + + std::cout << "test 8 passed" << std::endl; + +#endif // SOCI_POSTGRESQL_NOPARAMS + +} + +// test for named binding +void test9() +{ +// Not supported with older PostgreSQL +#ifndef SOCI_POSTGRESQL_NOPARAMS + + { + session sql(backEndFactory_, connectString_); + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + int i1 = 7; + int i2 = 8; + + // verify the exception is thrown if both by position + // and by name use elements are specified + try + { + sql << "insert into soci_test(i1, i2) values(:i1, :i2)", + use(i1, "i1"), use(i2); + + assert(false); + } + catch (soci_error const& e) + { + std::string what(e.what()); + assert(what == + "Binding for use elements must be either by position " + "or by name."); + } + + // normal test + sql << "insert into soci_test(i1, i2) values(:i1, :i2)", + use(i1, "i1"), use(i2, "i2"); + + i1 = 0; + i2 = 0; + sql << "select i1, i2 from soci_test", into(i1), into(i2); + assert(i1 == 7); + assert(i2 == 8); + + i2 = 0; + sql << "select i2 from soci_test where i1 = :i1", into(i2), use(i1); + assert(i2 == 8); + + sql << "delete from soci_test"; + + // test vectors + + std::vector v1; + v1.push_back(1); + v1.push_back(2); + v1.push_back(3); + + std::vector v2; + v2.push_back(4); + v2.push_back(5); + v2.push_back(6); + + sql << "insert into soci_test(i1, i2) values(:i1, :i2)", + use(v1, "i1"), use(v2, "i2"); + + sql << "select i2, i1 from soci_test order by i1 desc", + into(v1), into(v2); + assert(v1.size() == 3); + assert(v2.size() == 3); + assert(v1[0] == 6); + assert(v1[1] == 5); + assert(v1[2] == 4); + assert(v2[0] == 3); + assert(v2[1] == 2); + assert(v2[2] == 1); + } + } + + std::cout << "test 9 passed" << std::endl; + +#endif // SOCI_POSTGRESQL_NOPARAMS + +} + +// transaction test +void test10() +{ + { + session sql(backEndFactory_, connectString_); + + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + int count; + sql << "select count(*) from soci_test", into(count); + assert(count == 0); + + { + transaction tr(sql); + + sql << "insert into soci_test (id, name) values(1, 'John')"; + sql << "insert into soci_test (id, name) values(2, 'Anna')"; + sql << "insert into soci_test (id, name) values(3, 'Mike')"; + + tr.commit(); + } + { + transaction tr(sql); + + sql << "select count(*) from soci_test", into(count); + assert(count == 3); + + sql << "insert into soci_test (id, name) values(4, 'Stan')"; + + sql << "select count(*) from soci_test", into(count); + assert(count == 4); + + tr.rollback(); + + sql << "select count(*) from soci_test", into(count); + assert(count == 3); + } + { + transaction tr(sql); + + sql << "delete from soci_test"; + + sql << "select count(*) from soci_test", into(count); + assert(count == 0); + + tr.rollback(); + + sql << "select count(*) from soci_test", into(count); + assert(count == 3); + } + { + // additional test for detection of double commit + transaction tr(sql); + tr.commit(); + try + { + tr.commit(); + assert(false); + } + catch (soci_error const &e) + { + std::string msg = e.what(); + assert(msg == + "The transaction object cannot be handled twice."); + } + } + } + + std::cout << "test 10 passed" << std::endl; +} + +// test of use elements with indicators +void test11() +{ +#ifndef SOCI_POSTGRESQL_NOPARAMS + { + session sql(backEndFactory_, connectString_); + + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + indicator ind1 = i_ok; + indicator ind2 = i_ok; + + int id = 1; + int val = 10; + + sql << "insert into soci_test(id, val) values(:id, :val)", + use(id, ind1), use(val, ind2); + + id = 2; + val = 11; + ind2 = i_null; + sql << "insert into soci_test(id, val) values(:id, :val)", + use(id, ind1), use(val, ind2); + + sql << "select val from soci_test where id = 1", into(val, ind2); + assert(ind2 == i_ok); + assert(val == 10); + sql << "select val from soci_test where id = 2", into(val, ind2); + assert(ind2 == i_null); + + std::vector ids; + ids.push_back(3); + ids.push_back(4); + ids.push_back(5); + std::vector vals; + vals.push_back(12); + vals.push_back(13); + vals.push_back(14); + std::vector inds; + inds.push_back(i_ok); + inds.push_back(i_null); + inds.push_back(i_ok); + + sql << "insert into soci_test(id, val) values(:id, :val)", + use(ids), use(vals, inds); + + ids.resize(5); + vals.resize(5); + sql << "select id, val from soci_test order by id desc", + into(ids), into(vals, inds); + + assert(ids.size() == 5); + assert(ids[0] == 5); + assert(ids[1] == 4); + assert(ids[2] == 3); + assert(ids[3] == 2); + assert(ids[4] == 1); + assert(inds.size() == 5); + assert(inds[0] == i_ok); + assert(inds[1] == i_null); + assert(inds[2] == i_ok); + assert(inds[3] == i_null); + assert(inds[4] == i_ok); + assert(vals.size() == 5); + assert(vals[0] == 14); + assert(vals[2] == 12); + assert(vals[4] == 10); + } + + std::cout << "test 11 passed" << std::endl; + +#endif // SOCI_POSTGRESQL_NOPARAMS +} + +// Dynamic binding to Row objects +void test12() +{ + { + session sql(backEndFactory_, connectString_); + + sql.uppercase_column_names(true); + + auto_table_creator tableCreator(tc_.table_creator_2(sql)); + + row r; + sql << "select * from soci_test", into(r); + assert(sql.got_data() == false); + + sql << "insert into soci_test" + " values(3.14, 123, \'Johny\'," + << tc_.to_date_time("2005-12-19 22:14:17") + << ", 'a')"; + + // select into a row + { + row r; + statement st = (sql.prepare << + "select * from soci_test", into(r)); + st.execute(true); + assert(r.size() == 5); + + assert(r.get_properties(0).get_data_type() == dt_double); + assert(r.get_properties(1).get_data_type() == dt_integer); + assert(r.get_properties(2).get_data_type() == dt_string); + assert(r.get_properties(3).get_data_type() == dt_date); + + // type char is visible as string + // - to comply with the implementation for Oracle + assert(r.get_properties(4).get_data_type() == dt_string); + + assert(r.get_properties("NUM_INT").get_data_type() == dt_integer); + + assert(r.get_properties(0).get_name() == "NUM_FLOAT"); + assert(r.get_properties(1).get_name() == "NUM_INT"); + assert(r.get_properties(2).get_name() == "NAME"); + assert(r.get_properties(3).get_name() == "SOMETIME"); + assert(r.get_properties(4).get_name() == "CHR"); + + assert(equal_approx(r.get(0), 3.14)); + assert(r.get(1) == 123); + assert(r.get(2) == "Johny"); + std::tm t = { 0 }; + t = r.get(3); + assert(t.tm_year == 105); + + // again, type char is visible as string + assert(r.get(4) == "a"); + + assert(equal_approx(r.get("NUM_FLOAT"), 3.14)); + assert(r.get("NUM_INT") == 123); + assert(r.get("NAME") == "Johny"); + assert(r.get("CHR") == "a"); + + assert(r.get_indicator(0) == i_ok); + + // verify exception thrown on invalid get<> + bool caught = false; + try + { + r.get(0); + } + catch (std::bad_cast const &) + { + caught = true; + } + assert(caught); + + // additional test for stream-like extraction + { + double d; + int i; + std::string s; + std::tm t; + std::string c; + + r >> d >> i >> s >> t >> c; + + assert(equal_approx(d, 3.14)); + assert(i == 123); + assert(s == "Johny"); + assert(t.tm_year == 105); + assert(t.tm_mon == 11); + assert(t.tm_mday == 19); + assert(t.tm_hour == 22); + assert(t.tm_min == 14); + assert(t.tm_sec == 17); + assert(c == "a"); + } + } + + // additional test to check if the row object can be + // reused between queries + { + row r; + sql << "select * from soci_test", into(r); + + assert(r.size() == 5); + + assert(r.get_properties(0).get_data_type() == dt_double); + assert(r.get_properties(1).get_data_type() == dt_integer); + assert(r.get_properties(2).get_data_type() == dt_string); + assert(r.get_properties(3).get_data_type() == dt_date); + + sql << "select name, num_int from soci_test", into(r); + + assert(r.size() == 2); + + assert(r.get_properties(0).get_data_type() == dt_string); + assert(r.get_properties(1).get_data_type() == dt_integer); + } + } + + std::cout << "test 12 passed" << std::endl; +} + +// more dynamic bindings +void test13() +{ + session sql(backEndFactory_, connectString_); + + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + sql << "insert into soci_test(id, val) values(1, 10)"; + sql << "insert into soci_test(id, val) values(2, 20)"; + sql << "insert into soci_test(id, val) values(3, 30)"; + +#ifndef SOCI_POSTGRESQL_NOPARAMS + { + int id = 2; + row r; + sql << "select val from soci_test where id = :id", use(id), into(r); + + assert(r.size() == 1); + assert(r.get_properties(0).get_data_type() == dt_integer); + assert(r.get(0) == 20); + } + { + int id; + row r; + statement st = (sql.prepare << + "select val from soci_test where id = :id", use(id), into(r)); + + id = 2; + st.execute(true); + assert(r.size() == 1); + assert(r.get_properties(0).get_data_type() == dt_integer); + assert(r.get(0) == 20); + + id = 3; + st.execute(true); + assert(r.size() == 1); + assert(r.get_properties(0).get_data_type() == dt_integer); + assert(r.get(0) == 30); + + id = 1; + st.execute(true); + assert(r.size() == 1); + assert(r.get_properties(0).get_data_type() == dt_integer); + assert(r.get(0) == 10); + } +#else + { + row r; + sql << "select val from soci_test where id = 2", into(r); + + assert(r.size() == 1); + assert(r.get_properties(0).get_data_type() == dt_integer); + assert(r.get(0) == 20); + } +#endif // SOCI_POSTGRESQL_NOPARAMS + + std::cout << "test 13 passed" << std::endl; +} + +// More Dynamic binding to row objects +void test14() +{ + { + session sql(backEndFactory_, connectString_); + + sql.uppercase_column_names(true); + + auto_table_creator tableCreator(tc_.table_creator_3(sql)); + + row r1; + sql << "select * from soci_test", into(r1); + assert(sql.got_data() == false); + + sql << "insert into soci_test values('david', '(404)123-4567')"; + sql << "insert into soci_test values('john', '(404)123-4567')"; + sql << "insert into soci_test values('doe', '(404)123-4567')"; + + row r2; + statement st = (sql.prepare << "select * from soci_test", into(r2)); + st.execute(); + + assert(r2.size() == 2); + + int count = 0; + while (st.fetch()) + { + ++count; + assert(r2.get("PHONE") == "(404)123-4567"); + } + assert(count == 3); + } + std::cout << "test 14 passed" << std::endl; +} + +// test15 is like test14 but with a type_conversion instead of a row +void test15() +{ + session sql(backEndFactory_, connectString_); + + sql.uppercase_column_names(true); + + // simple conversion (between single basic type and user type) + + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + MyInt mi; + mi.set(123); + sql << "insert into soci_test(id) values(:id)", use(mi); + + int i; + sql << "select id from soci_test", into(i); + assert(i == 123); + + sql << "update soci_test set id = id + 1"; + + sql << "select id from soci_test", into(mi); + assert(mi.get() == 124); + } + + // simple conversion with use const + + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + MyInt mi; + mi.set(123); + + MyInt const & cmi = mi; + sql << "insert into soci_test(id) values(:id)", use(cmi); + + int i; + sql << "select id from soci_test", into(i); + assert(i == 123); + } + + // conversions based on values (many fields involved -> ORM) + + { + auto_table_creator tableCreator(tc_.table_creator_3(sql)); + + PhonebookEntry p1; + sql << "select * from soci_test", into(p1); + assert(p1.name == ""); + assert(p1.phone == ""); + + p1.name = "david"; + + // Note: uppercase column names are used here (and later on) + // for consistency with how they can be read from database + // (which means forced to uppercase on Oracle) and how they are + // set/get in the type conversion routines for PhonebookEntry. + // In short, IF the database is Oracle, + // then all column names for binding should be uppercase. + sql << "insert into soci_test values(:NAME, :PHONE)", use(p1); + sql << "insert into soci_test values('john', '(404)123-4567')"; + sql << "insert into soci_test values('doe', '(404)123-4567')"; + + PhonebookEntry p2; + statement st = (sql.prepare << "select * from soci_test", into(p2)); + st.execute(); + + int count = 0; + while (st.fetch()) + { + ++count; + if (p2.name == "david") + { + // see type_conversion + assert(p2.phone ==""); + } + else + { + assert(p2.phone == "(404)123-4567"); + } + } + assert(count == 3); + } + + // conversions based on values with use const + + { + auto_table_creator tableCreator(tc_.table_creator_3(sql)); + + PhonebookEntry p1; + p1.name = "Joe Coder"; + p1.phone = "123-456"; + + PhonebookEntry const & cp1 = p1; + + sql << "insert into soci_test values(:NAME, :PHONE)", use(cp1); + + PhonebookEntry p2; + sql << "select * from soci_test", into(p2); + assert(sql.got_data()); + + assert(p2.name == "Joe Coder"); + assert(p2.phone == "123-456"); + } + + // conversions based on accessor functions (as opposed to direct variable bindings) + + { + auto_table_creator tableCreator(tc_.table_creator_3(sql)); + + PhonebookEntry3 p1; + p1.setName("Joe Hacker"); + p1.setPhone("10010110"); + + sql << "insert into soci_test values(:NAME, :PHONE)", use(p1); + + PhonebookEntry3 p2; + sql << "select * from soci_test", into(p2); + assert(sql.got_data()); + + assert(p2.getName() == "Joe Hacker"); + assert(p2.getPhone() == "10010110"); + } + + { + // Use the PhonebookEntry2 type conversion, to test + // calls to values::get_indicator() + auto_table_creator tableCreator(tc_.table_creator_3(sql)); + + PhonebookEntry2 p1; + sql << "select * from soci_test", into(p1); + assert(p1.name == ""); + assert(p1.phone == ""); + p1.name = "david"; + + sql << "insert into soci_test values(:NAME, :PHONE)", use(p1); + sql << "insert into soci_test values('john', '(404)123-4567')"; + sql << "insert into soci_test values('doe', '(404)123-4567')"; + + PhonebookEntry2 p2; + statement st = (sql.prepare << "select * from soci_test", into(p2)); + st.execute(); + + int count = 0; + while (st.fetch()) + { + ++count; + if (p2.name == "david") + { + // see type_conversion + assert(p2.phone ==""); + } + else + { + assert(p2.phone == "(404)123-4567"); + } + } + assert(count == 3); + } + + std::cout << "test 15 passed" << std::endl; +} + +void test_prepared_insert_with_orm_type() +{ + { + session sql(backEndFactory_, connectString_); + + sql.uppercase_column_names(true); + auto_table_creator tableCreator(tc_.table_creator_3(sql)); + + PhonebookEntry temp; + PhonebookEntry e1 = { "name1", "phone1" }; + PhonebookEntry e2 = { "name2", "phone2" }; + + //sql << "insert into soci_test values (:NAME, :PHONE)", use(temp); + statement insertStatement = (sql.prepare << "insert into soci_test values (:NAME, :PHONE)", use(temp)); + + temp = e1; + insertStatement.execute(true); + temp = e2; + insertStatement.execute(true); + + int count = 0; + + sql << "select count(*) from soci_test where NAME in ('name1', 'name2')", into(count); + + assert(count == 2); + } + + std::cout << "test test_prepared_insert_with_orm_type passed" << std::endl; +} + +void test_placeholder_partial_matching_with_orm_type() +{ + { + session sql(backEndFactory_, connectString_); + sql.uppercase_column_names(true); + auto_table_creator tableCreator(tc_.table_creator_3(sql)); + + PhonebookEntry in = { "name1", "phone1" }; + std::string name = "nameA"; + sql << "insert into soci_test values (:NAMED, :PHONE)", use(in), use(name, "NAMED"); + + PhonebookEntry out; + sql << "select * from soci_test where PHONE = 'phone1'", into(out); + assert(out.name == "nameA"); + assert(out.phone == "phone1"); + } + + std::cout << "test test_placeholder_partial_matching_with_orm_type passed" << std::endl; +} + +// test for bulk fetch with single use +void test16() +{ +#ifndef SOCI_POSTGRESQL_NOPARAMS + { + session sql(backEndFactory_, connectString_); + + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + sql << "insert into soci_test(name, id) values('john', 1)"; + sql << "insert into soci_test(name, id) values('george', 2)"; + sql << "insert into soci_test(name, id) values('anthony', 1)"; + sql << "insert into soci_test(name, id) values('marc', 3)"; + sql << "insert into soci_test(name, id) values('julian', 1)"; + + int code = 1; + std::vector names(10); + sql << "select name from soci_test where id = :id order by name", + into(names), use(code); + + assert(names.size() == 3); + assert(names[0] == "anthony"); + assert(names[1] == "john"); + assert(names[2] == "julian"); + } +#endif // SOCI_POSTGRESQL_NOPARAMS + + std::cout << "test 16 passed" << std::endl; +} + +// test for basic logging support +void test17() +{ + session sql(backEndFactory_, connectString_); + + std::ostringstream log; + sql.set_log_stream(&log); + + try + { + sql << "drop table soci_test1"; + } + catch (...) {} + + assert(sql.get_last_query() == "drop table soci_test1"); + + sql.set_log_stream(NULL); + + try + { + sql << "drop table soci_test2"; + } + catch (...) {} + + assert(sql.get_last_query() == "drop table soci_test2"); + + sql.set_log_stream(&log); + + try + { + sql << "drop table soci_test3"; + } + catch (...) {} + + assert(sql.get_last_query() == "drop table soci_test3"); + assert(log.str() == + "drop table soci_test1\n" + "drop table soci_test3\n"); + + std::cout << "test 17 passed\n"; +} + +// test for rowset creation and copying +void test18() +{ + session sql(backEndFactory_, connectString_); + + // create and populate the test table + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + { + // Open empty rowset + rowset rs1 = (sql.prepare << "select * from soci_test"); + assert(rs1.begin() == rs1.end()); + } + + { + // Copy construction + rowset rs1 = (sql.prepare << "select * from soci_test"); + rowset rs2(rs1); + rowset rs3(rs1); + rowset rs4(rs3); + + assert(rs1.begin() == rs2.begin()); + assert(rs1.begin() == rs3.begin()); + assert(rs1.end() == rs2.end()); + assert(rs1.end() == rs3.end()); + } + + { + // Assignment + rowset rs1 = (sql.prepare << "select * from soci_test"); + rowset rs2 = (sql.prepare << "select * from soci_test"); + rowset rs3 = (sql.prepare << "select * from soci_test"); + rs1 = rs2; + rs3 = rs2; + + assert(rs1.begin() == rs2.begin()); + assert(rs1.begin() == rs3.begin()); + assert(rs1.end() == rs2.end()); + assert(rs1.end() == rs3.end()); + } + std::cout << "test 18 passed" << std::endl; +} + +// test for simple iterating using rowset iterator (without reading data) +void test19() +{ + session sql(backEndFactory_, connectString_); + + // create and populate the test table + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + { + sql << "insert into soci_test(id, val) values(1, 10)"; + sql << "insert into soci_test(id, val) values(2, 11)"; + sql << "insert into soci_test(id, val) values(3, NULL)"; + sql << "insert into soci_test(id, val) values(4, NULL)"; + sql << "insert into soci_test(id, val) values(5, 12)"; + { + rowset rs = (sql.prepare << "select * from soci_test"); + + assert(5 == std::distance(rs.begin(), rs.end())); + } + } + + std::cout << "test 19 passed" << std::endl; +} + +// test for reading rowset using iterator +void test20() +{ + session sql(backEndFactory_, connectString_); + + sql.uppercase_column_names(true); + + // create and populate the test table + auto_table_creator tableCreator(tc_.table_creator_2(sql)); + { + { + // Empty rowset + rowset rs = (sql.prepare << "select * from soci_test"); + assert(0 == std::distance(rs.begin(), rs.end())); + } + + { + // Non-empty rowset + sql << "insert into soci_test values(3.14, 123, \'Johny\'," + << tc_.to_date_time("2005-12-19 22:14:17") + << ", 'a')"; + sql << "insert into soci_test values(6.28, 246, \'Robert\'," + << tc_.to_date_time("2004-10-01 18:44:10") + << ", 'b')"; + + rowset rs = (sql.prepare << "select * from soci_test"); + + rowset::const_iterator it = rs.begin(); + assert(it != rs.end()); + + // + // First row + // + row const & r1 = (*it); + + // Properties + assert(r1.size() == 5); + assert(r1.get_properties(0).get_data_type() == dt_double); + assert(r1.get_properties(1).get_data_type() == dt_integer); + assert(r1.get_properties(2).get_data_type() == dt_string); + assert(r1.get_properties(3).get_data_type() == dt_date); + assert(r1.get_properties(4).get_data_type() == dt_string); + assert(r1.get_properties("NUM_INT").get_data_type() == dt_integer); + + // Data + + // Since we didn't specify order by in the above query, + // the 2 rows may be returned in either order + // (If we specify order by, we can't do it in a cross db + // compatible way, because the Oracle table for this has been + // created with lower case column names) + + std::string name = r1.get(2); + + assert(name == "Johny" || name == "Robert"); + if (name == "Johny") + { + assert(equal_approx(r1.get(0), 3.14)); + assert(r1.get(1) == 123); + assert(r1.get(2) == "Johny"); + std::tm t1 = { 0 }; + t1 = r1.get(3); + assert(t1.tm_year == 105); + assert(r1.get(4) == "a"); + assert(equal_approx(r1.get("NUM_FLOAT"), 3.14)); + assert(r1.get("NUM_INT") == 123); + assert(r1.get("NAME") == "Johny"); + assert(r1.get("CHR") == "a"); + } + else + { + assert(equal_approx(r1.get(0), 6.28)); + assert(r1.get(1) == 246); + assert(r1.get(2) == "Robert"); + std::tm t1 = r1.get(3); + assert(t1.tm_year == 104); + assert(r1.get(4) == "b"); + assert(equal_approx(r1.get("NUM_FLOAT"), 6.28)); + assert(r1.get("NUM_INT") == 246); + assert(r1.get("NAME") == "Robert"); + assert(r1.get("CHR") == "b"); + } + + // + // Iterate to second row + // + ++it; + assert(it != rs.end()); + + // + // Second row + // + row const & r2 = (*it); + + // Properties + assert(r2.size() == 5); + assert(r2.get_properties(0).get_data_type() == dt_double); + assert(r2.get_properties(1).get_data_type() == dt_integer); + assert(r2.get_properties(2).get_data_type() == dt_string); + assert(r2.get_properties(3).get_data_type() == dt_date); + assert(r2.get_properties(4).get_data_type() == dt_string); + assert(r2.get_properties("NUM_INT").get_data_type() == dt_integer); + + std::string newName = r2.get(2); + assert(name != newName); + assert(newName == "Johny" || newName == "Robert"); + + if (newName == "Johny") + { + assert(equal_approx(r2.get(0), 3.14)); + assert(r2.get(1) == 123); + assert(r2.get(2) == "Johny"); + std::tm t2 = r2.get(3); + assert(t2.tm_year == 105); + assert(r2.get(4) == "a"); + assert(equal_approx(r2.get("NUM_FLOAT"), 3.14)); + assert(r2.get("NUM_INT") == 123); + assert(r2.get("NAME") == "Johny"); + assert(r2.get("CHR") == "a"); + } + else + { + assert(equal_approx(r2.get(0), 6.28)); + assert(r2.get(1) == 246); + assert(r2.get(2) == "Robert"); + std::tm t2 = r2.get(3); + assert(t2.tm_year == 104); + assert(r2.get(4) == "b"); + assert(equal_approx(r2.get("NUM_FLOAT"), 6.28)); + assert(r2.get("NUM_INT") == 246); + assert(r2.get("NAME") == "Robert"); + assert(r2.get("CHR") == "b"); + } + } + + { + // Non-empty rowset with NULL values + sql << "insert into soci_test " + << "(num_int, num_float , name, sometime, chr) " + << "values (0, NULL, NULL, NULL, NULL)"; + + rowset rs = (sql.prepare + << "select num_int, num_float, name, sometime, chr " + << "from soci_test where num_int = 0"); + + rowset::const_iterator it = rs.begin(); + assert(it != rs.end()); + + // + // First row + // + row const& r1 = (*it); + + // Properties + assert(r1.size() == 5); + assert(r1.get_properties(0).get_data_type() == dt_integer); + assert(r1.get_properties(1).get_data_type() == dt_double); + assert(r1.get_properties(2).get_data_type() == dt_string); + assert(r1.get_properties(3).get_data_type() == dt_date); + assert(r1.get_properties(4).get_data_type() == dt_string); + + // Data + assert(r1.get_indicator(0) == soci::i_ok); + assert(r1.get(0) == 0); + assert(r1.get_indicator(1) == soci::i_null); + assert(r1.get_indicator(2) == soci::i_null); + assert(r1.get_indicator(3) == soci::i_null); + assert(r1.get_indicator(4) == soci::i_null); + } + } + + std::cout << "test 20 passed" << std::endl; +} + +// test for reading rowset using iterator +void test21() +{ + session sql(backEndFactory_, connectString_); + + // create and populate the test table + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + { + sql << "insert into soci_test(id) values(1)"; + sql << "insert into soci_test(id) values(2)"; + sql << "insert into soci_test(id) values(3)"; + sql << "insert into soci_test(id) values(4)"; + sql << "insert into soci_test(id) values(5)"; + { + rowset rs = (sql.prepare << "select id from soci_test order by id asc"); + + // 1st row + rowset::const_iterator pos = rs.begin(); + assert(1 == (*pos)); + + // 3rd row + std::advance(pos, 2); + assert(3 == (*pos)); + + // 5th row + std::advance(pos, 2); + assert(5 == (*pos)); + + // The End + ++pos; + assert(pos == rs.end()); + } + } + + std::cout << "test 21 passed" << std::endl; +} + +// test for handling 'use' and reading rowset using iterator +void test22() +{ + session sql(backEndFactory_, connectString_); + + // create and populate the test table + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + { + sql << "insert into soci_test(str) values('abc')"; + sql << "insert into soci_test(str) values('def')"; + sql << "insert into soci_test(str) values('ghi')"; + sql << "insert into soci_test(str) values('jkl')"; + { + // Expected result in numbers + std::string idle("def"); + rowset rs1 = (sql.prepare + << "select str from soci_test where str = :idle", + use(idle)); + + assert(1 == std::distance(rs1.begin(), rs1.end())); + + // Expected result in value + idle = "jkl"; + rowset rs2 = (sql.prepare + << "select str from soci_test where str = :idle", + use(idle)); + + assert(idle == *(rs2.begin())); + } + } + + std::cout << "test 22 passed" << std::endl; +} + +// test for handling troublemaker +void test23() +{ + session sql(backEndFactory_, connectString_); + + // create and populate the test table + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + { + sql << "insert into soci_test(str) values('abc')"; + { + // verify exception thrown + bool caught = false; + try + { + std::string troublemaker; + rowset rs1 = (sql.prepare << "select str from soci_test", + into(troublemaker)); + } + catch (soci_error const&) + { + caught = true; + } + assert(caught); + } + std::cout << "test 23 passed" << std::endl; + } + +} + +// test for handling NULL values with expected exception: +// "Null value fetched and no indicator defined." +void test24() +{ + session sql(backEndFactory_, connectString_); + + // create and populate the test table + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + { + sql << "insert into soci_test(val) values(1)"; + sql << "insert into soci_test(val) values(2)"; + sql << "insert into soci_test(val) values(NULL)"; + sql << "insert into soci_test(val) values(3)"; + { + // verify exception thrown + bool caught = false; + try + { + rowset rs = (sql.prepare << "select val from soci_test order by val asc"); + + int tester = 0; + for (rowset::const_iterator it = rs.begin(); it != rs.end(); ++it) + { + tester = *it; + } + (void)tester; + + // Never should get here + assert(false); + } + catch (soci_error const&) + { + caught = true; + } + assert(caught); + } + std::cout << "test 24 passed" << std::endl; + } +} + +// test25 is like test15 but with rowset and iterators use +void test25() +{ + session sql(backEndFactory_, connectString_); + + sql.uppercase_column_names(true); + + { + auto_table_creator tableCreator(tc_.table_creator_3(sql)); + + PhonebookEntry p1; + sql << "select * from soci_test", into(p1); + assert(p1.name == ""); + assert(p1.phone == ""); + + p1.name = "david"; + + sql << "insert into soci_test values(:NAME, :PHONE)", use(p1); + sql << "insert into soci_test values('john', '(404)123-4567')"; + sql << "insert into soci_test values('doe', '(404)123-4567')"; + + rowset rs = (sql.prepare << "select * from soci_test"); + + int count = 0; + for (rowset::const_iterator it = rs.begin(); it != rs.end(); ++it) + { + ++count; + PhonebookEntry const& p2 = (*it); + if (p2.name == "david") + { + // see type_conversion + assert(p2.phone ==""); + } + else + { + assert(p2.phone == "(404)123-4567"); + } + } + + assert(3 == count); + } + std::cout << "test 25 passed" << std::endl; +} + +// test for handling NULL values with boost::optional +// (both into and use) +void test26() +{ +#ifdef HAVE_BOOST + + session sql(backEndFactory_, connectString_); + + // create and populate the test table + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + { + sql << "insert into soci_test(val) values(7)"; + + { + // verify non-null value is fetched correctly + boost::optional opt; + sql << "select val from soci_test", into(opt); + assert(opt.is_initialized()); + assert(opt.get() == 7); + + // indicators can be used with optional + // (although that's just a consequence of implementation, + // not an intended feature - but let's test it anyway) + indicator ind; + opt.reset(); + sql << "select val from soci_test", into(opt, ind); + assert(opt.is_initialized()); + assert(opt.get() == 7); + assert(ind == i_ok); + + // verify null value is fetched correctly + sql << "select i1 from soci_test", into(opt); + assert(opt.is_initialized() == false); + + // and with indicator + opt = 5; + sql << "select i1 from soci_test", into(opt, ind); + assert(opt.is_initialized() == false); + assert(ind == i_null); + + // verify non-null is inserted correctly + opt = 3; + sql << "update soci_test set val = :v", use(opt); + int j = 0; + sql << "select val from soci_test", into(j); + assert(j == 3); + + // verify null is inserted correctly + opt.reset(); + sql << "update soci_test set val = :v", use(opt); + ind = i_ok; + sql << "select val from soci_test", into(j, ind); + assert(ind == i_null); + } + + // vector tests (select) + + { + sql << "delete from soci_test"; + + // simple readout of non-null data + + sql << "insert into soci_test(id, val, str) values(1, 5, \'abc\')"; + sql << "insert into soci_test(id, val, str) values(2, 6, \'def\')"; + sql << "insert into soci_test(id, val, str) values(3, 7, \'ghi\')"; + sql << "insert into soci_test(id, val, str) values(4, 8, null)"; + sql << "insert into soci_test(id, val, str) values(5, 9, \'mno\')"; + + std::vector > v(10); + sql << "select val from soci_test order by val", into(v); + + assert(v.size() == 5); + assert(v[0].is_initialized()); + assert(v[0].get() == 5); + assert(v[1].is_initialized()); + assert(v[1].get() == 6); + assert(v[2].is_initialized()); + assert(v[2].get() == 7); + assert(v[3].is_initialized()); + assert(v[3].get() == 8); + assert(v[4].is_initialized()); + assert(v[4].get() == 9); + + // readout of nulls + + sql << "update soci_test set val = null where id = 2 or id = 4"; + + std::vector ids(5); + sql << "select id, val from soci_test order by id", into(ids), into(v); + + assert(v.size() == 5); + assert(ids.size() == 5); + assert(v[0].is_initialized()); + assert(v[0].get() == 5); + assert(v[1].is_initialized() == false); + assert(v[2].is_initialized()); + assert(v[2].get() == 7); + assert(v[3].is_initialized() == false); + assert(v[4].is_initialized()); + assert(v[4].get() == 9); + + // readout with statement preparation + + int id = 1; + + ids.resize(3); + v.resize(3); + statement st = (sql.prepare << + "select id, val from soci_test order by id", into(ids), into(v)); + st.execute(); + while (st.fetch()) + { + for (std::size_t i = 0; i != v.size(); ++i) + { + assert(id == ids[i]); + + if (id == 2 || id == 4) + { + assert(v[i].is_initialized() == false); + } + else + { + assert(v[i].is_initialized() && v[i].get() == id + 4); + } + + ++id; + } + + ids.resize(3); + v.resize(3); + } + assert(id == 6); + } + + // and why not stress iterators and the dynamic binding, too! + + { + rowset rs = (sql.prepare << "select id, val, str from soci_test order by id"); + + rowset::const_iterator it = rs.begin(); + assert(it != rs.end()); + + row const& r1 = (*it); + + assert(r1.size() == 3); + + // Note: for the reason of differences between number(x,y) type and + // binary representation of integers, the following commented assertions + // do not work for Oracle. + // The problem is that for this single table the data type used in Oracle + // table creator for the id column is number(10,0), + // which allows to insert all int values. + // On the other hand, the column description scheme used in the Oracle + // backend figures out that the natural type for such a column + // is eUnsignedInt - this makes the following assertions fail. + // Other database backends (like PostgreSQL) use other types like int + // and this not only allows to insert all int values (obviously), + // but is also recognized as int (obviously). + // There is a similar problem with stream-like extraction, + // where internally get is called and the type mismatch is detected + // for the id column - that's why the code below skips this column + // and tests the remaining column only. + + //assert(r1.get_properties(0).get_data_type() == dt_integer); + assert(r1.get_properties(1).get_data_type() == dt_integer); + assert(r1.get_properties(2).get_data_type() == dt_string); + //assert(r1.get(0) == 1); + assert(r1.get(1) == 5); + assert(r1.get(2) == "abc"); + assert(r1.get >(1).is_initialized()); + assert(r1.get >(1).get() == 5); + assert(r1.get >(2).is_initialized()); + assert(r1.get >(2).get() == "abc"); + + ++it; + + row const& r2 = (*it); + + assert(r2.size() == 3); + + // assert(r2.get_properties(0).get_data_type() == dt_integer); + assert(r2.get_properties(1).get_data_type() == dt_integer); + assert(r2.get_properties(2).get_data_type() == dt_string); + //assert(r2.get(0) == 2); + try + { + // expect exception here, this is NULL value + (void)r1.get(1); + assert(false); + } + catch (soci_error const &) {} + + // but we can read it as optional + assert(r2.get >(1).is_initialized() == false); + + // stream-like data extraction + + ++it; + row const &r3 = (*it); + + boost::optional io; + boost::optional so; + + r3.skip(); // move to val and str columns + r3 >> io >> so; + + assert(io.is_initialized() && io.get() == 7); + assert(so.is_initialized() && so.get() == "ghi"); + + ++it; + row const &r4 = (*it); + + r3.skip(); // move to val and str columns + r4 >> io >> so; + + assert(io.is_initialized() == false); + assert(so.is_initialized() == false); + } + + // bulk inserts of non-null data + + { + sql << "delete from soci_test"; + + std::vector ids; + std::vector > v; + + ids.push_back(10); v.push_back(20); + ids.push_back(11); v.push_back(21); + ids.push_back(12); v.push_back(22); + ids.push_back(13); v.push_back(23); + + sql << "insert into soci_test(id, val) values(:id, :val)", + use(ids, "id"), use(v, "val"); + + int sum; + sql << "select sum(val) from soci_test", into(sum); + assert(sum == 86); + + // bulk inserts of some-null data + + sql << "delete from soci_test"; + + v[2].reset(); + v[3].reset(); + + sql << "insert into soci_test(id, val) values(:id, :val)", + use(ids, "id"), use(v, "val"); + + sql << "select sum(val) from soci_test", into(sum); + assert(sum == 41); + } + + // composability with user conversions + + { + sql << "delete from soci_test"; + + boost::optional omi1; + boost::optional omi2; + + omi1 = MyInt(125); + omi2.reset(); + + sql << "insert into soci_test(id, val) values(:id, :val)", + use(omi1), use(omi2); + + sql << "select id, val from soci_test", into(omi2), into(omi1); + + assert(omi1.is_initialized() == false); + assert(omi2.is_initialized() && omi2.get().get() == 125); + } + + // use with const optional and user conversions + + { + sql << "delete from soci_test"; + + boost::optional omi1; + boost::optional omi2; + + omi1 = MyInt(125); + omi2.reset(); + + boost::optional const & comi1 = omi1; + boost::optional const & comi2 = omi2; + + sql << "insert into soci_test(id, val) values(:id, :val)", + use(comi1), use(comi2); + + sql << "select id, val from soci_test", into(omi2), into(omi1); + + assert(omi1.is_initialized() == false); + assert(omi2.is_initialized() && omi2.get().get() == 125); + } + + // use with rowset and table containing null values + + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + sql << "insert into soci_test(id, val) values(1, 10)"; + sql << "insert into soci_test(id, val) values(2, 11)"; + sql << "insert into soci_test(id, val) values(3, NULL)"; + sql << "insert into soci_test(id, val) values(4, 13)"; + + rowset > rs = (sql.prepare << + "select val from soci_test order by id asc"); + + // 1st row + rowset >::const_iterator pos = rs.begin(); + assert((*pos).is_initialized()); + assert(10 == (*pos).get()); + + // 2nd row + ++pos; + assert((*pos).is_initialized()); + assert(11 == (*pos).get()); + + // 3rd row + ++pos; + assert((*pos).is_initialized() == false); + + // 4th row + ++pos; + assert((*pos).is_initialized()); + assert(13 == (*pos).get()); + } + } + + std::cout << "test 26 passed" << std::endl; +#else + std::cout << "test 26 skipped (no Boost)" << std::endl; +#endif // HAVE_BOOST +} + +// connection and reconnection tests +void test27() +{ + { + // empty session + session sql; + + // idempotent: + sql.close(); + + try + { + sql.reconnect(); + assert(false); + } + catch (soci_error const &e) + { + assert(e.what() == std::string( + "Cannot reconnect without previous connection.")); + } + + // open from empty session + sql.open(backEndFactory_, connectString_); + sql.close(); + + // reconnecting from closed session + sql.reconnect(); + + // opening already connected session + try + { + sql.open(backEndFactory_, connectString_); + assert(false); + } + catch (soci_error const &e) + { + assert(e.what() == std::string( + "Cannot open already connected session.")); + } + + sql.close(); + + // open from closed + sql.open(backEndFactory_, connectString_); + + // reconnect from already connected session + sql.reconnect(); + } + + { + session sql; + + try + { + sql << "this statement cannot execute"; + assert(false); + } + catch (soci_error const &e) + { + assert(e.what() == std::string("Session is not connected.")); + } + } + + std::cout << "test 27 passed" << std::endl; +} + +void test28() +{ +#ifdef HAVE_BOOST + session sql(backEndFactory_, connectString_); + + auto_table_creator tableCreator(tc_.table_creator_2(sql)); + { + boost::tuple t1(3.5, 7, "Joe Hacker"); + assert(equal_approx(t1.get<0>(), 3.5)); + assert(t1.get<1>() == 7); + assert(t1.get<2>() == "Joe Hacker"); + + sql << "insert into soci_test(num_float, num_int, name) values(:d, :i, :s)", use(t1); + + // basic query + + boost::tuple t2; + sql << "select num_float, num_int, name from soci_test", into(t2); + + assert(equal_approx(t2.get<0>(), 3.5)); + assert(t2.get<1>() == 7); + assert(t2.get<2>() == "Joe Hacker"); + + sql << "delete from soci_test"; + } + + { + // composability with boost::optional + + // use: + boost::tuple, std::string> t1( + 3.5, boost::optional(7), "Joe Hacker"); + assert(equal_approx(t1.get<0>(), 3.5)); + assert(t1.get<1>().is_initialized()); + assert(t1.get<1>().get() == 7); + assert(t1.get<2>() == "Joe Hacker"); + + sql << "insert into soci_test(num_float, num_int, name) values(:d, :i, :s)", use(t1); + + // into: + boost::tuple, std::string> t2; + sql << "select num_float, num_int, name from soci_test", into(t2); + + assert(equal_approx(t2.get<0>(), 3.5)); + assert(t2.get<1>().is_initialized()); + assert(t2.get<1>().get() == 7); + assert(t2.get<2>() == "Joe Hacker"); + + sql << "delete from soci_test"; + } + + { + // composability with user-provided conversions + + // use: + boost::tuple t1(3.5, 7, "Joe Hacker"); + assert(equal_approx(t1.get<0>(), 3.5)); + assert(t1.get<1>().get() == 7); + assert(t1.get<2>() == "Joe Hacker"); + + sql << "insert into soci_test(num_float, num_int, name) values(:d, :i, :s)", use(t1); + + // into: + boost::tuple t2; + + sql << "select num_float, num_int, name from soci_test", into(t2); + + assert(equal_approx(t2.get<0>(), 3.5)); + assert(t2.get<1>().get() == 7); + assert(t2.get<2>() == "Joe Hacker"); + + sql << "delete from soci_test"; + } + + { + // let's have fun - composition of tuple, optional and user-defined type + + // use: + boost::tuple, std::string> t1( + 3.5, boost::optional(7), "Joe Hacker"); + assert(equal_approx(t1.get<0>(), 3.5)); + assert(t1.get<1>().is_initialized()); + assert(t1.get<1>().get().get() == 7); + assert(t1.get<2>() == "Joe Hacker"); + + sql << "insert into soci_test(num_float, num_int, name) values(:d, :i, :s)", use(t1); + + // into: + boost::tuple, std::string> t2; + + sql << "select num_float, num_int, name from soci_test", into(t2); + + assert(equal_approx(t2.get<0>(), 3.5)); + assert(t2.get<1>().is_initialized()); + assert(t2.get<1>().get().get() == 7); + assert(t2.get<2>() == "Joe Hacker"); + + sql << "update soci_test set num_int = NULL"; + + sql << "select num_float, num_int, name from soci_test", into(t2); + + assert(equal_approx(t2.get<0>(), 3.5)); + assert(t2.get<1>().is_initialized() == false); + assert(t2.get<2>() == "Joe Hacker"); + } + + { + // rowset + + sql << "insert into soci_test(num_float, num_int, name) values(4.0, 8, 'Tony Coder')"; + sql << "insert into soci_test(num_float, num_int, name) values(4.5, NULL, 'Cecile Sharp')"; + sql << "insert into soci_test(num_float, num_int, name) values(5.0, 10, 'Djhava Ravaa')"; + + typedef boost::tuple, std::string> T; + + rowset rs = (sql.prepare + << "select num_float, num_int, name from soci_test order by num_float asc"); + + rowset::const_iterator pos = rs.begin(); + + assert(equal_approx(pos->get<0>(), 3.5)); + assert(pos->get<1>().is_initialized() == false); + assert(pos->get<2>() == "Joe Hacker"); + + ++pos; + assert(equal_approx(pos->get<0>(), 4.0)); + assert(pos->get<1>().is_initialized()); + assert(pos->get<1>().get() == 8); + assert(pos->get<2>() == "Tony Coder"); + + ++pos; + assert(equal_approx(pos->get<0>(), 4.5)); + assert(pos->get<1>().is_initialized() == false); + assert(pos->get<2>() == "Cecile Sharp"); + + ++pos; + assert(equal_approx(pos->get<0>(), 5.0)); + assert(pos->get<1>().is_initialized()); + assert(pos->get<1>().get() == 10); + assert(pos->get<2>() == "Djhava Ravaa"); + + ++pos; + assert(pos == rs.end()); + } + + std::cout << "test 28 passed" << std::endl; +#else + std::cout << "test 28 skipped (no Boost)" << std::endl; +#endif // HAVE_BOOST +} + +void test29() +{ +#ifdef HAVE_BOOST +#if defined(BOOST_VERSION) && BOOST_VERSION >= 103500 + + session sql(backEndFactory_, connectString_); + + auto_table_creator tableCreator(tc_.table_creator_2(sql)); + { + boost::fusion::vector t1(3.5, 7, "Joe Hacker"); + assert(equal_approx(boost::fusion::at_c<0>(t1), 3.5)); + assert(boost::fusion::at_c<1>(t1) == 7); + assert(boost::fusion::at_c<2>(t1) == "Joe Hacker"); + + sql << "insert into soci_test(num_float, num_int, name) values(:d, :i, :s)", use(t1); + + // basic query + + boost::fusion::vector t2; + sql << "select num_float, num_int, name from soci_test", into(t2); + + assert(equal_approx(boost::fusion::at_c<0>(t2), 3.5)); + assert(boost::fusion::at_c<1>(t2) == 7); + assert(boost::fusion::at_c<2>(t2) == "Joe Hacker"); + + sql << "delete from soci_test"; + } + + { + // composability with boost::optional + + // use: + boost::fusion::vector, std::string> t1( + 3.5, boost::optional(7), "Joe Hacker"); + assert(equal_approx(boost::fusion::at_c<0>(t1), 3.5)); + assert(boost::fusion::at_c<1>(t1).is_initialized()); + assert(boost::fusion::at_c<1>(t1).get() == 7); + assert(boost::fusion::at_c<2>(t1) == "Joe Hacker"); + + sql << "insert into soci_test(num_float, num_int, name) values(:d, :i, :s)", use(t1); + + // into: + boost::fusion::vector, std::string> t2; + sql << "select num_float, num_int, name from soci_test", into(t2); + + assert(equal_approx(boost::fusion::at_c<0>(t2), 3.5)); + assert(boost::fusion::at_c<1>(t2).is_initialized()); + assert(boost::fusion::at_c<1>(t2) == 7); + assert(boost::fusion::at_c<2>(t2) == "Joe Hacker"); + + sql << "delete from soci_test"; + } + + { + // composability with user-provided conversions + + // use: + boost::fusion::vector t1(3.5, 7, "Joe Hacker"); + assert(equal_approx(boost::fusion::at_c<0>(t1), 3.5)); + assert(boost::fusion::at_c<1>(t1).get() == 7); + assert(boost::fusion::at_c<2>(t1) == "Joe Hacker"); + + sql << "insert into soci_test(num_float, num_int, name) values(:d, :i, :s)", use(t1); + + // into: + boost::fusion::vector t2; + + sql << "select num_float, num_int, name from soci_test", into(t2); + + assert(equal_approx(boost::fusion::at_c<0>(t2), 3.5)); + assert(boost::fusion::at_c<1>(t2).get() == 7); + assert(boost::fusion::at_c<2>(t2) == "Joe Hacker"); + + sql << "delete from soci_test"; + } + + { + // let's have fun - composition of tuple, optional and user-defined type + + // use: + boost::fusion::vector, std::string> t1( + 3.5, boost::optional(7), "Joe Hacker"); + assert(equal_approx(boost::fusion::at_c<0>(t1), 3.5)); + assert(boost::fusion::at_c<1>(t1).is_initialized()); + assert(boost::fusion::at_c<1>(t1).get().get() == 7); + assert(boost::fusion::at_c<2>(t1) == "Joe Hacker"); + + sql << "insert into soci_test(num_float, num_int, name) values(:d, :i, :s)", use(t1); + + // into: + boost::fusion::vector, std::string> t2; + + sql << "select num_float, num_int, name from soci_test", into(t2); + + assert(equal_approx(boost::fusion::at_c<0>(t2), 3.5)); + assert(boost::fusion::at_c<1>(t2).is_initialized()); + assert(boost::fusion::at_c<1>(t2).get().get() == 7); + assert(boost::fusion::at_c<2>(t2) == "Joe Hacker"); + + sql << "update soci_test set num_int = NULL"; + + sql << "select num_float, num_int, name from soci_test", into(t2); + + assert(equal_approx(boost::fusion::at_c<0>(t2), 3.5)); + assert(boost::fusion::at_c<1>(t2).is_initialized() == false); + assert(boost::fusion::at_c<2>(t2) == "Joe Hacker"); + } + + { + // rowset + + sql << "insert into soci_test(num_float, num_int, name) values(4.0, 8, 'Tony Coder')"; + sql << "insert into soci_test(num_float, num_int, name) values(4.5, NULL, 'Cecile Sharp')"; + sql << "insert into soci_test(num_float, num_int, name) values(5.0, 10, 'Djhava Ravaa')"; + + typedef boost::fusion::vector, std::string> T; + + rowset rs = (sql.prepare + << "select num_float, num_int, name from soci_test order by num_float asc"); + + rowset::const_iterator pos = rs.begin(); + + assert(equal_approx(boost::fusion::at_c<0>(*pos), 3.5)); + assert(boost::fusion::at_c<1>(*pos).is_initialized() == false); + assert(boost::fusion::at_c<2>(*pos) == "Joe Hacker"); + + ++pos; + assert(equal_approx(boost::fusion::at_c<0>(*pos), 4.0)); + assert(boost::fusion::at_c<1>(*pos).is_initialized()); + assert(boost::fusion::at_c<1>(*pos).get() == 8); + assert(boost::fusion::at_c<2>(*pos) == "Tony Coder"); + + ++pos; + assert(equal_approx(boost::fusion::at_c<0>(*pos), 4.5)); + assert(boost::fusion::at_c<1>(*pos).is_initialized() == false); + assert(boost::fusion::at_c<2>(*pos) == "Cecile Sharp"); + + ++pos; + assert(equal_approx(boost::fusion::at_c<0>(*pos), 5.0)); + assert(boost::fusion::at_c<1>(*pos).is_initialized()); + assert(boost::fusion::at_c<1>(*pos).get() == 10); + assert(boost::fusion::at_c<2>(*pos) == "Djhava Ravaa"); + + ++pos; + assert(pos == rs.end()); + } + + std::cout << "test 29 passed" << std::endl; + +#else + std::cout << "test 29 skipped (no boost::fusion)" << std::endl; +#endif // BOOST_VERSION + +#else + std::cout << "test 29 skipped (no Boost)" << std::endl; +#endif // HAVE_BOOST +} + +// test for boost::gregorian::date +void test30() +{ +#ifdef HAVE_BOOST + + session sql(backEndFactory_, connectString_); + + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + std::tm nov15; + nov15.tm_year = 105; + nov15.tm_mon = 10; + nov15.tm_mday = 15; + nov15.tm_hour = 0; + nov15.tm_min = 0; + nov15.tm_sec = 0; + + sql << "insert into soci_test(tm) values(:tm)", use(nov15); + + boost::gregorian::date bgd; + sql << "select tm from soci_test", into(bgd); + + assert(bgd.year() == 2005); + assert(bgd.month() == 11); + assert(bgd.day() == 15); + + sql << "update soci_test set tm = NULL"; + try + { + sql << "select tm from soci_test", into(bgd); + assert(false); + } + catch (soci_error const & e) + { + assert(e.what() == std::string("Null value not allowed for this type")); + } + } + + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + boost::gregorian::date bgd(2008, boost::gregorian::May, 5); + + sql << "insert into soci_test(tm) values(:tm)", use(bgd); + + std::tm t; + sql << "select tm from soci_test", into(t); + + assert(t.tm_year == 108); + assert(t.tm_mon == 4); + assert(t.tm_mday == 5); + } + + std::cout << "test 30 passed" << std::endl; +#else + std::cout << "test 30 skipped (no Boost)" << std::endl; +#endif // HAVE_BOOST +} + +// connection pool - simple sequential test, no multiple threads +void test31() +{ + { + // phase 1: preparation + const size_t pool_size = 10; + connection_pool pool(pool_size); + + for (std::size_t i = 0; i != pool_size; ++i) + { + session & sql = pool.at(i); + sql.open(backEndFactory_, connectString_); + } + + // phase 2: usage + for (std::size_t i = 0; i != pool_size; ++i) + { + // poor man way to lease more than one connection + session sql_unused1(pool); + session sql(pool); + session sql_unused2(pool); + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + char c('a'); + sql << "insert into soci_test(c) values(:c)", use(c); + sql << "select c from soci_test", into(c); + assert(c == 'a'); + } + } + } + std::cout << "test 31 passed\n"; +} + +// Issue 66 - test query transformation callback feature +static std::string no_op_transform(std::string query) +{ + return query; +} + +static std::string lower_than_g(std::string query) +{ + return query + " WHERE c < 'g'"; +} + +struct where_condition : std::unary_function +{ + where_condition(std::string const& where) + : where_(where) + {} + + result_type operator()(argument_type query) const + { + return query + " WHERE " + where_; + } + + std::string where_; +}; + + +void run_query_transformation_test(session& sql) +{ + // create and populate the test table + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + for (char c = 'a'; c <= 'z'; ++c) + { + sql << "insert into soci_test(c) values(\'" << c << "\')"; + } + + char const* query = "select count(*) from soci_test"; + + // free function, no-op + { + sql.set_query_transformation(no_op_transform); + int count; + sql << query, into(count); + assert(count == 'z' - 'a' + 1); + } + + // free function + { + sql.set_query_transformation(lower_than_g); + int count; + sql << query, into(count); + assert(count == 'g' - 'a'); + } + + // function object with state + { + sql.set_query_transformation(where_condition("c > 'g' AND c < 'j'")); + int count = 0; + sql << query, into(count); + assert(count == 'j' - 'h'); + count = 0; + sql.set_query_transformation(where_condition("c > 's' AND c <= 'z'")); + sql << query, into(count); + assert(count == 'z' - 's'); + } + +// Bug in Visual Studio __cplusplus still means C++03 +// https://connect.microsoft.com/VisualStudio/feedback/details/763051/ +#if defined _MSC_VER && _MSC_VER>=1600 +#define SOCI_HAVE_CPP11 1 +#elif __cplusplus >= 201103L +#define SOCI_HAVE_CPP11 1 +#else +#undef SOCI_HAVE_CPP11 +#endif + +#ifdef SOCI_HAVE_CPP11 + // lambda + { + sql.set_query_transformation( + [](std::string const& query) { + return query + " WHERE c > 'g' AND c < 'j'"; + }); + + int count = 0; + sql << query, into(count); + assert(count == 'j' - 'h'); + } +#endif +#undef SOCI_HAVE_CPP11 + + // prepared statements + + // constant effect (pre-prepare set transformation) + { + // set transformation after statement is prepared + sql.set_query_transformation(lower_than_g); + // prepare statement + int count; + statement st = (sql.prepare << query, into(count)); + // observe transformation effect + st.execute(true); + assert(count == 'g' - 'a'); + // reset transformation + sql.set_query_transformation(no_op_transform); + // observe the same transformation, no-op set above has no effect + count = 0; + st.execute(true); + assert(count == 'g' - 'a'); + } + + // no effect (post-prepare set transformation) + { + // reset + sql.set_query_transformation(no_op_transform); + + // prepare statement + int count; + statement st = (sql.prepare << query, into(count)); + // set transformation after statement is prepared + sql.set_query_transformation(lower_than_g); + // observe no effect of WHERE clause injection + st.execute(true); + assert(count == 'z' - 'a' + 1); + } +} + +void test_query_transformation() +{ + { + session sql(backEndFactory_, connectString_); + run_query_transformation_test(sql); + } + std::cout << "test query_transformation passed" << std::endl; +} +void test_query_transformation_with_connection_pool() +{ + { + // phase 1: preparation + const size_t pool_size = 10; + connection_pool pool(pool_size); + + for (std::size_t i = 0; i != pool_size; ++i) + { + session & sql = pool.at(i); + sql.open(backEndFactory_, connectString_); + } + + session sql(pool); + run_query_transformation_test(sql); + } + std::cout << "test query_transformation with connection pool passed" << std::endl; +} + +// Originally, submitted to SQLite3 backend and later moved to common test. +// Test commit b394d039530f124802d06c3b1a969c3117683152 +// Author: Mika Fischer +// Date: Thu Nov 17 13:28:07 2011 +0100 +// Implement get_affected_rows for SQLite3 backend +void test_get_affected_rows() +{ + { + session sql(backEndFactory_, connectString_); + auto_table_creator tableCreator(tc_.table_creator_4(sql)); + if (!tableCreator.get()) + { + std::cout << "test get_affected_rows skipped (function not implemented)" << std::endl; + return; + } + + for (int i = 0; i != 10; i++) + { + sql << "insert into soci_test(val) values(:val)", use(i); + } + + statement st1 = (sql.prepare << + "update soci_test set val = val + 1"); + st1.execute(true); + + assert(st1.get_affected_rows() == 10); + + statement st2 = (sql.prepare << + "delete from soci_test where val <= 5"); + st2.execute(true); + + assert(st2.get_affected_rows() == 5); + + statement st3 = (sql.prepare << + "update soci_test set val = val + 1"); + st3.execute(true); + + assert(st3.get_affected_rows() == 5); + + std::vector v(5, 0); + for (std::size_t i = 0; i < v.size(); ++i) + { + v[i] = (7 + i); + } + + // test affected rows for bulk operations. + statement st4 = (sql.prepare << + "delete from soci_test where val = :v", use(v)); + st4.execute(true); + + assert(st4.get_affected_rows() == 5); + + std::vector w(2, "1"); + w[1] = "a"; // this invalid value may cause an exception. + statement st5 = (sql.prepare << + "insert into soci_test(val) values(:val)", use(w)); + try { st5.execute(true); } + catch(...) {} + + // confirm the partial insertion. + int val = 0; + sql << "select count(val) from soci_test", into(val); + if(val != 0) + { + // test the preserved 'number of rows + // affected' after a potential failure. + assert(st5.get_affected_rows() != 0); + } + } + + std::cout << "test get_affected_rows passed" << std::endl; +} + +// test fix for: Backend is not set properly with connection pool (pull #5) +void test_pull5() +{ + { + const size_t pool_size = 1; + connection_pool pool(pool_size); + + for (std::size_t i = 0; i != pool_size; ++i) + { + session & sql = pool.at(i); + sql.open(backEndFactory_, connectString_); + } + + soci::session sql(pool); + sql.reconnect(); + sql.begin(); // no crash expected + } + + std::cout << "test pull-5 passed\n"; +} + +// issue 67 - Allocated statement backend memory leaks on exception +// If the test runs under memory debugger and it passes, then +// soci::details::statement_impl::backEnd_ must not leak +void test_issue67() +{ + session sql(backEndFactory_, connectString_); + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + { + try + { + rowset rs1 = (sql.prepare << "select * from soci_testX"); + + // TODO: On Linux, no exception thrown; neither from prepare, nor from execute? + // soci_odbc_test_postgresql: + // /home/travis/build/SOCI/soci/src/core/test/common-tests.h:3505: + // void soci::tests::common_tests::test_issue67(): Assertion `!"exception expected"' failed. + //assert(!"exception expected"); // relax temporarily + } + catch (soci_error const &e) + { + (void)e; + assert("expected exception caught"); + std::cout << "test issue-67 passed - check memory debugger output for leaks" << std::endl; + } + } + +} + +// issue 154 - Calling undefine_and_bind and then define_and_bind causes a leak. +// If the test runs under memory debugger and it passes, then +// soci::details::standard_use_type_backend and vector_use_type_backend must not leak +void test_issue154() +{ + session sql(backEndFactory_, connectString_); + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + sql << "insert into soci_test(id) values (1)"; + { + int id = 1; + int val = 0; + statement st(sql); + st.exchange(use(id)); + st.alloc(); + st.prepare("select id from soci_test where id = :1"); + st.define_and_bind(); + st.undefine_and_bind(); + st.exchange(soci::into(val)); + st.define_and_bind(); + st.execute(true); + assert(val == 1); + } + // vector variation + { + std::vector ids(1, 2); + std::vector vals(1, 1); + int val = 0; + statement st(sql); + st.exchange(use(ids)); + st.alloc(); + st.prepare("insert into soci_test(id, val) values (:1, :2)"); + st.define_and_bind(); + st.undefine_and_bind(); + st.exchange(use(vals)); + st.define_and_bind(); + st.execute(true); + sql << "select val from soci_test where id = 2", into(val); + assert(val == 1); + } + std::cout << "test issue-154 passed - check memory debugger output for leaks" << std::endl; +} + +}; // class common_tests + +} // namespace tests + +} // namespace soci + +#endif // SOCI_COMMON_TESTS_H_INCLUDED diff --git a/src/core/transaction.cpp b/src/core/transaction.cpp new file mode 100644 index 0000000000..57b38a9e45 --- /dev/null +++ b/src/core/transaction.cpp @@ -0,0 +1,53 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_SOURCE +#include "transaction.h" +#include "error.h" + +using namespace soci; + +transaction::transaction(session& sql) + : handled_(false), sql_(sql) +{ + sql_.begin(); +} + +transaction::~transaction() +{ + if (handled_ == false) + { + try + { + rollback(); + } + catch (...) + {} + } +} + +void transaction::commit() +{ + if (handled_) + { + throw soci_error("The transaction object cannot be handled twice."); + } + + sql_.commit(); + handled_ = true; +} + +void transaction::rollback() +{ + if (handled_) + { + throw soci_error("The transaction object cannot be handled twice."); + } + + sql_.rollback(); + handled_ = true; +} diff --git a/src/core/transaction.h b/src/core/transaction.h new file mode 100644 index 0000000000..3b5f8ee335 --- /dev/null +++ b/src/core/transaction.h @@ -0,0 +1,38 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_TRANSACTION_H_INCLUDED +#define SOCI_TRANSACTION_H_INCLUDED + +#include "session.h" +#include "soci-config.h" + +namespace soci +{ + +class SOCI_DECL transaction +{ +public: + explicit transaction(session& sql); + + ~transaction(); + + void commit(); + void rollback(); + +private: + bool handled_; + session& sql_; + + // Disable copying + transaction(transaction const& other); + transaction& operator=(transaction const& other); +}; + +} // namespace soci + +#endif // SOCI_TRANSACTION_H_INCLUDED diff --git a/src/core/type-conversion-traits.h b/src/core/type-conversion-traits.h new file mode 100644 index 0000000000..8ebbb3bf7d --- /dev/null +++ b/src/core/type-conversion-traits.h @@ -0,0 +1,41 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_TYPE_CONVERSION_TRAITS_H_INCLUDED +#define SOCI_TYPE_CONVERSION_TRAITS_H_INCLUDED + +#include "soci-backend.h" + +namespace soci +{ + +// default traits class type_conversion, acts as pass through for row::get() +// when no actual conversion is needed. +template +struct type_conversion +{ + typedef T base_type; + + static void from_base(base_type const & in, indicator ind, T & out) + { + if (ind == i_null) + { + throw soci_error("Null value not allowed for this type"); + } + out = in; + } + + static void to_base(T const & in, base_type & out, indicator & ind) + { + out = in; + ind = i_ok; + } +}; + +} // namespace soci + +#endif // SOCI_TYPE_CONVERSION_TRAITS_H_INCLUDED diff --git a/src/core/type-conversion.h b/src/core/type-conversion.h new file mode 100644 index 0000000000..a20eeebe83 --- /dev/null +++ b/src/core/type-conversion.h @@ -0,0 +1,365 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_TYPE_CONVERSION_H_INCLUDED +#define SOCI_TYPE_CONVERSION_H_INCLUDED + +#include "type-conversion-traits.h" +#include "into-type.h" +#include "use-type.h" +// std +#include +#include +#include +#include + +namespace soci +{ + +namespace details +{ + +// this class is used to ensure correct order of construction +// of into_type and use_type elements that use type_conversion + +template +struct base_value_holder +{ + typename type_conversion::base_type val_; +}; + +// Automatically create into_type from a type_conversion + +template +class conversion_into_type + : private base_value_holder, + public into_type::base_type> +{ +public: + typedef typename type_conversion::base_type base_type; + + conversion_into_type(T & value) + : into_type(details::base_value_holder::val_, ownInd_) + , value_(value) + , ownInd_() + , ind_(ownInd_) + { + assert(ownInd_ == ind_); + } + + conversion_into_type(T & value, indicator & ind) + : into_type(details::base_value_holder::val_, ind) + , value_(value) + , ownInd_(ind) // unused, just keep the pair of indicator(s) consistent + , ind_(ind) + { + assert(ownInd_ == ind_); + } + + +private: + void convert_from_base() + { + type_conversion::from_base( + details::base_value_holder::val_, ind_, value_); + } + + T & value_; + + indicator ownInd_; + + // ind_ refers to either ownInd_, or the one provided by the user + // in any case, ind_ refers to some valid indicator + // and can be used by conversion routines + indicator & ind_; +}; + +// Automatically create use_type from a type_conversion + +template +class conversion_use_type + : private details::base_value_holder, + public use_type::base_type> +{ +public: + typedef typename type_conversion::base_type base_type; + + conversion_use_type(T & value, std::string const & name = std::string()) + : use_type(details::base_value_holder::val_, ownInd_, name) + , value_(value) + , ownInd_() + , ind_(ownInd_) + , readOnly_(false) + { + assert(ownInd_ == ind_); + + // TODO: likely to be removed (SHA: c166625a28f7c907318134f625ff5acea7d9a1f8) + //convert_to_base(); + } + + conversion_use_type(T const & value, std::string const & name = std::string()) + : use_type(details::base_value_holder::val_, ownInd_, name) + , value_(const_cast(value)) + , ownInd_() + , ind_(ownInd_) + , readOnly_(true) + { + assert(ownInd_ == ind_); + + // TODO: likely to be removed (SHA: c166625a28f7c907318134f625ff5acea7d9a1f8) + //convert_to_base(); + } + + conversion_use_type(T & value, indicator & ind, + std::string const & name = std::string()) + : use_type(details::base_value_holder::val_, ind, name) + , value_(value) + , ind_(ind) + , readOnly_(false) + { + // TODO: likely to be removed (SHA: c166625a28f7c907318134f625ff5acea7d9a1f8) + //convert_to_base(); + } + + conversion_use_type(T const & value, indicator & ind, + std::string const & name = std::string()) + : use_type(details::base_value_holder::val_, ind, name) + , value_(const_cast(value)) + , ind_(ind) + , readOnly_(true) + { + // TODO: likely to be removed (SHA: c166625a28f7c907318134f625ff5acea7d9a1f8) + //convert_to_base(); + } + + void convert_from_base() + { + // NOTE: + // readOnly_ flag indicates that use_type object has been generated + // based on non-const object passed by user as input argument. + // For const objects, this is effectively no-op conversion. + // See standard_use_type::post_use() for more details. + + if (readOnly_ == false) + { + type_conversion::from_base( + details::base_value_holder::val_, ind_, value_); + } + } + + void convert_to_base() + { + type_conversion::to_base(value_, + details::base_value_holder::val_, ind_); + } + +private: + T & value_; + + indicator ownInd_; + + // ind_ refers to either ownInd_, or the one provided by the user + // in any case, ind_ refers to some valid indicator + // and can be used by conversion routines + indicator & ind_; + + bool readOnly_; +}; + +// this class is used to ensure correct order of construction +// of vector based into_type and use_type elements that use type_conversion + +template +struct base_vector_holder +{ + base_vector_holder(std::size_t sz = 0) : vec_(sz) {} + mutable std::vector::base_type> vec_; +}; + +// Automatically create a std::vector based into_type from a type_conversion + +template +class conversion_into_type > + : private details::base_vector_holder, + public into_type::base_type> > +{ +public: + typedef typename std::vector + < + typename type_conversion::base_type + > base_type; + + conversion_into_type(std::vector & value) + : details::base_vector_holder(value.size()) + , into_type(details::base_vector_holder::vec_, ownInd_) + , value_(value) + , ownInd_() + , ind_(ownInd_) + { + assert(ownInd_ == ind_); + } + + conversion_into_type(std::vector & value, std::vector & ind) + : details::base_vector_holder(value.size()) + , into_type(details::base_vector_holder::vec_, ind) + , value_(value) + , ind_(ind) + {} + + virtual std::size_t size() const + { + // the user might have resized his vector in the meantime + // -> synchronize the base-value mirror to have the same size + + std::size_t const userSize = value_.size(); + details::base_vector_holder::vec_.resize(userSize); + return userSize; + } + + virtual void resize(std::size_t sz) + { + value_.resize(sz); + ind_.resize(sz); + details::base_vector_holder::vec_.resize(sz); + } + +private: + void convert_from_base() + { + std::size_t const sz = details::base_vector_holder::vec_.size(); + + for (std::size_t i = 0; i != sz; ++i) + { + type_conversion::from_base( + details::base_vector_holder::vec_[i], ind_[i], value_[i]); + } + } + + std::vector & value_; + + std::vector ownInd_; + + // ind_ refers to either ownInd_, or the one provided by the user + // in any case, ind_ refers to some valid vector of indicators + // and can be used by conversion routines + std::vector & ind_; +}; + + +// Automatically create a std::vector based use_type from a type_conversion + +template +class conversion_use_type > + : private details::base_vector_holder, + public use_type::base_type> > +{ +public: + typedef typename std::vector + < + typename type_conversion::base_type + > base_type; + + conversion_use_type(std::vector & value, + std::string const & name=std::string()) + : details::base_vector_holder(value.size()) + , use_type( + details::base_vector_holder::vec_, ownInd_, name) + , value_(value) + , ownInd_() + , ind_(ownInd_) + { + assert(ownInd_ == ind_); + } + + conversion_use_type(std::vector & value, + std::vector & ind, + std::string const & name = std::string()) + : details::base_vector_holder(value.size()) + , use_type( + details::base_vector_holder::vec_, ind, name) + , value_(value) + , ind_(ind) + {} + +private: + void convert_from_base() + { + std::size_t const sz = details::base_vector_holder::vec_.size(); + value_.resize(sz); + ind_.resize(sz); + for (std::size_t i = 0; i != sz; ++i) + { + type_conversion::from_base( + details::base_vector_holder::vec_[i], value_[i], ind_[i]); + } + } + + void convert_to_base() + { + std::size_t const sz = value_.size(); + details::base_vector_holder::vec_.resize(sz); + ind_.resize(sz); + for (std::size_t i = 0; i != sz; ++i) + { + type_conversion::to_base(value_[i], + details::base_vector_holder::vec_[i], ind_[i]); + } + } + + std::vector & value_; + + std::vector ownInd_; + + // ind_ refers to either ownInd_, or the one provided by the user + // in any case, ind_ refers to some valid vector of indicators + // and can be used by conversion routines + std::vector & ind_; +}; + +template +into_type_ptr do_into(T & t, user_type_tag) +{ + return into_type_ptr(new conversion_into_type(t)); +} + +template +into_type_ptr do_into(T & t, indicator & ind, user_type_tag) +{ + return into_type_ptr(new conversion_into_type(t, ind)); +} + +template +use_type_ptr do_use(T & t, std::string const & name, user_type_tag) +{ + return use_type_ptr(new conversion_use_type(t, name)); +} + +template +use_type_ptr do_use(T const & t, std::string const & name, user_type_tag) +{ + return use_type_ptr(new conversion_use_type(t, name)); +} + +template +use_type_ptr do_use(T & t, indicator & ind, + std::string const & name, user_type_tag) +{ + return use_type_ptr(new conversion_use_type(t, ind, name)); +} + +template +use_type_ptr do_use(T const & t, indicator & ind, + std::string const & name, user_type_tag) +{ + return use_type_ptr(new conversion_use_type(t, ind, name)); +} + +} // namespace details + +} // namespace soci + +#endif // SOCI_TYPE_CONVERSION_H_INCLUDED diff --git a/src/core/type-holder.h b/src/core/type-holder.h new file mode 100644 index 0000000000..2b51d96599 --- /dev/null +++ b/src/core/type-holder.h @@ -0,0 +1,68 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_TYPE_HOLDER_H_INCLUDED +#define SOCI_TYPE_HOLDER_H_INCLUDED +// std +#include + +namespace soci +{ + +namespace details +{ + +// Base class holder + derived class type_holder for storing type data +// instances in a container of holder objects +template +class type_holder; + +class holder +{ +public: + holder() {} + virtual ~holder() {} + + template + T get() + { + type_holder* p = dynamic_cast *>(this); + if (p) + { + return p->template value(); + } + else + { + throw std::bad_cast(); + } + } + +private: + + template + T value(); +}; + +template +class type_holder : public holder +{ +public: + type_holder(T * t) : t_(t) {} + ~type_holder() { delete t_; } + + template + TypeValue value() const { return *t_; } + +private: + T * t_; +}; + +} // namespace details + +} // namespace soci + +#endif // SOCI_TYPE_HOLDER_H_INCLUDED diff --git a/src/core/type-ptr.h b/src/core/type-ptr.h new file mode 100644 index 0000000000..492362f82b --- /dev/null +++ b/src/core/type-ptr.h @@ -0,0 +1,30 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_TYPE_PTR_H_INCLUDED +#define SOCI_TYPE_PTR_H_INCLUDED + +namespace soci { namespace details { + +template +class type_ptr +{ +public: + type_ptr(T * p) : p_(p) {} + ~type_ptr() { delete p_; } + + T * get() const { return p_; } + void release() const { p_ = 0; } + +private: + mutable T * p_; +}; + +} // namespace details +} // namespace soci + +#endif // SOCI_TYPE_PTR_H_INCLUDED diff --git a/src/core/unsigned-types.h b/src/core/unsigned-types.h new file mode 100644 index 0000000000..e9a02c64ab --- /dev/null +++ b/src/core/unsigned-types.h @@ -0,0 +1,114 @@ +// +// Copyright (C) 2010 Maciej Sobczak +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_UNSIGNED_TYPES_H_INCLUDED +#define SOCI_UNSIGNED_TYPES_H_INCLUDED + +#include "type-conversion-traits.h" +#include + +namespace soci +{ + +// simple fall-back for unsigned types + +template <> +struct type_conversion +{ + typedef long long base_type; + + static void from_base(base_type const & in, indicator ind, + unsigned char & out) + { + if (ind == i_null) + { + throw soci_error("Null value not allowed for this type."); + } + + const base_type max = (std::numeric_limits::max)(); + const base_type min = (std::numeric_limits::min)(); + if (in < min || in > max) + { + throw soci_error("Value outside of allowed range."); + } + + out = static_cast(in); + } + + static void to_base(unsigned char const & in, + base_type & out, indicator & ind) + { + out = static_cast(in); + ind = i_ok; + } +}; + +template <> +struct type_conversion +{ + typedef long long base_type; + + static void from_base(base_type const & in, indicator ind, + unsigned short & out) + { + if (ind == i_null) + { + throw soci_error("Null value not allowed for this type."); + } + + const long long max = (std::numeric_limits::max)(); + const long long min = (std::numeric_limits::min)(); + if (in < min || in > max) + { + throw soci_error("Value outside of allowed range."); + } + + out = static_cast(in); + } + + static void to_base(unsigned short const & in, + base_type & out, indicator & ind) + { + out = static_cast(in); + ind = i_ok; + } +}; + +template <> +struct type_conversion +{ + typedef long long base_type; + + static void from_base(base_type const & in, indicator ind, + unsigned int & out) + { + if (ind == i_null) + { + throw soci_error("Null value not allowed for this type."); + } + + const long long max = (std::numeric_limits::max)(); + const long long min = (std::numeric_limits::min)(); + if (in < min || in > max) + { + throw soci_error("Value outside of allowed range."); + } + + out = static_cast(in); + } + + static void to_base(unsigned int const & in, + base_type & out, indicator & ind) + { + out = static_cast(in); + ind = i_ok; + } +}; + +} // namespace soci + +#endif // SOCI_UNSIGNED_TYPES_H_INCLUDED diff --git a/src/core/use-type.cpp b/src/core/use-type.cpp new file mode 100644 index 0000000000..24185eda22 --- /dev/null +++ b/src/core/use-type.cpp @@ -0,0 +1,105 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_SOURCE +#include "use-type.h" +#include "statement.h" + +using namespace soci; +using namespace soci::details; + +standard_use_type::~standard_use_type() +{ + delete backEnd_; +} + +void standard_use_type::bind(statement_impl & st, int & position) +{ + if (backEnd_ == NULL) + { + backEnd_ = st.make_use_type_backend(); + } + if (name_.empty()) + { + backEnd_->bind_by_pos(position, data_, type_, readOnly_); + } + else + { + backEnd_->bind_by_name(name_, data_, type_, readOnly_); + } +} + +void standard_use_type::pre_use() +{ + // Handle IN direction of parameters of SQL statements and procedures + convert_to_base(); + backEnd_->pre_use(ind_); +} + +void standard_use_type::post_use(bool gotData) +{ + // Handle OUT direction of IN/OUT parameters of stored procedures + backEnd_->post_use(gotData, ind_); + convert_from_base(); + + // IMPORTANT: + // This treatment of input ("use") parameter as output data sink may be + // confusing, but it is necessary to store OUT data back in the same + // object as IN, of IN/OUT parameter. + // As there is no symmetry for IN/OUT in SQL and there are no OUT/IN + // we do not perform convert_to_base() for output ("into") parameter. + // See conversion_use_type::convert_from_base() for more details. +} + +void standard_use_type::clean_up() +{ + if (backEnd_ != NULL) + { + backEnd_->clean_up(); + } +} + +vector_use_type::~vector_use_type() +{ + delete backEnd_; +} + +void vector_use_type::bind(statement_impl & st, int & position) +{ + if (backEnd_ == NULL) + { + backEnd_ = st.make_vector_use_type_backend(); + } + if (name_.empty()) + { + backEnd_->bind_by_pos(position, data_, type_); + } + else + { + backEnd_->bind_by_name(name_, data_, type_); + } +} + +void vector_use_type::pre_use() +{ + convert_to_base(); + + backEnd_->pre_use(ind_ ? &ind_->at(0) : NULL); +} + +std::size_t vector_use_type::size() const +{ + return backEnd_->size(); +} + +void vector_use_type::clean_up() +{ + if (backEnd_ != NULL) + { + backEnd_->clean_up(); + } +} diff --git a/src/core/use-type.h b/src/core/use-type.h new file mode 100644 index 0000000000..6230cd9b16 --- /dev/null +++ b/src/core/use-type.h @@ -0,0 +1,239 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_USE_TYPE_H_INCLUDED +#define SOCI_USE_TYPE_H_INCLUDED + +#include "soci-backend.h" +#include "type-ptr.h" +#include "exchange-traits.h" +// std +#include +#include +#include + +namespace soci { namespace details { + +class statement_impl; + +// this is intended to be a base class for all classes that deal with +// binding input data (and OUT PL/SQL variables) +class SOCI_DECL use_type_base +{ +public: + virtual ~use_type_base() {} + + virtual void bind(statement_impl & st, int & position) = 0; + virtual void pre_use() = 0; + virtual void post_use(bool gotData) = 0; + virtual void clean_up() = 0; + + virtual std::size_t size() const = 0; // returns the number of elements +}; + +typedef type_ptr use_type_ptr; + +class SOCI_DECL standard_use_type : public use_type_base +{ +public: + standard_use_type(void* data, exchange_type type, + bool readOnly, std::string const& name = std::string()) + : data_(data) + , type_(type) + , ind_(NULL) + , readOnly_(readOnly) + , name_(name) + , backEnd_(NULL) + { + // FIXME: This was added with Ilia's patch + // https://github.com/SOCI/soci/commit/c166625a28f7c907318134f625ff5acea7d9a1f8 + // but it seems to be a troublemaker, causing duplicated conversions + //convert_to_base(); + } + + standard_use_type(void* data, exchange_type type, indicator& ind, + bool readOnly, std::string const& name = std::string()) + : data_(data) + , type_(type) + , ind_(&ind) + , readOnly_(readOnly) + , name_(name) + , backEnd_(NULL) + { + // FIXME + //convert_to_base(); + } + + virtual ~standard_use_type(); + virtual void bind(statement_impl & st, int & position); + std::string get_name() const { return name_; } + virtual void * get_data() { return data_; } + + // conversion hook (from arbitrary user type to base type) + virtual void convert_to_base() {} + virtual void convert_from_base() {} + +protected: + virtual void pre_use(); + +private: + virtual void post_use(bool gotData); + virtual void clean_up(); + virtual std::size_t size() const { return 1; } + + void* data_; + exchange_type type_; + indicator* ind_; + bool readOnly_; + std::string name_; + + standard_use_type_backend* backEnd_; +}; + +class SOCI_DECL vector_use_type : public use_type_base +{ +public: + vector_use_type(void* data, exchange_type type, + std::string const& name = std::string()) + : data_(data) + , type_(type) + , ind_(NULL) + , name_(name) + , backEnd_(NULL) + {} + + vector_use_type(void* data, exchange_type type, + std::vector const& ind, + std::string const& name = std::string()) + : data_(data) + , type_(type) + , ind_(&ind) + , name_(name) + , backEnd_(NULL) + {} + + ~vector_use_type(); + +private: + virtual void bind(statement_impl& st, int & position); + virtual void pre_use(); + virtual void post_use(bool) { /* nothing to do */ } + virtual void clean_up(); + virtual std::size_t size() const; + + void* data_; + exchange_type type_; + std::vector const* ind_; + std::string name_; + + vector_use_type_backend * backEnd_; + + virtual void convert_to_base() {} +}; + +// implementation for the basic types (those which are supported by the library +// out of the box without user-provided conversions) + +template +class use_type : public standard_use_type +{ +public: + use_type(T& t, std::string const& name = std::string()) + : standard_use_type(&t, + static_cast(exchange_traits::x_type), false, name) + {} + + use_type(T const& t, std::string const& name = std::string()) + : standard_use_type(const_cast(&t), + static_cast(exchange_traits::x_type), true, name) + {} + + use_type(T& t, indicator& ind, std::string const& name = std::string()) + : standard_use_type(&t, + static_cast(exchange_traits::x_type), ind, false, name) + {} + + use_type(T const& t, indicator& ind, std::string const& name = std::string()) + : standard_use_type(const_cast(&t), + static_cast(exchange_traits::x_type), ind, false, name) + {} +}; + +template +class use_type > : public vector_use_type +{ +public: + use_type(std::vector& v, std::string const& name = std::string()) + : vector_use_type(&v, + static_cast(exchange_traits::x_type), name) + {} + + use_type(std::vector const& v, std::string const& name = std::string()) + : vector_use_type(const_cast*>(&v), + static_cast(exchange_traits::x_type), name) + {} + + use_type(std::vector& v, std::vector const& ind, + std::string const& name = std::string()) + : vector_use_type(&v, + static_cast(exchange_traits::x_type), ind, name) + {} + + use_type(std::vector const& v, std::vector const& ind, + std::string const& name = std::string()) + : vector_use_type(const_cast *>(&v), + static_cast(exchange_traits::x_type), ind, name) + {} +}; + +// helper dispatchers for basic types + +template +use_type_ptr do_use(T & t, std::string const & name, basic_type_tag) +{ + return use_type_ptr(new use_type(t, name)); +} + +template +use_type_ptr do_use(T const & t, std::string const & name, basic_type_tag) +{ + return use_type_ptr(new use_type(t, name)); +} + +template +use_type_ptr do_use(T & t, indicator & ind, + std::string const & name, basic_type_tag) +{ + return use_type_ptr(new use_type(t, ind, name)); +} + +template +use_type_ptr do_use(T const & t, indicator & ind, + std::string const & name, basic_type_tag) +{ + return use_type_ptr(new use_type(t, ind, name)); +} + +template +use_type_ptr do_use(T & t, std::vector & ind, + std::string const & name, basic_type_tag) +{ + return use_type_ptr(new use_type(t, ind, name)); +} + +template +use_type_ptr do_use(T const & t, std::vector & ind, + std::string const & name, basic_type_tag) +{ + return use_type_ptr(new use_type(t, ind, name)); +} + +} // namespace details + +} // namesapce soci + +#endif // SOCI_USE_TYPE_H_INCLUDED diff --git a/src/core/use.h b/src/core/use.h new file mode 100644 index 0000000000..24c518146e --- /dev/null +++ b/src/core/use.h @@ -0,0 +1,87 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_USE_H_INCLUDED +#define SOCI_USE_H_INCLUDED + +#include "use-type.h" +#include "exchange-traits.h" +#include "type-conversion.h" + +namespace soci +{ + +// the use function is a helper for defining input variables +// these helpers work with both basic and user-defined types thanks to +// the tag-dispatching, as defined in exchange_traits template + +template +details::use_type_ptr use(T & t, std::string const & name = std::string()) +{ + return details::do_use(t, name, + typename details::exchange_traits::type_family()); +} + +template +details::use_type_ptr use(T const & t, + std::string const & name = std::string()) +{ + return details::do_use(t, name, + typename details::exchange_traits::type_family()); +} + +template +details::use_type_ptr use(T & t, indicator & ind, + std::string const &name = std::string()) +{ + return details::do_use(t, ind, name, + typename details::exchange_traits::type_family()); +} + +template +details::use_type_ptr use(T const & t, indicator & ind, + std::string const &name = std::string()) +{ + return details::do_use(t, ind, name, + typename details::exchange_traits::type_family()); +} + +template +details::use_type_ptr use(T & t, std::vector & ind, + std::string const & name = std::string()) +{ + return details::do_use(t, ind, name, + typename details::exchange_traits::type_family()); +} + +template +details::use_type_ptr use(T const & t, std::vector & ind, + std::string const & name = std::string()) +{ + return details::do_use(t, ind, name, + typename details::exchange_traits::type_family()); +} + +// for char buffer with run-time size information +template +details::use_type_ptr use(T & t, std::size_t bufSize, + std::string const & name = std::string()) +{ + return details::use_type_ptr(new details::use_type(t, bufSize)); +} + +// for char buffer with run-time size information +template +details::use_type_ptr use(T const & t, std::size_t bufSize, + std::string const & name = std::string()) +{ + return details::use_type_ptr(new details::use_type(t, bufSize)); +} + +} // namespace soci + +#endif // SOCI_USE_H_INCLUDED diff --git a/src/core/values-exchange.h b/src/core/values-exchange.h new file mode 100644 index 0000000000..b9d533a304 --- /dev/null +++ b/src/core/values-exchange.h @@ -0,0 +1,117 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_VALUES_EXCHANGE_H_INCLUDED +#define SOCI_VALUES_EXCHANGE_H_INCLUDED + +#include "values.h" +#include "into-type.h" +#include "use-type.h" +#include "row-exchange.h" +// std +#include +#include +#include + +namespace soci +{ + +namespace details +{ + +template <> +struct exchange_traits +{ + typedef basic_type_tag type_family; + + // dummy value to satisfy the template engine, never used + enum { x_type = 0 }; +}; + +template <> +class use_type : public use_type_base +{ +public: + use_type(values & v, std::string const & /*name*/ = std::string()) + : v_(v) + {} + + // we ignore the possibility to have the whole values as NULL + use_type(values & v, indicator /*ind*/, std::string const & /*name*/ = std::string()) + : v_(v) + {} + + virtual void bind(details::statement_impl & st, int & /*position*/) + { + v_.uppercase_column_names(st.session_.get_uppercase_column_names()); + + convert_to_base(); + st.bind(v_); + } + + virtual void post_use(bool /*gotData*/) + { + v_.reset_get_counter(); + convert_from_base(); + } + + virtual void pre_use() {convert_to_base();} + virtual void clean_up() {v_.clean_up();} + virtual std::size_t size() const { return 1; } + + // these are used only to re-dispatch to derived class + // (the derived class might be generated automatically by + // user conversions) + virtual void convert_to_base() {} + virtual void convert_from_base() {} + +private: + values & v_; +}; + +// this is not supposed to be used - no support for bulk ORM +template <> +class use_type > +{ +private: + use_type(); +}; + +template <> +class into_type : public into_type +{ +public: + into_type(values & v) + : into_type(v.get_row()), v_(v) + {} + + into_type(values & v, indicator & ind) + : into_type(v.get_row(), ind), v_(v) + {} + + void clean_up() + { + v_.clean_up(); + } + +private: + values & v_; +}; + +// this is not supposed to be used - no support for bulk ORM +template <> +class into_type > +{ +private: + into_type(); +}; + +} // namespace details + +} // namespace soci + +#endif // SOCI_VALUES_EXCHANGE_H_INCLUDED diff --git a/src/core/values.cpp b/src/core/values.cpp new file mode 100644 index 0000000000..3883415c26 --- /dev/null +++ b/src/core/values.cpp @@ -0,0 +1,69 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#define SOCI_SOURCE +#include "values.h" +#include "row.h" + +#include +#include +#include +#include + +using namespace soci; +using namespace soci::details; + +indicator values::get_indicator(std::size_t pos) const +{ + if (row_) + { + return row_->get_indicator(pos); + } + else + { + return *indicators_[pos]; + } +} + +indicator values::get_indicator(std::string const& name) const +{ + if (row_) + { + return row_->get_indicator(name); + } + else + { + std::map::const_iterator it = index_.find(name); + if (it == index_.end()) + { + std::ostringstream msg; + msg << "Column '" << name << "' not found"; + throw soci_error(msg.str()); + } + return *indicators_[it->second]; + } +} + +column_properties const& values::get_properties(std::size_t pos) const +{ + if (row_) + { + return row_->get_properties(pos); + } + + throw soci_error("Rowset is empty"); +} + +column_properties const& values::get_properties(std::string const& name) const +{ + if (row_) + { + return row_->get_properties(name); + } + + throw soci_error("Rowset is empty"); +} diff --git a/src/core/values.h b/src/core/values.h new file mode 100644 index 0000000000..d17cb33246 --- /dev/null +++ b/src/core/values.h @@ -0,0 +1,349 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_VALUES_H_INCLUDED +#define SOCI_VALUES_H_INCLUDED + +#include "statement.h" +#include "into-type.h" +#include "use-type.h" +// std +#include +#include +#include +#include +#include +#include + +namespace soci +{ + +namespace details +{ + +class copy_base +{ +public: + virtual ~copy_base() {} +}; + +template +struct copy_holder : public copy_base +{ + copy_holder(T const & v) : value_(v) {} + + T value_; +}; + +} // namespace details + +class SOCI_DECL values +{ + friend class details::statement_impl; + friend class details::into_type; + friend class details::use_type; + +public: + + values() : row_(NULL), currentPos_(0), uppercaseColumnNames_(false) {} + + indicator get_indicator(std::size_t pos) const; + indicator get_indicator(std::string const & name) const; + + template + T get(std::size_t pos) const + { + if (row_ != NULL) + { + return row_->get(pos); + } + else if (*indicators_[pos] != i_null) + { + return get_from_uses(pos); + } + else + { + std::ostringstream msg; + msg << "Column at position " + << static_cast(pos) + << " contains NULL value and no default was provided"; + throw soci_error(msg.str()); + } + } + + template + T get(std::size_t pos, T const & nullValue) const + { + if (row_ != NULL) + { + return row_->get(pos, nullValue); + } + else if (*indicators_[pos] == i_null) + { + return nullValue; + } + else + { + return get_from_uses(pos); + } + } + + template + T get(std::string const & name) const + { + return row_ != NULL ? row_->get(name) : get_from_uses(name); + } + + template + T get(std::string const & name, T const & nullValue) const + { + return row_ != NULL + ? row_->get(name, nullValue) + : get_from_uses(name, nullValue); + } + + template + values const & operator>>(T & value) const + { + if (row_ != NULL) + { + // row maintains its own position counter + // which is automatically reset when needed + + *row_ >> value; + } + else if (*indicators_[currentPos_] != i_null) + { + // if there is no row object, then the data can be + // extracted from the locally stored use elements, + // but for this the position counter has to be maintained + // as well + + value = get_from_uses(currentPos_); + ++currentPos_; + } + else + { + std::ostringstream msg; + msg << "Column at position " + << static_cast(currentPos_) + << " contains NULL value and no default was provided"; + throw soci_error(msg.str()); + } + + return *this; + } + + void skip(std::size_t num = 1) const + { + if (row_ != NULL) + { + row_->skip(num); + } + else + { + currentPos_ += num; + } + } + + void reset_get_counter() const + { + if (row_ != NULL) + { + row_->reset_get_counter(); + } + else + { + currentPos_ = 0; + } + } + + template + void set(std::string const & name, T const & value, indicator indic = i_ok) + { + typedef typename type_conversion::base_type base_type; + if(index_.find(name) == index_.end()) + { + index_.insert(std::make_pair(name, uses_.size())); + + indicator * pind = new indicator(indic); + indicators_.push_back(pind); + + base_type baseValue; + if (indic == i_ok) + { + type_conversion::to_base(value, baseValue, *pind); + } + + details::copy_holder * pcopy = + new details::copy_holder(baseValue); + deepCopies_.push_back(pcopy); + + uses_.push_back(new details::use_type( + pcopy->value_, *pind, name)); + } + else + { + size_t index = index_.find(name)->second; + *indicators_[index] = indic; + if (indic == i_ok) + { + type_conversion::to_base( + value, + static_cast*>(deepCopies_[index])->value_, + *indicators_[index]); + } + } + } + + template + void set(const T & value, indicator indic = i_ok) + { + indicator * pind = new indicator(indic); + indicators_.push_back(pind); + + typedef typename type_conversion::base_type base_type; + base_type baseValue; + type_conversion::to_base(value, baseValue, *pind); + + details::copy_holder * pcopy = + new details::copy_holder(baseValue); + deepCopies_.push_back(pcopy); + + uses_.push_back(new details::use_type( + pcopy->value_, *pind)); + } + + template + values & operator<<(T const & value) + { + set(value); + return *this; + } + + void uppercase_column_names(bool forceToUpper) + { + uppercaseColumnNames_ = forceToUpper; + } + + column_properties const& get_properties(std::size_t pos) const; + column_properties const& get_properties(std::string const &name) const; + +private: + + //TODO To make values generally usable outside of type_conversion's, + // these should be reference counted smart pointers + row * row_; + std::vector uses_; + std::map unused_; + std::vector indicators_; + std::map index_; + std::vector deepCopies_; + + mutable std::size_t currentPos_; + + bool uppercaseColumnNames_; + + // When type_conversion::to() is called, a values object is created + // without an underlying row object. In that case, get_from_uses() + // returns the underlying field values + template + T get_from_uses(std::string const & name, T const & nullValue) const + { + std::map::const_iterator pos = index_.find(name); + if (pos != index_.end()) + { + if (*indicators_[pos->second] == i_null) + { + return nullValue; + } + + return get_from_uses(pos->second); + } + throw soci_error("Value named " + name + " not found."); + } + + template + T get_from_uses(std::string const & name) const + { + std::map::const_iterator pos = index_.find(name); + if (pos != index_.end()) + { + return get_from_uses(pos->second); + } + throw soci_error("Value named " + name + " not found."); + } + + template + T get_from_uses(std::size_t pos) const + { + details::standard_use_type* u = uses_[pos]; + + typedef typename type_conversion::base_type base_type; + + if (dynamic_cast *>(u)) + { + base_type const & baseValue = *static_cast(u->get_data()); + + T val; + indicator ind = *indicators_[pos]; + type_conversion::from_base(baseValue, ind, val); + return val; + } + else + { + std::ostringstream msg; + msg << "Value at position " + << static_cast(pos) + << " was set using a different type" + " than the one passed to get()"; + throw soci_error(msg.str()); + } + } + + row& get_row() + { + row_ = new row(); + row_->uppercase_column_names(uppercaseColumnNames_); + + return * row_; + } + + // this is called by Statement::bind(values) + void add_unused(details::use_type_base * u, indicator * i) + { + static_cast(u)->convert_to_base(); + unused_.insert(std::make_pair(u, i)); + } + + // this is called by details::into_type::clean_up() + // and use_type::clean_up() + void clean_up() + { + delete row_; + row_ = NULL; + + // delete any uses and indicators which were created by set() but + // were not bound by the Statement + // (bound uses and indicators are deleted in Statement::clean_up()) + for (std::map::iterator pos = + unused_.begin(); pos != unused_.end(); ++pos) + { + delete pos->first; + delete pos->second; + } + + for (std::size_t i = 0; i != deepCopies_.size(); ++i) + { + delete deepCopies_[i]; + } + } +}; + +} // namespace soci + +#endif // SOCI_VALUES_H_INCLUDED diff --git a/src/core/version.h b/src/core/version.h new file mode 100644 index 0000000000..10179047dd --- /dev/null +++ b/src/core/version.h @@ -0,0 +1,32 @@ +// SOCI version.hpp configuration header file + +// +// Copyright (C) 2011 Mateusz Loskot +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_VERSION_HPP +#define SOCI_VERSION_HPP + +// +// Caution, this is the only SOCI header that is guarenteed +// to change with every SOCI release, including this header +// will cause a recompile every time a new SOCI version is +// released. +// +// SOCI_VERSION % 100 is the patch level +// SOCI_VERSION / 100 % 1000 is the minor version +// SOCI_VERSION / 100000 is the major version + +#define SOCI_VERSION 300203 + +// +// SOCI_LIB_VERSION must be defined to be the same as SOCI_VERSION +// but as a *string* in the form "x_y[_z]" where x is the major version +// number, y is the minor version number, and z is the patch level if not 0. + +#define SOCI_LIB_VERSION "3_2_3" + +#endif // SOCI_VERSION_HPP diff --git a/src/ideas.txt b/src/ideas.txt new file mode 100644 index 0000000000..37fb670bf3 --- /dev/null +++ b/src/ideas.txt @@ -0,0 +1,134 @@ +$Id: ideas.txt,v 1.8 2006/12/12 07:39:22 msobczak Exp $ + +This file contains a raw bunch of ideas for future releases. +Not all of these ideas will necessarily make sense - they are here to get them together. + +--- +Source of many concepts +http://lists.boost.org/Archives/boost/2006/12/113961.php + +--- +RAII for transactions. + +--- +bjam +Add JamFile files + +--- +Refactoring of core, more fine-grained file structure. + +--- +Session constructor overload that accepts map for param=value pairs. +This should rather be provided as a separate function? + +--- +Query construction utilities (kind of Ultimate++) - can be easily incorporated into SOCI by just making them streamable. + +--- +CLOB + +--- +Streaming interface for BLOB + +--- +Standard names for Session constructor. + +--- +wstring +Unicode support + +--- +Handle locales in Session (so that operator<< is immune to strange global locale in the user program). It might even make sense to expose imbue(), so that users set up whatever locale they want. +It might even make sense to expose the whole stream object. +Alternatively, the backend should decide on the locale, because the backend will know best how to format numbers, dates, etc. + +--- +sql << "select...", into(x, default(7)); + +Note: default is a reserved word. + +--- +Provide statement-wide flag for eNoData case (because actually it *is* statement-wide, not field-wide). With this, boost.optional would handle the eNull case and the indicators could be dropped. + +--- +Session sql("mysql://user:password@host/database"); + +So that Session tries to find backend_mysql.(dll|so) if not yet registered. +--- + +number of rows affected (insert/update + select?) +--- + +query backend for supported featureset at runtime +--- + +Rowset +--- + +boost::optional? + +--- +Rowset, including Rowset - way to indicate nulls? +Additional pair based val/indicator interface? + +--- +Consolidate iteration methods? +most radical: do we still need Statement::fetch()? into()? +(Rowset can currently be used for any query, supports indicators, +defaults, and no need to check for eNodata) + +--- +ColumnProperties() more logically belongs to Rowset than to Row +However Row::ColumnProperties() still needed if we support into(Row) + +--- +sql.prepare by default when constructing Rowsets and Statements? +Rowset rs = (sql << "select n from t";) + +--- +row[i].get() instead of row.get(i) +row["col"].get() instead of row.get("col") + +--- +Make more member functions private + +--- +Values class should be reference counted + +--- +CSV backend + +Example: +Session s("csv:///etc/protocols"); +rowset rs = (s.prepare << "1:*"); +copy(rs.begin(), rs.end(), ...); + +where "1:*" is taken from the top of my head and would mean "first field +from all rows" + +- joins are tricky + +--- +DBF backend, similar to CSV + +Session s("dbf:///table.dbf"); +rowset rs = (s.prepare << "1:*") // first field from all rows +rowset rs = (s.prepare << "firstname:*") // 'firstname' field from all rows +rowset rs = (s.prepare << "firstname='John'") // rows where 'firstname' value is 'John' + +Sub-concepts: + +- joins are tricky + +- boolean operators (<,>,=,<=,=> and <>) and WHERE-like clause support as a query + +rowset rs = (s.prepare << "age > 28") // rows where field 'age' is less than 28 +rowset rs = (s.prepare << "age <> 28") // rows where field 'age' is less or more than 28 +rowset rs = (s.prepare << "firstname='John' AND age > 28") // multi-fields combined queries + +- very simple home-made SQL parser or SQL-like queries support (see OGR utils from http://www.gdal.org) + +--- +Session::reconnect (should connect again with the same params). +Why not also Session::open/close? + diff --git a/src/languages/ada/postgresql_client.gpr b/src/languages/ada/postgresql_client.gpr new file mode 100644 index 0000000000..f29b3498b4 --- /dev/null +++ b/src/languages/ada/postgresql_client.gpr @@ -0,0 +1,8 @@ +project PostgreSQL_Client is + for Externally_Built use "true"; + for Source_Dirs use (); + for Library_Dir use "/usr/local/lib"; + for Library_Name use "pq"; + for Library_Kind use "static"; +end PostgreSQL_Client; + diff --git a/src/languages/ada/soci-mysql.ads b/src/languages/ada/soci-mysql.ads new file mode 100644 index 0000000000..10156fbc61 --- /dev/null +++ b/src/languages/ada/soci-mysql.ads @@ -0,0 +1,16 @@ +-- Copyright (C) 2008-2011 Maciej Sobczak +-- Distributed under the Boost Software License, Version 1.0. +-- (See accompanying file LICENSE_1_0.txt or copy at +-- http://www.boost.org/LICENSE_1_0.txt) + +package SOCI.MySQL is + + -- + -- Registers the MySQL backend so that it is ready for use + -- by the dynamic backend loader. + -- + procedure Register_Factory_MySQL; + pragma Import (C, Register_Factory_MySQL, + "register_factory_mysql"); + +end SOCI.MySQL; diff --git a/src/languages/ada/soci-oracle.ads b/src/languages/ada/soci-oracle.ads new file mode 100644 index 0000000000..ed8bc6672b --- /dev/null +++ b/src/languages/ada/soci-oracle.ads @@ -0,0 +1,16 @@ +-- Copyright (C) 2008-2011 Maciej Sobczak +-- Distributed under the Boost Software License, Version 1.0. +-- (See accompanying file LICENSE_1_0.txt or copy at +-- http://www.boost.org/LICENSE_1_0.txt) + +package SOCI.Oracle is + + -- + -- Registers the Oracle backend so that it is ready for use + -- by the dynamic backend loader. + -- + procedure Register_Factory_Oracle; + pragma Import (C, Register_Factory_Oracle, + "register_factory_oracle"); + +end SOCI.Oracle; diff --git a/src/languages/ada/soci-postgresql.ads b/src/languages/ada/soci-postgresql.ads new file mode 100644 index 0000000000..fc750b2572 --- /dev/null +++ b/src/languages/ada/soci-postgresql.ads @@ -0,0 +1,16 @@ +-- Copyright (C) 2008-2011 Maciej Sobczak +-- Distributed under the Boost Software License, Version 1.0. +-- (See accompanying file LICENSE_1_0.txt or copy at +-- http://www.boost.org/LICENSE_1_0.txt) + +package SOCI.PostgreSQL is + + -- + -- Registers the PostgreSQL backend so that it is ready for use + -- by the dynamic backend loader. + -- + procedure Register_Factory_PostgreSQL; + pragma Import (C, Register_Factory_PostgreSQL, + "register_factory_postgresql"); + +end SOCI.PostgreSQL; diff --git a/src/languages/ada/soci.adb b/src/languages/ada/soci.adb new file mode 100644 index 0000000000..7f72f1bbf6 --- /dev/null +++ b/src/languages/ada/soci.adb @@ -0,0 +1,1465 @@ +-- Copyright (C) 2008-2011 Maciej Sobczak +-- Distributed under the Boost Software License, Version 1.0. +-- (See accompanying file LICENSE_1_0.txt or copy at +-- http://www.boost.org/LICENSE_1_0.txt) + +with Ada.Strings.Fixed; +with Interfaces.C.Strings; + +package body SOCI is + + procedure Check_Session_State (Handle : in Session_Handle) is + + function Soci_Session_State (S : in Session_Handle) return Interfaces.C.int; + + pragma Import (C, Soci_Session_State, "soci_session_state"); + + function Soci_Session_Error_Message + (S : in Session_Handle) + return Interfaces.C.Strings.chars_ptr; + pragma Import (C, Soci_Session_Error_Message, "soci_session_error_message"); + + State : constant Interfaces.C.int := Soci_Session_State (Handle); + Bad_State : constant Interfaces.C.int := 0; + + use type Interfaces.C.int; + + begin + if State = Bad_State then + declare + Message : constant String := + Interfaces.C.Strings.Value (Soci_Session_Error_Message (Handle)); + begin + raise Database_Error with Message; + end; + end if; + end Check_Session_State; + + function Make_Session_Handle (Connection_String : in String) return Session_Handle is + + function Soci_Create_Session (C : in Interfaces.C.char_array) return Session_Handle; + pragma Import (C, Soci_Create_Session, "soci_create_session"); + + Connection_String_C : constant Interfaces.C.char_array := + Interfaces.C.To_C (Connection_String); + + Handle : constant Session_Handle := + Soci_Create_Session (Connection_String_C); + + begin + if Handle = Null_Session_Handle then + raise Database_Error with "Cannot create session object."; + else + return Handle; + end if; + end Make_Session_Handle; + + function Data_State_To_Int (State : in Data_State) return Interfaces.C.int is + begin + if State = Data_Not_Null then + return 1; + else + return 0; + end if; + end Data_State_To_Int; + + function Int_To_Data_State (State : in Interfaces.C.int) return Data_State is + use type Interfaces.C.int; + begin + if State /= 0 then + return Data_Not_Null; + else + return Data_Null; + end if; + end Int_To_Data_State; + + procedure Check_Is_Open (This : in Session'Class) is + begin + if not This.Initialized then + raise Database_Error with "Session is not initialized."; + end if; + end Check_Is_Open; + + procedure Check_Statement_State (Handle : in Statement_Handle) is + + function Soci_Statement_State (S : in Statement_Handle) return Interfaces.C.int; + pragma Import (C, Soci_Statement_State, "soci_statement_state"); + + function Soci_Statement_Error_Message + (S : in Statement_Handle) + return Interfaces.C.Strings.chars_ptr; + pragma Import (C, Soci_Statement_Error_Message, "soci_statement_error_message"); + + State : constant Interfaces.C.int := Soci_Statement_State (Handle); + Bad_State : constant Interfaces.C.int := 0; + + use type Interfaces.C.int; + + begin + if State = Bad_State then + declare + Message : constant String := + Interfaces.C.Strings.Value (Soci_Statement_Error_Message (Handle)); + begin + raise Database_Error with Message; + end; + end if; + end Check_Statement_State; + + function String_To_Time (Source : in String) return Ada.Calendar.Time is + + Year_N : Natural; + Month_N : Natural; + Day_N : Natural; + Hour_N : Natural; + Minute_N : Natural; + Second_N : Natural; + + procedure Get_Next_Number + (Source : in String; + Position : in out Natural; + Result : out Natural) is + + I : Natural; + begin + I := Ada.Strings.Fixed.Index (Source => Source, + Pattern => " ", + From => Position); + + if I /= 0 then + Result := Natural'Value (Source (Position .. I)); + Position := I + 1; + else + Result := Natural'Value (Source (Position .. Source'Last)); + Position := 0; + end if; + end Get_Next_Number; + + Pos : Natural := 1; + + begin + + Get_Next_Number (Source => Source, Position => Pos, Result => Year_N); + Get_Next_Number (Source => Source, Position => Pos, Result => Month_N); + Get_Next_Number (Source => Source, Position => Pos, Result => Day_N); + Get_Next_Number (Source => Source, Position => Pos, Result => Hour_N); + Get_Next_Number (Source => Source, Position => Pos, Result => Minute_N); + Get_Next_Number (Source => Source, Position => Pos, Result => Second_N); + + return Ada.Calendar.Time_Of (Year_N, Month_N, Day_N, + Duration (Hour_N * 3_600 + Minute_N * 60 + Second_N)); + + end String_To_Time; + + function Time_To_String (Date : in Ada.Calendar.Time) return String is + + Year : Ada.Calendar.Year_Number; + Month : Ada.Calendar.Month_Number; + Day : Ada.Calendar.Day_Number; + Seconds : Ada.Calendar.Day_Duration; + + Hour : Natural; + Minute : Natural; + Seconds_N : Natural; + + begin + Ada.Calendar.Split (Date, Year, Month, Day, Seconds); + Seconds_N := Natural (Seconds); + + Hour := Seconds_N / 3_600; + Minute := (Seconds_N - Natural (Hour) * 3_600) / 60; + Seconds_N := Seconds_N - Natural (Hour) * 3_600 - Natural (Minute) * 60; + return Ada.Calendar.Year_Number'Image (Year) & " " & + Ada.Calendar.Month_Number'Image (Month) & " " & + Ada.Calendar.Day_Number'Image (Day) & " " & + Natural'Image (Hour) & " " & + Natural'Image (Minute) & " " & + Natural'Image (Seconds_N); + end Time_To_String; + + + function Make_Session (Connection_String : in String) return Session is + begin + return S : Session do + S.Handle := Make_Session_Handle (Connection_String); + S.Initialized := True; + Check_Session_State (S.Handle); + end return; + end Make_Session; + + procedure Open (This : in out Session; Connection_String : in String) is + begin + if This.Initialized then + raise Database_Error with "Session is already initialized."; + else + declare + Handle : constant Session_Handle := + Make_Session_Handle (Connection_String); + begin + Check_Session_State (Handle); + + This.Handle := Handle; + This.Initialized := True; + end; + end if; + end Open; + + procedure Close (This : in out Session) is + + procedure Soci_Destroy_Session (S : in Session_Handle); + pragma Import (C, Soci_Destroy_Session, "soci_destroy_session"); + + begin + if This.Initialized then + if This.Belongs_To_Pool then + raise Database_Error with "Cannot close session - not an owner (session in pool)."; + else + Soci_Destroy_Session (This.Handle); + This.Initialized := False; + end if; + end if; + end Close; + + function Is_Open (This : in Session) return Boolean is + begin + return This.Initialized; + end Is_Open; + + procedure Finalize (This : in out Session) is + begin + if This.Initialized then + if This.Belongs_To_Pool then + This.Pool.all.Give_Back (This.Position_In_Pool); + This.Initialized := False; + else + This.Close; + end if; + end if; + end Finalize; + + procedure Start (This : in Session) is + + procedure Soci_Begin (S : in Session_Handle); + pragma Import (C, Soci_Begin, "soci_begin"); + + begin + Check_Is_Open (This); + Soci_Begin (This.Handle); + Check_Session_State (This.Handle); + end Start; + + procedure Commit (This : in Session) is + + procedure Soci_Commit (S : in Session_Handle); + pragma Import (C, Soci_Commit, "soci_commit"); + + begin + Check_Is_Open (This); + Soci_Commit (This.Handle); + Check_Session_State (This.Handle); + end Commit; + + procedure Rollback (This : in Session) is + + procedure Soci_Rollback (S : in Session_Handle); + pragma Import (C, Soci_Rollback, "soci_rollback"); + + begin + Check_Is_Open (This); + Soci_Rollback (This.Handle); + Check_Session_State (This.Handle); + end Rollback; + + procedure Execute (This : in Session; Query : in String) is + S : Statement := Make_Statement (This); + begin + S.Prepare (Query); + S.Execute; + end Execute; + + protected body Connection_Pool_PS is + + procedure Open (Position : in Positive; Connection_String : in String) is + begin + if Position > Size then + raise Database_Error with "Index out of range."; + end if; + + Connections (Position).Open (Connection_String); + end Open; + + procedure Close (Position : in Positive) is + begin + if Position > Size then + raise Database_Error with "Index out of range."; + end if; + + if Is_Used (Position) then + raise Database_Error with "Cannot close connection that is currently in use."; + end if; + + Connections (Position).Close; + end Close; + + entry Lease (S : in out Session'Class) when Available is + Found : Boolean := False; + begin + if S.Initialized then + raise Database_Error with "This session is already initialized."; + end if; + + -- Find some connection in the pool that is not currently used. + for I in 1 .. Size loop + if not Is_Used (I) then + Check_Is_Open (Connections (I)); + + S.Handle := Connections (I).Handle; + S.Initialized := True; + S.Belongs_To_Pool := True; + S.Position_In_Pool := I; + + -- WORKAROUND: + -- The S.Pool component is set in the Lease procedure + -- of the Connection_Pool type, because here the access + -- to the protected object could not be taken (compiler bug). + + Is_Used (I) := True; + Found := True; + exit; + end if; + end loop; + + if not Found then + raise Database_Error with "Internal error."; + end if; + + -- Update the Available flag. + Found := False; + for I in 1 .. Size loop + if not Is_Used (I) then + Found := True; + exit; + end if; + end loop; + Available := Found; + + end Lease; + + procedure Give_Back (Position : in Positive) is + begin + if Position > Size then + raise Database_Error with "Index out of range."; + end if; + + if not Is_Used (Position) then + raise Database_Error with "Cannot give back connection that is not in use."; + end if; + + Is_Used (Position) := False; + Available := True; + end Give_Back; + + end Connection_Pool_PS; + + procedure Open + (This : in out Connection_Pool; + Position : in Positive; + Connection_String : in String) is + begin + This.Pool.Open (Position, Connection_String); + end Open; + + procedure Close (This : in out Connection_Pool; Position : in Positive) is + begin + This.Pool.Close (Position); + end Close; + + procedure Lease (This : in out Connection_Pool; S : in out Session'Class) is + begin + This.Pool.Lease (S); + + -- WORKAROUND: + -- The S.Pool component is set here because the access + -- to protected object cannot be taken in protected body (compiler bug.) + + -- JUSTIFICATION: + -- The Unchecked_Access is taken here to enable the session to properly + -- "unregister" from the pool in Session's Finalize. + -- An alternative would be to rely on the user to explicitly unlock + -- the appropriate entry in the pool, which is too error prone. + -- It is assumed that connection pool always has wider lifetime + -- than that of the session which is temporarily leased from the pool + -- - this guarantees that S.Pool always points to a valid pool object. + + S.Pool := This.Pool'Unchecked_Access; + end Lease; + + function Make_Statement (Sess : in Session'Class) return Statement is + + function Soci_Create_Statement (Sess : in Session_Handle) return Statement_Handle; + pragma Import (C, Soci_Create_Statement, "soci_create_statement"); + + begin + Check_Is_Open (Sess); + + declare + Handle : constant Statement_Handle := + Soci_Create_Statement (Sess.Handle); + begin + + return S : Statement do + S.Handle := Handle; + S.Initialized := True; + Check_Statement_State (S.Handle); + end return; + end; + end Make_Statement; + + procedure Finalize (This : in out Statement) is + + procedure Soci_Destroy_Statement (S : in Statement_Handle); + pragma Import (C, Soci_Destroy_Statement, "soci_destroy_statement"); + + begin + if This.Initialized then + Soci_Destroy_Statement (This.Handle); + This.Initialized := False; + end if; + end Finalize; + + procedure Prepare (This : in Statement; Query : in String) is + + procedure Soci_Prepare (St : in Statement_Handle; Q : in Interfaces.C.char_array); + pragma Import (C, Soci_Prepare, "soci_prepare"); + + Query_C : constant Interfaces.C.char_array := Interfaces.C.To_C (Query); + + begin + Soci_Prepare (This.Handle, Query_C); + Check_Statement_State (This.Handle); + end Prepare; + + procedure Execute (This : in Statement; With_Data_Exchange : in Boolean := False) is + Result : constant Boolean := This.Execute (With_Data_Exchange); + begin + null; + end Execute; + + function Execute + (This : in Statement; + With_Data_Exchange : in Boolean := False) + return Boolean is + + function Soci_Execute + (St : in Statement_Handle; + WDE : in Interfaces.C.int) + return Interfaces.C.int; + pragma Import (C, Soci_Execute, "soci_execute"); + + WDE_C : Interfaces.C.int; + Result : Interfaces.C.int; + + use type Interfaces.C.int; + + begin + if With_Data_Exchange then + WDE_C := 1; + else + WDE_C := 0; + end if; + + Result := Soci_Execute (This.Handle, WDE_C); + Check_Statement_State (This.Handle); + + return Result /= 0; + end Execute; + + function Fetch (This : in Statement) return Boolean is + + function Soci_Fetch (St : in Statement_Handle) return Interfaces.C.int; + pragma Import (C, Soci_Fetch, "soci_fetch"); + + Result : constant Interfaces.C.int := Soci_Fetch (This.Handle); + + use type Interfaces.C.int; + + begin + Check_Statement_State (This.Handle); + return Result /= 0; + end Fetch; + + function Got_Data (This : in Statement) return Boolean is + + function Soci_Got_Data (St : in Statement_Handle) return Interfaces.C.int; + pragma Import (C, Soci_Got_Data, "soci_got_data"); + + Result : constant Interfaces.C.int := Soci_Got_Data (This.Handle); + + use type Interfaces.C.int; + + begin + Check_Statement_State (This.Handle); + return Result /= 0; + end Got_Data; + + function Into_String (This : in Statement) return Into_Position is + + function Soci_Into_String (St : in Statement_Handle) return Interfaces.C.int; + pragma Import (C, Soci_Into_String, "soci_into_string"); + + Result : constant Interfaces.C.int := Soci_Into_String (This.Handle); + + begin + Check_Statement_State (This.Handle); + return Into_Position (Result); + end Into_String; + + function Into_Integer (This : in Statement) return Into_Position is + + function Soci_Into_Int (St : in Statement_Handle) return Interfaces.C.int; + pragma Import (C, Soci_Into_Int, "soci_into_int"); + + Result : constant Interfaces.C.int := Soci_Into_Int (This.Handle); + + begin + Check_Statement_State (This.Handle); + return Into_Position (Result); + end Into_Integer; + + function Into_Long_Long_Integer (This : in Statement) return Into_Position is + + function Soci_Into_Long_Long (St : in Statement_Handle) return Interfaces.C.int; + pragma Import (C, Soci_Into_Long_Long, "soci_into_long_long"); + + Result : constant Interfaces.C.int := Soci_Into_Long_Long (This.Handle); + + begin + Check_Statement_State (This.Handle); + return Into_Position (Result); + end Into_Long_Long_Integer; + + function Into_Long_Float (This : in Statement) return Into_Position is + + function Soci_Into_Double (St : in Statement_Handle) return Interfaces.C.int; + pragma Import (C, Soci_Into_Double, "soci_into_double"); + + Result : constant Interfaces.C.int := Soci_Into_Double (This.Handle); + + begin + Check_Statement_State (This.Handle); + return Into_Position (Result); + end Into_Long_Float; + + function Into_Time (This : in Statement) return Into_Position is + + function Soci_Into_Date (St : in Statement_Handle) return Interfaces.C.int; + pragma Import (C, Soci_Into_Date, "soci_into_date"); + + Result : constant Interfaces.C.int := Soci_Into_Date (This.Handle); + + begin + Check_Statement_State (This.Handle); + return Into_Position (Result); + end Into_Time; + + function Into_Vector_String (This : in Statement) return Into_Position is + + function Soci_Into_String_V (St : in Statement_Handle) return Interfaces.C.int; + pragma Import (C, Soci_Into_String_V, "soci_into_string_v"); + + Result : constant Interfaces.C.int := Soci_Into_String_V (This.Handle); + + begin + Check_Statement_State (This.Handle); + return Into_Position (Result); + end Into_Vector_String; + + function Into_Vector_Integer (This : in Statement) return Into_Position is + + function Soci_Into_Int_V (St : in Statement_Handle) return Interfaces.C.int; + pragma Import (C, Soci_Into_Int_V, "soci_into_int_v"); + + Result : constant Interfaces.C.int := Soci_Into_Int_V (This.Handle); + + begin + Check_Statement_State (This.Handle); + return Into_Position (Result); + end Into_Vector_Integer; + + function Into_Vector_Long_Long_Integer (This : in Statement) return Into_Position is + + function Soci_Into_Long_Long_V (St : in Statement_Handle) return Interfaces.C.int; + pragma Import (C, Soci_Into_Long_Long_V, "soci_into_long_long_v"); + + Result : constant Interfaces.C.int := Soci_Into_Long_Long_V (This.Handle); + + begin + Check_Statement_State (This.Handle); + return Into_Position (Result); + end Into_Vector_Long_Long_Integer; + + function Into_Vector_Long_Float (This : in Statement) return Into_Position is + + function Soci_Into_Double_V (St : in Statement_Handle) return Interfaces.C.int; + pragma Import (C, Soci_Into_Double_V, "soci_into_double_v"); + + Result : constant Interfaces.C.int := Soci_Into_Double_V (This.Handle); + + begin + Check_Statement_State (This.Handle); + return Into_Position (Result); + end Into_Vector_Long_Float; + + function Into_Vector_Time (This : in Statement) return Into_Position is + + function Soci_Into_Date_V (St : in Statement_Handle) return Interfaces.C.int; + pragma Import (C, Soci_Into_Date_V, "soci_into_date_v"); + + Result : constant Interfaces.C.int := Soci_Into_Date_V (This.Handle); + + begin + Check_Statement_State (This.Handle); + return Into_Position (Result); + end Into_Vector_Time; + + function Get_Into_State + (This : in Statement; + Position : in Into_Position) + return Data_State is + + function Soci_Get_Into_State + (St : in Statement_Handle; + P : in Interfaces.C.int) + return Interfaces.C.int; + pragma Import (C, Soci_Get_Into_State, "soci_get_into_state"); + + Result : constant Interfaces.C.int := + Soci_Get_Into_State (This.Handle, Interfaces.C.int (Position)); + + use type Interfaces.C.int; + + begin + Check_Statement_State (This.Handle); + return Int_To_Data_State (Result); + end Get_Into_State; + + function Get_Into_String + (This : in Statement; + Position : in Into_Position) + return String is + + function Soci_Get_Into_String + (St : in Statement_Handle; + P : in Interfaces.C.int) + return Interfaces.C.Strings.chars_ptr; + pragma Import (C, Soci_Get_Into_String, "soci_get_into_string"); + + Result : constant Interfaces.C.Strings.chars_ptr := + Soci_Get_Into_String (This.Handle, Interfaces.C.int (Position)); + + begin + Check_Statement_State (This.Handle); + return Interfaces.C.Strings.Value (Result); + end Get_Into_String; + + function Get_Into_Integer + (This : in Statement; + Position : in Into_Position) + return DB_Integer is + + function Soci_Get_Into_Int + (St : in Statement_Handle; + P : in Interfaces.C.int) + return Interfaces.C.int; + pragma Import (C, Soci_Get_Into_Int, "soci_get_into_int"); + + Result : constant Interfaces.C.int := + Soci_Get_Into_Int (This.Handle, Interfaces.C.int (Position)); + + begin + Check_Statement_State (This.Handle); + return DB_Integer (Result); + end Get_Into_Integer; + + function Get_Into_Long_Long_Integer + (This : in Statement; + Position : in Into_Position) + return DB_Long_Long_Integer is + + function Soci_Get_Into_Long_Long + (St : in Statement_Handle; + P : in Interfaces.C.int) + return Interfaces.Integer_64; + pragma Import (C, Soci_Get_Into_Long_Long, "soci_get_into_long_long"); + + Result : constant Interfaces.Integer_64 := + Soci_Get_Into_Long_Long (This.Handle, Interfaces.C.int (Position)); + + begin + Check_Statement_State (This.Handle); + return DB_Long_Long_Integer (Result); + end Get_Into_Long_Long_Integer; + + function Get_Into_Long_Float + (This : in Statement; + Position : in Into_Position) + return DB_Long_Float is + + function Soci_Get_Into_Double + (St : in Statement_Handle; + P : in Interfaces.C.int) + return Interfaces.C.double; + pragma Import (C, Soci_Get_Into_Double, "soci_get_into_double"); + + Result : constant Interfaces.C.double := + Soci_Get_Into_Double (This.Handle, Interfaces.C.int (Position)); + + begin + Check_Statement_State (This.Handle); + return DB_Long_Float (Result); + end Get_Into_Long_Float; + + function Get_Into_Time + (This : in Statement; + Position : in Into_Position) + return Ada.Calendar.Time is + + function Soci_Get_Into_Date + (St : in Statement_Handle; + P : in Interfaces.C.int) + return Interfaces.C.Strings.chars_ptr; + pragma Import (C, Soci_Get_Into_Date, "soci_get_into_date"); + + Result_C : constant Interfaces.C.Strings.chars_ptr := + Soci_Get_Into_Date (This.Handle, Interfaces.C.int (Position)); + Result : constant String := Interfaces.C.Strings.Value (Result_C); + + begin + Check_Statement_State (This.Handle); + return String_To_Time (Result); + end Get_Into_Time; + + function Get_Into_Vectors_Size (This : in Statement) return Natural is + + function Soci_Into_Get_Size_V + (St : in Statement_Handle) + return Interfaces.C.int; + pragma Import (C, Soci_Into_Get_Size_V, "soci_into_get_size_v"); + + Result_C : constant Interfaces.C.int := Soci_Into_Get_Size_V (This.Handle); + + begin + Check_Statement_State (This.Handle); + return Natural (Result_C); + end Get_Into_Vectors_Size; + + function Into_Vectors_First_Index (This : in Statement) return Vector_Index is + begin + return 0; + end Into_Vectors_First_Index; + + function Into_Vectors_Last_Index (This : in Statement) return Vector_Index is + begin + return Vector_Index (This.Get_Into_Vectors_Size - 1); + end Into_Vectors_Last_Index; + + procedure Into_Vectors_Resize (This : in Statement; New_Size : in Natural) is + + procedure Soci_Into_Resize_V + (St : in Statement_Handle; + New_Size : in Interfaces.C.int); + pragma Import (C, Soci_Into_Resize_V, "soci_into_resize_v"); + + begin + Soci_Into_Resize_V (This.Handle, Interfaces.C.int (New_Size)); + Check_Statement_State (This.Handle); + end Into_Vectors_Resize; + + function Get_Into_Vector_State + (This : in Statement; + Position : in Into_Position; + Index : in Vector_Index) + return Data_State is + + function Soci_Get_Into_State_V + (St : in Statement_Handle; + P : in Interfaces.C.int; + I : in Interfaces.C.int) + return Interfaces.C.int; + pragma Import (C, Soci_Get_Into_State_V, "soci_get_into_state_v"); + + Result : constant Interfaces.C.int := + Soci_Get_Into_State_V + (This.Handle, + Interfaces.C.int (Position), + Interfaces.C.int (Index)); + + use type Interfaces.C.int; + + begin + Check_Statement_State (This.Handle); + return Int_To_Data_State (Result); + end Get_Into_Vector_State; + + function Get_Into_Vector_String + (This : in Statement; + Position : in Into_Position; + Index : in Vector_Index) return String is + + function Soci_Get_Into_String_V + (St : in Statement_Handle; + P : in Interfaces.C.int; + I : in Interfaces.C.int) + return Interfaces.C.Strings.chars_ptr; + pragma Import (C, Soci_Get_Into_String_V, "soci_get_into_string_v"); + + Result : constant Interfaces.C.Strings.chars_ptr := + Soci_Get_Into_String_V (This.Handle, + Interfaces.C.int (Position), + Interfaces.C.int (Index)); + + begin + Check_Statement_State (This.Handle); + return Interfaces.C.Strings.Value (Result); + end Get_Into_Vector_String; + + function Get_Into_Vector_Integer + (This : in Statement; + Position : in Into_Position; + Index : in Vector_Index) + return DB_Integer is + + function Soci_Get_Into_Int_V + (St : in Statement_Handle; + P : in Interfaces.C.int; + I : in Interfaces.C.int) + return Interfaces.C.int; + pragma Import (C, Soci_Get_Into_Int_V, "soci_get_into_int_v"); + + Result : constant Interfaces.C.int := + Soci_Get_Into_Int_V (This.Handle, + Interfaces.C.int (Position), + Interfaces.C.int (Index)); + + begin + Check_Statement_State (This.Handle); + return DB_Integer (Result); + end Get_Into_Vector_Integer; + + function Get_Into_Vector_Long_Long_Integer + (This : in Statement; + Position : in Into_Position; + Index : in Vector_Index) + return DB_Long_Long_Integer is + + function Soci_Get_Into_Long_Long_V + (St : in Statement_Handle; + P : in Interfaces.C.int; + I : in Interfaces.C.int) + return Interfaces.Integer_64; + pragma Import (C, Soci_Get_Into_Long_Long_V, "soci_get_into_long_long_v"); + + Result : constant Interfaces.Integer_64 := + Soci_Get_Into_Long_Long_V (This.Handle, + Interfaces.C.int (Position), + Interfaces.C.int (Index)); + + begin + Check_Statement_State (This.Handle); + return DB_Long_Long_Integer (Result); + end Get_Into_Vector_Long_Long_Integer; + + function Get_Into_Vector_Long_Float + (This : in Statement; + Position : in Into_Position; + Index : in Vector_Index) + return DB_Long_Float is + + function Soci_Get_Into_Double_V + (St : in Statement_Handle; + P : in Interfaces.C.int; + I : in Interfaces.C.int) + return Interfaces.C.double; + pragma Import (C, Soci_Get_Into_Double_V, "soci_get_into_double_v"); + + Result : constant Interfaces.C.double := + Soci_Get_Into_Double_V (This.Handle, + Interfaces.C.int (Position), + Interfaces.C.int (Index)); + + begin + Check_Statement_State (This.Handle); + return DB_Long_Float (Result); + end Get_Into_Vector_Long_Float; + + function Get_Into_Vector_Time + (This : in Statement; + Position : in Into_Position; + Index : in Vector_Index) + return Ada.Calendar.Time is + + function Soci_Get_Into_Date_V + (St : in Statement_Handle; + P : in Interfaces.C.int; + I : in Interfaces.C.int) + return Interfaces.C.Strings.chars_ptr; + pragma Import (C, Soci_Get_Into_Date_V, "soci_get_into_date_v"); + + Result_C : constant Interfaces.C.Strings.chars_ptr := + Soci_Get_Into_Date_V (This.Handle, + Interfaces.C.int (Position), + Interfaces.C.int (Index)); + Result : constant String := Interfaces.C.Strings.Value (Result_C); + + begin + Check_Statement_State (This.Handle); + return String_To_Time (Result); + end Get_Into_Vector_Time; + + procedure Use_String (This : in Statement; Name : in String) is + + procedure Soci_Use_String + (St : in Statement_Handle; + Name : in Interfaces.C.char_array); + pragma Import (C, Soci_Use_String, "soci_use_string"); + + Name_C : constant Interfaces.C.char_array := Interfaces.C.To_C (Name); + + begin + Soci_Use_String (This.Handle, Name_C); + Check_Statement_State (This.Handle); + end Use_String; + + procedure Use_Integer (This : in Statement; Name : in String) is + + procedure Soci_Use_Int + (St : in Statement_Handle; + Name : in Interfaces.C.char_array); + pragma Import (C, Soci_Use_Int, "soci_use_int"); + + Name_C : constant Interfaces.C.char_array := Interfaces.C.To_C (Name); + + begin + Soci_Use_Int (This.Handle, Name_C); + Check_Statement_State (This.Handle); + end Use_Integer; + + procedure Use_Long_Long_Integer (This : in Statement; Name : in String) is + + procedure Soci_Use_Long_Long + (St : in Statement_Handle; + Name : in Interfaces.C.char_array); + pragma Import (C, Soci_Use_Long_Long, "soci_use_long_long"); + + Name_C : constant Interfaces.C.char_array := Interfaces.C.To_C (Name); + + begin + Soci_Use_Long_Long (This.Handle, Name_C); + Check_Statement_State (This.Handle); + end Use_Long_Long_Integer; + + procedure Use_Long_Float (This : in Statement; Name : in String) is + + procedure Soci_Use_Double + (St : in Statement_Handle; + Name : in Interfaces.C.char_array); + pragma Import (C, Soci_Use_Double, "soci_use_double"); + + Name_C : constant Interfaces.C.char_array := Interfaces.C.To_C (Name); + + begin + Soci_Use_Double (This.Handle, Name_C); + Check_Statement_State (This.Handle); + end Use_Long_Float; + + procedure Use_Time (This : in Statement; Name : in String) is + + procedure Soci_Use_Date + (St : in Statement_Handle; + Name : in Interfaces.C.char_array); + pragma Import (C, Soci_Use_Date, "soci_use_date"); + + Name_C : constant Interfaces.C.char_array := Interfaces.C.To_C (Name); + + begin + Soci_Use_Date (This.Handle, Name_C); + Check_Statement_State (This.Handle); + end Use_Time; + + procedure Use_Vector_String (This : in Statement; Name : in String) is + + procedure Soci_Use_String_V + (St : in Statement_Handle; + Name : in Interfaces.C.char_array); + pragma Import (C, Soci_Use_String_V, "soci_use_string_v"); + + Name_C : constant Interfaces.C.char_array := Interfaces.C.To_C (Name); + + begin + Soci_Use_String_V (This.Handle, Name_C); + Check_Statement_State (This.Handle); + end Use_Vector_String; + + procedure Use_Vector_Integer (This : in Statement; Name : in String) is + + procedure Soci_Use_Int_V + (St : in Statement_Handle; + Name : in Interfaces.C.char_array); + pragma Import (C, Soci_Use_Int_V, "soci_use_int_v"); + + Name_C : constant Interfaces.C.char_array := Interfaces.C.To_C (Name); + + begin + Soci_Use_Int_V (This.Handle, Name_C); + Check_Statement_State (This.Handle); + end Use_Vector_Integer; + + procedure Use_Vector_Long_Long_Integer (This : in Statement; Name : in String) is + + procedure Soci_Use_Long_Long_V + (St : in Statement_Handle; + Name : in Interfaces.C.char_array); + pragma Import (C, Soci_Use_Long_Long_V, "soci_use_long_long_v"); + + Name_C : constant Interfaces.C.char_array := Interfaces.C.To_C (Name); + + begin + Soci_Use_Long_Long_V (This.Handle, Name_C); + Check_Statement_State (This.Handle); + end Use_Vector_Long_Long_Integer; + + procedure Use_Vector_Long_Float (This : in Statement; Name : in String) is + + procedure Soci_Use_Double_V + (St : in Statement_Handle; + Name : in Interfaces.C.char_array); + pragma Import (C, Soci_Use_Double_V, "soci_use_double_v"); + + Name_C : constant Interfaces.C.char_array := Interfaces.C.To_C (Name); + + begin + Soci_Use_Double_V (This.Handle, Name_C); + Check_Statement_State (This.Handle); + end Use_Vector_Long_Float; + + procedure Use_Vector_Time (This : in Statement; Name : in String) is + + procedure Soci_Use_Date_V + (St : in Statement_Handle; + Name : in Interfaces.C.char_array); + pragma Import (C, Soci_Use_Date_V, "soci_use_date_v"); + + Name_C : constant Interfaces.C.char_array := Interfaces.C.To_C (Name); + + begin + Soci_Use_Date_V (This.Handle, Name_C); + Check_Statement_State (This.Handle); + end Use_Vector_Time; + + procedure Set_Use_State + (This : in Statement; + Name : in String; + State : in Data_State) is + + procedure Soci_Set_Use_State + (St : in Statement_Handle; + Name : in Interfaces.C.char_array; + State : in Interfaces.C.int); + pragma Import (C, Soci_Set_Use_State, "soci_set_use_state"); + + Name_C : constant Interfaces.C.char_array := Interfaces.C.To_C (Name); + State_C : constant Interfaces.C.int := Data_State_To_Int (State); + + begin + Soci_Set_Use_State (This.Handle, Name_C, State_C); + Check_Statement_State (This.Handle); + end Set_Use_State; + + procedure Set_Use_String + (This : in Statement; + Name : in String; + Value : in String) is + + procedure Soci_Set_Use_String + (St : in Statement_Handle; + Name : in Interfaces.C.char_array; + Value : in Interfaces.C.char_array); + pragma Import (C, Soci_Set_Use_String, "soci_set_use_string"); + + Name_C : constant Interfaces.C.char_array := Interfaces.C.To_C (Name); + Value_C : constant Interfaces.C.char_array := Interfaces.C.To_C (Value); + + begin + Soci_Set_Use_String (This.Handle, Name_C, Value_C); + Check_Statement_State (This.Handle); + end Set_Use_String; + + procedure Set_Use_Integer + (This : in Statement; + Name : in String; + Value : in DB_Integer) is + + procedure Soci_Set_Use_Int + (St : in Statement_Handle; + Name : in Interfaces.C.char_array; + Value : in Interfaces.C.int); + pragma Import (C, Soci_Set_Use_Int, "soci_set_use_int"); + + Name_C : constant Interfaces.C.char_array := Interfaces.C.To_C (Name); + Value_C : constant Interfaces.C.int := Interfaces.C.int (Value); + + begin + Soci_Set_Use_Int (This.Handle, Name_C, Value_C); + Check_Statement_State (This.Handle); + end Set_Use_Integer; + + procedure Set_Use_Long_Long_Integer + (This : in Statement; + Name : in String; + Value : in DB_Long_Long_Integer) is + + procedure Soci_Set_Use_Long_Long + (St : in Statement_Handle; + Name : in Interfaces.C.char_array; + Value : in Interfaces.Integer_64); + pragma Import (C, Soci_Set_Use_Long_Long, "soci_set_use_long_long"); + + Name_C : constant Interfaces.C.char_array := Interfaces.C.To_C (Name); + Value_C : constant Interfaces.Integer_64 := Interfaces.Integer_64 (Value); + + begin + Soci_Set_Use_Long_Long (This.Handle, Name_C, Value_C); + Check_Statement_State (This.Handle); + end Set_Use_Long_Long_Integer; + + procedure Set_Use_Long_Float + (This : in Statement; + Name : in String; + Value : in DB_Long_Float) is + + procedure Soci_Set_Use_Double + (St : in Statement_Handle; + Name : in Interfaces.C.char_array; + Value : in Interfaces.C.double); + pragma Import (C, Soci_Set_Use_Double, "soci_set_use_double"); + + Name_C : constant Interfaces.C.char_array := Interfaces.C.To_C (Name); + Value_C : constant Interfaces.C.double := Interfaces.C.double (Value); + + begin + Soci_Set_Use_Double (This.Handle, Name_C, Value_C); + Check_Statement_State (This.Handle); + end Set_Use_Long_Float; + + procedure Set_Use_Time + (This : in Statement; + Name : in String; + Value : in Ada.Calendar.Time) is + + procedure Soci_Set_Use_Date + (St : in Statement_Handle; + Name : in Interfaces.C.char_array; + Value : in Interfaces.C.char_array); + pragma Import (C, Soci_Set_Use_Date, "soci_set_use_date"); + + Name_C : constant Interfaces.C.char_array := Interfaces.C.To_C (Name); + Value_C : constant Interfaces.C.char_array := + Interfaces.C.To_C (Time_To_String (Value)); + + begin + Soci_Set_Use_Date (This.Handle, Name_C, Value_C); + Check_Statement_State (This.Handle); + end Set_Use_Time; + + function Get_Use_Vectors_Size (This : in Statement) return Natural is + + function Soci_Use_Get_Size_V + (St : in Statement_Handle) + return Interfaces.C.int; + pragma Import (C, Soci_Use_Get_Size_V, "soci_use_get_size_v"); + + Result_C : constant Interfaces.C.int := Soci_Use_Get_Size_V (This.Handle); + + begin + Check_Statement_State (This.Handle); + return Natural (Result_C); + end Get_Use_Vectors_Size; + + function Use_Vectors_First_Index (This : in Statement) return Vector_Index is + begin + return 0; + end Use_Vectors_First_Index; + + function Use_Vectors_Last_Index (This : in Statement) return Vector_Index is + begin + return Vector_Index (This.Get_Use_Vectors_Size - 1); + end Use_Vectors_Last_Index; + + procedure Use_Vectors_Resize (This : in Statement; New_Size : in Natural) is + + procedure Soci_Use_Resize_V + (St : in Statement_Handle; + New_Size : in Interfaces.C.int); + pragma Import (C, Soci_Use_Resize_V, "soci_use_resize_v"); + + begin + Soci_Use_Resize_V (This.Handle, Interfaces.C.int (New_Size)); + Check_Statement_State (This.Handle); + end Use_Vectors_Resize; + + procedure Set_Use_Vector_State + (This : in Statement; + Name : in String; + Index : in Vector_Index; + State : in Data_State) is + + procedure Soci_Set_Use_State_V + (St : in Statement_Handle; + Name : in Interfaces.C.char_array; + Index : in Interfaces.C.int; + State : in Interfaces.C.int); + pragma Import (C, Soci_Set_Use_State_V, "soci_set_use_state_v"); + + Name_C : constant Interfaces.C.char_array := Interfaces.C.To_C (Name); + Index_C : constant Interfaces.C.int := Interfaces.C.int (Index); + State_C : constant Interfaces.C.int := Data_State_To_Int (State); + + begin + Soci_Set_Use_State_V (This.Handle, Name_C, Index_C, State_C); + Check_Statement_State (This.Handle); + end Set_Use_Vector_State; + + procedure Set_Use_Vector_String + (This : in Statement; + Name : in String; + Index : in Vector_Index; + Value : in String) is + + procedure Soci_Set_Use_String_V + (St : in Statement_Handle; + Name : in Interfaces.C.char_array; + Index : in Interfaces.C.int; + Value : in Interfaces.C.char_array); + pragma Import (C, Soci_Set_Use_String_V, "soci_set_use_string_v"); + + Name_C : constant Interfaces.C.char_array := Interfaces.C.To_C (Name); + Index_C : constant Interfaces.C.int := Interfaces.C.int (Index); + Value_C : constant Interfaces.C.char_array := Interfaces.C.To_C (Value); + + begin + Soci_Set_Use_String_V (This.Handle, Name_C, Index_C, Value_C); + Check_Statement_State (This.Handle); + end Set_Use_Vector_String; + + procedure Set_Use_Vector_Integer + (This : in Statement; + Name : in String; + Index : in Vector_Index; + Value : in DB_Integer) is + + procedure Soci_Set_Use_Int_V + (St : in Statement_Handle; + Name : in Interfaces.C.char_array; + Index : in Interfaces.C.int; + Value : in Interfaces.C.int); + pragma Import (C, Soci_Set_Use_Int_V, "soci_set_use_int_v"); + + Name_C : constant Interfaces.C.char_array := Interfaces.C.To_C (Name); + Index_C : constant Interfaces.C.int := Interfaces.C.int (Index); + Value_C : constant Interfaces.C.int := Interfaces.C.int (Value); + + begin + Soci_Set_Use_Int_V (This.Handle, Name_C, Index_C, Value_C); + Check_Statement_State (This.Handle); + end Set_Use_Vector_Integer; + + procedure Set_Use_Vector_Long_Long_Integer + (This : in Statement; + Name : in String; + Index : in Vector_Index; + Value : in DB_Long_Long_Integer) is + + procedure Soci_Set_Use_Long_Long_V + (St : in Statement_Handle; + Name : in Interfaces.C.char_array; + Index : in Interfaces.C.int; + Value : in Interfaces.Integer_64); + pragma Import (C, Soci_Set_Use_Long_Long_V, "soci_set_use_long_long_v"); + + Name_C : constant Interfaces.C.char_array := Interfaces.C.To_C (Name); + Index_C : constant Interfaces.C.int := Interfaces.C.int (Index); + Value_C : constant Interfaces.Integer_64 := Interfaces.Integer_64 (Value); + + begin + Soci_Set_Use_Long_Long_V (This.Handle, Name_C, Index_C, Value_C); + Check_Statement_State (This.Handle); + end Set_Use_Vector_Long_Long_Integer; + + procedure Set_Use_Vector_Long_Float + (This : in Statement; + Name : in String; + Index : in Vector_Index; + Value : in DB_Long_Float) is + + procedure Soci_Set_Use_Double_V + (St : in Statement_Handle; + Name : in Interfaces.C.char_array; + Index : in Interfaces.C.int; + Value : in Interfaces.C.double); + pragma Import (C, Soci_Set_Use_Double_V, "soci_set_use_double_v"); + + Name_C : constant Interfaces.C.char_array := Interfaces.C.To_C (Name); + Index_C : constant Interfaces.C.int := Interfaces.C.int (Index); + Value_C : constant Interfaces.C.double := Interfaces.C.double (Value); + + begin + Soci_Set_Use_Double_V (This.Handle, Name_C, Index_C, Value_C); + Check_Statement_State (This.Handle); + end Set_Use_Vector_Long_Float; + + procedure Set_Use_Vector_Time + (This : in Statement; + Name : in String; + Index : in Vector_Index; + Value : in Ada.Calendar.Time) is + + procedure Soci_Set_Use_Date_V + (St : in Statement_Handle; + Name : in Interfaces.C.char_array; + Index : in Interfaces.C.int; + Value : in Interfaces.C.char_array); + pragma Import (C, Soci_Set_Use_Date_V, "soci_set_use_date_v"); + + Name_C : constant Interfaces.C.char_array := Interfaces.C.To_C (Name); + Index_C : constant Interfaces.C.int := Interfaces.C.int (Index); + Value_C : constant Interfaces.C.char_array := + Interfaces.C.To_C (Time_To_String (Value)); + + begin + Soci_Set_Use_Date_V (This.Handle, Name_C, Index_C, Value_C); + Check_Statement_State (This.Handle); + end Set_Use_Vector_Time; + + function Get_Use_State + (This : in Statement; + Name : in String) return Data_State is + + function Soci_Get_Use_State + (St : in Statement_Handle; + Name : in Interfaces.C.char_array) + return Interfaces.C.int; + pragma Import (C, Soci_Get_Use_State, "soci_get_use_state"); + + Name_C : constant Interfaces.C.char_array := Interfaces.C.To_C (Name); + Result : constant Interfaces.C.int := + Soci_Get_Use_State (This.Handle, Name_C); + + use type Interfaces.C.int; + + begin + Check_Statement_State (This.Handle); + return Int_To_Data_State (Result); + end Get_Use_State; + + function Get_Use_String + (This : in Statement; + Name : in String) + return String is + + function Soci_Get_Use_String + (St : in Statement_Handle; + Name : in Interfaces.C.char_array) + return Interfaces.C.Strings.chars_ptr; + pragma Import (C, Soci_Get_Use_String, "soci_get_use_string"); + + Name_C : constant Interfaces.C.char_array := Interfaces.C.To_C (Name); + Result : constant Interfaces.C.Strings.chars_ptr := + Soci_Get_Use_String (This.Handle, Name_C); + + begin + Check_Statement_State (This.Handle); + return Interfaces.C.Strings.Value (Result); + end Get_Use_String; + + function Get_Use_Integer + (This : in Statement; + Name : in String) + return DB_Integer is + + function Soci_Get_Use_Int + (St : in Statement_Handle; + Name : in Interfaces.C.char_array) + return Interfaces.C.int; + pragma Import (C, Soci_Get_Use_Int, "soci_get_use_int"); + + Name_C : constant Interfaces.C.char_array := Interfaces.C.To_C (Name); + Result : constant Interfaces.C.int := + Soci_Get_Use_Int (This.Handle, Name_C); + + begin + Check_Statement_State (This.Handle); + return DB_Integer (Result); + end Get_Use_Integer; + + function Get_Use_Long_Long_Integer + (This : in Statement; + Name : in String) + return DB_Long_Long_Integer is + + function Soci_Get_Use_Long_Long + (St : in Statement_Handle; + Name : in Interfaces.C.char_array) + return Interfaces.Integer_64; + pragma Import (C, Soci_Get_Use_Long_Long, "soci_get_use_long_long"); + + Name_C : constant Interfaces.C.char_array := Interfaces.C.To_C (Name); + Result : constant Interfaces.Integer_64 := + Soci_Get_Use_Long_Long (This.Handle, Name_C); + + begin + Check_Statement_State (This.Handle); + return DB_Long_Long_Integer (Result); + end Get_Use_Long_Long_Integer; + + function Get_Use_Long_Float + (This : in Statement; + Name : in String) + return DB_Long_Float is + + function Soci_Get_Use_Double + (St : in Statement_Handle; + Name : in Interfaces.C.char_array) + return Interfaces.C.double; + pragma Import (C, Soci_Get_Use_Double, "soci_get_use_double"); + + Name_C : constant Interfaces.C.char_array := Interfaces.C.To_C (Name); + Result : constant Interfaces.C.double := + Soci_Get_Use_Double (This.Handle, Name_C); + + begin + Check_Statement_State (This.Handle); + return DB_Long_Float (Result); + end Get_Use_Long_Float; + + function Get_Use_Time + (This : in Statement; + Name : in String) + return Ada.Calendar.Time is + + function Soci_Get_Use_Date + (St : in Statement_Handle; + Name : in Interfaces.C.char_array) + return Interfaces.C.Strings.chars_ptr; + pragma Import (C, Soci_Get_Use_Date, "soci_get_use_date"); + + Name_C : constant Interfaces.C.char_array := Interfaces.C.To_C (Name); + Result_C : constant Interfaces.C.Strings.chars_ptr := + Soci_Get_Use_Date (This.Handle, Name_C); + Result : constant String := Interfaces.C.Strings.Value (Result_C); + + begin + Check_Statement_State (This.Handle); + return String_To_Time (Result); + end Get_Use_Time; + +end SOCI; diff --git a/src/languages/ada/soci.ads b/src/languages/ada/soci.ads new file mode 100644 index 0000000000..93b59678d6 --- /dev/null +++ b/src/languages/ada/soci.ads @@ -0,0 +1,486 @@ +-- +-- Thin wrapper for the simple interface of the SOCI database access library. +-- +-- Copyright (C) 2008-2011 Maciej Sobczak +-- Distributed under the Boost Software License, Version 1.0. +-- (See accompanying file LICENSE_1_0.txt or copy at +-- http://www.boost.org/LICENSE_1_0.txt) + +with Ada.Calendar; +with Interfaces.C; + +private with System; +private with Ada.Finalization; + +package SOCI is + + -- + -- General exception related to database and library usage. + -- + + Database_Error : exception; + + -- + -- Session. + -- + + type Session is tagged limited private; + + not overriding + function Make_Session (Connection_String : in String) return Session; + + not overriding + procedure Open (This : in out Session; Connection_String : in String); + + not overriding + procedure Close (This : in out Session); + + not overriding + function Is_Open (This : in Session) return Boolean; + + -- Transaction management. + + not overriding + procedure Start (This : in Session); + + not overriding + procedure Commit (This : in Session); + + not overriding + procedure Rollback (This : in Session); + + -- Immediate query execution. + not overriding + procedure Execute (This : in Session; Query : in String); + + -- + -- Connection pool management. + -- + + type Connection_Pool (Size : Positive) is tagged limited private; + + not overriding + procedure Open + (This : in out Connection_Pool; + Position : in Positive; + Connection_String : in String); + + not overriding + procedure Close (This : in out Connection_Pool; Position : in Positive); + + not overriding + procedure Lease (This : in out Connection_Pool; S : in out Session'Class); + + -- + -- Statement. + -- + + type Statement (<>) is tagged limited private; + + type Data_State is (Data_Null, Data_Not_Null); + + type Into_Position is private; + + type Vector_Index is new Natural; + + not overriding + function Make_Statement (Sess : in Session'Class) return Statement; + + -- Statement preparation and execution. + + not overriding + procedure Prepare (This : in Statement; Query : in String); + + not overriding + procedure Execute + (This : in Statement; + With_Data_Exchange : in Boolean := False); + + not overriding + function Execute + (This : in Statement; + With_Data_Exchange : in Boolean := False) return Boolean; + + not overriding + function Fetch (This : in Statement) return Boolean; + + not overriding + function Got_Data (This : in Statement) return Boolean; + + -- + -- Data items handling. + -- + + -- Database-specific types. + -- These types are most likely identical to standard Integer, + -- Long_Long_Integer and Long_Float, but are defined distinctly + -- to avoid interfacing problems with other compilers. + + type DB_Integer is new Interfaces.C.int; + type DB_Long_Long_Integer is new Interfaces.Integer_64; + type DB_Long_Float is new Interfaces.C.double; + + -- Creation of single into elements. + + not overriding + function Into_String (This : in Statement) return Into_Position; + + not overriding + function Into_Integer (This : in Statement) return Into_Position; + + not overriding + function Into_Long_Long_Integer (This : in Statement) return Into_Position; + + not overriding + function Into_Long_Float (This : in Statement) return Into_Position; + + not overriding + function Into_Time (This : in Statement) return Into_Position; + + -- Creation of vector into elements. + + not overriding + function Into_Vector_String (This : in Statement) return Into_Position; + + not overriding + function Into_Vector_Integer (This : in Statement) return Into_Position; + + not overriding + function Into_Vector_Long_Long_Integer (This : in Statement) return Into_Position; + + not overriding + function Into_Vector_Long_Float (This : in Statement) return Into_Position; + + not overriding + function Into_Vector_Time (This : in Statement) return Into_Position; + + -- Inspection of single into elements. + + not overriding + function Get_Into_State + (This : in Statement; + Position : in Into_Position) + return Data_State; + + not overriding + function Get_Into_String + (This : in Statement; + Position : in Into_Position) + return String; + + not overriding + function Get_Into_Integer + (This : in Statement; + Position : in Into_Position) + return DB_Integer; + + not overriding + function Get_Into_Long_Long_Integer + (This : in Statement; + Position : in Into_Position) + return DB_Long_Long_Integer; + + not overriding + function Get_Into_Long_Float + (This : in Statement; + Position : in Into_Position) + return DB_Long_Float; + + not overriding + function Get_Into_Time + (This : in Statement; + Position : in Into_Position) + return Ada.Calendar.Time; + + -- Inspection of vector into elements. + + not overriding + function Get_Into_Vectors_Size (This : in Statement) return Natural; + + not overriding + function Into_Vectors_First_Index (This : in Statement) return Vector_Index; + + not overriding + function Into_Vectors_Last_Index (This : in Statement) return Vector_Index; + + not overriding + procedure Into_Vectors_Resize (This : in Statement; New_Size : in Natural); + + not overriding + function Get_Into_Vector_State + (This : in Statement; + Position : in Into_Position; + Index : in Vector_Index) + return Data_State; + + not overriding + function Get_Into_Vector_String + (This : in Statement; + Position : in Into_Position; + Index : in Vector_Index) + return String; + + not overriding + function Get_Into_Vector_Integer + (This : in Statement; + Position : in Into_Position; + Index : in Vector_Index) + return DB_Integer; + + not overriding + function Get_Into_Vector_Long_Long_Integer + (This : in Statement; + Position : in Into_Position; + Index : in Vector_Index) + return DB_Long_Long_Integer; + + not overriding + function Get_Into_Vector_Long_Float + (This : in Statement; + Position : in Into_Position; + Index : in Vector_Index) + return DB_Long_Float; + + not overriding + function Get_Into_Vector_Time + (This : in Statement; + Position : in Into_Position; + Index : in Vector_Index) + return Ada.Calendar.Time; + + -- Creation of single use elements. + + not overriding + procedure Use_String (This : in Statement; Name : in String); + + not overriding + procedure Use_Integer (This : in Statement; Name : in String); + + not overriding + procedure Use_Long_Long_Integer (This : in Statement; Name : in String); + + not overriding + procedure Use_Long_Float (This : in Statement; Name : in String); + + not overriding + procedure Use_Time (This : in Statement; Name : in String); + + -- Creation of vector use elements. + + not overriding + procedure Use_Vector_String (This : in Statement; Name : in String); + + not overriding + procedure Use_Vector_Integer (This : in Statement; Name : in String); + + not overriding + procedure Use_Vector_Long_Long_Integer (This : in Statement; Name : in String); + + not overriding + procedure Use_Vector_Long_Float (This : in Statement; Name : in String); + + not overriding + procedure Use_Vector_Time (This : in Statement; Name : in String); + + -- Modifiers for single use elements. + + not overriding + procedure Set_Use_State + (This : in Statement; + Name : in String; + State : in Data_State); + + not overriding + procedure Set_Use_String + (This : in Statement; + Name : in String; + Value : in String); + + not overriding + procedure Set_Use_Integer + (This : in Statement; + Name : in String; + Value : in DB_Integer); + + not overriding + procedure Set_Use_Long_Long_Integer + (This : in Statement; + Name : in String; + Value : in DB_Long_Long_Integer); + + not overriding + procedure Set_Use_Long_Float + (This : in Statement; + Name : in String; + Value : in DB_Long_Float); + + not overriding + procedure Set_Use_Time + (This : in Statement; + Name : in String; + Value : in Ada.Calendar.Time); + + -- Modifiers for vector use elements. + + not overriding + function Get_Use_Vectors_Size (This : in Statement) return Natural; + + not overriding + function Use_Vectors_First_Index (This : in Statement) return Vector_Index; + + not overriding + function Use_Vectors_Last_Index (This : in Statement) return Vector_Index; + + not overriding + procedure Use_Vectors_Resize (This : in Statement; New_Size : in Natural); + + not overriding + procedure Set_Use_Vector_State + (This : in Statement; + Name : in String; + Index : in Vector_Index; + State : in Data_State); + + not overriding + procedure Set_Use_Vector_String + (This : in Statement; + Name : in String; + Index : in Vector_Index; + Value : in String); + + not overriding + procedure Set_Use_Vector_Integer + (This : in Statement; + Name : in String; + Index : in Vector_Index; + Value : in DB_Integer); + + not overriding + procedure Set_Use_Vector_Long_Long_Integer + (This : in Statement; + Name : in String; + Index : in Vector_Index; + Value : in DB_Long_Long_Integer); + + not overriding + procedure Set_Use_Vector_Long_Float + (This : in Statement; + Name : in String; + Index : in Vector_Index; + Value : in DB_Long_Float); + + not overriding + procedure Set_Use_Vector_Time + (This : in Statement; + Name : in String; + Index : in Vector_Index; + Value : in Ada.Calendar.Time); + + -- Inspection of single use elements. + -- + -- Note: Use elements can be modified by the database if they + -- are bound to out and inout parameters of stored procedures + -- (although this is not supported by all database backends). + -- This feature is available only for single use elements. + + not overriding + function Get_Use_State + (This : in Statement; + Name : in String) + return Data_State; + + not overriding + function Get_Use_String + (This : in Statement; + Name : in String) + return String; + + not overriding + function Get_Use_Integer + (This : in Statement; + Name : in String) + return DB_Integer; + + not overriding + function Get_Use_Long_Long_Integer + (This : in Statement; + Name : in String) + return DB_Long_Long_Integer; + + not overriding + function Get_Use_Long_Float + (This : in Statement; + Name : in String) + return DB_Long_Float; + + not overriding + function Get_Use_Time + (This : in Statement; + Name : in String) + return Ada.Calendar.Time; + +private + + -- Connection pool and supporting types. + + type Connection_Array is array (Positive range <>) of Session; + type Used_Array is array (Positive range <>) of Boolean; + + -- Protected state for the connection pool. + protected type Connection_Pool_PS (Size : Positive) is + + procedure Open (Position : in Positive; Connection_String : in String); + procedure Close (Position : in Positive); + + entry Lease (S : in out Session'Class); + procedure Give_Back (Position : in Positive); + + private + + Connections : Connection_Array (1 .. Size); + Is_Used : Used_Array (1 .. Size) := (others => False); + Available : Boolean := True; + + end Connection_Pool_PS; + type Connection_Pool_PS_Ptr is access all Connection_Pool_PS; + + type Connection_Pool (Size : Positive) is tagged limited record + Pool : aliased Connection_Pool_PS (Size); + end record; + + -- Session and supporting types. + + type Session_Handle is new System.Address; + + Null_Session_Handle : constant Session_Handle := + Session_Handle (System.Null_Address); + + type Session is new Ada.Finalization.Limited_Controlled with record + Handle : Session_Handle; + Initialized : Boolean := False; + Belongs_To_Pool : Boolean := False; + Pool : Connection_Pool_PS_Ptr; + Position_In_Pool : Positive; + end record; + + overriding + procedure Finalize (This : in out Session); + + -- Statement and supporting types. + + type Statement_Handle is new System.Address; + + Null_Statement_Handle : constant Statement_Handle := + Statement_Handle (System.Null_Address); + + type Statement is new Ada.Finalization.Limited_Controlled with record + Handle : Statement_Handle; + Initialized : Boolean := False; + end record; + + overriding + procedure Finalize (This : in out Statement); + + type Into_Position is new Natural; + +end SOCI; diff --git a/src/languages/ada/soci_ada.gpr b/src/languages/ada/soci_ada.gpr new file mode 100644 index 0000000000..fe783c8d5f --- /dev/null +++ b/src/languages/ada/soci_ada.gpr @@ -0,0 +1,14 @@ +with "std_cpp.gpr"; +with "soci_core.gpr"; + +project SOCI_Ada is + for Source_Dirs use ("."); + for Object_Dir use "."; + for Library_Name use "soci_ada"; + for Library_Dir use "lib"; + for Library_Kind use "static"; + + package Compiler is + for Default_Switches ("Ada") use ("-gnat05", "-O2"); + end Compiler; +end SOCI_Ada; diff --git a/src/languages/ada/soci_core.gpr b/src/languages/ada/soci_core.gpr new file mode 100644 index 0000000000..0283780714 --- /dev/null +++ b/src/languages/ada/soci_core.gpr @@ -0,0 +1,7 @@ +project SOCI_Core is + for Externally_Built use "true"; + for Source_Dirs use (); + for Library_Dir use "../../core"; + for Library_Name use "soci_core"; + for Library_Kind use "static"; +end SOCI_Core; diff --git a/src/languages/ada/soci_postgresql.gpr b/src/languages/ada/soci_postgresql.gpr new file mode 100644 index 0000000000..3ce31dbeff --- /dev/null +++ b/src/languages/ada/soci_postgresql.gpr @@ -0,0 +1,8 @@ +project SOCI_PostgreSQL is + for Externally_Built use "true"; + for Source_Dirs use (); + for Library_Dir use "../../backends/postgresql"; + for Library_Name use "soci_postgresql"; + for Library_Kind use "static"; +end SOCI_PostgreSQL; + diff --git a/src/languages/ada/std_cpp.gpr b/src/languages/ada/std_cpp.gpr new file mode 100644 index 0000000000..699a5adc2c --- /dev/null +++ b/src/languages/ada/std_cpp.gpr @@ -0,0 +1,7 @@ +project Std_Cpp is + for Externally_Built use "true"; + for Source_Dirs use (); + for Library_Dir use "."; + for Library_Name use "stdc++"; + for Library_Kind use "static"; +end Std_Cpp; diff --git a/src/languages/ada/test/postgresql_test.adb b/src/languages/ada/test/postgresql_test.adb new file mode 100644 index 0000000000..0d671a4822 --- /dev/null +++ b/src/languages/ada/test/postgresql_test.adb @@ -0,0 +1,609 @@ +with SOCI; +with SOCI.PostgreSQL; +with Ada.Text_IO; +with Ada.Calendar; +with Ada.Exceptions; +with Ada.Numerics.Discrete_Random; +with Ada.Command_Line; + +procedure PostgreSQL_Test is + + procedure Test_1 (Connection_String : in String) is + begin + Ada.Text_IO.Put_Line ("testing basic constructor function"); + + declare + S : SOCI.Session := SOCI.Make_Session (Connection_String); + begin + null; + end; + exception + when E : SOCI.Database_Error => + Ada.Text_IO.Put_Line ("Database_Error: "); + Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Message (E)); + end Test_1; + + procedure Test_2 (Connection_String : in String) is + S : SOCI.Session; + begin + Ada.Text_IO.Put_Line ("testing open/close"); + + S.Close; + S.Open (Connection_String); + S.Close; + end Test_2; + + procedure Test_3 (Connection_String : in String) is + begin + Ada.Text_IO.Put_Line ("testing empty start/commit"); + + declare + S : SOCI.Session := SOCI.Make_Session (Connection_String); + begin + S.Start; + S.Commit; + end; + end Test_3; + + procedure Test_4 (Connection_String : in String) is + begin + Ada.Text_IO.Put_Line ("testing simple statements"); + + declare + SQL : SOCI.Session := SOCI.Make_Session (Connection_String); + begin + SQL.Execute ("create table ada_test ( i integer )"); + SQL.Execute ("drop table ada_test"); + end; + end Test_4; + + procedure Test_5 (Connection_String : in String) is + begin + Ada.Text_IO.Put_Line ("testing independent statements"); + + declare + SQL : SOCI.Session := SOCI.Make_Session (Connection_String); + St_1 : SOCI.Statement := SOCI.Make_Statement (SQL); + St_2 : SOCI.Statement := SOCI.Make_Statement (SQL); + begin + St_1.Prepare ("create table ada_test ( i integer )"); + St_2.Prepare ("drop table ada_test"); + St_1.Execute; + St_2.Execute; + end; + end Test_5; + + procedure Test_6 (Connection_String : in String) is + begin + Ada.Text_IO.Put_Line ("testing data types and into elements"); + + declare + SQL : SOCI.Session := SOCI.Make_Session (Connection_String); + begin + declare + St : SOCI.Statement := SOCI.Make_Statement (SQL); + Pos : SOCI.Into_Position; + begin + Pos := St.Into_String; + St.Prepare ("select 'Hello'"); + St.Execute (True); + pragma Assert (St.Get_Into_String (Pos) = "Hello"); + end; + declare + St : SOCI.Statement := SOCI.Make_Statement (SQL); + Pos : SOCI.Into_Position; + Value : SOCI.DB_Integer; + use type SOCI.DB_Integer; + begin + Pos := St.Into_Integer; + St.Prepare ("select 123"); + St.Execute (True); + Value := St.Get_Into_Integer (Pos); + pragma Assert (Value = 123); + end; + declare + St : SOCI.Statement := SOCI.Make_Statement (SQL); + Pos : SOCI.Into_Position; + Value : SOCI.DB_Long_Long_Integer; + use type SOCI.DB_Long_Long_Integer; + begin + Pos := St.Into_Long_Long_Integer; + St.Prepare ("select 10000000000"); + St.Execute (True); + Value := St.Get_Into_Long_Long_Integer (Pos); + pragma Assert (Value = 10_000_000_000); + end; + declare + St : SOCI.Statement := SOCI.Make_Statement (SQL); + Pos : SOCI.Into_Position; + Value : SOCI.DB_Long_Float; + use type SOCI.DB_Long_Float; + begin + Pos := St.Into_Long_Float; + St.Prepare ("select 3.625"); + St.Execute (True); + Value := St.Get_Into_Long_Float (Pos); + pragma Assert (Value = SOCI.DB_Long_Float (3.625)); + end; + declare + St : SOCI.Statement := SOCI.Make_Statement (SQL); + Pos : SOCI.Into_Position; + Value : Ada.Calendar.Time; + begin + Pos := St.Into_Time; + St.Prepare ("select timestamp '2008-06-30 21:01:02'"); + St.Execute (True); + Value := St.Get_Into_Time (Pos); + pragma Assert (Ada.Calendar.Year (Value) = 2008); + pragma Assert (Ada.Calendar.Month (Value) = 6); + pragma Assert (Ada.Calendar.Day (Value) = 30); + pragma Assert + (Ada.Calendar.Seconds (Value) = + Ada.Calendar.Day_Duration (21 * 3_600 + 1 * 60 + 2)); + end; + end; + end Test_6; + + procedure Test_7 (Connection_String : in String) is + begin + Ada.Text_IO.Put_Line ("testing types with into vectors"); + + declare + SQL : SOCI.Session := SOCI.Make_Session (Connection_String); + St : SOCI.Statement := SOCI.Make_Statement (SQL); + Pos_Id : SOCI.Into_Position; + Pos_Str : SOCI.Into_Position; + Pos_LL : SOCI.Into_Position; + Pos_LF : SOCI.Into_Position; + Pos_TM : SOCI.Into_Position; + + use type SOCI.Data_State; + use type Ada.Calendar.Time; + use type SOCI.DB_Integer; + use type SOCI.DB_Long_Long_Integer; + use type SOCI.DB_Long_Float; + + begin + SQL.Execute ("create table soci_test (" & + " id integer," & + " str varchar (20)," & + " ll bigint," & + " lf double precision," & + " tm timestamp" & + ")"); + SQL.Execute ("insert into soci_test (id, str, ll, lf, tm)" & + " values (1, 'abc', 10000000000, 3.0, timestamp '2008-06-30 21:01:02')"); + SQL.Execute ("insert into soci_test (id, str, ll, lf, tm)" & + " values (2, 'xyz', -10000000001, -3.125, timestamp '2008-07-01 21:01:03')"); + SQL.Execute ("insert into soci_test (id, str, ll, lf, tm)" & + " values (3, null, null, null, null)"); + + Pos_Id := St.Into_Vector_Integer; + Pos_Str := St.Into_Vector_String; + Pos_LL := St.Into_Vector_Long_Long_Integer; + Pos_LF := St.Into_Vector_Long_Float; + Pos_TM := St.Into_Vector_Time; + + St.Into_Vectors_Resize (10); -- arbitrary batch size + + St.Prepare ("select id, str, ll, lf, tm from soci_test order by id"); + St.Execute (True); + + pragma Assert (St.Get_Into_Vectors_Size = 3); + + pragma Assert (St.Get_Into_Vector_Integer (Pos_Id, 0) = 1); + pragma Assert (St.Get_Into_Vector_State (Pos_Str, 0) = SOCI.Data_Not_Null); + pragma Assert (St.Get_Into_Vector_String (Pos_Str, 0) = "abc"); + pragma Assert (St.Get_Into_Vector_Long_Long_Integer (Pos_LL, 0) = 10_000_000_000); + pragma Assert (St.Get_Into_Vector_Long_Float (Pos_LF, 0) = SOCI.DB_Long_Float (3.0)); + pragma Assert (St.Get_Into_Vector_Time (Pos_TM, 0) = + Ada.Calendar.Time_Of (2008, 6, 30, + Duration (21 * 3_600 + 1 * 60 + 2))); + + pragma Assert (St.Get_Into_Vector_Integer (Pos_Id, 1) = 2); + pragma Assert (St.Get_Into_Vector_State (Pos_Str, 1) = SOCI.Data_Not_Null); + pragma Assert (St.Get_Into_Vector_String (Pos_Str, 1) = "xyz"); + pragma Assert (St.Get_Into_Vector_Long_Long_Integer (Pos_LL, 1) = -10_000_000_001); + pragma Assert (St.Get_Into_Vector_Long_Float (Pos_LF, 1) = SOCI.DB_Long_Float (-3.125)); + pragma Assert (St.Get_Into_Vector_Time (Pos_TM, 1) = + Ada.Calendar.Time_Of (2008, 7, 1, + Duration (21 * 3_600 + 1 * 60 + 3))); + + pragma Assert (St.Get_Into_Vector_Integer (Pos_Id, 2) = 3); + pragma Assert (St.Get_Into_Vector_State (Pos_Str, 2) = SOCI.Data_Null); + pragma Assert (St.Get_Into_Vector_State (Pos_LL, 2) = SOCI.Data_Null); + pragma Assert (St.Get_Into_Vector_State (Pos_LF, 2) = SOCI.Data_Null); + pragma Assert (St.Get_Into_Vector_State (Pos_TM, 2) = SOCI.Data_Null); + + SQL.Execute ("drop table soci_test"); + end; + end Test_7; + + procedure Test_8 (Connection_String : in String) is + begin + Ada.Text_IO.Put_Line ("testing multi-batch operation with into vectors"); + + declare + SQL : SOCI.Session := SOCI.Make_Session (Connection_String); + St : SOCI.Statement := SOCI.Make_Statement (SQL); + Pos_Id : SOCI.Into_Position; + Got_Data : Boolean; + + use type SOCI.DB_Integer; + begin + SQL.Execute ("create table soci_test (" & + " id integer" & + ")"); + SQL.Execute ("insert into soci_test (id) values (1)"); + SQL.Execute ("insert into soci_test (id) values (2)"); + SQL.Execute ("insert into soci_test (id) values (3)"); + SQL.Execute ("insert into soci_test (id) values (4)"); + SQL.Execute ("insert into soci_test (id) values (5)"); + SQL.Execute ("insert into soci_test (id) values (6)"); + SQL.Execute ("insert into soci_test (id) values (7)"); + SQL.Execute ("insert into soci_test (id) values (8)"); + SQL.Execute ("insert into soci_test (id) values (9)"); + SQL.Execute ("insert into soci_test (id) values (10)"); + + Pos_Id := St.Into_Vector_Integer; + St.Into_Vectors_Resize (4); -- batch of 4 elements + + St.Prepare ("select id from soci_test order by id"); + St.Execute; + + Got_Data := St.Fetch; + pragma Assert (Got_Data); + pragma Assert (St.Get_Into_Vectors_Size = 4); + pragma Assert (St.Get_Into_Vector_Integer (Pos_Id, 0) = 1); + pragma Assert (St.Get_Into_Vector_Integer (Pos_Id, 1) = 2); + pragma Assert (St.Get_Into_Vector_Integer (Pos_Id, 2) = 3); + pragma Assert (St.Get_Into_Vector_Integer (Pos_Id, 3) = 4); + + Got_Data := St.Fetch; + pragma Assert (Got_Data); + pragma Assert (St.Get_Into_Vectors_Size = 4); + pragma Assert (St.Get_Into_Vector_Integer (Pos_Id, 0) = 5); + pragma Assert (St.Get_Into_Vector_Integer (Pos_Id, 1) = 6); + pragma Assert (St.Get_Into_Vector_Integer (Pos_Id, 2) = 7); + pragma Assert (St.Get_Into_Vector_Integer (Pos_Id, 3) = 8); + + Got_Data := St.Fetch; + pragma Assert (Got_Data); + pragma Assert (St.Get_Into_Vectors_Size = 2); + pragma Assert (St.Get_Into_Vector_Integer (Pos_Id, 0) = 9); + pragma Assert (St.Get_Into_Vector_Integer (Pos_Id, 1) = 10); + + Got_Data := St.Fetch; + pragma Assert (not Got_Data); + pragma Assert (St.Get_Into_Vectors_Size = 0); + + SQL.Execute ("drop table soci_test"); + end; + end Test_8; + + procedure Test_9 (Connection_String : in String) is + begin + Ada.Text_IO.Put_Line ("testing data types and use elements"); + + declare + SQL : SOCI.Session := SOCI.Make_Session (Connection_String); + + use type SOCI.DB_Integer; + use type SOCI.DB_Long_Long_Integer; + use type SOCI.DB_Long_Float; + begin + declare + St : SOCI.Statement := SOCI.Make_Statement (SQL); + Pos : SOCI.Into_Position; + begin + St.Use_String ("value"); + St.Set_Use_String ("value", "123"); + Pos := St.Into_Integer; + St.Prepare ("select cast(:value as integer)"); + St.Execute (True); + pragma Assert (St.Get_Into_Integer (Pos) = 123); + end; + declare + St : SOCI.Statement := SOCI.Make_Statement (SQL); + Pos : SOCI.Into_Position; + begin + St.Use_Integer ("value"); + St.Set_Use_Integer ("value", 123); + Pos := St.Into_String; + St.Prepare ("select cast(:value as text)"); + St.Execute (True); + pragma Assert (St.Get_Into_String (Pos) = "123"); + end; + declare + St : SOCI.Statement := SOCI.Make_Statement (SQL); + Pos : SOCI.Into_Position; + begin + St.Use_Long_Long_Integer ("value"); + St.Set_Use_Long_Long_Integer ("value", 10_000_000_000); + Pos := St.Into_String; + St.Prepare ("select cast(:value as text)"); + St.Execute (True); + pragma Assert (St.Get_Into_String (Pos) = "10000000000"); + end; + declare + St : SOCI.Statement := SOCI.Make_Statement (SQL); + Pos : SOCI.Into_Position; + begin + St.Use_Long_Float ("value"); + St.Set_Use_Long_Float ("value", SOCI.DB_Long_Float (5.625)); + Pos := St.Into_String; + St.Prepare ("select cast(:value as text)"); + St.Execute (True); + pragma Assert (St.Get_Into_String (Pos) = "5.625"); + end; + declare + St : SOCI.Statement := SOCI.Make_Statement (SQL); + Pos : SOCI.Into_Position; + begin + St.Use_Time ("value"); + St.Set_Use_Time ("value", Ada.Calendar.Time_Of + (2008, 7, 1, Ada.Calendar.Day_Duration (3723))); + Pos := St.Into_String; + St.Prepare ("select cast(:value as text)"); + St.Execute (True); + pragma Assert (St.Get_Into_String (Pos) = "2008-07-01 01:02:03"); + end; + declare + St : SOCI.Statement := SOCI.Make_Statement (SQL); + Pos : SOCI.Into_Position; + use type SOCI.Data_State; + begin + St.Use_Integer ("value"); + St.Set_Use_State ("value", SOCI.Data_Null); + Pos := St.Into_Integer; + St.Prepare ("select cast(:value as integer)"); + St.Execute (True); + pragma Assert (St.Get_Into_State (Pos) = SOCI.Data_Null); + end; + end; + end Test_9; + + procedure Test_10 (Connection_String : in String) is + begin + Ada.Text_IO.Put_Line ("testing vector use elements and row traversal with single into elements"); + + declare + SQL : SOCI.Session := SOCI.Make_Session (Connection_String); + + Time_1 : constant Ada.Calendar.Time := Ada.Calendar.Time_Of + (2008, 7, 1, Ada.Calendar.Day_Duration (1)); + Time_2 : constant Ada.Calendar.Time := Ada.Calendar.Time_Of + (2008, 7, 2, Ada.Calendar.Day_Duration (2)); + Time_3 : constant Ada.Calendar.Time := Ada.Calendar.Time_Of + (2008, 7, 3, Ada.Calendar.Day_Duration (3)); + Time_4 : constant Ada.Calendar.Time := Ada.Calendar.Time_Of + (2008, 7, 4, Ada.Calendar.Day_Duration (4)); + Time_5 : constant Ada.Calendar.Time := Ada.Calendar.Time_Of + (2008, 7, 5, Ada.Calendar.Day_Duration (5)); + + begin + SQL.Execute ("create table soci_test (" & + " id integer," & + " str varchar (20)," & + " ll bigint," & + " lf double precision," & + " tm timestamp" & + ")"); + + declare + St : SOCI.Statement := SOCI.Make_Statement (SQL); + begin + St.Use_Vector_Integer ("id"); + St.Use_Vector_String ("str"); + St.Use_Vector_Long_Long_Integer ("ll"); + St.Use_Vector_Long_Float ("lf"); + St.Use_Vector_Time ("tm"); + + St.Use_Vectors_Resize (6); + St.Set_Use_Vector_Integer ("id", 0, 1); + St.Set_Use_Vector_Integer ("id", 1, 2); + St.Set_Use_Vector_Integer ("id", 2, 3); + St.Set_Use_Vector_Integer ("id", 3, 4); + St.Set_Use_Vector_Integer ("id", 4, 5); + St.Set_Use_Vector_Integer ("id", 5, 6); + St.Set_Use_Vector_String ("str", 0, "abc"); + St.Set_Use_Vector_String ("str", 1, "def"); + St.Set_Use_Vector_String ("str", 2, "ghi"); + St.Set_Use_Vector_String ("str", 3, "jklm"); + St.Set_Use_Vector_String ("str", 4, "no"); + St.Set_Use_Vector_State ("str", 5, SOCI.Data_Null); + St.Set_Use_Vector_Long_Long_Integer ("ll", 0, 10_000_000_000); + St.Set_Use_Vector_Long_Long_Integer ("ll", 1, 10_000_000_001); + St.Set_Use_Vector_Long_Long_Integer ("ll", 2, 10_000_000_002); + St.Set_Use_Vector_Long_Long_Integer ("ll", 3, 10_000_000_003); + St.Set_Use_Vector_Long_Long_Integer ("ll", 4, 10_000_000_004); + St.Set_Use_Vector_State ("ll", 5, SOCI.Data_Null); + St.Set_Use_Vector_Long_Float ("lf", 0, SOCI.DB_Long_Float (0.0)); + St.Set_Use_Vector_Long_Float ("lf", 1, SOCI.DB_Long_Float (0.125)); + St.Set_Use_Vector_Long_Float ("lf", 2, SOCI.DB_Long_Float (0.25)); + St.Set_Use_Vector_Long_Float ("lf", 3, SOCI.DB_Long_Float (0.5)); + St.Set_Use_Vector_Long_Float ("lf", 4, SOCI.DB_Long_Float (0.625)); + St.Set_Use_Vector_State ("lf", 5, SOCI.Data_Null); + St.Set_Use_Vector_Time ("tm", 0, Time_1); + St.Set_Use_Vector_Time ("tm", 1, Time_2); + St.Set_Use_Vector_Time ("tm", 2, Time_3); + St.Set_Use_Vector_Time ("tm", 3, Time_4); + St.Set_Use_Vector_Time ("tm", 4, Time_5); + St.Set_Use_Vector_State ("tm", 5, SOCI.Data_Null); + + St.Prepare ("insert into soci_test (id, str, ll, lf, tm)" & + " values (:id, :str, :ll, :lf, :tm)"); + St.Execute (True); + end; + declare + St : SOCI.Statement := SOCI.Make_Statement (SQL); + Pos_Id : SOCI.Into_Position; + Pos_Str : SOCI.Into_Position; + Pos_LL : SOCI.Into_Position; + Pos_LF : SOCI.Into_Position; + Pos_TM : SOCI.Into_Position; + Got_Data : Boolean; + + use type Ada.Calendar.Time; + use type SOCI.Data_State; + use type SOCI.DB_Integer; + use type SOCI.DB_Long_Long_Integer; + use type SOCI.DB_Long_Float; + + begin + Pos_Id := St.Into_Integer; + Pos_Str := St.Into_String; + Pos_LL := St.Into_Long_Long_Integer; + Pos_LF := St.Into_Long_Float; + Pos_TM := St.Into_Time; + + St.Prepare ("select id, str, ll, lf, tm from soci_test order by id"); + St.Execute; + + Got_Data := St.Fetch; + pragma Assert (Got_Data); + pragma Assert (St.Get_Into_Integer (Pos_Id) = 1); + pragma Assert (St.Get_Into_String (Pos_Str) = "abc"); + pragma Assert (St.Get_Into_Long_Long_Integer (Pos_LL) = 10_000_000_000); + pragma Assert (St.Get_Into_Long_Float (Pos_LF) = SOCI.DB_Long_Float (0.0)); + pragma Assert (St.Get_Into_Time (Pos_TM) = Time_1); + + Got_Data := St.Fetch; + pragma Assert (Got_Data); + pragma Assert (St.Get_Into_Integer (Pos_Id) = 2); + pragma Assert (St.Get_Into_String (Pos_Str) = "def"); + pragma Assert (St.Get_Into_Long_Long_Integer (Pos_LL) = 10_000_000_001); + pragma Assert (St.Get_Into_Long_Float (Pos_LF) = SOCI.DB_Long_Float (0.125)); + pragma Assert (St.Get_Into_Time (Pos_TM) = Time_2); + + Got_Data := St.Fetch; + pragma Assert (Got_Data); + pragma Assert (St.Get_Into_Integer (Pos_Id) = 3); + pragma Assert (St.Get_Into_String (Pos_Str) = "ghi"); + pragma Assert (St.Get_Into_Long_Long_Integer (Pos_LL) = 10_000_000_002); + pragma Assert (St.Get_Into_Long_Float (Pos_LF) = SOCI.DB_Long_Float (0.25)); + pragma Assert (St.Get_Into_Time (Pos_TM) = Time_3); + + Got_Data := St.Fetch; + pragma Assert (Got_Data); + pragma Assert (St.Get_Into_Integer (Pos_Id) = 4); + pragma Assert (St.Get_Into_String (Pos_Str) = "jklm"); + pragma Assert (St.Get_Into_Long_Long_Integer (Pos_LL) = 10_000_000_003); + pragma Assert (St.Get_Into_Long_Float (Pos_LF) = SOCI.DB_Long_Float (0.5)); + pragma Assert (St.Get_Into_Time (Pos_TM) = Time_4); + + Got_Data := St.Fetch; + pragma Assert (Got_Data); + pragma Assert (St.Get_Into_Integer (Pos_Id) = 5); + pragma Assert (St.Get_Into_String (Pos_Str) = "no"); + pragma Assert (St.Get_Into_Long_Long_Integer (Pos_LL) = 10_000_000_004); + pragma Assert (St.Get_Into_Long_Float (Pos_LF) = SOCI.DB_Long_Float (0.625)); + pragma Assert (St.Get_Into_Time (Pos_TM) = Time_5); + + Got_Data := St.Fetch; + pragma Assert (Got_Data); + pragma Assert (St.Get_Into_State (Pos_Id) = SOCI.Data_Not_Null); + pragma Assert (St.Get_Into_Integer (Pos_Id) = 6); + pragma Assert (St.Get_Into_State (Pos_Str) = SOCI.Data_Null); + pragma Assert (St.Get_Into_State (Pos_LL) = SOCI.Data_Null); + pragma Assert (St.Get_Into_State (Pos_LF) = SOCI.Data_Null); + pragma Assert (St.Get_Into_State (Pos_TM) = SOCI.Data_Null); + + Got_Data := St.Fetch; + pragma Assert (not Got_Data); + end; + + SQL.Execute ("drop table soci_test"); + end; + end Test_10; + + procedure Test_11 (Connection_String : in String) is + + -- test parameters: + Pool_Size : constant := 3; + Number_Of_Tasks : constant := 10; + Iterations_Per_Task : constant := 1000; + + type Small_Integer is mod 20; + package My_Random is new Ada.Numerics.Discrete_Random (Small_Integer); + Rand : My_Random.Generator; + + Pool : SOCI.Connection_Pool (Pool_Size); + + begin + Ada.Text_IO.Put_Line ("testing connection pool"); + + My_Random.Reset (Rand); + + for I in 1 .. Pool_Size loop + Pool.Open (I, Connection_String); + end loop; + + declare + SQL : SOCI.Session := SOCI.Make_Session (Connection_String); + begin + SQL.Execute ("create table soci_test ( id integer )"); + end; + + declare + + task type Worker; + task body Worker is + begin + + for I in 1 .. Iterations_Per_Task loop + declare + SQL : SOCI.Session; + V : Small_Integer; + begin + Pool.Lease (SQL); + + V := My_Random.Random (Rand); + SQL.Execute ("insert into soci_test (id) values (" & + Small_Integer'Image (V) & ")"); + end; + end loop; + exception + when others => + Ada.Text_IO.Put_Line ("An exception occured in the worker task."); + end Worker; + + W : array (1 .. Number_Of_Tasks) of Worker; + + begin + Ada.Text_IO.Put_Line ("--> waiting for the tasks to complete (might take a while)"); + end; + + declare + SQL : SOCI.Session := SOCI.Make_Session (Connection_String); + begin + SQL.Execute ("drop table soci_test"); + end; + end Test_11; + +begin + if Ada.Command_Line.Argument_Count /= 1 then + Ada.Text_IO.Put_Line ("Expecting one argument: connection string"); + return; + end if; + + declare + Connection_String : String := Ada.Command_Line.Argument (1); + begin + Ada.Text_IO.Put_Line ("testing with " & Connection_String); + + SOCI.PostgreSQL.Register_Factory_PostgreSQL; + + Test_1 (Connection_String); + Test_2 (Connection_String); + Test_3 (Connection_String); + Test_4 (Connection_String); + Test_5 (Connection_String); + Test_6 (Connection_String); + Test_7 (Connection_String); + Test_8 (Connection_String); + Test_9 (Connection_String); + Test_10 (Connection_String); + Test_11 (Connection_String); + end; +end PostgreSQL_Test; diff --git a/src/languages/ada/test/postgresql_test.gpr b/src/languages/ada/test/postgresql_test.gpr new file mode 100644 index 0000000000..1af59d3267 --- /dev/null +++ b/src/languages/ada/test/postgresql_test.gpr @@ -0,0 +1,12 @@ +with "../soci_ada.gpr"; +with "../soci_postgresql.gpr"; +with "../postgresql_client.gpr"; + +project PostgreSQL_Test is + for Main use ("postgresql_test"); + + package Compiler is + for Default_Switches ("Ada") use ("-gnat05", "-gnata"); + end Compiler; +end PostgreSQL_Test; + diff --git a/www/articles.html b/www/articles.html new file mode 100644 index 0000000000..78860a9c6f --- /dev/null +++ b/www/articles.html @@ -0,0 +1,42 @@ + + + + + + SOCI - articles + + + + + + + + + + + + + +
+

The following articles were published about SOCI:

+ +
+ +Fork us on GitHub + + diff --git a/www/doc.html b/www/doc.html new file mode 100644 index 0000000000..5fdfa04e29 --- /dev/null +++ b/www/doc.html @@ -0,0 +1,45 @@ + + + + + + SOCI - documentation + + + + + + + + + + + + + +
+

SOCI documentation:

+ +
+ + + Fork us on GitHub + + + diff --git a/www/doc/README.md b/www/doc/README.md new file mode 100644 index 0000000000..720b530c81 --- /dev/null +++ b/www/doc/README.md @@ -0,0 +1,11 @@ +SOCI Documentation +================== + +/www/doc directory is a placeholder for documentation published online, +with subdirectories of versioned documentation, one per each release: + +* doc/3.2 +* doc/3.1 +... + +Documentation pages are copied from /doc directory of each release. diff --git a/www/doc/index.html b/www/doc/index.html new file mode 100644 index 0000000000..fd07c376b7 --- /dev/null +++ b/www/doc/index.html @@ -0,0 +1,45 @@ + + + + + + SOCI - documentation + + + + + + + + + + + + + +
+

SOCI documentation:

+ +
+ + + Fork us on GitHub + + + diff --git a/www/events.html b/www/events.html new file mode 100644 index 0000000000..0946043732 --- /dev/null +++ b/www/events.html @@ -0,0 +1,97 @@ + + + + + + SOCI - events + + + + + + + + + + + + + +
+

Events:

+ +2013-09-11: +
3.2.2 version is released. +
+2013-04-13: +
3.2.1 version is released. +
+
+2013-03-25: +
3.2.0 version is released. +
+
+2012-11-12: +
SOCI development (Git repository and bug tracker) moved to github.com/SOCI. +
+
+2011-10-11: +
3.1.0 version is released. +
+
+2008-07-10: +
3.0.0 version is released. +
+
+2006-12-04: +
2.2.0 version is released. +
+
+2006-05-15: +
2.1.0 version is released. +
+
+2006-01-16: +
2.0.0 version is released. +
+
+2005-10-14: +
1.2.1 version is released. +
+
+2005-10-02: +
1.2.0 version is released. +
+
+2005-03-06: +
1.1.0 version is released. +
+
+2004-10-16: +
1.0.1 version is released. +
+
+2004-10-12: +
SOCI project is registered at SourceForge. +
+
+2004-09-29: +
First version of SOCI is announced on comp.lang.c++.moderated. +
+
+ +Fork us on GitHub + + diff --git a/www/forkus_github.png b/www/forkus_github.png new file mode 100644 index 0000000000000000000000000000000000000000..e782f36b9a158d2681e7bafca818242479eedce0 GIT binary patch literal 5735 zcmX|F2T)U6*QJFLdhZG-0wEOXMTDSeM8QxJLaza70YRGd01Aj8y-7!U3n;xyGjxz3 zT>ojGbRS~qJG+xLlL$9r?~@;~ErljbD8*sHfn(BzB1J`18R+KFqP)DkOli4r zZp{p=APo=wBR(5PmST{wu9VZJ?eahNG1cQ-IV^$r4y%^n^~JclOizaM#l-=%U9l4! zy>x3e;V-JvtNqh)gQabZ82x(nG=9ZP!NP4tM`6)sWmjU?+>+a*VTMmd234=KbDMt< zF`UlDoh9v1++oMSr-DwY+Bl#4bJ`&FG3Rte;jZ0EpaS~SuWP)7kH7F|IotF{>gy6` zo(D@pTAsH}OMVNse(q}_ptoDUO*V}1sRs4!Eckpqtg=vd=roy;wcA?=9&+K&k*nli zkQ(+UxJn$$22b=obeXqasi(m6h2#1vEBVdi;$qN>B2U$=ZV}XEie>O)XA*0NGp~n! z6o}}YlzoyK&Gd23yco)<|E0%!9ctAZv6Z4?=w^7zYN)V>d~s6iR{2Z=$NC?mYI7njlCRFQ&>9L`LsG3B@s=3K{VItVq3FeM`7G z*>)f<%SU3%w{{65@3sG4+1(eXK=^!V5IH+oz*fs{PA*5dQ)x1WXnWro&6u27DH`e~ zK8R4b>+D}q{8|w$+h07A!QaAY=AUa8l5&_dVjQwxXp9oOJz2=V6YHC6C9cz9_+rpFp3fu>5o=8Q@)-&!x$frk-5ZMhkz^N~P>#N*;?;$(((l!G|>r;L@?Wgo_(H0=^KXh5d>RU}Tc3JaZw zTjiH>qw1HOoM2o&E;qOAJ^MuK9B5{DAH!$tTBy zaO38HWODpxP?(*#6mm#9ojS&KE=${*q68z$k^bW_{WX)&rfWt>*_$15Ls{)bso*2t zgI>Vy+8f$AhY#}t}iBG zZ8T6ZG-PkC3wV6ao8^E23?~YmSh~p>1AofE3T#if%D>MotzbHZOi=_0WNSD}X;E8R zWmN*!*928(J)M2;_{a)}oaQdGy2?6=ejgw8s9FKQWh&yG+41*CxDK_gTQ{ajeur%P z>1c=5pk?)Lb$zCJBGr&RZB}O)s=Nx*15*>9&sM7>lA4aoppVETV!jn?NZTSIa&_I> zB}tXdFDZu?DzlOlL$zkeMQhRUZDcD#(HS6|^RiC=*oPe!!``cFRT6$7upz);7Rf}I+EuWUK zbq%Y|00mj}WM1PK@q21ivLV@ldna-`FL^$uZ#%SB%OF#*t?re^s<=J?BYUY|90^<+ zX$R)GV7Qi@|A!54R+e=1CN7ZGcV_?<%wJXyPHNj>v6e@o@ns=zY6F%o9ERJ&91&ck zW$raxD+2dr#1A(SoseL!_9|0;Wp4~!GO3sMnIK9EXG@8XPQD-$cUx?8|5?{vYOv0X zHs~YPmo(D(mBRBhOj|K-&HSxu2u#xQ5p3?ENTH=*4B7W5HIi=f zzx^c?B#!m+ede7li{nR*!nK(TTPJT`ZzIU5A#RY{+fkNoI(;L>*-MD+N|^--@$t<< z4|G#l&6-kvk~;eqa9k8kFsYCkc5%P?*?!MUaDp8=_ZXLJ_iETyb*x0x&rX!HM5YO1 zOq(|eC}#m3AwyZCj= zbN{bvVJ=Ez4!^b~FILyrPSq~~^c5tCGVwB-23}-qW>w^|IN|``54{rn?z;d?&9LMUudkkks*iNSKAn z;XbHJ$#Zhcd(~P7dm}(V63 z?NaZ<4(n|HJ;t^1{v1)>6*(a1aQhPoCgX$xezV3yaG!$SQy6KYwZ_Hy)DKFZ1Y!H_ zAOV&8#|~TZ=aYqU_bds`JkMjh#*nT$daD&0k6Xwfwa3FP0WF$3#9ttK zosas^N?WmI{UQjNj8dl@QE&=dAD5U*Zrk`2Jy&2+^R;z4u*G|~q1s`|R=8w2R9LWf zViqfr%14o0%cgO=`v*ABlmQOR+8tl(tYsxl$4L9UmnT9GwNl>eiA;c(e_?^n$Y}^n zImvW6hxWxtt3w5`A1g^xs{3s=VZtgUY`02;zR(lH_K`Ox6FXyn6PQ7(o70lZ4?Y9m zH?WV`Ft?M4B6(NPCa7Xp zU%m2P+|DeZ%W<5YafdykP_R%xI%Krpv!SWz(hp&1A=ArFmId9#joeb_#t|Nvt0JXz^pc>WI5*N&rcI zZ+{n<$opTu#MCWnj*&HTeLo2Ay;HchLO$gB(=}mXRPY;8^fyKNnl>c zfxh}Z3XKOU`b%&QUhhLsV)e=({;ibfcg4Xfr=+j^1EVyhe}HcUKJJ>{dv$LzHs~A$ zWcj}EE;qWcRdufiQ?{HSjH>E>?r-SJ57Y8rcGQavs?WqoAb&lQX z-|U7%(=onPO23rO-a6z;R4Sig0eTdVKh!)oU^;(!eC6zaMMxSOw35!aIe}^m7(VCg zX!z-3%YgU=QogKkKhyjVuPW8s)eh?$spN1W#UcC8p+mkpR}}wcAK@W__HM~T+ni(2 z7i7{JiSrs^mhhe0bgoXoL%4A;9!MrX*qc z1odBwMb_ROd=&#}7lr`^9OtavcW-3_+2?60Wna;U3NKZSX?6U^z0p?ISvNXOYEf5} z4MdIRK;SK-@73*^lW}4LU)--4`Y}cx*{IR!K$no(>ydlS?{Is@{cHo9=(J<}r#zb* zr2PcqO_71QSt1nrvzfI{$AqN(j|}NaOrGSmb$@5r`tGd_<|{PgTo(Hqa~}x$J6YX%rfl$USfi&eBeJ^EY2lAvl$}M z6&PmeR4;&#UCER zbN{x^ge>O%&l?>tRu^egGJHB!0{%@u*`Sw*w-l56IY@pxNv?Ev{(c@j-N@;!KR?L1 zyB_6VPu0=t>D5KvJ|6S)w>JRwUvihXggXK_of{;aJre!oskEzt;I~}f%e!c!bMNPr zo&IZhr)E`mr~(Fqn_ue$EO&-YmniU3Bsq6Kz>`U4^POh@fd*QMrD^Hs8)wq|Po10= z`F(6q;snVQ$<7yVT}DlE3$4H4p$_!QLnfjT*|t{O2Q2(AnC&qjivvj?o0k>98V`tJ z`<~wyR+!xWDJre!#(bUZpsm&ZZpQ8vAl%^TtTfjNO|jNx9IM$`$d=jKwnb12i2Kc} zpy^f86w)v5GX_7t{gw=f zjF}Ha_G$ZbN&_#OmM*Zh{X)@o-OuTMd~1HDLhM6pB*aE{d%Ls!v9$b)Teh1rPZ;!I zlne0;ou&OC)y6G}h@@wy-tx9Yp~$%5EKBSzK_z!!{m&be8ew4=5K24aD zI-r69?|QY0j}GKTsD>YTa717{!v5<$3G;T)Ha2KAgt(ickRHCnP?WrS>j^}O0PQQg zvVYh`6c}-B>t88wbEOe5tbfP6#^OuA%0vyY``N=AMFR3mlIfTi-$nz9-^R^0hN(Zr zI5C6~JLLH&dvn6QL64hMp%Fdb7b^=)cljChbDd_{!tXv_qz^gTJX;U8#5l!<3HuOL zH)*HE=n(_TmVeVIAnq6Ahq{26$sPb%h`ifkrR)FBAYQW=D!vknoR50LU2ftrhV19R zoU#jYD;VP3s9Ix+7@JX`; z1Yl)SOxjw@Ujb^26wd+P1_;{zA_O`9{WHgTs{}1j!{=kuG%Ska@zsDN=8L6@MTShM z=ve<__VVN6jd!?)!DHkQ@G?8MnOO(2PfJk+L_BVPq-`6ku_gJZN&L%>D|X(fl}szW z90-;t?C;L{f4f8^#*Pz2>7VcYcFgnlI-&+ww_Km2rzyMSG+;tJtExYd^fdkc zLmEH@=iXeU0)3RUbqF89(ZQZe*VDwy5&b!U`?J3Ji+r@BWH1;={4vD?6D5SF{FU2- zWEJx@3prvynmvuxHP3+heie65t^-6Fo;)*Oqg8kx&(OxXNS~w`Rp>|a1a@&5mfXUs zS|q?u{wy?t{{+)J67x^LGHmqjxxWNSEoql}BEd?y1bvMN-?GyMXcV~@GiJV3C+(dn zH9ut^%a;W@@O=tfaItFI>uHso^Yf8S5tnBQB;O{D)`uQ1tF`iPrGp?|2AP?@@d*9%QI5RdLRtaLVv(?NdHcF? zrvgLB8J=*H4JgER!FBg=TUoIw^)Hm$K=DSNuO#379LB`w-k$bgpv`%4lmoK9Bl;G8 zrh(uc#Lif-D=K8MVO#L`TkpOK_CBPRQ*Nf;>ShdCA`WqT>FIOb2&81n*C@iB&l7GN z^Yee|a}W^{|HN*GW+6s~`0=;BY6wX52GJ%Sh>H^T zvhhbm8p7-kB>ywTM@`DNLjhV+Op&70`Vekn;4y|UU2rvPKY$Dh=*<_<6L|mHmtOzRv;{%WxLeYi?~3G21#k|C!~1PRL}XL-tWg- zJy1YpuZcGJ{F7jLDUYX^myIe~1Ju}nd=tbzr<&^g0ZqyIHOiU?%faI2I_1%n@}%bQ z=y%T7sJu$#L6l;>Qp)Blg?iRQIL%8kEb)!I$ZE5r>w8H!!1@7E? literal 0 HcmV?d00001 diff --git a/www/index.html b/www/index.html new file mode 100644 index 0000000000..1d4879c8ad --- /dev/null +++ b/www/index.html @@ -0,0 +1,134 @@ + + + + + + SOCI + + + + + + + + + + + + + +
+

SOCI is a database access library +for C++ that makes the illusion of embedding +SQL queries in the regular +C++ code, staying entirely within the Standard C++.

+ +

The idea is to provide C++ programmers a way to access SQL +databases in the most natural and intuitive way. If you find existing +libraries too difficult for your needs or just distracting, SOCI can be +a good alternative.

+ +

The simplest motivating code example for the SQL query that is supposed +to retrieve a single row is:

+
+int id = ...;
+string name;
+int salary;
+
+sql << "select name, salary from persons where id = " << id,
+       into(name), into(salary);
+
+ +

and the following benefits from extensive support for object-relational +mapping:

+
+int id = ...;
+Person p;
+
+sql << "select first_name, last_name, date_of_birth "
+       "from persons where id = " << id,
+       into(p);
+
+ +

Integration with STL is also supported:

+ +
+Rowset<string> rs = (sql.prepare << "select name from persons");
+copy(rs.begin(), rs.end(), ostream_iterator<string>(cout, "\n"));
+
+ +

SOCI offers also extensive integration with Boost datatypes (optional, tuple and fusion) and flexible support +for user-defined datatypes.

+ +

Even though SOCI is mainly a C++ library, it also allows to use it from other programming languages. Currently the package contains the Ada binding, with more bindings likely to come in the future.

+ +

Starting from its 2.0.0 release, SOCI uses the plug-in architecture for +backends - this allows to target various database servers. +Currently (3.2.2), the following database systems are supported:

+
    +
  • DB2
  • +
  • Firebird
  • +
  • MySQL
  • +
  • ODBC (generic backend)
  • +
  • Oracle
  • +
  • PostgreSQL
  • +
  • SQLite3
  • +
+ +

The intent of the library is to cover as many database technologies as +possible. For this, the project has to rely on volunteer contributions +from other programmers, who have expertise with the existing database +interfaces and would like to help writing dedicated backends.
+If you are interested in participating, please contact the project admin.

+ +

The SOCI library is distributed under the terms of the Boost +Software License.

+ +

All SOCI downloads are hosted on SourceForge.net servers. The current +stable release (3.2.2) can be downloaded here, +and all previous releases are available +here.

+ +

The development of SOCI happens on GitHub. All repositories live +under the SOCI +organization where all Git repositories are available.

+ +

The main Git repository with SOCI source code can be cloned with:

+
+$ git clone git://github.com/SOCI/soci.git
+
+ +

The Issues tracker is open +for bug reports and patches submission.

+ +

The best way to contribute to SOCI is to follow the typical GitHub workflow: +fork SOCI, apply your edits and submit Pull Request. +
Feel free to join SOCI development!

+ +

To meet other users, please consider subscribing to the +SOCI-users mailing list. +There is also SOCI-devel mailing list +available dedicated to development discussions only.

+ +

There is also community-driven Wiki +and FAQ hosted at GitHub, where everybody +is welcome to contribute.

+
+ +Fork us on GitHub + + diff --git a/www/links.html b/www/links.html new file mode 100644 index 0000000000..d2d18cbcee --- /dev/null +++ b/www/links.html @@ -0,0 +1,78 @@ + + + + + + SOCI - links + + + + + + + + + + + + + +
+

Some links you may find useful:

+ +ORAPP - Oracle OCI C++ Interface Library + +
+ +OCI C++ Library + +
+ +Oracle, ODBC and DB2-CLI Template Library + +
+ +Oracle C++ Call Interface + +
+ +Oracle Open Source Projects + +
+ +Database Template Library + +
+ +MyMySQL - A lightweight C++ wrapper around MySQL client API + +
+ +CMake Cross Platform make + +
+ +Fork us on GitHub + + diff --git a/www/people.html b/www/people.html new file mode 100644 index 0000000000..eba7029fb3 --- /dev/null +++ b/www/people.html @@ -0,0 +1,122 @@ + + + + + + + + SOCI - people + + + + + + + + + + + + + +
+

SOCI People:

+

Core team developers and contributors, current and previous, all people actively involved +

+ Maciej Sobczak (see homepage) +
+ Project admin
+ Core development
+ Initial Oracle Call Interface support (author)
+ Plug-in backend architecture (author)
+ PostgreSQL backend (author)
+ Documentation +

+ Mateusz Łoskot (see homepage) +
+ Project admin
+ Core development
+ Iterator support
+ Oracle backend (maintainer)
+ PostgreSQL backend (maintainer)
+ Build, test and release troublemaker
+ Documentation +

+ Paweł Aleksander Fedoryński (see homepage) +
+ MySQL backend (author, maintainer)
+ Documentation +

+ Steve Hutton (see homepage) +
+ Core development
+ Bulk (vector) operations
+ Dynamic result binding
+ Object-Relational mapping facilities
+ Common tests framework
+ Documentation +

+ David Courtney (see homepage) +
+ SQLite backend (author)
+ MS SQL Server (via ODBC) backend (author) +

+ Rafał Bobrowski +
+ Firebird backend (author) +

+
+
+ Vadim Zeitlin (see GitHub) +
+ ODBC backend (maintainer)
+ Core development
+

+ Viacheslav Naydenov (see GitHub) +
+ Firebird backend (maintainer)
+ Core development
+

+ Alex Ott (see homepage) +
+ Plug-in backend architecture
+ Core development
+

+ Denis Chapligin (see GitHub) +
+ IBM DB2 backend (author) +

+ Denis Arnaud (see GitHub) +
+ Fedora and Debian packaging
+ Contributor
+ Troubleshooter
+

+ Julian Taylor (see GitHub) +
+ Debian packaging
+ Contributor
+ Troubleshooter
+

+ Sergei Nikulov (see GitHub) +
+ SQLite3 contributor
+ Troubleshooter +

+
+
Fork us on GitHub + + diff --git a/www/style.css b/www/style.css new file mode 100644 index 0000000000..399ff792f9 --- /dev/null +++ b/www/style.css @@ -0,0 +1,87 @@ +body +{ + background-color: white; + color: black; + margin-left: 100px; + margin-right: 100px; +/* font-family: arial, sans-serif; */ +} + +table.banner +{ + width: 100%; + border-bottom-color: black; + border-bottom-style: solid; + border-bottom-width: 1px; + padding-top: 16px; + padding-right: 16px; + padding-bottom: 5px; +} + +td.banner_left +{ + font-size: x-large; + font-weight: bold; + font-stretch: expanded; +} + +td.banner_right +{ + text-align: right; +} + +table.footer +{ + width: 100%; + border-top-color: black; + border-top-style: solid; + border-top-width: 1px; + padding-bottom: 16px; + padding-top: 5px; +} + +table.main +{ + width: 60%; + padding-top: 25px; +} + +td.main_navigator +{ + vertical-align: top; +} + +td.main_text +{ + vertical-align: top; + padding-left: 20px; +} + +span.bold +{ + font-weight: bold; +} + +span.literal +{ + font-weight: bold; + background-color: white; + color: #8B0000; +} + +div.indent +{ + margin-left: 40px; +} + +div.column-left +{ + width: 50%; + float: left; +} + +div.column-right +{ + width: 50%; + float: right; +}