/pdo_sqlsrv/pdo_util.cpp

# · C++ · 612 lines · 507 code · 63 blank · 42 comment · 36 complexity · 7a775a5a3be97b645a5188aafcd72835 MD5 · raw file

  1. //---------------------------------------------------------------------------------------------------------------------------------
  2. // File: pdo_util.cpp
  3. //
  4. // Contents: Utility functions used by both connection or statement functions
  5. //
  6. // Copyright Microsoft Corporation
  7. //
  8. // Licensed under the Apache License, Version 2.0 (the "License");
  9. // you may not use this file except in compliance with the License.
  10. //
  11. // You may obtain a copy of the License at:
  12. // http://www.apache.org/licenses/LICENSE-2.0
  13. //
  14. // Unless required by applicable law or agreed to in writing, software
  15. // distributed under the License is distributed on an "AS IS" BASIS,
  16. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17. // See the License for the specific language governing permissions and
  18. // limitations under the License.
  19. //---------------------------------------------------------------------------------------------------------------------------------
  20. #include "pdo_sqlsrv.h"
  21. #include "zend_exceptions.h"
  22. // *** internal constants ***
  23. namespace {
  24. const char WARNING_TEMPLATE[] = "SQLSTATE: %1!s!\nError Code: %2!d!\nMError Message: %3!s!\n";
  25. const char EXCEPTION_MSG_TEMPLATE[] = "SQLSTATE[%s]: %s";
  26. char EXCEPTION_PROPERTY_MSG[] = "message";
  27. char EXCEPTION_PROPERTY_CODE[] = "code";
  28. char EXCEPTION_PROPERTY_ERRORINFO[] = "errorInfo";
  29. const int MAX_DIGITS = 11; // +-2 billion = 10 digits + 1 for the sign if negative
  30. // buffer used to hold a formatted log message prior to actually logging it.
  31. const int LOG_MSG_SIZE = 2048;
  32. char log_msg[ LOG_MSG_SIZE ];
  33. // internal error that says that FormatMessage failed
  34. SQLCHAR INTERNAL_FORMAT_ERROR[] = "An internal error occurred. FormatMessage failed writing an error message.";
  35. // build the object and throw the PDO exception
  36. void pdo_sqlsrv_throw_exception( sqlsrv_error_const* error TSRMLS_DC );
  37. }
  38. // pdo driver error messages
  39. // errors have 3 components, the SQLSTATE (always 'IMSSP'), the error message, and an error code, which for us is always < 0
  40. pdo_error PDO_ERRORS[] = {
  41. {
  42. SQLSRV_ERROR_DRIVER_NOT_INSTALLED,
  43. { IMSSP, (SQLCHAR*) "This extension requires either the Microsoft SQL Server 2008 Native Client (SP1 or later) or the "
  44. "Microsoft SQL Server 2008 R2 Native Client ODBC Driver to communicate with SQL Server. Neither of those ODBC Drivers are currently installed. "
  45. "Access the following URL to download the Microsoft SQL Server 2008 R2 Native Client ODBC driver for %1!s!: "
  46. "http://go.microsoft.com/fwlink/?LinkId=163712", -1, true }
  47. },
  48. {
  49. SQLSRV_ERROR_ZEND_HASH,
  50. { IMSSP, (SQLCHAR*) "An error occurred creating or accessing a Zend hash table.", -2, false }
  51. },
  52. {
  53. PDO_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE,
  54. { IMSSP, (SQLCHAR*) "An invalid PHP type was specified as an output parameter. DateTime objects, NULL values, and streams "
  55. "cannot be specified as output parameters.", -3, false }
  56. },
  57. {
  58. SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE,
  59. { IMSSP, (SQLCHAR*) "An invalid type for parameter %1!d! was specified. Only booleans, integers, floating point "
  60. "numbers, strings, and streams may be used as parameters.", -4, true }
  61. },
  62. {
  63. SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE,
  64. { IMSSP, (SQLCHAR*) "An invalid SQL Server type for parameter %1!d! was specified.", -5, true }
  65. },
  66. {
  67. SQLSRV_ERROR_INVALID_PARAMETER_ENCODING,
  68. { IMSSP, (SQLCHAR*) "An invalid encoding was specified for parameter %1!d!.", -6, true }
  69. },
  70. {
  71. SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE,
  72. { IMSSP, (SQLCHAR*) "An error occurred translating string for input param %1!d! to UCS-2: %2!s!", -7, true }
  73. },
  74. {
  75. SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE,
  76. { IMSSP, (SQLCHAR*) "An error occurred translating string for an output param to UTF-8: %1!s!", -8, true }
  77. },
  78. {
  79. SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE,
  80. { IMSSP, (SQLCHAR*) "An error occurred translating the connection string to UTF-16: %1!s!", -9, true }
  81. },
  82. {
  83. SQLSRV_ERROR_ZEND_STREAM,
  84. { IMSSP, (SQLCHAR*) "An error occurred reading from a PHP stream.", -10, false }
  85. },
  86. {
  87. SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE,
  88. { IMSSP, (SQLCHAR*) "An error occurred translating a PHP stream from UTF-8 to UTF-16: %1!s!", -11, true }
  89. },
  90. {
  91. SQLSRV_ERROR_UNKNOWN_SERVER_VERSION,
  92. { IMSSP, (SQLCHAR*) "Failed to retrieve the server version. Unable to continue.", -12, false }
  93. },
  94. {
  95. SQLSRV_ERROR_FETCH_PAST_END,
  96. { IMSSP, (SQLCHAR*) "There are no more rows in the active result set. Since this result set is not scrollable, "
  97. "no more data may be retrieved.", -13, false }
  98. },
  99. {
  100. SQLSRV_ERROR_STATEMENT_NOT_EXECUTED,
  101. { IMSSP, (SQLCHAR*) "The statement must be executed before results can be retrieved.", -14, false }
  102. },
  103. {
  104. SQLSRV_ERROR_NO_FIELDS,
  105. { IMSSP, (SQLCHAR*) "The active result for the query contains no fields.", -15, false }
  106. },
  107. {
  108. SQLSRV_ERROR_FETCH_NOT_CALLED,
  109. { IMSSP, (SQLCHAR*) "Internal pdo_sqlsrv error: Tried to retrieve a field before one of the PDOStatement::fetch "
  110. "functions was called.", -16, false }
  111. },
  112. {
  113. SQLSRV_ERROR_NO_DATA,
  114. { IMSSP, (SQLCHAR*)"Field %1!d! returned no data.", -17, true }
  115. },
  116. {
  117. SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE,
  118. { IMSSP, (SQLCHAR*)"An error occurred translating string for a field to UTF-8: %1!s!", -18, true }
  119. },
  120. {
  121. SQLSRV_ERROR_ZEND_HASH_CREATE_FAILED,
  122. { IMSSP, (SQLCHAR*) "Zend returned an error when creating an associative array.", -19, false }
  123. },
  124. {
  125. SQLSRV_ERROR_NEXT_RESULT_PAST_END,
  126. { IMSSP, (SQLCHAR*)"There are no more results returned by the query.", -20, false }
  127. },
  128. {
  129. SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED,
  130. { IMSSP, (SQLCHAR*) "An unescaped right brace (}) was found in either the user name or password. All right braces must be"
  131. " escaped with another right brace (}}).", -21, false }
  132. },
  133. {
  134. SQLSRV_ERROR_UNESCAPED_RIGHT_BRACE_IN_DSN,
  135. { IMSSP, (SQLCHAR*) "An unescaped right brace (}) was found in the DSN string for keyword '%1!s!'. All right braces "
  136. "must be escaped with another right brace (}}).", -22, true }
  137. },
  138. {
  139. SQLSRV_ERROR_INVALID_OPTION_TYPE_INT,
  140. { IMSSP, (SQLCHAR*) "Invalid value type for option %1!s! was specified. Integer type was expected.", -23, true }
  141. },
  142. {
  143. SQLSRV_ERROR_INVALID_OPTION_TYPE_STRING,
  144. { IMSSP, (SQLCHAR*) "Invalid value type for option %1!s! was specified. String type was expected.", -24, true }
  145. },
  146. {
  147. SQLSRV_ERROR_CONN_OPTS_WRONG_TYPE,
  148. { IMSSP, (SQLCHAR*) "Expected an array of options for the connection. Connection options must be passed as an array of "
  149. "key/value pairs.", -25, false }
  150. },
  151. {
  152. SQLSRV_ERROR_INVALID_CONNECTION_KEY,
  153. { IMSSP, (SQLCHAR*) "An invalid connection option key type was received. Option key types must be strings.", -26, false }
  154. },
  155. {
  156. SQLSRV_ERROR_INVALID_TYPE,
  157. { IMSSP, (SQLCHAR*) "Invalid type.", -27, false }
  158. },
  159. {
  160. PDO_SQLSRV_ERROR_INVALID_COLUMN_INDEX,
  161. {IMSSP, (SQLCHAR*)"An invalid column number was specified.", -28, false }
  162. },
  163. {
  164. SQLSRV_ERROR_MAX_PARAMS_EXCEEDED,
  165. { IMSSP, (SQLCHAR*) "Tried to bind parameter number %1!d!. SQL Server supports a maximum of 2100 parameters.", -29, true }
  166. },
  167. {
  168. SQLSRV_ERROR_INVALID_OPTION_KEY,
  169. { IMSSP, (SQLCHAR*) "Invalid option key %1!s! specified.", -30, true }
  170. },
  171. {
  172. SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE,
  173. { IMSSP, (SQLCHAR*) "Invalid value %1!s! specified for option PDO::SQLSRV_ATTR_QUERY_TIMEOUT.", -31, true }
  174. },
  175. {
  176. SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE,
  177. { IMSSP, (SQLCHAR*) "The value passed for the 'Scrollable' statement option is invalid.", -32, false }
  178. },
  179. {
  180. PDO_SQLSRV_ERROR_INVALID_DBH_ATTR,
  181. { IMSSP, (SQLCHAR*) "An invalid attribute was designated on the PDO object.", -33, false }
  182. },
  183. {
  184. PDO_SQLSRV_ERROR_INVALID_STMT_ATTR,
  185. { IMSSP, (SQLCHAR*) "An invalid attribute was designated on the PDOStatement object.", -34, false }
  186. },
  187. {
  188. PDO_SQLSRV_ERROR_INVALID_ENCODING,
  189. { IMSSP, (SQLCHAR*) "An invalid encoding was specified for SQLSRV_ATTR_ENCODING.", -35, false }
  190. },
  191. {
  192. PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM,
  193. { IMSSP, (SQLCHAR*) "An invalid type or value was given for the parameter driver data. Only encoding constants "
  194. "such as PDO::SQLSRV_ENCODING_UTF8 may be used as parameter driver options.", -36, false }
  195. },
  196. {
  197. PDO_SQLSRV_ERROR_PDO_STMT_UNSUPPORTED,
  198. { IMSSP, (SQLCHAR*) "PDO::PARAM_STMT is not a supported parameter type.", -37, false }
  199. },
  200. {
  201. PDO_SQLSRV_ERROR_UNSUPPORTED_DBH_ATTR,
  202. { IMSSP, (SQLCHAR*) "An unsupported attribute was designated on the PDO object.", -38, false }
  203. },
  204. {
  205. PDO_SQLSRV_ERROR_STMT_LEVEL_ATTR,
  206. { IMSSP, (SQLCHAR*) "The given attribute is only supported on the PDOStatement object.", -39, false }
  207. },
  208. {
  209. PDO_SQLSRV_ERROR_READ_ONLY_DBH_ATTR,
  210. { IMSSP, (SQLCHAR*) "A read-only attribute was designated on the PDO object.", -40, false }
  211. },
  212. {
  213. PDO_SQLSRV_ERROR_INVALID_DSN_STRING,
  214. {IMSSP, (SQLCHAR*)"An invalid DSN string was specified.", -41, false }
  215. },
  216. {
  217. PDO_SQLSRV_ERROR_INVALID_DSN_KEY,
  218. { IMSSP, (SQLCHAR*) "An invalid keyword '%1!s!' was specified in the DSN string.", -42, true }
  219. },
  220. {
  221. PDO_SQLSRV_ERROR_INVALID_STMT_OPTION,
  222. { IMSSP, (SQLCHAR*) "An invalid statement option was specified.", -43, false }
  223. },
  224. {
  225. PDO_SQLSRV_ERROR_INVALID_CURSOR_TYPE,
  226. { IMSSP, (SQLCHAR*) "An invalid cursor type was specified for either PDO::ATTR_CURSOR or "
  227. "PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE", -44, false }
  228. },
  229. {
  230. PDO_SQLSRV_ERROR_PARAM_PARSE,
  231. { IMSSP, (SQLCHAR*) "An error occurred substituting the named parameters.", -45, false }
  232. },
  233. {
  234. PDO_SQLSRV_ERROR_LAST_INSERT_ID,
  235. { IMSSP, (SQLCHAR*) "An error occurred retrieving the last insert id.", -46, false }
  236. },
  237. {
  238. SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE,
  239. { IMSSP, (SQLCHAR*) "An error occurred translating the query string to UTF-16: %1!s!.", -47, true }
  240. },
  241. {
  242. PDO_SQLSRV_ERROR_INVALID_COLUMN_DRIVER_DATA,
  243. { IMSSP, (SQLCHAR*) "An invalid type or value was given as bound column driver data for column %1!d!. Only "
  244. "encoding constants such as PDO::SQLSRV_ENCODING_UTF8 may be used as bound column driver data.", -48, true }
  245. },
  246. {
  247. PDO_SQLSRV_ERROR_COLUMN_TYPE_DOES_NOT_SUPPORT_ENCODING,
  248. { IMSSP, (SQLCHAR*) "An encoding was specified for column %1!d!. Only PDO::PARAM_LOB and PDO::PARAM_STR column types "
  249. "can take an encoding option.", -49, true }
  250. },
  251. {
  252. PDO_SQLSRV_ERROR_INVALID_DRIVER_COLUMN_ENCODING,
  253. { IMSSP, (SQLCHAR*) "Invalid encoding specified for column %1!d!.", -50, true }
  254. },
  255. {
  256. PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_TYPE,
  257. { IMSSP, (SQLCHAR*) "An encoding was specified for parameter %1!d!. Only PDO::PARAM_LOB and PDO::PARAM_STR can take an "
  258. "encoding option.", -51, true }
  259. },
  260. {
  261. PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_ENCODING,
  262. { IMSSP, (SQLCHAR*) "Invalid encoding specified for parameter %1!d!.", -52, true }
  263. },
  264. {
  265. PDO_SQLSRV_ERROR_CURSOR_ATTR_AT_PREPARE_ONLY,
  266. { IMSSP, (SQLCHAR*) "The PDO::ATTR_CURSOR and PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE attributes may only be set in the "
  267. "$driver_options array of PDO::prepare.", -53, false }
  268. },
  269. {
  270. SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED,
  271. { IMSSP, (SQLCHAR*) "String data, right truncated for output parameter %1!d!.", -54, true }
  272. },
  273. {
  274. SQLSRV_ERROR_INPUT_OUTPUT_PARAM_TYPE_MATCH,
  275. { IMSSP, (SQLCHAR*) "Types for parameter value and PDO::PARAM_* constant must be compatible for input/output "
  276. "parameter %1!d!.", -55, true }
  277. },
  278. {
  279. PDO_SQLSRV_ERROR_INVALID_PARAM_DIRECTION,
  280. { IMSSP, (SQLCHAR*) "Invalid direction specified for parameter %1!d!. Input/output parameters must have a length.",
  281. -56, true }
  282. },
  283. {
  284. PDO_SQLSRV_ERROR_INVALID_OUTPUT_STRING_SIZE,
  285. { IMSSP, (SQLCHAR*) "Invalid size for output string parameter %1!d!. Input/output string parameters must have an "
  286. "explicit length.", -57, true }
  287. },
  288. {
  289. PDO_SQLSRV_ERROR_FUNCTION_NOT_IMPLEMENTED,
  290. { IMSSP, (SQLCHAR*) "This function is not implemented by this driver.", -58, false }
  291. },
  292. {
  293. /* The stream related errors are not currently used in PDO, but the core layer can throw the stream related
  294. errors so having a mapping here */
  295. SQLSRV_ERROR_STREAMABLE_TYPES_ONLY,
  296. { IMSSP, (SQLCHAR*) "Only char, nchar, varchar, nvarchar, binary, varbinary, and large object types can be read by using "
  297. "streams.", -59, false}
  298. },
  299. {
  300. SQLSRV_ERROR_STREAM_CREATE,
  301. { IMSSP, (SQLCHAR*)"An error occurred while retrieving a SQL Server field as a stream.", -60, false }
  302. },
  303. {
  304. SQLSRV_ERROR_MARS_OFF,
  305. { IMSSP, (SQLCHAR*)"The connection cannot process this operation because there is a statement with pending results. "
  306. "To make the connection available for other queries, either fetch all results or cancel or free the statement. "
  307. "For more information, see the product documentation about the MultipleActiveResultSets connection option.", -61, false }
  308. },
  309. {
  310. SQLSRV_ERROR_FIELD_INDEX_ERROR,
  311. { IMSSP, (SQLCHAR*)"Fields within a row must be accessed in ascending order. Cannot retrieve field %1!d! because its "
  312. "index is less than the index of a field that has already been retrieved (%2!d!).", -62, true }
  313. },
  314. {
  315. PDO_SQLSRV_ERROR_INVALID_DSN_VALUE,
  316. { IMSSP, (SQLCHAR*) "An invalid value was specified for the keyword '%1!s!' in the DSN string.", -63, true }
  317. },
  318. {
  319. PDO_SQLSRV_ERROR_SERVER_NOT_SPECIFIED,
  320. { IMSSP, (SQLCHAR*) "Server keyword was not specified in the DSN string.", -64, false }
  321. },
  322. {
  323. PDO_SQLSRV_ERROR_DSN_STRING_ENDED_UNEXPECTEDLY,
  324. { IMSSP, (SQLCHAR*) "The DSN string ended unexpectedly.", -65, false }
  325. },
  326. {
  327. PDO_SQLSRV_ERROR_EXTRA_SEMI_COLON_IN_DSN_STRING,
  328. { IMSSP, (SQLCHAR*) "An extra semi-colon was encountered in the DSN string at character (byte-count) position '%1!d!' .",
  329. -66, true }
  330. },
  331. {
  332. PDO_SQLSRV_ERROR_RCB_MISSING_IN_DSN_VALUE,
  333. { IMSSP, (SQLCHAR*) "An expected right brace (}) was not found in the DSN string for the value of the keyword '%1!s!'.",
  334. -67, true }
  335. },
  336. {
  337. PDO_SQLSRV_ERROR_DQ_ATTR_AT_PREPARE_ONLY,
  338. { IMSSP, (SQLCHAR*) "The PDO::SQLSRV_ATTR_DIRECT_QUERY attribute may only be set in the $driver_options array of "
  339. "PDO::prepare.", -68, false }
  340. },
  341. {
  342. PDO_SQLSRV_ERROR_INVALID_CURSOR_WITH_SCROLL_TYPE,
  343. { IMSSP, (SQLCHAR*) "The PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE attribute may only be set when PDO::ATTR_CURSOR is set to "
  344. "PDO::CURSOR_SCROLL in the $driver_options array of PDO::prepare.", -69, false }
  345. },
  346. {
  347. SQLSRV_ERROR_INVALID_BUFFER_LIMIT,
  348. { IMSSP, (SQLCHAR*) "The PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE attribute is not a number or the number is not "
  349. "positive. Only positive numbers are valid for this attribute.", -70, false }
  350. },
  351. {
  352. SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED,
  353. { IMSSP, (SQLCHAR*) "Memory limit of %1!d! KB exceeded for buffered query", -71, true }
  354. },
  355. { -1, {} }
  356. };
  357. // Returns a sqlsrv_error for a given error code.
  358. sqlsrv_error_const* get_error_message( unsigned int sqlsrv_error_code ) {
  359. sqlsrv_error_const *error_message = NULL;
  360. int zr = zend_hash_index_find( g_pdo_errors_ht, sqlsrv_error_code, reinterpret_cast<void**>( &error_message ));
  361. if( zr == FAILURE ) {
  362. DIE( "get_error_message: zend_hash_index_find returned failure for sqlsrv_error_code = %1!d!", sqlsrv_error_code );
  363. }
  364. SQLSRV_ASSERT( error_message != NULL, "get_error_message: error_message was null");
  365. return error_message;
  366. }
  367. // PDO error handler for the environment context.
  368. bool pdo_sqlsrv_handle_env_error( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC,
  369. va_list* print_args )
  370. {
  371. SQLSRV_ASSERT(( ctx != NULL ), "pdo_sqlsrv_handle_env_error: sqlsrv_context was null" );
  372. pdo_dbh_t* dbh = reinterpret_cast<pdo_dbh_t*>( ctx.driver());
  373. SQLSRV_ASSERT(( dbh != NULL ), "pdo_sqlsrv_handle_env_error: pdo_dbh_t was null" );
  374. sqlsrv_error_auto_ptr error;
  375. if( sqlsrv_error_code != SQLSRV_ERROR_ODBC ) {
  376. core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, SEV_ERROR TSRMLS_CC, print_args );
  377. }
  378. else {
  379. bool err = core_sqlsrv_get_odbc_error( ctx, 1, error, SEV_ERROR TSRMLS_CC );
  380. SQLSRV_ASSERT( err == true, "No ODBC error was found" );
  381. }
  382. strcpy_s( dbh->error_code, sizeof( pdo_error_type ), reinterpret_cast<const char*>( error->sqlstate ));
  383. switch( dbh->error_mode ) {
  384. case PDO_ERRMODE_EXCEPTION:
  385. if( !warning ) {
  386. pdo_sqlsrv_throw_exception( error TSRMLS_CC );
  387. }
  388. ctx.set_last_error( error );
  389. break;
  390. default:
  391. DIE( "pdo_sqlsrv_handle_env_error: Unexpected error mode. %1!d!", dbh->error_mode );
  392. break;
  393. }
  394. // we don't transfer the zval_auto_ptr since set_last_error increments the zval ref count
  395. // return error ignored = true for warnings.
  396. return ( warning ? true : false );
  397. }
  398. // pdo error handler for the dbh context.
  399. bool pdo_sqlsrv_handle_dbh_error( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC,
  400. va_list* print_args )
  401. {
  402. pdo_dbh_t* dbh = reinterpret_cast<pdo_dbh_t*>( ctx.driver());
  403. SQLSRV_ASSERT( dbh != NULL, "pdo_sqlsrv_handle_dbh_error: Null dbh passed" );
  404. sqlsrv_error_auto_ptr error;
  405. if( sqlsrv_error_code != SQLSRV_ERROR_ODBC ) {
  406. core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, SEV_ERROR TSRMLS_CC, print_args );
  407. }
  408. else {
  409. bool err = core_sqlsrv_get_odbc_error( ctx, 1, error, SEV_ERROR TSRMLS_CC );
  410. SQLSRV_ASSERT( err == true, "No ODBC error was found" );
  411. }
  412. SQLSRV_STATIC_ASSERT( sizeof( error->sqlstate ) <= sizeof( dbh->error_code ));
  413. strcpy_s( dbh->error_code, sizeof( dbh->error_code ), reinterpret_cast<const char*>( error->sqlstate ));
  414. switch( dbh->error_mode ) {
  415. case PDO_ERRMODE_EXCEPTION:
  416. if( !warning ) {
  417. pdo_sqlsrv_throw_exception( error TSRMLS_CC );
  418. }
  419. ctx.set_last_error( error );
  420. break;
  421. case PDO_ERRMODE_WARNING:
  422. if( !warning ) {
  423. unsigned int msg_len = strlen( reinterpret_cast<const char*>( error->native_message )) + SQL_SQLSTATE_BUFSIZE
  424. + MAX_DIGITS + 1;
  425. sqlsrv_malloc_auto_ptr<char> msg;
  426. msg = static_cast<char*>( sqlsrv_malloc( msg_len ));
  427. core_sqlsrv_format_message( msg, msg_len, WARNING_TEMPLATE, error->sqlstate, error->native_code,
  428. error->native_message );
  429. php_error( E_WARNING, msg );
  430. sqlsrv_free( msg );
  431. }
  432. ctx.set_last_error( error );
  433. break;
  434. case PDO_ERRMODE_SILENT:
  435. ctx.set_last_error( error );
  436. break;
  437. default:
  438. DIE( "Unknown error mode. %1!d!", dbh->error_mode );
  439. break;
  440. }
  441. // return error ignored = true for warnings.
  442. return ( warning ? true : false );
  443. }
  444. // PDO error handler for the statement context.
  445. bool pdo_sqlsrv_handle_stmt_error( sqlsrv_context& ctx, unsigned int sqlsrv_error_code, bool warning TSRMLS_DC,
  446. va_list* print_args )
  447. {
  448. pdo_stmt_t* pdo_stmt = reinterpret_cast<pdo_stmt_t*>( ctx.driver());
  449. SQLSRV_ASSERT( pdo_stmt != NULL && pdo_stmt->dbh != NULL, "pdo_sqlsrv_handle_stmt_error: Null statement or dbh passed" );
  450. sqlsrv_error_auto_ptr error;
  451. if( sqlsrv_error_code != SQLSRV_ERROR_ODBC ) {
  452. core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, SEV_ERROR TSRMLS_CC, print_args );
  453. }
  454. else {
  455. bool err = core_sqlsrv_get_odbc_error( ctx, 1, error, SEV_ERROR TSRMLS_CC );
  456. SQLSRV_ASSERT( err == true, "No ODBC error was found" );
  457. }
  458. SQLSRV_STATIC_ASSERT( sizeof( error->sqlstate ) <= sizeof( pdo_stmt->error_code ));
  459. strcpy_s( pdo_stmt->error_code, sizeof( pdo_stmt->error_code ), reinterpret_cast<const char*>( error->sqlstate ));
  460. switch( pdo_stmt->dbh->error_mode ) {
  461. case PDO_ERRMODE_EXCEPTION:
  462. if( !warning ) {
  463. pdo_sqlsrv_throw_exception( error TSRMLS_CC );
  464. }
  465. ctx.set_last_error( error );
  466. break;
  467. case PDO_ERRMODE_WARNING:
  468. if( !warning ) {
  469. unsigned int msg_len = strlen( reinterpret_cast<const char*>( error->native_message )) + SQL_SQLSTATE_BUFSIZE
  470. + MAX_DIGITS + 1;
  471. sqlsrv_malloc_auto_ptr<char> msg;
  472. msg = static_cast<char*>( sqlsrv_malloc( msg_len ));
  473. core_sqlsrv_format_message( msg, msg_len, WARNING_TEMPLATE, error->sqlstate, error->native_code,
  474. error->native_message );
  475. php_error( E_WARNING, msg );
  476. sqlsrv_free( msg );
  477. }
  478. ctx.set_last_error( error );
  479. break;
  480. case PDO_ERRMODE_SILENT:
  481. ctx.set_last_error( error );
  482. break;
  483. default:
  484. DIE( "Unknown error mode. %1!d!", pdo_stmt->dbh->error_mode );
  485. break;
  486. }
  487. // return error ignored = true for warnings.
  488. return ( warning ? true : false );
  489. }
  490. // Transfer a sqlsrv_context's error to a PDO zval. The standard format for a zval error is 3 elements:
  491. // 0, native code
  492. // 1, native message
  493. // 2, SQLSTATE of the error (driver specific error messages are 'IMSSP')
  494. void pdo_sqlsrv_retrieve_context_error( sqlsrv_error const* last_error, zval* pdo_zval )
  495. {
  496. if( last_error ) {
  497. // SQLSTATE is already present in the zval.
  498. add_next_index_long( pdo_zval, last_error->native_code );
  499. add_next_index_string( pdo_zval, reinterpret_cast<char*>( last_error->native_message ), 1 /*dup*/ );
  500. }
  501. else {
  502. add_next_index_null( pdo_zval ); /* native code */
  503. add_next_index_null( pdo_zval ); /* native message */
  504. }
  505. }
  506. // Formats the error message and writes to the php error log.
  507. void pdo_sqlsrv_log( unsigned int severity TSRMLS_DC, const char* msg, va_list* print_args )
  508. {
  509. if( (severity & PDO_SQLSRV_G( log_severity )) == 0 ) {
  510. return;
  511. }
  512. DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, msg, 0, 0, log_msg, LOG_MSG_SIZE, print_args );
  513. // if an error occurs for FormatMessage, we just output an internal error occurred.
  514. if( rc == 0 ) {
  515. SQLSRV_STATIC_ASSERT( sizeof( INTERNAL_FORMAT_ERROR ) < sizeof( log_msg ));
  516. std::copy( INTERNAL_FORMAT_ERROR, INTERNAL_FORMAT_ERROR + sizeof( INTERNAL_FORMAT_ERROR ), log_msg );
  517. }
  518. php_log_err( log_msg TSRMLS_CC );
  519. }
  520. namespace {
  521. void pdo_sqlsrv_throw_exception( sqlsrv_error_const* error TSRMLS_DC )
  522. {
  523. zval_auto_ptr ex_obj;
  524. MAKE_STD_ZVAL( ex_obj );
  525. zend_class_entry* ex_class = pdo_get_exception_class();
  526. int zr = object_init_ex( ex_obj, ex_class );
  527. SQLSRV_ASSERT( zr != FAILURE, "Failed to initialize exception object" );
  528. sqlsrv_malloc_auto_ptr<char> ex_msg;
  529. size_t ex_msg_len = strlen( reinterpret_cast<const char*>( error->native_message )) + SQL_SQLSTATE_BUFSIZE +
  530. 12 + 1; // 12 = "SQLSTATE[]: "
  531. ex_msg = reinterpret_cast<char*>( sqlsrv_malloc( ex_msg_len ));
  532. snprintf( ex_msg, ex_msg_len, EXCEPTION_MSG_TEMPLATE, error->sqlstate, error->native_message );
  533. zend_update_property_string( ex_class, ex_obj, EXCEPTION_PROPERTY_MSG, sizeof( EXCEPTION_PROPERTY_MSG ) - 1,
  534. ex_msg TSRMLS_CC );
  535. zend_update_property_string( ex_class, ex_obj, EXCEPTION_PROPERTY_CODE, sizeof( EXCEPTION_PROPERTY_CODE ) - 1,
  536. reinterpret_cast<char*>( error->sqlstate ) TSRMLS_CC );
  537. zval_auto_ptr ex_error_info;
  538. MAKE_STD_ZVAL( ex_error_info );
  539. array_init( ex_error_info );
  540. add_next_index_string( ex_error_info, reinterpret_cast<char*>( error->sqlstate ), 1 /* dup */ );
  541. add_next_index_long( ex_error_info, error->native_code );
  542. add_next_index_string( ex_error_info, reinterpret_cast<char*>( error->native_message ), 1 /* dup */ );
  543. zend_update_property( ex_class, ex_obj, EXCEPTION_PROPERTY_ERRORINFO, sizeof( EXCEPTION_PROPERTY_ERRORINFO ) - 1,
  544. ex_error_info TSRMLS_CC );
  545. zend_throw_exception_object( ex_obj TSRMLS_CC );
  546. ex_msg.transferred();
  547. ex_obj.transferred();
  548. }
  549. }