PageRenderTime 28ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/classes/fDatabase.php

https://bitbucket.org/ZilIsiltk/flourish
PHP | 3211 lines | 2016 code | 415 blank | 780 comment | 505 complexity | aa8866de51a13830825efd3031007590 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /**
  3. * Provides a common API for different databases - will automatically use any installed extension
  4. *
  5. * This class is implemented to use the UTF-8 character encoding. Please see
  6. * http://flourishlib.com/docs/UTF-8 for more information.
  7. *
  8. * The following databases are supported:
  9. *
  10. * - [http://ibm.com/db2 DB2]
  11. * - [http://microsoft.com/sql/ MSSQL]
  12. * - [http://mysql.com MySQL]
  13. * - [http://oracle.com Oracle]
  14. * - [http://postgresql.org PostgreSQL]
  15. * - [http://sqlite.org SQLite]
  16. *
  17. * The class will automatically use the first of the following extensions it finds:
  18. *
  19. * - DB2
  20. * - [http://php.net/ibm_db2 ibm_db2]
  21. * - [http://php.net/pdo_ibm pdo_ibm]
  22. * - MSSQL
  23. * - [http://msdn.microsoft.com/en-us/library/cc296221.aspx sqlsrv]
  24. * - [http://php.net/pdo_dblib pdo_dblib]
  25. * - [http://php.net/mssql mssql] (or [http://php.net/sybase sybase])
  26. * - MySQL
  27. * - [http://php.net/mysql mysql]
  28. * - [http://php.net/mysqli mysqli]
  29. * - [http://php.net/pdo_mysql pdo_mysql]
  30. * - Oracle
  31. * - [http://php.net/oci8 oci8]
  32. * - [http://php.net/pdo_oci pdo_oci]
  33. * - PostgreSQL
  34. * - [http://php.net/pgsql pgsql]
  35. * - [http://php.net/pdo_pgsql pdo_pgsql]
  36. * - SQLite
  37. * - [http://php.net/pdo_sqlite pdo_sqlite] (for v3.x)
  38. * - [http://php.net/sqlite sqlite] (for v2.x)
  39. *
  40. * The `odbc` and `pdo_odbc` extensions are not supported due to character
  41. * encoding and stability issues on Windows, and functionality on non-Windows
  42. * operating systems.
  43. *
  44. * @copyright Copyright (c) 2007-2010 Will Bond
  45. * @author Will Bond [wb] <will@flourishlib.com>
  46. * @license http://flourishlib.com/license
  47. *
  48. * @package Flourish
  49. * @link http://flourishlib.com/fDatabase
  50. *
  51. * @version 1.0.0b31
  52. * @changes 1.0.0b31 Fixed handling auto-incrementing values for MySQL when the `INTO` keyword is left out of an `INSERT` statement [wb, 2010-11-04]
  53. * @changes 1.0.0b30 Fixed the pgsql, mssql and mysql extensions to force a new connection instead of reusing an existing one [wb, 2010-08-17]
  54. * @changes 1.0.0b29 Backwards Compatibility Break - removed ::enableSlowQueryWarnings(), added ability to replicate via ::registerHookCallback() [wb, 2010-08-10]
  55. * @changes 1.0.0b28 Backwards Compatibility Break - removed ODBC support. Added support for the `pdo_ibm` extension. [wb, 2010-07-31]
  56. * @changes 1.0.0b27 Fixed a bug with running multiple copies of a SQL statement with string values through a single ::translatedQuery() call [wb, 2010-07-14]
  57. * @changes 1.0.0b26 Updated the class to use new fCore functionality [wb, 2010-07-05]
  58. * @changes 1.0.0b25 Added IBM DB2 support [wb, 2010-04-13]
  59. * @changes 1.0.0b24 Fixed an auto-incrementing transaction bug with Oracle and debugging issues with all databases [wb, 2010-03-17]
  60. * @changes 1.0.0b23 Resolved another bug with capturing auto-incrementing values for PostgreSQL and Oracle [wb, 2010-03-15]
  61. * @changes 1.0.0b22 Changed ::clearCache() to also clear the cache on the fSQLTranslation [wb, 2010-03-09]
  62. * @changes 1.0.0b21 Added ::execute() for result-less SQL queries, ::prepare() and ::translatedPrepare() to create fStatement objects for prepared statements, support for prepared statements in ::query() and ::unbufferedQuery(), fixed default caching key for ::enableCaching() [wb, 2010-03-02]
  63. * @changes 1.0.0b20 Added a parameter to ::enableCaching() to provide a key token that will allow cached values to be shared between multiple databases with the same schema [wb, 2009-10-28]
  64. * @changes 1.0.0b19 Added support for escaping identifiers (column and table names) to ::escape(), added support for database schemas, rewrote internal SQL string spliting [wb, 2009-10-22]
  65. * @changes 1.0.0b18 Updated the class for the new fResult and fUnbufferedResult APIs, fixed ::unescape() to not touch NULLs [wb, 2009-08-12]
  66. * @changes 1.0.0b17 Added the ability to pass an array of all values as a single parameter to ::escape() instead of one value per parameter [wb, 2009-08-11]
  67. * @changes 1.0.0b16 Fixed PostgreSQL and Oracle from trying to get auto-incrementing values on inserts when explicit values were given [wb, 2009-08-06]
  68. * @changes 1.0.0b15 Fixed a bug where auto-incremented values would not be detected when table names were quoted [wb, 2009-07-15]
  69. * @changes 1.0.0b14 Changed ::determineExtension() and ::determineCharacterSet() to be protected instead of private [wb, 2009-07-08]
  70. * @changes 1.0.0b13 Updated ::escape() to accept arrays of values for insertion into full SQL strings [wb, 2009-07-06]
  71. * @changes 1.0.0b12 Updates to ::unescape() to improve performance [wb, 2009-06-15]
  72. * @changes 1.0.0b11 Changed replacement values in preg_replace() calls to be properly escaped [wb, 2009-06-11]
  73. * @changes 1.0.0b10 Changed date/time/timestamp escaping from `strtotime()` to fDate/fTime/fTimestamp for better localization support [wb, 2009-06-01]
  74. * @changes 1.0.0b9 Fixed a bug with ::escape() where floats that start with a . were encoded as `NULL` [wb, 2009-05-09]
  75. * @changes 1.0.0b8 Added Oracle support, change PostgreSQL code to no longer cause lastval() warnings, added support for arrays of values to ::escape() [wb, 2009-05-03]
  76. * @changes 1.0.0b7 Updated for new fCore API [wb, 2009-02-16]
  77. * @changes 1.0.0b6 Fixed a bug with executing transaction queries when using the mysqli extension [wb, 2009-02-12]
  78. * @changes 1.0.0b5 Changed @ error suppression operator to `error_reporting()` calls [wb, 2009-01-26]
  79. * @changes 1.0.0b4 Added a few error suppression operators back in so that developers don't get errors and exceptions [wb, 2009-01-14]
  80. * @changes 1.0.0b3 Removed some unnecessary error suppresion operators [wb, 2008-12-11]
  81. * @changes 1.0.0b2 Fixed a bug with PostgreSQL when using the PDO extension and executing an INSERT statement [wb, 2008-12-11]
  82. * @changes 1.0.0b The initial implementation [wb, 2007-09-25]
  83. */
  84. class fDatabase
  85. {
  86. /**
  87. * Composes text using fText if loaded
  88. *
  89. * @param string $message The message to compose
  90. * @param mixed $component A string or number to insert into the message
  91. * @param mixed ...
  92. * @return string The composed and possible translated message
  93. */
  94. static protected function compose($message)
  95. {
  96. $args = array_slice(func_get_args(), 1);
  97. if (class_exists('fText', FALSE)) {
  98. return call_user_func_array(
  99. array('fText', 'compose'),
  100. array($message, $args)
  101. );
  102. } else {
  103. return vsprintf($message, $args);
  104. }
  105. }
  106. /**
  107. * An fCache object to cache the schema info to
  108. *
  109. * @var fCache
  110. */
  111. private $cache;
  112. /**
  113. * The cache prefix to use for cache entries
  114. *
  115. * @var string
  116. */
  117. private $cache_prefix;
  118. /**
  119. * Database connection resource or PDO object
  120. *
  121. * @var mixed
  122. */
  123. private $connection;
  124. /**
  125. * The database name
  126. *
  127. * @var string
  128. */
  129. private $database;
  130. /**
  131. * If debugging is enabled
  132. *
  133. * @var boolean
  134. */
  135. private $debug;
  136. /**
  137. * A temporary error holder for the mssql extension
  138. *
  139. * @var string
  140. */
  141. private $error;
  142. /**
  143. * The extension to use for the database specified
  144. *
  145. * Options include:
  146. *
  147. * - `'ibm_db2'`
  148. * - `'mssql'`
  149. * - `'mysql'`
  150. * - `'mysqli'`
  151. * - `'oci8'`
  152. * - `'pgsql'`
  153. * - `'sqlite'`
  154. * - `'sqlsrv'`
  155. * - `'pdo'`
  156. *
  157. * @var string
  158. */
  159. protected $extension;
  160. /**
  161. * Hooks callbacks to be used for accessing and modifying queries
  162. *
  163. * This array will have the structure:
  164. *
  165. * {{{
  166. * array(
  167. * 'unmodified' => array({callbacks}),
  168. * 'extracted' => array({callbacks}),
  169. * 'run' => array({callbacks})
  170. * )
  171. * }}}
  172. *
  173. * @var array
  174. */
  175. private $hook_callbacks;
  176. /**
  177. * The host the database server is located on
  178. *
  179. * @var string
  180. */
  181. private $host;
  182. /**
  183. * If a transaction is in progress
  184. *
  185. * @var boolean
  186. */
  187. private $inside_transaction;
  188. /**
  189. * The password for the user specified
  190. *
  191. * @var string
  192. */
  193. private $password;
  194. /**
  195. * The port number for the host
  196. *
  197. * @var string
  198. */
  199. private $port;
  200. /**
  201. * The total number of seconds spent executing queries
  202. *
  203. * @var float
  204. */
  205. private $query_time;
  206. /**
  207. * A cache of database-specific code
  208. *
  209. * @var array
  210. */
  211. protected $schema_info;
  212. /**
  213. * The last executed fStatement object
  214. *
  215. * @var fStatement
  216. */
  217. private $statement;
  218. /**
  219. * The fSQLTranslation object for this database
  220. *
  221. * @var object
  222. */
  223. private $translation;
  224. /**
  225. * The database type: `'db2'`, `'mssql'`, `'mysql'`, `'oracle'`, `'postgresql'`, or `'sqlite'`
  226. *
  227. * @var string
  228. */
  229. private $type;
  230. /**
  231. * The unbuffered query instance
  232. *
  233. * @var fUnbufferedResult
  234. */
  235. private $unbuffered_result;
  236. /**
  237. * The user to connect to the database as
  238. *
  239. * @var string
  240. */
  241. private $username;
  242. /**
  243. * Configures the connection to a database - connection is not made until the first query is executed
  244. *
  245. * @param string $type The type of the database: `'db2'`, `'mssql'`, `'mysql'`, `'oracle'`, `'postgresql'`, `'sqlite'`
  246. * @param string $database Name of the database. If SQLite the path to the database file.
  247. * @param string $username Database username - not used for SQLite
  248. * @param string $password The password for the username specified - not used for SQLite
  249. * @param string $host Database server host or IP, defaults to localhost - not used for SQLite. MySQL socket connection can be made by entering `'sock:'` followed by the socket path. PostgreSQL socket connection can be made by passing just `'sock:'`.
  250. * @param integer $port The port to connect to, defaults to the standard port for the database type specified - not used for SQLite
  251. * @return fDatabase
  252. */
  253. public function __construct($type, $database, $username=NULL, $password=NULL, $host=NULL, $port=NULL)
  254. {
  255. $valid_types = array('db2', 'mssql', 'mysql', 'oracle', 'postgresql', 'sqlite');
  256. if (!in_array($type, $valid_types)) {
  257. throw new fProgrammerException(
  258. 'The database type specified, %1$s, is invalid. Must be one of: %2$s.',
  259. $type,
  260. join(', ', $valid_types)
  261. );
  262. }
  263. if (empty($database)) {
  264. throw new fProgrammerException('No database was specified');
  265. }
  266. if ($host === NULL) {
  267. $host = 'localhost';
  268. }
  269. $this->type = $type;
  270. $this->database = $database;
  271. $this->username = $username;
  272. $this->password = $password;
  273. $this->host = $host;
  274. $this->port = $port;
  275. $this->hook_callbacks = array(
  276. 'unmodified' => array(),
  277. 'extracted' => array(),
  278. 'run' => array()
  279. );
  280. $this->schema_info = array();
  281. $this->determineExtension();
  282. }
  283. /**
  284. * Closes the open database connection
  285. *
  286. * @internal
  287. *
  288. * @return void
  289. */
  290. public function __destruct()
  291. {
  292. if (!$this->connection) { return; }
  293. fCore::debug('Total query time: ' . $this->query_time . ' seconds', $this->debug);
  294. if ($this->extension == 'ibm_db2') {
  295. db2_close($this->connection);
  296. } elseif ($this->extension == 'mssql') {
  297. mssql_close($this->connection);
  298. } elseif ($this->extension == 'mysql') {
  299. mysql_close($this->connection);
  300. } elseif ($this->extension == 'mysqli') {
  301. mysqli_close($this->connection);
  302. } elseif ($this->extension == 'oci8') {
  303. oci_close($this->connection);
  304. } elseif ($this->extension == 'pgsql') {
  305. pg_close($this->connection);
  306. } elseif ($this->extension == 'sqlite') {
  307. sqlite_close($this->connection);
  308. } elseif ($this->extension == 'sqlsrv') {
  309. sqlsrv_close($this->connection);
  310. } elseif ($this->extension == 'pdo') {
  311. // PDO objects close their own connections when destroyed
  312. }
  313. }
  314. /**
  315. * All requests that hit this method should be requests for callbacks
  316. *
  317. * @internal
  318. *
  319. * @param string $method The method to create a callback for
  320. * @return callback The callback for the method requested
  321. */
  322. public function __get($method)
  323. {
  324. return array($this, $method);
  325. }
  326. /**
  327. * Checks to see if an SQL error occured
  328. *
  329. * @param fResult|fUnbufferedResult|boolean $result The result object for the query
  330. * @param mixed $extra_info The sqlite extension will pass a string error message, the oci8 extension will pass the statement resource
  331. * @param string $sql The SQL that was executed
  332. * @return void
  333. */
  334. private function checkForError($result, $extra_info=NULL, $sql=NULL)
  335. {
  336. if ($result === FALSE || $result->getResult() === FALSE) {
  337. if ($this->extension == 'ibm_db2') {
  338. if (is_resource($extra_info)) {
  339. $message = db2_stmt_errormsg($extra_info);
  340. } else {
  341. $message = db2_stmt_errormsg();
  342. }
  343. } elseif ($this->extension == 'mssql') {
  344. $message = $this->error;
  345. unset($this->error);
  346. } elseif ($this->extension == 'mysql') {
  347. $message = mysql_error($this->connection);
  348. } elseif ($this->extension == 'mysqli') {
  349. if (is_object($extra_info)) {
  350. $message = $extra_info->error;
  351. } else {
  352. $message = mysqli_error($this->connection);
  353. }
  354. } elseif ($this->extension == 'oci8') {
  355. $error_info = oci_error($extra_info);
  356. $message = $error_info['message'];
  357. } elseif ($this->extension == 'pgsql') {
  358. $message = pg_last_error($this->connection);
  359. } elseif ($this->extension == 'sqlite') {
  360. $message = $extra_info;
  361. } elseif ($this->extension == 'sqlsrv') {
  362. $error_info = sqlsrv_errors(SQLSRV_ERR_ALL);
  363. $message = $error_info[0]['message'];
  364. } elseif ($this->extension == 'pdo') {
  365. if ($extra_info instanceof PDOStatement) {
  366. $error_info = $extra_info->errorInfo();
  367. } else {
  368. $error_info = $this->connection->errorInfo();
  369. }
  370. if (empty($error_info[2])) {
  371. $error_info[2] = 'Unknown error - this usually indicates a bug in the PDO driver';
  372. }
  373. $message = $error_info[2];
  374. }
  375. $db_type_map = array(
  376. 'db2' => 'DB2',
  377. 'mssql' => 'MSSQL',
  378. 'mysql' => 'MySQL',
  379. 'oracle' => 'Oracle',
  380. 'postgresql' => 'PostgreSQL',
  381. 'sqlite' => 'SQLite'
  382. );
  383. throw new fSQLException(
  384. '%1$s error (%2$s) in %3$s',
  385. $db_type_map[$this->type],
  386. $message,
  387. is_object($result) ? $result->getSQL() : $sql
  388. );
  389. }
  390. }
  391. /**
  392. * Clears all of the schema info out of the object and, if set, the fCache object
  393. *
  394. * @return void
  395. */
  396. public function clearCache()
  397. {
  398. $this->schema_info = array();
  399. if ($this->cache) {
  400. $this->cache->delete($this->makeCachePrefix() . 'schema_info');
  401. }
  402. if ($this->type == 'mssql') {
  403. $this->determineCharacterSet();
  404. }
  405. if ($this->translation) {
  406. $this->translation->clearCache();
  407. }
  408. }
  409. /**
  410. * Connects to the database specified if no connection exists
  411. *
  412. * @return void
  413. */
  414. private function connectToDatabase()
  415. {
  416. // Don't try to reconnect if we are already connected
  417. if ($this->connection) { return; }
  418. // Establish a connection to the database
  419. if ($this->extension == 'pdo') {
  420. $username = $this->username;
  421. $password = $this->password;
  422. if ($this->type == 'db2') {
  423. if ($this->host === NULL && $this->port === NULL) {
  424. $dsn = 'ibm:DSN:' . $this->database;
  425. } else {
  426. $dsn = 'ibm:DRIVER={IBM DB2 ODBC DRIVER};DATABASE=' . $this->database . ';HOSTNAME=' . $this->host . ';';
  427. $dsn .= 'PORT=' . ($this->port ? $this->port : 60000) . ';';
  428. $dsn .= 'PROTOCOL=TCPIP;UID=' . $username . ';PWD=' . $password . ';';
  429. $username = NULL;
  430. $password = NULL;
  431. }
  432. } elseif ($this->type == 'mssql') {
  433. $separator = (fCore::checkOS('windows')) ? ',' : ':';
  434. $port = ($this->port) ? $separator . $this->port : '';
  435. $driver = (fCore::checkOs('windows')) ? 'mssql' : 'dblib';
  436. $dsn = $driver . ':host=' . $this->host . $port . ';dbname=' . $this->database;
  437. } elseif ($this->type == 'mysql') {
  438. if (substr($this->host, 0, 5) == 'sock:') {
  439. $dsn = 'mysql:unix_socket=' . substr($this->host, 5) . ';dbname=' . $this->database;
  440. } else {
  441. $port = ($this->port) ? ';port=' . $this->port : '';
  442. $dsn = 'mysql:host=' . $this->host . ';dbname=' . $this->database . $port;
  443. }
  444. } elseif ($this->type == 'oracle') {
  445. $port = ($this->port) ? ':' . $this->port : '';
  446. $dsn = 'oci:dbname=' . $this->host . $port . '/' . $this->database . ';charset=AL32UTF8';
  447. } elseif ($this->type == 'postgresql') {
  448. $dsn = 'pgsql:dbname=' . $this->database;
  449. if ($this->host && $this->host != 'sock:') {
  450. $dsn .= ' host=' . $this->host;
  451. }
  452. if ($this->port) {
  453. $dsn .= ' port=' . $this->port;
  454. }
  455. } elseif ($this->type == 'sqlite') {
  456. $dsn = 'sqlite:' . $this->database;
  457. }
  458. try {
  459. $this->connection = new PDO($dsn, $username, $password);
  460. if ($this->type == 'mysql') {
  461. $this->connection->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, 1);
  462. }
  463. } catch (PDOException $e) {
  464. $this->connection = FALSE;
  465. }
  466. }
  467. if ($this->extension == 'sqlite') {
  468. $this->connection = sqlite_open($this->database);
  469. }
  470. if ($this->extension == 'ibm_db2') {
  471. $username = $this->username;
  472. $password = $this->password;
  473. if ($this->host === NULL && $this->port === NULL) {
  474. $connection_string = $this->database;
  475. } else {
  476. $connection_string = 'DATABASE=' . $this->database . ';HOSTNAME=' . $this->host . ';';
  477. $connection_string .= 'PORT=' . ($this->port ? $this->port : 60000) . ';';
  478. $connection_string .= 'PROTOCOL=TCPIP;UID=' . $this->username . ';PWD=' . $this->password . ';';
  479. $username = NULL;
  480. $password = NULL;
  481. }
  482. $options = array(
  483. 'autocommit' => DB2_AUTOCOMMIT_ON,
  484. 'DB2_ATTR_CASE' => DB2_CASE_LOWER
  485. );
  486. $this->connection = db2_connect($connection_string, $username, $password, $options);
  487. }
  488. if ($this->extension == 'mssql') {
  489. $separator = (fCore::checkOS('windows')) ? ',' : ':';
  490. $this->connection = mssql_connect(($this->port) ? $this->host . $separator . $this->port : $this->host, $this->username, $this->password, TRUE);
  491. if ($this->connection !== FALSE && mssql_select_db($this->database, $this->connection) === FALSE) {
  492. $this->connection = FALSE;
  493. }
  494. }
  495. if ($this->extension == 'mysql') {
  496. if (substr($this->host, 0, 5) == 'sock:') {
  497. $host = substr($this->host, 4);
  498. } elseif ($this->port) {
  499. $host = $this->host . ':' . $this->port;
  500. } else {
  501. $host = $this->host;
  502. }
  503. $this->connection = mysql_connect($host, $this->username, $this->password, TRUE);
  504. if ($this->connection !== FALSE && mysql_select_db($this->database, $this->connection) === FALSE) {
  505. $this->connection = FALSE;
  506. }
  507. }
  508. if ($this->extension == 'mysqli') {
  509. if (substr($this->host, 0, 5) == 'sock:') {
  510. $this->connection = mysqli_connect('localhost', $this->username, $this->password, $this->database, $this->port, substr($this->host, 5));
  511. } elseif ($this->port) {
  512. $this->connection = mysqli_connect($this->host, $this->username, $this->password, $this->database, $this->port);
  513. } else {
  514. $this->connection = mysqli_connect($this->host, $this->username, $this->password, $this->database);
  515. }
  516. }
  517. if ($this->extension == 'oci8') {
  518. $this->connection = oci_connect($this->username, $this->password, $this->host . ($this->port ? ':' . $this->port : '') . '/' . $this->database, 'AL32UTF8');
  519. }
  520. if ($this->extension == 'pgsql') {
  521. $connection_string = "dbname='" . addslashes($this->database) . "'";
  522. if ($this->host && $this->host != 'sock:') {
  523. $connection_string .= " host='" . addslashes($this->host) . "'";
  524. }
  525. if ($this->username) {
  526. $connection_string .= " user='" . addslashes($this->username) . "'";
  527. }
  528. if ($this->password) {
  529. $connection_string .= " password='" . addslashes($this->password) . "'";
  530. }
  531. if ($this->port) {
  532. $connection_string .= " port='" . $this->port . "'";
  533. }
  534. $this->connection = pg_connect($connection_string, PGSQL_CONNECT_FORCE_NEW);
  535. }
  536. if ($this->extension == 'sqlsrv') {
  537. $options = array(
  538. 'Database' => $this->database,
  539. 'UID' => $this->username,
  540. 'PWD' => $this->password
  541. );
  542. $this->connection = sqlsrv_connect($this->host . ',' . $this->port, $options);
  543. }
  544. // Ensure the connection was established
  545. if ($this->connection === FALSE) {
  546. throw new fConnectivityException(
  547. 'Unable to connect to database'
  548. );
  549. }
  550. // Make MySQL act more strict and use UTF-8
  551. if ($this->type == 'mysql') {
  552. $this->execute("SET SQL_MODE = 'REAL_AS_FLOAT,PIPES_AS_CONCAT,ANSI_QUOTES,IGNORE_SPACE'");
  553. $this->execute("SET NAMES 'utf8'");
  554. $this->execute("SET CHARACTER SET utf8");
  555. }
  556. // Make SQLite behave like other DBs for assoc arrays
  557. if ($this->type == 'sqlite') {
  558. $this->execute('PRAGMA short_column_names = 1');
  559. }
  560. // Fix some issues with mssql
  561. if ($this->type == 'mssql') {
  562. if (!isset($this->schema_info['character_set'])) {
  563. $this->determineCharacterSet();
  564. }
  565. $this->execute('SET TEXTSIZE 65536');
  566. $this->execute('SET QUOTED_IDENTIFIER ON');
  567. }
  568. // Make PostgreSQL use UTF-8
  569. if ($this->type == 'postgresql') {
  570. $this->execute("SET NAMES 'UTF8'");
  571. }
  572. // Oracle has different date and timestamp defaults
  573. if ($this->type == 'oracle') {
  574. $this->execute("ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD'");
  575. $this->execute("ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS'");
  576. $this->execute("ALTER SESSION SET NLS_TIMESTAMP_TZ_FORMAT = 'YYYY-MM-DD HH24:MI:SS TZR'");
  577. $this->execute("ALTER SESSION SET NLS_TIME_FORMAT = 'HH24:MI:SS'");
  578. $this->execute("ALTER SESSION SET NLS_TIME_TZ_FORMAT = 'HH24:MI:SS TZR'");
  579. }
  580. }
  581. /**
  582. * Determines the character set of a SQL Server database
  583. *
  584. * @return void
  585. */
  586. protected function determineCharacterSet()
  587. {
  588. $this->schema_info['character_set'] = 'WINDOWS-1252';
  589. $this->schema_info['character_set'] = $this->query("SELECT 'WINDOWS-' + CONVERT(VARCHAR, COLLATIONPROPERTY(CONVERT(NVARCHAR, DATABASEPROPERTYEX(DB_NAME(), 'Collation')), 'CodePage')) AS charset")->fetchScalar();
  590. if ($this->cache) {
  591. $this->cache->set($this->makeCachePrefix() . 'schema_info', $this->schema_info);
  592. }
  593. }
  594. /**
  595. * Figures out which extension to use for the database type selected
  596. *
  597. * @return void
  598. */
  599. protected function determineExtension()
  600. {
  601. switch ($this->type) {
  602. case 'db2':
  603. if (extension_loaded('ibm_db2')) {
  604. $this->extension = 'ibm_db2';
  605. } elseif (class_exists('PDO', FALSE) && in_array('ibm', PDO::getAvailableDrivers())) {
  606. $this->extension = 'pdo';
  607. } else {
  608. $type = 'DB2';
  609. $exts = 'ibm_db2, pdo_ibm';
  610. }
  611. break;
  612. case 'mssql':
  613. if (extension_loaded('sqlsrv')) {
  614. $this->extension = 'sqlsrv';
  615. } elseif (extension_loaded('mssql')) {
  616. $this->extension = 'mssql';
  617. } elseif (class_exists('PDO', FALSE) && (in_array('dblib', PDO::getAvailableDrivers()) || in_array('mssql', PDO::getAvailableDrivers()))) {
  618. $this->extension = 'pdo';
  619. } else {
  620. $type = 'MSSQL';
  621. $exts = 'mssql, sqlsrv, pdo_dblib (linux), pdo_mssql (windows)';
  622. }
  623. break;
  624. case 'mysql':
  625. if (extension_loaded('mysqli')) {
  626. $this->extension = 'mysqli';
  627. } elseif (class_exists('PDO', FALSE) && in_array('mysql', PDO::getAvailableDrivers())) {
  628. $this->extension = 'pdo';
  629. } elseif (extension_loaded('mysql')) {
  630. $this->extension = 'mysql';
  631. } else {
  632. $type = 'MySQL';
  633. $exts = 'mysql, pdo_mysql, mysqli';
  634. }
  635. break;
  636. case 'oracle':
  637. if (extension_loaded('oci8')) {
  638. $this->extension = 'oci8';
  639. } elseif (class_exists('PDO', FALSE) && in_array('oci', PDO::getAvailableDrivers())) {
  640. $this->extension = 'pdo';
  641. } else {
  642. $type = 'Oracle';
  643. $exts = 'oci8, pdo_oci';
  644. }
  645. break;
  646. case 'postgresql':
  647. if (extension_loaded('pgsql')) {
  648. $this->extension = 'pgsql';
  649. } elseif (class_exists('PDO', FALSE) && in_array('pgsql', PDO::getAvailableDrivers())) {
  650. $this->extension = 'pdo';
  651. } else {
  652. $type = 'PostgreSQL';
  653. $exts = 'pgsql, pdo_pgsql';
  654. }
  655. break;
  656. case 'sqlite':
  657. $sqlite_version = 0;
  658. if (file_exists($this->database)) {
  659. $database_handle = fopen($this->database, 'r');
  660. $database_version = fread($database_handle, 64);
  661. fclose($database_handle);
  662. if (strpos($database_version, 'SQLite format 3') !== FALSE) {
  663. $sqlite_version = 3;
  664. } elseif (strpos($database_version, '** This file contains an SQLite 2.1 database **') !== FALSE) {
  665. $sqlite_version = 2;
  666. } else {
  667. throw new fConnectivityException(
  668. 'The database specified does not appear to be a valid %1$s or %2$s database',
  669. 'SQLite v2.1',
  670. 'v3'
  671. );
  672. }
  673. }
  674. if ((!$sqlite_version || $sqlite_version == 3) && class_exists('PDO', FALSE) && in_array('sqlite', PDO::getAvailableDrivers())) {
  675. $this->extension = 'pdo';
  676. } elseif ($sqlite_version == 3 && (!class_exists('PDO', FALSE) || !in_array('sqlite', PDO::getAvailableDrivers()))) {
  677. throw new fEnvironmentException(
  678. 'The database specified is an %1$s database and the %2$s extension is not installed',
  679. 'SQLite v3',
  680. 'pdo_sqlite'
  681. );
  682. } elseif ((!$sqlite_version || $sqlite_version == 2) && extension_loaded('sqlite')) {
  683. $this->extension = 'sqlite';
  684. } elseif ($sqlite_version == 2 && !extension_loaded('sqlite')) {
  685. throw new fEnvironmentException(
  686. 'The database specified is an %1$s database and the %2$s extension is not installed',
  687. 'SQLite v2.1',
  688. 'sqlite'
  689. );
  690. } else {
  691. $type = 'SQLite';
  692. $exts = 'pdo_sqlite, sqlite';
  693. }
  694. break;
  695. }
  696. if (!$this->extension) {
  697. throw new fEnvironmentException(
  698. 'The server does not have any of the following extensions for %2$s support: %2$s',
  699. $type,
  700. $exts
  701. );
  702. }
  703. }
  704. /**
  705. * Sets the schema info to be cached to the fCache object specified
  706. *
  707. * @param fCache $cache The cache to cache to
  708. * @param string $key_token Internal use only! (this will be used in the cache key to uniquely identify the cache for this fDatabase object)
  709. * @return void
  710. */
  711. public function enableCaching($cache, $key_token=NULL)
  712. {
  713. $this->cache = $cache;
  714. if ($key_token !== NULL) {
  715. $this->cache_prefix = 'fDatabase::' . $this->type . '::' . $key_token . '::';
  716. }
  717. $this->schema_info = $this->cache->get($this->makeCachePrefix() . 'schema_info', array());
  718. }
  719. /**
  720. * Sets if debug messages should be shown
  721. *
  722. * @param boolean $flag If debugging messages should be shown
  723. * @return void
  724. */
  725. public function enableDebugging($flag)
  726. {
  727. $this->debug = (boolean) $flag;
  728. }
  729. /**
  730. * Escapes a value for insertion into SQL
  731. *
  732. * The valid data types are:
  733. *
  734. * - `'blob'`
  735. * - `'boolean'`
  736. * - `'date'`
  737. * - `'float'`
  738. * - `'identifier'`
  739. * - `'integer'`
  740. * - `'string'` (also varchar, char or text)
  741. * - `'varchar'`
  742. * - `'char'`
  743. * - `'text'`
  744. * - `'time'`
  745. * - `'timestamp'`
  746. *
  747. * In addition to being able to specify the data type, you can also pass
  748. * in an SQL statement with data type placeholders in the following form:
  749. *
  750. * - `%l` for a blob
  751. * - `%b` for a boolean
  752. * - `%d` for a date
  753. * - `%f` for a float
  754. * - `%r` for an indentifier (table or column name)
  755. * - `%i` for an integer
  756. * - `%s` for a string
  757. * - `%t` for a time
  758. * - `%p` for a timestamp
  759. *
  760. * Depending on what `$sql_or_type` and `$value` are, the output will be
  761. * slightly different. If `$sql_or_type` is a data type or a single
  762. * placeholder and `$value` is:
  763. *
  764. * - a scalar value - an escaped SQL string is returned
  765. * - an array - an array of escaped SQL strings is returned
  766. *
  767. * If `$sql_or_type` is a SQL string and `$value` is:
  768. *
  769. * - a scalar value - the escaped value is inserted into the SQL string
  770. * - an array - the escaped values are inserted into the SQL string separated by commas
  771. *
  772. * If `$sql_or_type` is a SQL string, it is also possible to pass an array
  773. * of all values as a single parameter instead of one value per parameter.
  774. * An example would look like the following:
  775. *
  776. * {{{
  777. * #!php
  778. * $db->escape(
  779. * "SELECT * FROM users WHERE status = %s AND authorization_level = %s",
  780. * array('Active', 'Admin')
  781. * );
  782. * }}}
  783. *
  784. * @param string $sql_or_type This can either be the data type to escape or an SQL string with a data type placeholder - see method description
  785. * @param mixed $value The value to escape - both single values and arrays of values are supported, see method description for details
  786. * @param mixed ...
  787. * @return mixed The escaped value/SQL or an array of the escaped values
  788. */
  789. public function escape($sql_or_type, $value)
  790. {
  791. $values = array_slice(func_get_args(), 1);
  792. if (sizeof($values) < 1) {
  793. throw new fProgrammerException(
  794. 'No value was specified to escape'
  795. );
  796. }
  797. // Convert all objects into strings
  798. $values = $this->scalarize($values);
  799. $value = array_shift($values);
  800. // Handle single value escaping
  801. $callback = NULL;
  802. switch ($sql_or_type) {
  803. case 'blob':
  804. case '%l':
  805. $callback = $this->escapeBlob;
  806. break;
  807. case 'boolean':
  808. case '%b':
  809. $callback = $this->escapeBoolean;
  810. break;
  811. case 'date':
  812. case '%d':
  813. $callback = $this->escapeDate;
  814. break;
  815. case 'float':
  816. case '%f':
  817. $callback = $this->escapeFloat;
  818. break;
  819. case 'identifier':
  820. case '%r':
  821. $callback = $this->escapeIdentifier;
  822. break;
  823. case 'integer':
  824. case '%i':
  825. $callback = $this->escapeInteger;
  826. break;
  827. case 'string':
  828. case 'varchar':
  829. case 'char':
  830. case 'text':
  831. case '%s':
  832. $callback = $this->escapeString;
  833. break;
  834. case 'time':
  835. case '%t':
  836. $callback = $this->escapeTime;
  837. break;
  838. case 'timestamp':
  839. case '%p':
  840. $callback = $this->escapeTimestamp;
  841. break;
  842. }
  843. if ($callback) {
  844. if (is_array($value)) {
  845. // If the values were passed as a single array, this handles that
  846. if (count($value) == 1 && is_array(current($value))) {
  847. $value = current($value);
  848. }
  849. return array_map($callback, $value);
  850. }
  851. return call_user_func($callback, $value);
  852. }
  853. // Fix \' in MySQL and PostgreSQL
  854. if(($this->type == 'mysql' || $this->type == 'postgresql') && strpos($sql_or_type, '\\') !== FALSE) {
  855. $sql_or_type = preg_replace("#(?<!\\\\)((\\\\{2})*)\\\\'#", "\\1''", $sql_or_type);
  856. }
  857. // Separate the SQL from quoted values
  858. $parts = $this->splitSQL($sql_or_type);
  859. $temp_sql = '';
  860. $strings = array();
  861. // Replace strings with a placeholder so they don't mess up the regex parsing
  862. foreach ($parts as $part) {
  863. if ($part[0] == "'") {
  864. $strings[] = $part;
  865. $part = ':string_' . (sizeof($strings)-1);
  866. }
  867. $temp_sql .= $part;
  868. }
  869. // If the values were passed as a single array, this handles that
  870. $placeholders = preg_match_all('#%[lbdfristp]\b#', $temp_sql, $trash);
  871. if (count($values) == 0 && is_array($value) && count($value) == $placeholders) {
  872. $values = $value;
  873. $value = array_shift($values);
  874. }
  875. array_unshift($values, $value);
  876. $sql = $this->escapeSQL($temp_sql, $values);
  877. $string_number = 0;
  878. foreach ($strings as $string) {
  879. $string = strtr($string, array('\\' => '\\\\', '$' => '\\$'));
  880. $sql = preg_replace('#:string_' . $string_number++ . '\b#', $string, $sql);
  881. }
  882. return $sql;
  883. }
  884. /**
  885. * Escapes a blob for use in SQL, includes surround quotes when appropriate
  886. *
  887. * A `NULL` value will be returned as `'NULL'`
  888. *
  889. * @param string $value The blob to escape
  890. * @return string The escaped blob
  891. */
  892. private function escapeBlob($value)
  893. {
  894. if ($value === NULL) {
  895. return 'NULL';
  896. }
  897. $this->connectToDatabase();
  898. if ($this->type == 'db2') {
  899. return "BLOB(X'" . bin2hex($value) . "')";
  900. } elseif ($this->type == 'mysql') {
  901. return "x'" . bin2hex($value) . "'";
  902. } elseif ($this->type == 'postgresql') {
  903. $output = '';
  904. for ($i=0; $i<strlen($value); $i++) {
  905. $output .= '\\\\' . str_pad(decoct(ord($value[$i])), 3, '0', STR_PAD_LEFT);
  906. }
  907. return "E'" . $output . "'";
  908. } elseif ($this->extension == 'sqlite') {
  909. return "'" . bin2hex($value) . "'";
  910. } elseif ($this->type == 'sqlite') {
  911. return "X'" . bin2hex($value) . "'";
  912. } elseif ($this->type == 'mssql') {
  913. return '0x' . bin2hex($value);
  914. } elseif ($this->type == 'oracle') {
  915. return "'" . bin2hex($value) . "'";
  916. }
  917. }
  918. /**
  919. * Escapes a boolean for use in SQL, includes surround quotes when appropriate
  920. *
  921. * A `NULL` value will be returned as `'NULL'`
  922. *
  923. * @param boolean $value The boolean to escape
  924. * @return string The database equivalent of the boolean passed
  925. */
  926. private function escapeBoolean($value)
  927. {
  928. if ($value === NULL) {
  929. return 'NULL';
  930. }
  931. if (in_array($this->type, array('postgresql', 'mysql'))) {
  932. return ($value) ? 'TRUE' : 'FALSE';
  933. } elseif (in_array($this->type, array('mssql', 'sqlite', 'db2'))) {
  934. return ($value) ? "'1'" : "'0'";
  935. } elseif ($this->type == 'oracle') {
  936. return ($value) ? '1' : '0';
  937. }
  938. }
  939. /**
  940. * Escapes a date for use in SQL, includes surrounding quotes
  941. *
  942. * A `NULL` or invalid value will be returned as `'NULL'`
  943. *
  944. * @param string $value The date to escape
  945. * @return string The escaped date
  946. */
  947. private function escapeDate($value)
  948. {
  949. if ($value === NULL) {
  950. return 'NULL';
  951. }
  952. try {
  953. $value = new fDate($value);
  954. return "'" . $value->format('Y-m-d') . "'";
  955. } catch (fValidationException $e) {
  956. return 'NULL';
  957. }
  958. }
  959. /**
  960. * Escapes a float for use in SQL
  961. *
  962. * A `NULL` value will be returned as `'NULL'`
  963. *
  964. * @param float $value The float to escape
  965. * @return string The escaped float
  966. */
  967. private function escapeFloat($value)
  968. {
  969. if ($value === NULL) {
  970. return 'NULL';
  971. }
  972. if (!strlen($value)) {
  973. return 'NULL';
  974. }
  975. if (!preg_match('#^[+\-]?([0-9]+(\.[0-9]+)?|(\.[0-9]+))$#D', $value)) {
  976. return 'NULL';
  977. }
  978. return (string) $value;
  979. }
  980. /**
  981. * Escapes an identifier for use in SQL, necessary for reserved words
  982. *
  983. * @param string $value The identifier to escape
  984. * @return string The escaped identifier
  985. */
  986. private function escapeIdentifier($value)
  987. {
  988. $value = '"' . str_replace(
  989. array('"', '.'),
  990. array('', '"."'),
  991. $value
  992. ) . '"';
  993. if (in_array($this->type, array('oracle', 'db2'))) {
  994. $value = strtoupper($value);
  995. }
  996. return $value;
  997. }
  998. /**
  999. * Escapes an integer for use in SQL
  1000. *
  1001. * A `NULL` or invalid value will be returned as `'NULL'`
  1002. *
  1003. * @param integer $value The integer to escape
  1004. * @return string The escaped integer
  1005. */
  1006. private function escapeInteger($value)
  1007. {
  1008. if ($value === NULL) {
  1009. return 'NULL';
  1010. }
  1011. if (!strlen($value)) {
  1012. return 'NULL';
  1013. }
  1014. if (!preg_match('#^([+\-]?[0-9]+)(\.[0-9]*)?$#D', $value, $matches)) {
  1015. return 'NULL';
  1016. }
  1017. return str_replace('+', '', $matches[1]);
  1018. }
  1019. /**
  1020. * Escapes a string for use in SQL, includes surrounding quotes
  1021. *
  1022. * A `NULL` value will be returned as `'NULL'`.
  1023. *
  1024. * @param string $value The string to escape
  1025. * @return string The escaped string
  1026. */
  1027. private function escapeString($value)
  1028. {
  1029. if ($value === NULL) {
  1030. return 'NULL';
  1031. }
  1032. $this->connectToDatabase();
  1033. if ($this->type == 'db2') {
  1034. return "'" . str_replace("'", "''", $value) . "'";
  1035. } elseif ($this->extension == 'mysql') {
  1036. return "'" . mysql_real_escape_string($value, $this->connection) . "'";
  1037. } elseif ($this->extension == 'mysqli') {
  1038. return "'" . mysqli_real_escape_string($this->connection, $value) . "'";
  1039. } elseif ($this->extension == 'pgsql') {
  1040. return "'" . pg_escape_string($value) . "'";
  1041. } elseif ($this->extension == 'sqlite') {
  1042. return "'" . sqlite_escape_string($value) . "'";
  1043. } elseif ($this->type == 'oracle') {
  1044. return "'" . str_replace("'", "''", $value) . "'";
  1045. } elseif ($this->type == 'mssql') {
  1046. // If there are any non-ASCII characters, we need to escape
  1047. if (preg_match('#[^\x00-\x7F]#', $value)) {
  1048. preg_match_all('#.|^\z#us', $value, $characters);
  1049. $output = "";
  1050. $last_type = NULL;
  1051. foreach ($characters[0] as $character) {
  1052. if (strlen($character) > 1) {
  1053. $b = array_map('ord', str_split($character));
  1054. switch (strlen($character)) {
  1055. case 2:
  1056. $bin = substr(decbin($b[0]), 3) .
  1057. substr(decbin($b[1]), 2);
  1058. break;
  1059. case 3:
  1060. $bin = substr(decbin($b[0]), 4) .
  1061. substr(decbin($b[1]), 2) .
  1062. substr(decbin($b[2]), 2);
  1063. break;
  1064. // If it is a 4-byte character, MSSQL can't store it
  1065. // so instead store a ?
  1066. default:
  1067. $output .= '?';
  1068. continue;
  1069. }
  1070. if ($last_type == 'nchar') {
  1071. $output .= '+';
  1072. } elseif ($last_type == 'char') {
  1073. $output .= "'+";
  1074. }
  1075. $output .= "NCHAR(" . bindec($bin) . ")";
  1076. $last_type = 'nchar';
  1077. } else {
  1078. if (!$last_type) {
  1079. $output .= "'";
  1080. } elseif ($last_type == 'nchar') {
  1081. $output .= "+'";
  1082. }
  1083. $output .= $character;
  1084. // Escape single quotes
  1085. if ($character == "'") {
  1086. $output .= "'";
  1087. }
  1088. $last_type = 'char';
  1089. }
  1090. }
  1091. if ($last_type == 'char') {
  1092. $output .= "'";
  1093. } elseif (!$last_type) {
  1094. $output .= "''";
  1095. }
  1096. // ASCII text is normal
  1097. } else {
  1098. $output = "'" . str_replace("'", "''", $value) . "'";
  1099. }
  1100. # a \ before a \r\n has to be escaped with another \
  1101. return preg_replace('#(?<!\\\\)\\\\(?=\r\n)#', '\\\\\\\\', $output);
  1102. } elseif ($this->extension == 'pdo') {
  1103. return $this->connection->quote($value);
  1104. }
  1105. }
  1106. /**
  1107. * Takes a SQL string and an array of values and replaces the placeholders with the value
  1108. *
  1109. * @param string $sql The SQL string containing placeholders
  1110. * @param array $values An array of values to escape into the SQL
  1111. * @return string The SQL with the values escaped into it
  1112. */
  1113. private function escapeSQL($sql, $values)
  1114. {
  1115. $original_sql = $sql;
  1116. $pieces = preg_split('#(%[lbdfristp])\b#', $sql, -1, PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY);
  1117. $sql = '';
  1118. $value = array_shift($values);
  1119. $missing_values = -1;
  1120. foreach ($pieces as $piece) {
  1121. switch ($piece) {
  1122. case '%l':
  1123. $callback = $this->escapeBlob;
  1124. break;
  1125. case '%b':
  1126. $callback = $this->escapeBoolean;
  1127. break;
  1128. case '%d':
  1129. $callback = $this->escapeDate;
  1130. break;
  1131. case '%f':
  1132. $callback = $this->escapeFloat;
  1133. break;
  1134. case '%r':
  1135. $callback = $this->escapeIdentifier;
  1136. break;
  1137. case '%i':
  1138. $callback = $this->escapeInteger;
  1139. break;
  1140. case '%s':
  1141. $callback = $this->escapeString;
  1142. break;
  1143. case '%t':
  1144. $callback = $this->escapeTime;
  1145. break;
  1146. case '%p':
  1147. $callback = $this->escapeTimestamp;
  1148. break;
  1149. default:
  1150. $sql .= $piece;
  1151. continue 2;
  1152. }
  1153. if (is_array($value)) {
  1154. $sql .= join(', ', array_map($callback, $value));
  1155. } else {
  1156. $sql .= call_user_func($callback, $value);
  1157. }
  1158. if (sizeof($values)) {
  1159. $value = array_shift($values);
  1160. } else {
  1161. $value = NULL;
  1162. $missing_values++;
  1163. }
  1164. }
  1165. if ($missing_values > 0) {
  1166. throw new fProgrammerException(
  1167. '%1$s value(s) are missing for the placeholders in: %2$s',
  1168. $missing_values,
  1169. $original_sql
  1170. );
  1171. }
  1172. if (sizeof($values)) {
  1173. throw new fProgrammerException(
  1174. '%1$s extra value(s) were passed for the placeholders in: %2$s',
  1175. sizeof($values),
  1176. $original_sql
  1177. );
  1178. }
  1179. return $sql;
  1180. }
  1181. /**
  1182. * Escapes a time for use in SQL, includes surrounding quotes
  1183. *
  1184. * A `NULL` or invalid value will be returned as `'NULL'`
  1185. *
  1186. * @param string $value The time to escape
  1187. * @return string The escaped time
  1188. */
  1189. private function escapeTime($value)
  1190. {
  1191. if ($value === NULL) {
  1192. return 'NULL';
  1193. }
  1194. try {
  1195. $value = new fTime($value);
  1196. if ($this->type == 'mssql' || $this->type == 'oracle') {
  1197. return "'" . $value->format('1970-01-01 H:i:s') . "'";
  1198. }
  1199. return "'" . $value->format('H:i:s') . "'";
  1200. } catch (fValidationException $e) {
  1201. return 'NULL';
  1202. }
  1203. }
  1204. /**
  1205. * Escapes a timestamp for use in SQL, includes surrounding quotes
  1206. *
  1207. * A `NULL` or invalid value will be returned as `'NULL'`
  1208. *
  1209. * @param string $value The timestamp to escape
  1210. * @return string The escaped timestamp
  1211. */
  1212. private function escapeTimestamp($value)
  1213. {
  1214. if ($value === NULL) {
  1215. return 'NULL';
  1216. }
  1217. try {
  1218. $value = new fTimestamp($value);
  1219. return "'" . $value->format('Y-m-d H:i:s') . "'";
  1220. } catch (fValidationException $e) {
  1221. return 'NULL';
  1222. }
  1223. }
  1224. /**
  1225. * Executes one or more SQL queries without returning any results
  1226. *
  1227. * @param string|fStatement $statement One or more SQL statements in a string or an fStatement prepared statement
  1228. * @param mixed $value The optional value(s) to place into any placeholders in the SQL - see ::escape() for details
  1229. * @param mixed ...
  1230. * @return void
  1231. */
  1232. public function execute($statement)
  1233. {
  1234. $args = func_get_args();
  1235. $params = array_slice($args, 1);
  1236. if (is_object($statement)) {
  1237. return $this->run($statement, NULL, $params);
  1238. }
  1239. $queries = $this->prepareSQL($statement, $params, FALSE);
  1240. $output = array();
  1241. foreach ($queries as $query) {
  1242. $this->run($query);
  1243. }
  1244. }
  1245. /**
  1246. * Takes in a string of SQL that contains multiple queries and returns any array of them
  1247. *
  1248. * @param string $sql The string of SQL to parse for queries
  1249. * @return array The individual SQL queries
  1250. */
  1251. private function explodeQueries($sql)
  1252. {
  1253. $sql_queries = array();
  1254. // Separate the SQL from quoted values
  1255. preg_match_all("#(?:'([^']*(?:'')*)*?')|(?:[^']+)#", $sql, $matches);
  1256. $cur_sql = '';
  1257. foreach ($matches[0] as $match) {
  1258. // This is a quoted string value, don't do anything to it
  1259. if ($match[0] == "'") {
  1260. $cur_sql .= $match;
  1261. // Handle the SQL, exploding on any ; that isn't escaped with a \
  1262. } else {
  1263. $sql_strings = preg_split('#(?<!\\\\);#', $match);
  1264. $cur_sql .= $sql_strings[0];
  1265. for ($i=1; $i < sizeof($sql_strings); $i++) {
  1266. $cur_sql = trim($cur_sql);
  1267. if ($cur_sql) {
  1268. $sql_queries[] = $cur_sql;
  1269. }
  1270. $cur_sql = $sql_strings[$i];
  1271. }
  1272. }
  1273. }
  1274. if (trim($cur_sql)) {
  1275. $sql_queries[] = $cur_sql;
  1276. }
  1277. return $sql_queries;
  1278. }
  1279. /**
  1280. * Returns the database connection resource or object
  1281. *
  1282. * @return mixed The database connection
  1283. */
  1284. public function getConnection()
  1285. {
  1286. $this->connectToDatabase();
  1287. return $this->connection;
  1288. }
  1289. /**
  1290. * Gets the name of the database currently connected to
  1291. *
  1292. * @return string The name of the database currently connected to
  1293. */
  1294. public function getDatabase()
  1295. {
  1296. return $this->database;
  1297. }
  1298. /**
  1299. * Gets the php extension being used
  1300. *
  1301. * @internal
  1302. *
  1303. * @return string The php extension used for database interaction
  1304. */
  1305. public function getExtension()
  1306. {
  1307. return $this->extension;
  1308. }
  1309. /**
  1310. * Gets the host for this database
  1311. *
  1312. * @return string The host
  1313. */
  1314. public function getHost()
  1315. {
  1316. return $this->host;
  1317. }
  1318. /**
  1319. * Gets the port for this database
  1320. *
  1321. * @return string The port
  1322. */
  1323. public function getPort()
  1324. {
  1325. return $this->port;
  1326. }
  1327. /**
  1328. * Gets the fSQLTranslation object used for translated queries
  1329. *
  1330. * @return fSQLTranslation The SQL translation object
  1331. */
  1332. public function getSQLTranslation()
  1333. {
  1334. if (!$this->translation) { new fSQLTranslation($this); }
  1335. return $this->translation;
  1336. }
  1337. /**
  1338. * Gets the database type
  1339. *
  1340. * @return string The database type: `'mssql'`, `'mysql'`, `'postgresql'` or `'sqlite'`
  1341. */
  1342. public function getType()
  1343. {
  1344. return $this->type;
  1345. }
  1346. /**
  1347. * Gets the username for this database
  1348. *
  1349. * @return string The username
  1350. */
  1351. public function getUsername()
  1352. {
  1353. return $this->username;
  1354. }
  1355. /**
  1356. * Will grab the auto incremented value from the last query (if one exists)
  1357. *
  1358. * @param fResult $result The result object for the query
  1359. * @param mixed $resource Only applicable for `pdo`, `oci8` and `sqlsrv` extentions or `mysqli` prepared statements - this is either the `PDOStatement` object, `mysqli_stmt` object or the `oci8` or `sqlsrv` resource
  1360. * @return void
  1361. */
  1362. private function handleAutoIncrementedValue($result, $resource=NULL)
  1363. {
  1364. if (!preg_match('#^\s*INSERT\s+(?:INTO\s+)?(?:`|"|\[)?(["\w.]+)(?:`|"|\])?#i', $result->getSQL(), $table_match)) {
  1365. $result->setAutoIncrementedValue(NULL);
  1366. return;
  1367. }
  1368. $quoted_table = $table_match[1];
  1369. $table = str_replace('"', '', strtolower($table_match[1]));
  1370. $insert_id = NULL;
  1371. if ($this->type == 'oracle') {
  1372. if (!isset($this->schema_info['sequences'])) {
  1373. $sql = "SELECT
  1374. LOWER(OWNER) AS \"SCHEMA\",
  1375. LOWER(TABLE_NAME) AS \"TABLE\",
  1376. TRIGGER_BODY
  1377. FROM
  1378. ALL_TRIGGERS
  1379. WHERE
  1380. TRIGGERING_EVENT = 'INSERT' AND
  1381. STATUS = 'ENABLED' AND
  1382. TRIGGER_NAME NOT LIKE 'BIN\$%' AND
  1383. OWNER NOT IN (
  1384. 'SYS',
  1385. 'SYSTEM',
  1386. 'OUTLN',
  1387. 'ANONYMOUS',
  1388. 'AURORA\$ORB\$UNAUTHENTICATED',
  1389. 'AWR_STAGE',
  1390. 'CSMIG',
  1391. 'CTXSYS',
  1392. 'DBSNMP',
  1393. 'DIP',
  1394. 'DMSYS',
  1395. 'DSSYS',
  1396. 'EXFSYS',
  1397. 'FLOWS_020100',
  1398. 'FLOWS_FILES',
  1399. 'LBACSYS',
  1400. 'MDSYS',
  1401. 'ORACLE_OCM',
  1402. 'ORDPLUGINS',
  1403. 'ORDSYS',
  1404. 'PERFSTAT',
  1405. 'TRACESVR',
  1406. 'TSMSYS',
  1407. 'XDB'
  1408. )";
  1409. $this->schema_info['sequences'] = array();
  1410. foreach ($this->query($sql) as $row) {
  1411. if (preg_match('#SELECT\s+(["\w.]+).nextval\s+INTO\s+:new\.(\w+)\s+FROM\s+dual#i', $row['trigger_body'], $matches)) {
  1412. $table_name = $row['table'];
  1413. if ($row['schema'] != strtolower($this->username)) {
  1414. $table_name = $row['schema'] . '.' . $table_name;
  1415. }
  1416. $this->schema_info['sequences'][$table_name] = array('sequence' => $matches[1], 'column' => str_replace('"', '', $matches[2]));
  1417. }
  1418. }
  1419. if ($this->cache) {
  1420. $this->cache->set($this->makeCachePrefix() . 'schema_info', $this->schema_info);
  1421. }
  1422. }
  1423. if (!isset($this->schema_info['sequences'][$table]) || preg_match('#INSERT\s+INTO\s+"?' . preg_quote($quoted_table, '#') . '"?\s+\([^\)]*?(\b|")' . preg_quote($this->schema_info['sequences'][$table]['column'], '#') . '(\b|")#i', $result->getSQL())) {
  1424. return;
  1425. }
  1426. $insert_id_sql = "SELECT " . $this->schema_info['sequences'][$table]['sequence'] . ".currval AS INSERT_ID FROM dual";
  1427. }
  1428. if ($this->type == 'postgresql') {
  1429. if (!isset($this->schema_info['sequences'])) {
  1430. $sql = "SELECT
  1431. pg_namespace.nspname AS \"schema\",
  1432. pg_class.relname AS \"table\",
  1433. pg_attribute.attname AS column
  1434. FROM
  1435. pg_attribute INNER JOIN
  1436. pg_class ON pg_attribute.attrelid = pg_class.oid INNER JOIN
  1437. pg_namespace ON pg_class.relnamespace = pg_namespace.oid INNER JOIN
  1438. pg_attrdef ON pg_class.oid = pg_attrdef.adrelid AND pg_attribute.attnum = pg_attrdef.adnum
  1439. WHERE
  1440. NOT pg_attribute.attisdropped AND
  1441. pg_attrdef.adsrc LIKE 'nextval(%'";
  1442. $this->schema_info['sequences'] = array();
  1443. foreach ($this->query($sql) as $row) {
  1444. $table_name = strtolower($row['table']);
  1445. if ($row['schema'] != 'public') {
  1446. $table_name = $row['schema'] . '.' . $table_name;
  1447. }
  1448. $this->schema_info['sequences'][$table_name] = $row['column'];
  1449. }
  1450. if ($this->cache) {
  1451. $this->cache->set($this->makeCachePrefix() . 'schema_info', $this->schema_info);
  1452. }
  1453. }
  1454. if (!isset($this->schema_info['sequences'][$table]) || preg_match('#INSERT\s+INTO\s+"?' . preg_quote($quoted_table, '#') . '"?\s+\([^\)]*?(\b|")' . preg_quote($this->schema_info['sequences'][$table], '#') . '(\b|")#i', $result->getSQL())) {
  1455. return;
  1456. }
  1457. }
  1458. if ($this->extension == 'ibm_db2') {
  1459. $insert_id_res = db2_exec($this->connection, "SELECT IDENTITY_VAL_LOCAL() FROM SYSIBM.SYSDUMMY1");
  1460. $insert_id_row = db2_fetch_assoc($insert_id_res);
  1461. $insert_id = current($insert_id_row);
  1462. db2_free_result($insert_id_res);
  1463. } elseif ($this->extension == 'mssql') {
  1464. $insert_id_res = mssql_query("SELECT @@IDENTITY AS insert_id", $this->connection);
  1465. $insert_id = mssql_result($insert_id_res, 0, 'insert_id');
  1466. mssql_free_result($insert_id_res);
  1467. } elseif ($this->extension == 'mysql') {
  1468. $insert_id = mysql_insert_id($this->connection);
  1469. } elseif ($this->extension == 'mysqli') {
  1470. if (is_object($resource)) {
  1471. $insert_id = mysqli_stmt_insert_id($resource);
  1472. } else {
  1473. $insert_id = mysqli_insert_id($this->connection);
  1474. }
  1475. } elseif ($this->extension == 'oci8') {
  1476. $oci_statement = oci_parse($this->connection, $insert_id_sql);
  1477. oci_execute($oci_statement, $this->inside_transaction ? OCI_DEFAULT : OCI_COMMIT_ON_SUCCESS);
  1478. $insert_id_row = oci_fetch_array($oci_statement, OCI_ASSOC);
  1479. $insert_id = $insert_id_row['INSERT_ID'];
  1480. oci_free_statement($oci_statement);
  1481. } elseif ($this->extension == 'pgsql') {
  1482. $insert_id_res = pg_query($this->connection, "SELECT lastval()");
  1483. $insert_id_row = pg_fetch_assoc($insert_id_res);
  1484. $insert_id = array_shift($insert_id_row);
  1485. pg_free_result($insert_id_res);
  1486. } elseif ($this->extension == 'sqlite') {
  1487. $insert_id = sqlite_last_insert_rowid($this->connection);
  1488. } elseif ($this->exte

Large files files are truncated, but you can click here to view the full file