File indexing completed on 2025-02-23 05:15:18
0001 // 0002 // Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, David Courtney 0003 // Distributed under the Boost Software License, Version 1.0. 0004 // (See accompanying file LICENSE_1_0.txt or copy at 0005 // http://www.boost.org/LICENSE_1_0.txt) 0006 // 0007 0008 #define SOCI_ODBC_SOURCE 0009 #include "soci/soci-platform.h" 0010 #include "soci/odbc/soci-odbc.h" 0011 #include "soci/session.h" 0012 0013 #include "soci-autostatement.h" 0014 0015 #include <cstdio> 0016 0017 using namespace soci; 0018 using namespace soci::details; 0019 0020 char const * soci::odbc_option_driver_complete = "odbc.driver_complete"; 0021 0022 odbc_session_backend::odbc_session_backend( 0023 connection_parameters const & parameters) 0024 : henv_(0), hdbc_(0), product_(prod_uninitialized) 0025 { 0026 SQLRETURN rc; 0027 0028 // Allocate environment handle 0029 rc = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv_); 0030 if (is_odbc_error(rc)) 0031 { 0032 throw soci_error("Unable to get environment handle"); 0033 } 0034 0035 // Set the ODBC version environment attribute 0036 rc = SQLSetEnvAttr(henv_, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0); 0037 if (is_odbc_error(rc)) 0038 { 0039 throw odbc_soci_error(SQL_HANDLE_ENV, henv_, "setting ODBC version 3"); 0040 } 0041 0042 // Allocate connection handle 0043 rc = SQLAllocHandle(SQL_HANDLE_DBC, henv_, &hdbc_); 0044 if (is_odbc_error(rc)) 0045 { 0046 throw odbc_soci_error(SQL_HANDLE_DBC, hdbc_, 0047 "allocating connection handle"); 0048 } 0049 0050 SQLCHAR outConnString[1024]; 0051 SQLSMALLINT strLength = 0; 0052 0053 // Prompt the user for any missing information (typically UID/PWD) in the 0054 // connection string by default but allow overriding this using "prompt" 0055 // option and also suppress prompts when reconnecting, see the comment in 0056 // soci::session::reconnect(). 0057 SQLHWND hwnd_for_prompt = NULL; 0058 unsigned completion = SQL_DRIVER_COMPLETE; 0059 0060 if (parameters.is_option_on(option_reconnect)) 0061 { 0062 completion = SQL_DRIVER_NOPROMPT; 0063 } 0064 else 0065 { 0066 std::string completionString; 0067 if (parameters.get_option(odbc_option_driver_complete, completionString)) 0068 { 0069 // The value of the option is supposed to be just the integer value of 0070 // one of SQL_DRIVER_XXX constants but don't check for the exact value in 0071 // case more of them are added in the future, the ODBC driver will return 0072 // an error if we pass it an invalid value anyhow. 0073 if (std::sscanf(completionString.c_str(), "%u", &completion) != 1) 0074 { 0075 throw soci_error("Invalid non-numeric driver completion option value \"" + 0076 completionString + "\"."); 0077 } 0078 } 0079 } 0080 0081 #ifdef _WIN32 0082 if (completion != SQL_DRIVER_NOPROMPT) 0083 hwnd_for_prompt = ::GetDesktopWindow(); 0084 #endif // _WIN32 0085 0086 std::string const & connectString = parameters.get_connect_string(); 0087 0088 // This "infinite" loop can be executed at most twice. 0089 std::string errContext; 0090 for (;;) 0091 { 0092 rc = SQLDriverConnect(hdbc_, hwnd_for_prompt, 0093 sqlchar_cast(connectString), 0094 (SQLSMALLINT)connectString.size(), 0095 outConnString, 1024, &strLength, 0096 static_cast<SQLUSMALLINT>(completion)); 0097 0098 // Don't use is_odbc_error() here as it doesn't consider SQL_NO_DATA to be 0099 // an error -- but it is one here, as it's returned if a message box shown 0100 // by SQLDriverConnect() was cancelled and this means we failed to connect. 0101 switch (rc) 0102 { 0103 case SQL_SUCCESS: 0104 case SQL_SUCCESS_WITH_INFO: 0105 break; 0106 0107 case SQL_NO_DATA: 0108 throw soci_error("Connecting to the database cancelled by user."); 0109 0110 default: 0111 odbc_soci_error err(SQL_HANDLE_DBC, hdbc_, "connecting to database"); 0112 0113 // If connection pooling had been enabled by the application, we 0114 // would get HY110 ODBC error for any connection attempt not using 0115 // SQL_DRIVER_NOPROMPT, so it's worth retrying with it in this 0116 // case: in the worst case, we'll hit 28000 ODBC error (login 0117 // failed), which we'll report together with the context helping to 0118 // understand where it came from. 0119 if (memcmp(err.odbc_error_code(), "HY110", 6) == 0 && 0120 completion != SQL_DRIVER_NOPROMPT) 0121 { 0122 errContext = "while retrying to connect without prompting, as " 0123 "prompting the user is not supported when using " 0124 "pooled connections"; 0125 completion = SQL_DRIVER_NOPROMPT; 0126 continue; 0127 } 0128 0129 if (!errContext.empty()) 0130 err.add_context(errContext); 0131 0132 throw err; 0133 } 0134 0135 // This loop only runs once unless we retry in case of HY110 above. 0136 break; 0137 } 0138 0139 connection_string_.assign((const char*)outConnString, strLength); 0140 0141 reset_transaction(); 0142 0143 configure_connection(); 0144 } 0145 0146 void odbc_session_backend::configure_connection() 0147 { 0148 if ( get_database_product() == prod_postgresql ) 0149 { 0150 // Increase the number of digits used for floating point values to 0151 // ensure that the conversions to/from text round trip correctly, which 0152 // is not the case with the default value of 0. Use the maximal 0153 // supported value, which was 2 until 9.x and is 3 since it. 0154 0155 char product_ver[1024]; 0156 SQLSMALLINT len = sizeof(product_ver); 0157 SQLRETURN rc = SQLGetInfo(hdbc_, SQL_DBMS_VER, product_ver, len, &len); 0158 if (is_odbc_error(rc)) 0159 { 0160 throw odbc_soci_error(SQL_HANDLE_DBC, henv_, 0161 "getting PostgreSQL ODBC driver version"); 0162 } 0163 0164 // The returned string is of the form "##.##.#### ...", but we don't 0165 // need to parse it fully, we just need the major version which, 0166 // conveniently, comes first. 0167 unsigned major_ver = 0; 0168 if (std::sscanf(product_ver, "%u", &major_ver) != 1) 0169 { 0170 throw soci_error("DBMS version \"" + std::string(product_ver) + 0171 "\" in unrecognizable format."); 0172 } 0173 0174 details::auto_statement<odbc_statement_backend> st(*this); 0175 0176 std::string const q(major_ver >= 9 ? "SET extra_float_digits = 3" 0177 : "SET extra_float_digits = 2"); 0178 rc = SQLExecDirect(st.hstmt_, sqlchar_cast(q), static_cast<SQLINTEGER>(q.size())); 0179 0180 if (is_odbc_error(rc)) 0181 { 0182 throw odbc_soci_error(SQL_HANDLE_DBC, henv_, 0183 "setting extra_float_digits for PostgreSQL"); 0184 } 0185 0186 // This is extracted from pgapifunc.h header from psqlODBC driver. 0187 enum 0188 { 0189 SQL_ATTR_PGOPT_UNKNOWNSASLONGVARCHAR = 65544 0190 }; 0191 0192 // Also configure the driver to handle unknown types, such as "xml", 0193 // that we use for x_xmltype, as long varchar instead of limiting them 0194 // to 256 characters (by default). 0195 rc = SQLSetConnectAttr(hdbc_, SQL_ATTR_PGOPT_UNKNOWNSASLONGVARCHAR, (SQLPOINTER)1, 0); 0196 0197 // Ignore the error from this one, failure to set it is not fatal and 0198 // the attribute is only supported in very recent version of the driver 0199 // (>= 9.6.300). Using "UnknownsAsLongVarchar=1" in odbc.ini (or 0200 // setting the corresponding option in the driver dialog box) should 0201 // work with all versions however. 0202 } 0203 } 0204 0205 odbc_session_backend::~odbc_session_backend() 0206 { 0207 clean_up(); 0208 } 0209 0210 bool odbc_session_backend::is_connected() 0211 { 0212 details::auto_statement<odbc_statement_backend> st(*this); 0213 0214 // The name of the table we check for is irrelevant, as long as we have a 0215 // working connection, it should still find (or, hopefully, not) something. 0216 return !is_odbc_error(SQLTables(st.hstmt_, 0217 NULL, SQL_NTS, 0218 NULL, SQL_NTS, 0219 sqlchar_cast("bloordyblop"), SQL_NTS, 0220 NULL, SQL_NTS)); 0221 } 0222 0223 void odbc_session_backend::begin() 0224 { 0225 SQLRETURN rc = SQLSetConnectAttr( hdbc_, SQL_ATTR_AUTOCOMMIT, 0226 (SQLPOINTER)SQL_AUTOCOMMIT_OFF, 0 ); 0227 if (is_odbc_error(rc)) 0228 { 0229 throw odbc_soci_error(SQL_HANDLE_DBC, hdbc_, "beginning transaction"); 0230 } 0231 } 0232 0233 void odbc_session_backend::commit() 0234 { 0235 SQLRETURN rc = SQLEndTran(SQL_HANDLE_DBC, hdbc_, SQL_COMMIT); 0236 if (is_odbc_error(rc)) 0237 { 0238 throw odbc_soci_error(SQL_HANDLE_DBC, hdbc_, "committing transaction"); 0239 } 0240 reset_transaction(); 0241 } 0242 0243 void odbc_session_backend::rollback() 0244 { 0245 SQLRETURN rc = SQLEndTran(SQL_HANDLE_DBC, hdbc_, SQL_ROLLBACK); 0246 if (is_odbc_error(rc)) 0247 { 0248 throw odbc_soci_error(SQL_HANDLE_DBC, hdbc_, "rolling back transaction"); 0249 } 0250 reset_transaction(); 0251 } 0252 0253 bool odbc_session_backend::get_next_sequence_value( 0254 session & s, std::string const & sequence, long long & value) 0255 { 0256 std::string query; 0257 0258 switch ( get_database_product() ) 0259 { 0260 case prod_db2: 0261 query = "select next value for " + sequence + " from SYSIBM.SYSDUMMY1"; 0262 break; 0263 0264 case prod_firebird: 0265 query = "select next value for " + sequence + " from rdb$database"; 0266 break; 0267 0268 case prod_oracle: 0269 query = "select " + sequence + ".nextval from dual"; 0270 break; 0271 0272 case prod_postgresql: 0273 query = "select nextval('" + sequence + "')"; 0274 break; 0275 0276 case prod_mssql: 0277 case prod_mysql: 0278 case prod_sqlite: 0279 // These RDBMS implement get_last_insert_id() instead. 0280 return false; 0281 0282 case prod_unknown: 0283 // For this one we can't do anything at all. 0284 return false; 0285 0286 case prod_uninitialized: 0287 // This is not supposed to happen at all but still cover this case 0288 // here to avoid gcc warnings about unhandled enum values in a 0289 // switch. 0290 return false; 0291 } 0292 0293 s << query, into(value); 0294 0295 return true; 0296 } 0297 0298 bool odbc_session_backend::get_last_insert_id( 0299 session & s, std::string const & table, long long & value) 0300 { 0301 std::string query; 0302 0303 switch ( get_database_product() ) 0304 { 0305 case prod_db2: 0306 query = "SELECT IDENTITY_VAL_LOCAL() AS LASTID FROM SYSIBM.SYSDUMMY1"; 0307 break; 0308 0309 case prod_mssql: 0310 { 0311 // We can't use `ident_current()` because it doesn't 0312 // distinguish between the empty table and a table with one 0313 // row inserted and returns `seed_value` in both cases, while 0314 // we need previous to the initial value in the former 0315 // (i.e. `seed_value` - `increment_value`). 0316 long long last, seed, inc; 0317 indicator ind; 0318 0319 s << "select last_value, seed_value, increment_value " 0320 "from sys.identity_columns where " 0321 "object_id = object_id('" << table << "')" 0322 , into(last, ind), into(seed), into(inc); 0323 0324 if (ind == i_null) 0325 { 0326 value = seed - inc; 0327 } 0328 else 0329 { 0330 value = last; 0331 } 0332 } 0333 return true; 0334 0335 case prod_mysql: 0336 query = "select last_insert_id()"; 0337 break; 0338 0339 case prod_sqlite: 0340 query = "select last_insert_rowid()"; 0341 break; 0342 0343 case prod_firebird: 0344 case prod_oracle: 0345 case prod_postgresql: 0346 // For these RDBMS get_next_sequence_value() should have been used. 0347 return false; 0348 0349 0350 case prod_unknown: 0351 // For this one we can't do anything at all. 0352 return false; 0353 0354 case prod_uninitialized: 0355 // As above, this is not supposed to happen but put it here to 0356 // mollify gcc. 0357 return false; 0358 } 0359 0360 s << query, into(value); 0361 0362 return true; 0363 } 0364 0365 std::string odbc_session_backend::get_dummy_from_table() const 0366 { 0367 std::string table; 0368 0369 switch ( get_database_product() ) 0370 { 0371 case prod_db2: 0372 table = "SYSIBM.SYSDUMMY1"; 0373 break; 0374 0375 case prod_firebird: 0376 table = "rdb$database"; 0377 break; 0378 0379 case prod_oracle: 0380 table = "dual"; 0381 break; 0382 0383 case prod_mssql: 0384 case prod_mysql: 0385 case prod_sqlite: 0386 case prod_postgresql: 0387 // No special dummy table needed. 0388 break; 0389 0390 // These cases are here just to make the switch exhaustive, we 0391 // can't really do anything about them anyhow. 0392 case prod_unknown: 0393 case prod_uninitialized: 0394 break; 0395 } 0396 0397 return table; 0398 } 0399 0400 void odbc_session_backend::reset_transaction() 0401 { 0402 SQLRETURN rc = SQLSetConnectAttr( hdbc_, SQL_ATTR_AUTOCOMMIT, 0403 (SQLPOINTER)SQL_AUTOCOMMIT_ON, 0 ); 0404 if (is_odbc_error(rc)) 0405 { 0406 throw odbc_soci_error(SQL_HANDLE_DBC, hdbc_, "enabling auto commit"); 0407 } 0408 } 0409 0410 0411 void odbc_session_backend::clean_up() 0412 { 0413 SQLRETURN rc = SQLDisconnect(hdbc_); 0414 if (is_odbc_error(rc)) 0415 { 0416 throw odbc_soci_error(SQL_HANDLE_DBC, hdbc_, "disconnecting"); 0417 } 0418 0419 rc = SQLFreeHandle(SQL_HANDLE_DBC, hdbc_); 0420 if (is_odbc_error(rc)) 0421 { 0422 throw odbc_soci_error(SQL_HANDLE_DBC, hdbc_, "freeing connection"); 0423 } 0424 0425 rc = SQLFreeHandle(SQL_HANDLE_ENV, henv_); 0426 if (is_odbc_error(rc)) 0427 { 0428 throw odbc_soci_error(SQL_HANDLE_ENV, henv_, "freeing environment"); 0429 } 0430 } 0431 0432 odbc_statement_backend * odbc_session_backend::make_statement_backend() 0433 { 0434 return new odbc_statement_backend(*this); 0435 } 0436 0437 odbc_rowid_backend * odbc_session_backend::make_rowid_backend() 0438 { 0439 return new odbc_rowid_backend(*this); 0440 } 0441 0442 odbc_blob_backend * odbc_session_backend::make_blob_backend() 0443 { 0444 return new odbc_blob_backend(*this); 0445 } 0446 0447 odbc_session_backend::database_product 0448 odbc_session_backend::get_database_product() const 0449 { 0450 // Cache the product type, it's not going to change during our life time. 0451 if (product_ != prod_uninitialized) 0452 return product_; 0453 0454 char product_name[1024]; 0455 SQLSMALLINT len = sizeof(product_name); 0456 SQLRETURN rc = SQLGetInfo(hdbc_, SQL_DBMS_NAME, product_name, len, &len); 0457 if (is_odbc_error(rc)) 0458 { 0459 throw odbc_soci_error(SQL_HANDLE_DBC, henv_, 0460 "getting ODBC driver name"); 0461 } 0462 0463 if (strcmp(product_name, "Firebird") == 0) 0464 product_ = prod_firebird; 0465 else if (strcmp(product_name, "Microsoft SQL Server") == 0) 0466 product_ = prod_mssql; 0467 else if (strcmp(product_name, "MySQL") == 0) 0468 product_ = prod_mysql; 0469 else if (strcmp(product_name, "Oracle") == 0) 0470 product_ = prod_oracle; 0471 else if (strcmp(product_name, "PostgreSQL") == 0) 0472 product_ = prod_postgresql; 0473 else if (strcmp(product_name, "SQLite") == 0) 0474 product_ = prod_sqlite; 0475 else if (strstr(product_name, "DB2") == product_name) // "DB2/LINUXX8664" 0476 product_ = prod_db2; 0477 else 0478 product_ = prod_unknown; 0479 0480 return product_; 0481 }