PageRenderTime 70ms CodeModel.GetById 32ms RepoModel.GetById 0ms app.codeStats 0ms

/solar/source/solar/Solar/Sql/Adapter.php

https://bitbucket.org/Sanakan/noise
PHP | 2641 lines | 1471 code | 142 blank | 1028 comment | 55 complexity | 8d3a0a5c383d33c112c1d2fa1759e34c MD5 | raw file
Possible License(s): MIT

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

  1. <?php
  2. /**
  3. *
  4. * Abstract base class for specific RDBMS adapters.
  5. *
  6. * When writing an adapter, you need to override these abstract methods:
  7. *
  8. * {{code: php
  9. * abstract protected function _fetchTableList();
  10. * abstract protected function _fetchTableCols($table);
  11. * abstract protected function _createSequence($name, $start = 1);
  12. * abstract protected function _dropSequence($name);
  13. * abstract protected function _nextSequence($name);
  14. * abstract protected function _dropIndex($table, $name);
  15. * abstract protected function _modAutoincPrimary(&$coldef, $autoinc, $primary);
  16. * }}
  17. *
  18. * If the backend needs identifier deconfliction (e.g., PostgreSQL), you will
  19. * want to override _modIndexName() and _modSequenceName(). Most times this
  20. * will not be necessary.
  21. *
  22. * If the backend does not have explicit "LIMIT ... OFFSET" support,
  23. * you will want to override _modSelect($stmt, $parts) to rewrite the query
  24. * in order to emulate limit/select behavior. This is particularly necessary
  25. * for Microsoft SQL and Oracle.
  26. *
  27. * @category Solar
  28. *
  29. * @package Solar_Sql
  30. *
  31. * @author Paul M. Jones <pmjones@solarphp.com>
  32. *
  33. * @license http://opensource.org/licenses/bsd-license.php BSD
  34. *
  35. * @version $Id: Adapter.php 4612 2010-06-19 13:45:49Z pmjones $
  36. *
  37. */
  38. abstract class Solar_Sql_Adapter extends Solar_Base {
  39. /**
  40. *
  41. * Default configuration values.
  42. *
  43. * @config string host Host specification (typically 'localhost').
  44. *
  45. * @config string port Port number for the host name.
  46. *
  47. * @config string sock The Unix socket for the connection. Should not be used with
  48. * host and port.
  49. *
  50. * @config string user Connect to the database as this username.
  51. *
  52. * @config string pass Password associated with the username.
  53. *
  54. * @config string name Database name (or file path, or TNS name).
  55. *
  56. * @config bool profiling Turn on query profiling?
  57. *
  58. * @config dependency cache The cache to use, if any, for the lists of
  59. * table names, table columns, etc.
  60. *
  61. * @var array
  62. *
  63. */
  64. protected $_Solar_Sql_Adapter = array(
  65. 'host' => null,
  66. 'port' => null,
  67. 'sock' => null,
  68. 'user' => null,
  69. 'pass' => null,
  70. 'name' => null,
  71. 'profiling' => false,
  72. 'cache' => array('adapter' => 'Solar_Cache_Adapter_Var'),
  73. );
  74. /**
  75. *
  76. * A cache object for keeping query results.
  77. *
  78. * @var Solar_Cache_Adapter
  79. *
  80. */
  81. protected $_cache;
  82. /**
  83. *
  84. * Prefix all cache keys with this string.
  85. *
  86. * @var string
  87. *
  88. */
  89. protected $_cache_key_prefix;
  90. /**
  91. *
  92. * Map of Solar generic types to RDBMS native types used when creating
  93. * portable tables.
  94. *
  95. * See the individual adapters for specific mappings.
  96. *
  97. * The available generic column types are ...
  98. *
  99. * `char`
  100. * : A fixed-length string of 1-255 characters.
  101. *
  102. * `varchar`
  103. * : A variable-length string of 1-255 characters.
  104. *
  105. * `bool`
  106. * : A true/false boolean, generally stored as an integer 1 or 0. May
  107. * also be stored as null, allowing for ternary logic.
  108. *
  109. * `smallint`
  110. * : A 2-byte integer in the range of -32767 ... +32768.
  111. *
  112. * `int`
  113. * : A 4-byte integer in the range of -2,147,483,648 ... +2,147,483,647.
  114. *
  115. * `bigint`
  116. * : An 8-byte integer, value range roughly (-9,223,372,036,854,780,000
  117. * ... +9,223,372,036,854,779,999).
  118. *
  119. * `numeric`
  120. * : A fixed-point decimal number of a specific size (total number of
  121. * digits) and scope (the number of those digits to the right of the
  122. * decimal point).
  123. *
  124. * `float`
  125. * : A double-precision floating-point decimal number.
  126. *
  127. * `clob`
  128. * : A character large object with a size of up to 2,147,483,647 bytes
  129. * (about 2 GB).
  130. *
  131. * `date`
  132. * : An ISO 8601 date; for example, '1979-11-07'.
  133. *
  134. * `time`
  135. * : An ISO 8601 time; for example, '12:34:56'.
  136. *
  137. * `timestamp`
  138. * : An ISO 8601 timestamp without a timezone offset; for example,
  139. * '1979-11-07 12:34:56'.
  140. *
  141. * @var array
  142. *
  143. */
  144. protected $_solar_native = array(
  145. 'bool' => null,
  146. 'char' => null,
  147. 'varchar' => null,
  148. 'smallint' => null,
  149. 'int' => null,
  150. 'bigint' => null,
  151. 'numeric' => null,
  152. 'float' => null,
  153. 'clob' => null,
  154. 'date' => null,
  155. 'time' => null,
  156. 'timestamp' => null,
  157. );
  158. /**
  159. *
  160. * Map of native RDBMS types to Solar generic types used when reading
  161. * table column information.
  162. *
  163. * See the individual adapters for specific mappings.
  164. *
  165. * @var array
  166. *
  167. * @see fetchTableCols()
  168. *
  169. */
  170. protected $_native_solar = array();
  171. /**
  172. *
  173. * A PDO object for accessing the RDBMS.
  174. *
  175. * @var object
  176. *
  177. */
  178. protected $_pdo = null;
  179. /**
  180. *
  181. * The PDO adapter DSN type.
  182. *
  183. * This might not be the same as the Solar adapter type.
  184. *
  185. * @var string
  186. *
  187. */
  188. protected $_pdo_type = null;
  189. /**
  190. *
  191. * Max identifier lengths for table, column, and index names used when
  192. * creating portable tables.
  193. *
  194. * We use 30 characters to comply with Oracle maximums.
  195. *
  196. * @var array
  197. *
  198. */
  199. protected $_maxlen = 30;
  200. /**
  201. *
  202. * A quick-and-dirty query profile array.
  203. *
  204. * Each element is an array, where the first value is the query execution
  205. * time in microseconds, and the second value is the query string.
  206. *
  207. * Only populated when the `profiling` config key is true.
  208. *
  209. * @var array
  210. *
  211. */
  212. protected $_profile = array();
  213. /**
  214. *
  215. * Whether or not profiling is turned on.
  216. *
  217. * @var bool
  218. *
  219. */
  220. protected $_profiling = false;
  221. /**
  222. *
  223. * A PDO-style DSN, for example, "mysql:host=127.0.0.1;dbname=test".
  224. *
  225. * @var string
  226. *
  227. */
  228. protected $_dsn;
  229. /**
  230. *
  231. * The quote character before an identifier name (table, index, etc).
  232. *
  233. * @var string
  234. *
  235. */
  236. protected $_ident_quote_prefix = null;
  237. /**
  238. *
  239. * The quote character after an identifier name (table, index, etc).
  240. *
  241. * @var string
  242. *
  243. */
  244. protected $_ident_quote_suffix = null;
  245. /**
  246. *
  247. * Post-construction tasks to complete object construction.
  248. *
  249. * @return void
  250. *
  251. */
  252. protected function _postConstruct()
  253. {
  254. parent::_postConstruct();
  255. // turn on profiling?
  256. $this->setProfiling($this->_config['profiling']);
  257. // set a cache object
  258. $this->_cache = Solar::dependency(
  259. 'Solar_Cache',
  260. $this->_config['cache']
  261. );
  262. // follow-on setup
  263. $this->_setup();
  264. }
  265. /**
  266. *
  267. * Follow-on setup from the constructor; useful for extended classes.
  268. *
  269. * @return void
  270. *
  271. */
  272. protected function _setup()
  273. {
  274. // set the DSN from the config info
  275. $this->_setDsn();
  276. // set the cache-key prefix
  277. $this->setCacheKeyPrefix();
  278. }
  279. /**
  280. *
  281. * Turns profiling on and off.
  282. *
  283. * @param bool $flag True to turn profiling on, false to turn it off.
  284. *
  285. * @return void
  286. *
  287. */
  288. public function setProfiling($flag)
  289. {
  290. $this->_profiling = (bool) $flag;
  291. }
  292. /**
  293. *
  294. * Returns the cache object.
  295. *
  296. * @return Solar_Cache
  297. *
  298. * @see $_cache
  299. *
  300. */
  301. public function getCache()
  302. {
  303. return $this->_cache;
  304. }
  305. /**
  306. *
  307. * Injects a cache dependency for `$_cache`.
  308. *
  309. * @param mixed $spec A [[Solar::dependency()]] specification.
  310. *
  311. * @return void
  312. *
  313. * @see $_cache
  314. *
  315. */
  316. public function setCache($spec)
  317. {
  318. $this->_cache = Solar::dependency('Solar_Cache', $spec);
  319. }
  320. /**
  321. *
  322. * Sets the connection-specific cache key prefix.
  323. *
  324. * @param string $prefix The cache-key prefix. When null, defaults to
  325. * the class name, a slash, and the md5() of the DSN.
  326. *
  327. * @return string
  328. *
  329. */
  330. public function setCacheKeyPrefix($prefix = null)
  331. {
  332. if ($prefix === null) {
  333. $prefix = get_class($this) . '/' . md5($this->_dsn);
  334. }
  335. $this->_cache_key_prefix = $prefix;
  336. }
  337. /**
  338. *
  339. * Gets the connection-specific cache key prefix.
  340. *
  341. * @return string
  342. *
  343. */
  344. public function getCacheKeyPrefix()
  345. {
  346. return $this->_cache_key_prefix;
  347. }
  348. /**
  349. *
  350. * Get the query profile array.
  351. *
  352. * @return array An array of queries executed by the adapter.
  353. *
  354. */
  355. public function getProfile()
  356. {
  357. return $this->_profile;
  358. }
  359. /**
  360. *
  361. * Get the PDO connection object (connects to the database if needed).
  362. *
  363. * @return PDO
  364. *
  365. */
  366. public function getPdo()
  367. {
  368. $this->connect();
  369. return $this->_pdo;
  370. }
  371. // -----------------------------------------------------------------
  372. //
  373. // Connection and basic queries
  374. //
  375. // -----------------------------------------------------------------
  376. /**
  377. *
  378. * Sets the DSN value for the connection from the config info.
  379. *
  380. * @return void
  381. *
  382. */
  383. protected function _setDsn()
  384. {
  385. $this->_dsn = $this->_buildDsn($this->_config);
  386. }
  387. /**
  388. *
  389. * Creates a PDO-style DSN.
  390. *
  391. * For example, "mysql:host=127.0.0.1;dbname=test"
  392. *
  393. * @param array $info An array with host, post, name, etc. keys.
  394. *
  395. * @return string The DSN string.
  396. *
  397. */
  398. protected function _buildDsn($info)
  399. {
  400. $dsn = array();
  401. if (! empty($info['host'])) {
  402. $dsn[] = 'host=' . $info['host'];
  403. }
  404. if (! empty($info['port'])) {
  405. $dsn[] = 'port=' . $info['port'];
  406. }
  407. if (! empty($info['name'])) {
  408. $dsn[] = 'dbname=' . $info['name'];
  409. }
  410. return $this->_pdo_type . ':' . implode(';', $dsn);
  411. }
  412. /**
  413. *
  414. * Creates a PDO object and connects to the database.
  415. *
  416. * Also sets the query-cache key prefix.
  417. *
  418. * @return void
  419. *
  420. */
  421. public function connect()
  422. {
  423. // if we already have a PDO object, no need to re-connect.
  424. if ($this->_pdo) {
  425. return;
  426. }
  427. // start profile time
  428. $time = microtime(true);
  429. // attempt the connection
  430. $this->_pdo = new PDO(
  431. $this->_dsn,
  432. $this->_config['user'],
  433. $this->_config['pass']
  434. );
  435. // retain connection info
  436. $this->_pdo->solar_conn = array(
  437. 'dsn' => $this->_dsn,
  438. 'user' => $this->_config['user'],
  439. 'pass' => $this->_config['pass'],
  440. 'type' => 'single',
  441. 'key' => null,
  442. );
  443. // post-connection tasks
  444. $this->_postConnect();
  445. // retain the profile data?
  446. $this->_addProfile($time, '__CONNECT');
  447. }
  448. /**
  449. *
  450. * After connection, set various connection attributes.
  451. *
  452. * @return void
  453. *
  454. */
  455. protected function _postConnect()
  456. {
  457. // always emulate prepared statements; this is faster, and works
  458. // better with CREATE, DROP, ALTER statements. requires PHP 5.1.3
  459. // or later. note that we do this *first* (before using exceptions)
  460. // because not all adapters support it.
  461. $this->_pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
  462. $this->_pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
  463. // always use exceptions
  464. $this->_pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  465. // force names to lower case
  466. $this->_pdo->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER);
  467. }
  468. /**
  469. *
  470. * Closes the database connection.
  471. *
  472. * This isn't generally necessary as PHP will automatically close the
  473. * connection in the end of the script execution, but it can be useful
  474. * to free resources when a script needs to connect tomultiple databases
  475. * in sequence.
  476. *
  477. * @return void
  478. *
  479. */
  480. public function disconnect()
  481. {
  482. $this->_pdo = null;
  483. }
  484. /**
  485. *
  486. * Gets a full cache key.
  487. *
  488. * @param string $key The partial cache key.
  489. *
  490. * @return string The full cache key.
  491. *
  492. */
  493. protected function _getCacheKey($key)
  494. {
  495. return $this->_cache_key_prefix . "/$key";
  496. }
  497. /**
  498. *
  499. * Prepares and executes an SQL statement, optionally binding values
  500. * to named parameters in the statement.
  501. *
  502. * This is the most-direct way to interact with the database; you
  503. * pass an SQL statement to the method, then the adapter uses
  504. * [[php::PDO | ]] to execute the statement and return a result.
  505. *
  506. * {{code: php
  507. * $sql = Solar::factory('Solar_Sql');
  508. *
  509. * // $result is a PDOStatement
  510. * $result = $sql->query('SELECT * FROM table');
  511. * }}
  512. *
  513. * To help prevent SQL injection attacks, you should **always** quote
  514. * the values used in a direct query. Use [[Solar_Sql_Adapter::quote() | quote()]],
  515. * [[Solar_Sql_Adapter::quoteInto() | quoteInto()]], or
  516. * [[Solar_Sql_Adapter::quoteMulti() | quoteMulti()]] to accomplish this.
  517. * Even easier, use the automated value binding provided by the query()
  518. * method:
  519. *
  520. * {{code: php
  521. * // BAD AND SCARY:
  522. * $result = $sql->query('SELECT * FROM table WHERE foo = $bar');
  523. *
  524. * // Much much better:
  525. * $result = $sql->query(
  526. * 'SELECT * FROM table WHERE foo = :bar',
  527. * array('bar' => $bar)
  528. * );
  529. * }}
  530. *
  531. * Note that adapters provide convenience methods to automatically quote
  532. * values on common operations:
  533. *
  534. * - [[Solar_Sql_Adapter::insert()]]
  535. * - [[Solar_Sql_Adapter::update()]]
  536. * - [[Solar_Sql_Adapter::delete()]]
  537. *
  538. * Additionally, the [[Solar_Sql_Select]] class is dedicated to
  539. * safely creating portable SELECT statements, so you may wish to use that
  540. * instead of writing literal SELECTs.
  541. *
  542. *
  543. * Automated Binding of Values in PHP 5.2.1 and Later
  544. * --------------------------------------------------
  545. *
  546. * With PDO in PHP 5.2.1 and later, we can no longer just throw an array
  547. * of data at the statement for binding. We now need to bind values
  548. * specifically to their respective placeholders.
  549. *
  550. * In addition, we can't bind one value to multiple identical named
  551. * placeholders; we need to bind that same value multiple times. So if
  552. * `:foo` is used three times, PDO uses `:foo` the first time, `:foo2` the
  553. * second time, and `:foo3` the third time.
  554. *
  555. * This query() method examins the statement for all `:name` placeholders
  556. * and attempts to bind data from the `$data` array. The regular-expression
  557. * it uses is a little braindead; it cannot tell if the :name placeholder
  558. * is literal text or really a place holder.
  559. *
  560. * As such, you should *either* use the `$data` array for named-placeholder
  561. * value binding at query() time, *or* bind-as-you-go when building the
  562. * statement, not both. If you do, you are on your own to make sure
  563. * that nothing looking like a `:name` placeholder exists in the literal text.
  564. *
  565. * Question-mark placeholders are not supported for automatic value
  566. * binding at query() time.
  567. *
  568. * @param string $stmt The text of the SQL statement, optionally with
  569. * named placeholders.
  570. *
  571. * @param array $data An associative array of data to bind to the named
  572. * placeholders.
  573. *
  574. * @return PDOStatement
  575. *
  576. */
  577. public function query($stmt, $data = array())
  578. {
  579. $this->connect();
  580. // begin the profile time
  581. $time = microtime(true);
  582. // prepre the statement and bind data to it
  583. $prep = $this->_prepare($stmt);
  584. $this->_bind($prep, $data);
  585. // now try to execute
  586. try {
  587. $prep->execute();
  588. } catch (PDOException $e) {
  589. throw $this->_exception('ERR_QUERY_FAILED', array(
  590. 'pdo_code' => $e->getCode(),
  591. 'pdo_text' => $e->getMessage(),
  592. 'host' => $this->_config['host'],
  593. 'port' => $this->_config['port'],
  594. 'user' => $this->_config['user'],
  595. 'name' => $this->_config['name'],
  596. 'stmt' => $stmt,
  597. 'data' => $data,
  598. 'pdo_trace' => $e->getTraceAsString(),
  599. ));
  600. }
  601. // retain the profile data?
  602. $this->_addProfile($time, $prep, $data);
  603. // done!
  604. return $prep;
  605. }
  606. /**
  607. *
  608. * Prepares an SQL query as a PDOStatement object.
  609. *
  610. * @param string $stmt The text of the SQL statement, optionally with
  611. * named placeholders.
  612. *
  613. * @return PDOStatement
  614. *
  615. */
  616. protected function _prepare($stmt)
  617. {
  618. // prepare the statment
  619. try {
  620. $prep = $this->_pdo->prepare($stmt);
  621. $prep->solar_conn = $this->_pdo->solar_conn;
  622. } catch (PDOException $e) {
  623. throw $this->_exception('ERR_PREPARE_FAILED', array(
  624. 'pdo_code' => $e->getCode(),
  625. 'pdo_text' => $e->getMessage(),
  626. 'host' => $this->_config['host'],
  627. 'port' => $this->_config['port'],
  628. 'sock' => $this->_config['sock'],
  629. 'user' => $this->_config['user'],
  630. 'name' => $this->_config['name'],
  631. 'stmt' => $stmt,
  632. 'pdo_trace' => $e->getTraceAsString(),
  633. ));
  634. }
  635. return $prep;
  636. }
  637. /**
  638. *
  639. * Binds an array of scalars as values into a prepared PDOStatment.
  640. *
  641. * Array element values that are themselves arrays will not be bound
  642. * correctly, because PDO expects scalar values only.
  643. *
  644. * @param PDOStatement $prep The prepared PDOStatement.
  645. *
  646. * @param array $data The scalar values to bind into the PDOStatement.
  647. *
  648. * @return void
  649. *
  650. */
  651. protected function _bind($prep, $data)
  652. {
  653. // was data passed for binding?
  654. if (! $data) {
  655. return;
  656. }
  657. // find all :placeholder matches. note that this is a little
  658. // brain-dead; it will find placeholders in literal text, which
  659. // will cause errors later. so in general, you should *either*
  660. // bind at query time *or* bind as you go, not both.
  661. preg_match_all(
  662. "/\W:([a-zA-Z_][a-zA-Z0-9_]*)/m",
  663. $prep->queryString . "\n",
  664. $matches
  665. );
  666. // bind values to placeholders, repeating as needed
  667. $repeat = array();
  668. foreach ($matches[1] as $key) {
  669. // only attempt to bind if the data key exists.
  670. // this allows for nulls and empty strings.
  671. if (! array_key_exists($key, $data)) {
  672. // skip it
  673. continue;
  674. }
  675. // what does PDO expect as the placeholder name?
  676. if (empty($repeat[$key])) {
  677. // first time is ":foo"
  678. $repeat[$key] = 1;
  679. $name = $key;
  680. } else {
  681. // repeated times of ":foo" are treated by PDO as
  682. // ":foo2", ":foo3", etc.
  683. $repeat[$key] ++;
  684. $name = $key . $repeat[$key];
  685. }
  686. // bind the value to the placeholder name
  687. $prep->bindValue($name, $data[$key]);
  688. }
  689. }
  690. /**
  691. *
  692. * Adds an element to the profile array.
  693. *
  694. * @param int $time The microtime when the profile element started.
  695. *
  696. * @param string|PDOStatement $spec The SQL statement being profiled.
  697. *
  698. * @param array $data Any data bound into the statement.
  699. *
  700. * @return void
  701. *
  702. */
  703. protected function _addProfile($time, $spec, $data = null)
  704. {
  705. if (! $this->_profiling) {
  706. return;
  707. }
  708. if ($spec instanceof PDOStatement) {
  709. $conn = $spec->solar_conn;
  710. $stmt = $spec->queryString;
  711. } else {
  712. $conn = null;
  713. $stmt = $spec;
  714. }
  715. $timespan = microtime(true) - $time;
  716. $e = new Exception();
  717. $this->_profile[] = array(
  718. 'time' => $timespan,
  719. 'stmt' => $stmt,
  720. 'data' => $data,
  721. 'conn' => $conn,
  722. 'trace' => $e->getTraceAsString(),
  723. );
  724. }
  725. // -----------------------------------------------------------------
  726. //
  727. // Transactions
  728. //
  729. // -----------------------------------------------------------------
  730. /**
  731. *
  732. * Leave autocommit mode and begin a transaction.
  733. *
  734. * @return void
  735. *
  736. */
  737. public function begin()
  738. {
  739. $this->connect();
  740. $time = microtime(true);
  741. $result = $this->_pdo->beginTransaction();
  742. $this->_addProfile($time, '__BEGIN');
  743. return $result;
  744. }
  745. /**
  746. *
  747. * Commit a transaction and return to autocommit mode.
  748. *
  749. * @return void
  750. *
  751. */
  752. public function commit()
  753. {
  754. $this->connect();
  755. $time = microtime(true);
  756. $result = $this->_pdo->commit();
  757. $this->_addProfile($time, '__COMMIT');
  758. return $result;
  759. }
  760. /**
  761. *
  762. * Roll back a transaction and return to autocommit mode.
  763. *
  764. * @return void
  765. *
  766. */
  767. public function rollback()
  768. {
  769. $this->connect();
  770. $time = microtime(true);
  771. $result = $this->_pdo->rollBack();
  772. $this->_addProfile($time, '__ROLLBACK');
  773. return $result;
  774. }
  775. // -----------------------------------------------------------------
  776. //
  777. // Manipulation
  778. //
  779. // -----------------------------------------------------------------
  780. /**
  781. *
  782. * Inserts a row of data into a table.
  783. *
  784. * Automatically applies [[Solar_Sql_Adapter::quote() | ]] to the data
  785. * values for you.
  786. *
  787. * For example:
  788. *
  789. * {{code: php
  790. * $sql = Solar::factory('Solar_Sql');
  791. *
  792. * $table = 'invaders';
  793. * $data = array(
  794. * 'foo' => 'bar',
  795. * 'baz' => 'dib',
  796. * 'zim' => 'gir'
  797. * );
  798. *
  799. * $rows_affected = $sql->insert($table, $data);
  800. * // calls 'INSERT INTO invaders (foo, baz, zim) VALUES ("bar", "dib", "gir")'
  801. * }}
  802. *
  803. * @param string $table The table to insert data into.
  804. *
  805. * @param array $data An associative array where the key is the column
  806. * name and the value is the value to insert for that column.
  807. *
  808. * @return int The number of rows affected, typically 1.
  809. *
  810. */
  811. public function insert($table, $data)
  812. {
  813. // the base statement
  814. $table = $this->quoteName($table);
  815. $stmt = "INSERT INTO $table ";
  816. // col names come from the array keys
  817. $keys = array_keys($data);
  818. // quote the col names
  819. $cols = array();
  820. foreach ($keys as $key) {
  821. $cols[] = $this->quoteName($key);
  822. }
  823. // add quoted col names
  824. $stmt .= '(' . implode(', ', $cols) . ') ';
  825. // add value placeholders (use unquoted key names)
  826. $stmt .= 'VALUES (:' . implode(', :', $keys) . ')';
  827. // execute the statement
  828. $result = $this->query($stmt, $data);
  829. return $result->rowCount();
  830. }
  831. /**
  832. *
  833. * Updates a table with specified data based on a WHERE clause.
  834. *
  835. * Automatically applies [[Solar_Sql_Adapter::quote() | ]] to the data
  836. * values for you.
  837. *
  838. * @param string $table The table to udpate.
  839. *
  840. * @param array $data An associative array where the key is the column
  841. * name and the value is the value to use for that column.
  842. *
  843. * @param string|array $where The SQL WHERE clause to limit which
  844. * rows are updated.
  845. *
  846. * @return int The number of rows affected.
  847. *
  848. */
  849. public function update($table, $data, $where)
  850. {
  851. // the base statement
  852. $table = $this->quoteName($table);
  853. $stmt = "UPDATE $table SET ";
  854. // add "col = :col" pairs to the statement
  855. $tmp = array();
  856. foreach ($data as $col => $val) {
  857. $tmp[] = $this->quoteName($col) . " = :$col";
  858. }
  859. $stmt .= implode(', ', $tmp);
  860. // add the where clause
  861. if ($where) {
  862. $where = $this->quoteMulti($where, ' AND ');
  863. $where = $this->quoteNamesIn($where);
  864. $stmt .= " WHERE $where";
  865. }
  866. // execute the statement
  867. $result = $this->query($stmt, $data);
  868. return $result->rowCount();
  869. }
  870. /**
  871. *
  872. * Deletes rows from the table based on a WHERE clause.
  873. *
  874. * For example ...
  875. *
  876. * {{code: php
  877. * $sql = Solar::factory('Solar_Sql');
  878. *
  879. * $table = 'events';
  880. * $where = $sql->quoteInto('status = ?', 'cancelled');
  881. * $rows_affected = $sql->delete($table, $where);
  882. *
  883. * // calls 'DELETE FROM events WHERE status = "cancelled"'
  884. * }}
  885. *
  886. * For the $where parameter, you can also pass multiple WHERE conditions as
  887. * an array to be "AND"ed together.
  888. *
  889. * {{code: php
  890. * $sql = Solar::factory('Solar_Sql');
  891. *
  892. * $table = 'events';
  893. * $where = array(
  894. * "date >= ?" => '2006-01-01',
  895. * "date <= ?" => '2006-01-31',
  896. * "status = ?" => 'cancelled',
  897. * );
  898. *
  899. * $rows_affected = $sql->delete($table, $where);
  900. *
  901. * // calls:
  902. * // DELETE FROM events WHERE date >= "2006-01-01"
  903. * // AND date <= "2006-01-31" AND status = "cancelled"
  904. * }}
  905. *
  906. * @param string $table The table to delete from.
  907. *
  908. * @param string|array $where The SQL WHERE clause to limit which
  909. * rows are deleted.
  910. *
  911. * @return int The number of rows affected.
  912. *
  913. */
  914. public function delete($table, $where)
  915. {
  916. if ($where) {
  917. $where = $this->quoteMulti($where, ' AND ');
  918. $where = $this->quoteNamesIn($where);
  919. }
  920. $table = $this->quoteName($table);
  921. $result = $this->query("DELETE FROM $table WHERE $where");
  922. return $result->rowCount();
  923. }
  924. // -----------------------------------------------------------------
  925. //
  926. // Retrieval
  927. //
  928. // -----------------------------------------------------------------
  929. /**
  930. *
  931. * Fetches all rows from the database using sequential keys.
  932. *
  933. * @param array|string $spec An array of component parts for a
  934. * SELECT, or a literal query string.
  935. *
  936. * @param array $data An associative array of data to bind into the
  937. * SELECT statement.
  938. *
  939. * @return array
  940. *
  941. */
  942. public function fetchAll($spec, $data = array())
  943. {
  944. $result = $this->fetchPdo($spec, $data);
  945. return $result->fetchAll(PDO::FETCH_ASSOC);
  946. }
  947. /**
  948. *
  949. * Fetches all rows from the database using associative keys (defined by
  950. * the first column).
  951. *
  952. * N.b.: if multiple rows have the same first column value, the last
  953. * row with that value will override earlier rows.
  954. *
  955. * @param array|string $spec An array of component parts for a
  956. * SELECT, or a literal query string.
  957. *
  958. * @param array $data An associative array of data to bind into the
  959. * SELECT statement.
  960. *
  961. * @return array
  962. *
  963. */
  964. public function fetchAssoc($spec, $data = array())
  965. {
  966. $result = $this->fetchPdo($spec, $data);
  967. $data = array();
  968. while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
  969. $key = current($row); // value of the first element
  970. $data[$key] = $row;
  971. }
  972. return $data;
  973. }
  974. /**
  975. *
  976. * Fetches the first column of all rows as a sequential array.
  977. *
  978. * @param array|string $spec An array of component parts for a
  979. * SELECT, or a literal query string.
  980. *
  981. * @param array $data An associative array of data to bind into the
  982. * SELECT statement.
  983. *
  984. * @return array
  985. *
  986. */
  987. public function fetchCol($spec, $data = array())
  988. {
  989. $result = $this->fetchPdo($spec, $data);
  990. return $result->fetchAll(PDO::FETCH_COLUMN, 0);
  991. }
  992. /**
  993. *
  994. * Fetches the very first value (i.e., first column of the first row).
  995. *
  996. * When $spec is an array, automatically sets LIMIT 1 OFFSET 0 to limit
  997. * the results to a single row.
  998. *
  999. * @param array|string $spec An array of component parts for a
  1000. * SELECT, or a literal query string.
  1001. *
  1002. * @param array $data An associative array of data to bind into the
  1003. * SELECT statement.
  1004. *
  1005. * @return mixed
  1006. *
  1007. */
  1008. public function fetchValue($spec, $data = array())
  1009. {
  1010. if (is_array($spec)) {
  1011. // automatically limit to the first row only,
  1012. // but leave the offset alone.
  1013. $spec['limit']['count'] = 1;
  1014. }
  1015. $result = $this->fetchPdo($spec, $data);
  1016. return $result->fetchColumn(0);
  1017. }
  1018. /**
  1019. *
  1020. * Fetches an associative array of all rows as key-value pairs (first
  1021. * column is the key, second column is the value).
  1022. *
  1023. * @param array|string $spec An array of component parts for a
  1024. * SELECT, or a literal query string.
  1025. *
  1026. * @param array $data An associative array of data to bind into the
  1027. * SELECT statement.
  1028. *
  1029. * @return array
  1030. *
  1031. */
  1032. public function fetchPairs($spec, $data = array())
  1033. {
  1034. $result = $this->fetchPdo($spec, $data);
  1035. $data = array();
  1036. while ($row = $result->fetch(PDO::FETCH_NUM)) {
  1037. $data[$row[0]] = $row[1];
  1038. }
  1039. return $data;
  1040. }
  1041. /**
  1042. *
  1043. * Fetches a PDOStatement result object.
  1044. *
  1045. * @param array|string $spec An array of component parts for a
  1046. * SELECT, or a literal query string.
  1047. *
  1048. * @param array $data An associative array of data to bind into the
  1049. * SELECT statement.
  1050. *
  1051. * @return PDOStatement
  1052. *
  1053. */
  1054. public function fetchPdo($spec, $data = array())
  1055. {
  1056. // build the statement from its component parts if needed
  1057. if (is_array($spec)) {
  1058. $stmt = $this->_select($spec);
  1059. } else {
  1060. $stmt = $spec;
  1061. }
  1062. // execute and get the PDOStatement result object
  1063. return $this->query($stmt, $data);
  1064. }
  1065. /**
  1066. *
  1067. * Fetches one row from the database.
  1068. *
  1069. * When $spec is an array, automatically sets LIMIT 1 OFFSET 0 to limit
  1070. * the results to a single row.
  1071. *
  1072. * @param array|string $spec An array of component parts for a
  1073. * SELECT, or a literal query string.
  1074. *
  1075. * @param array $data An associative array of data to bind into the
  1076. * SELECT statement.
  1077. *
  1078. * @return array
  1079. *
  1080. */
  1081. public function fetchOne($spec, $data = array())
  1082. {
  1083. if (is_array($spec)) {
  1084. // automatically limit to the first row only,
  1085. // but leave the offset alone.
  1086. $spec['limit']['count'] = 1;
  1087. }
  1088. $result = $this->fetchPdo($spec, $data);
  1089. return $result->fetch(PDO::FETCH_ASSOC);
  1090. }
  1091. /**
  1092. *
  1093. * Builds the SQL statement and returns it as a string instead of
  1094. * executing it. Useful for debugging.
  1095. *
  1096. * @param array|string $spec An array of component parts for a
  1097. * SELECT, or a literal query string.
  1098. *
  1099. * @return string
  1100. *
  1101. */
  1102. public function fetchSql($spec)
  1103. {
  1104. // build the statement from its component parts if needed
  1105. if (is_array($spec)) {
  1106. return $this->_select($spec);
  1107. } else {
  1108. return $spec;
  1109. }
  1110. }
  1111. /**
  1112. *
  1113. * Returns a SELECT statement built from its component parts.
  1114. *
  1115. * @param array $parts The component parts of the SELECT.
  1116. *
  1117. * @return string The SELECT string.
  1118. *
  1119. */
  1120. protected function _select($parts)
  1121. {
  1122. // buid the statment
  1123. if (empty($parts['compound'])) {
  1124. $stmt = $this->_selectSingle($parts);
  1125. } else {
  1126. $stmt = $this->_selectCompound($parts);
  1127. }
  1128. // modify per adapter
  1129. $this->_modSelect($stmt, $parts);
  1130. // done!
  1131. return $stmt;
  1132. }
  1133. /**
  1134. *
  1135. * Builds a single SELECT command string from its component parts,
  1136. * without the LIMIT portions; those are left to the individual adapters.
  1137. *
  1138. * @param array $parts The parts of the SELECT statement, generally
  1139. * from a Solar_Sql_Select object.
  1140. *
  1141. * @return string A SELECT command string.
  1142. *
  1143. */
  1144. protected function _selectSingle($parts)
  1145. {
  1146. $default = array(
  1147. 'distinct' => null,
  1148. 'cols' => array(),
  1149. 'from' => array(),
  1150. 'join' => array(),
  1151. 'where' => array(),
  1152. 'group' => array(),
  1153. 'having' => array(),
  1154. 'order' => array(),
  1155. );
  1156. $parts = array_merge($default, $parts);
  1157. // is this a SELECT or SELECT DISTINCT?
  1158. if ($parts['distinct']) {
  1159. $stmt = "SELECT DISTINCT\n ";
  1160. } else {
  1161. $stmt = "SELECT\n ";
  1162. }
  1163. // add columns
  1164. $stmt .= implode(",\n ", $parts['cols']) . "\n";
  1165. // from these tables
  1166. $stmt .= $this->_selectSingleFrom($parts['from']);
  1167. // joined to these tables
  1168. if ($parts['join']) {
  1169. $list = array();
  1170. foreach ($parts['join'] as $join) {
  1171. $tmp = '';
  1172. // add the type (LEFT, INNER, etc)
  1173. if (! empty($join['type'])) {
  1174. $tmp .= $join['type'] . ' ';
  1175. }
  1176. // add the table name and condition
  1177. $tmp .= 'JOIN ' . $join['name'];
  1178. $tmp .= ' ON ' . $join['cond'];
  1179. // add to the list
  1180. $list[] = $tmp;
  1181. }
  1182. // add the list of all joins
  1183. $stmt .= implode("\n", $list) . "\n";
  1184. }
  1185. // with these where conditions
  1186. if ($parts['where']) {
  1187. $stmt .= "WHERE\n ";
  1188. $stmt .= implode("\n ", $parts['where']) . "\n";
  1189. }
  1190. // grouped by these columns
  1191. if ($parts['group']) {
  1192. $stmt .= "GROUP BY\n ";
  1193. $stmt .= implode(",\n ", $parts['group']) . "\n";
  1194. }
  1195. // having these conditions
  1196. if ($parts['having']) {
  1197. $stmt .= "HAVING\n ";
  1198. $stmt .= implode("\n ", $parts['having']) . "\n";
  1199. }
  1200. // ordered by these columns
  1201. if ($parts['order']) {
  1202. $stmt .= "ORDER BY\n ";
  1203. $stmt .= implode(",\n ", $parts['order']) . "\n";
  1204. }
  1205. // done!
  1206. return $stmt;
  1207. }
  1208. /**
  1209. *
  1210. * Builds the FROM clause for a SELECT command.
  1211. *
  1212. * @param array $from The array of FROM clause elements.
  1213. *
  1214. * @return string The FROM clause.
  1215. *
  1216. */
  1217. protected function _selectSingleFrom($from)
  1218. {
  1219. return "FROM\n "
  1220. . implode(",\n ", $from)
  1221. . "\n";
  1222. }
  1223. /**
  1224. *
  1225. * Builds a compound SELECT command string from its component parts,
  1226. * without the LIMIT portions; those are left to the individual adapters.
  1227. *
  1228. * @param array $parts The parts of the SELECT statement, generally
  1229. * from a Solar_Sql_Select object.
  1230. *
  1231. * @return string A SELECT command string.
  1232. *
  1233. */
  1234. protected function _selectCompound($parts)
  1235. {
  1236. // the select statement to build up
  1237. $stmt = '';
  1238. // default parts of each 'compound' element
  1239. $default = array(
  1240. 'type' => null, // 'UNION', 'UNION ALL', etc.
  1241. 'spec' => null, // array or string for the SELECT statement
  1242. );
  1243. // combine the compound elements
  1244. foreach ((array) $parts['compound'] as $compound) {
  1245. // make sure we have the default elements
  1246. $compound = array_merge($default, $compound);
  1247. // is it an array of select parts?
  1248. if (is_array($compound['spec'])) {
  1249. // yes, build a select string from them
  1250. $select = $this->_select($compound['spec']);
  1251. } else {
  1252. // no, assume it's already a select string
  1253. $select = $compound['spec'];
  1254. }
  1255. // do we need to add the compound type?
  1256. // note that the first compound type will be ignored.
  1257. if ($stmt) {
  1258. $stmt .= strtoupper($compound['type']) . "\n";
  1259. }
  1260. // now add the select itself
  1261. $stmt .= "(" . $select . ")\n";
  1262. }
  1263. // add any overall order
  1264. if (! empty($parts['order'])) {
  1265. $stmt .= "ORDER BY\n ";
  1266. $stmt .= implode(",\n ", $parts['order']) . "\n";
  1267. }
  1268. // done!
  1269. return $stmt;
  1270. }
  1271. /**
  1272. *
  1273. * Modifies a SELECT statement in place to add a LIMIT clause.
  1274. *
  1275. * The default code adds a LIMIT for MySQL, PostgreSQL, and Sqlite, but
  1276. * adapters can override as needed.
  1277. *
  1278. * @param string &$stmt The SELECT statement.
  1279. *
  1280. * @param array &$parts The orignal SELECT component parts, in case the
  1281. * adapter needs them.
  1282. *
  1283. * @return void
  1284. *
  1285. */
  1286. protected function _modSelect(&$stmt, &$parts)
  1287. {
  1288. // determine count
  1289. $count = ! empty($parts['limit']['count'])
  1290. ? (int) $parts['limit']['count']
  1291. : 0;
  1292. // determine offset
  1293. $offset = ! empty($parts['limit']['offset'])
  1294. ? (int) $parts['limit']['offset']
  1295. : 0;
  1296. // add the count and offset
  1297. if ($count > 0) {
  1298. $stmt .= "LIMIT $count";
  1299. if ($offset > 0) {
  1300. $stmt .= " OFFSET $offset";
  1301. }
  1302. }
  1303. }
  1304. // -----------------------------------------------------------------
  1305. //
  1306. // Quoting
  1307. //
  1308. // -----------------------------------------------------------------
  1309. /**
  1310. *
  1311. * Safely quotes a value for an SQL statement.
  1312. *
  1313. * If an array is passed as the value, the array values are quoted
  1314. * and then returned as a comma-separated string; this is useful
  1315. * for generating IN() lists.
  1316. *
  1317. * {{code: php
  1318. * $sql = Solar::factory('Solar_Sql');
  1319. *
  1320. * $safe = $sql->quote('foo"bar"');
  1321. * // $safe == "'foo\"bar\"'"
  1322. *
  1323. * $safe = $sql->quote(array('one', 'two', 'three'));
  1324. * // $safe == "'one', 'two', 'three'"
  1325. * }}
  1326. *
  1327. * @param mixed $val The value to quote.
  1328. *
  1329. * @return string An SQL-safe quoted value (or a string of
  1330. * separated-and-quoted values).
  1331. *
  1332. */
  1333. public function quote($val)
  1334. {
  1335. if (is_array($val)) {
  1336. // quote array values, not keys, then combine with commas.
  1337. foreach ($val as $k => $v) {
  1338. $val[$k] = $this->quote($v);
  1339. }
  1340. return implode(', ', $val);
  1341. } else {
  1342. // quote all other scalars, including numerics
  1343. $this->connect();
  1344. return $this->_pdo->quote($val);
  1345. }
  1346. }
  1347. /**
  1348. *
  1349. * Quotes a value and places into a piece of text at a placeholder; the
  1350. * placeholder is a question-mark.
  1351. *
  1352. * {{code: php
  1353. * $sql = Solar::factory('Solar_Sql');
  1354. *
  1355. * // replace one placeholder
  1356. * $text = "WHERE date >= ?";
  1357. * $data = "2005-01-01";
  1358. * $safe = $sql->quoteInto($text, $data);
  1359. * // => "WHERE date >= '2005-01-02'"
  1360. *
  1361. * // replace multiple placeholders
  1362. * $text = "WHERE date BETWEEN ? AND ?";
  1363. * $data = array("2005-01-01", "2005-01-31");
  1364. * $safe = $sql->quoteInto($text, $data);
  1365. * // => "WHERE date BETWEEN '2005-01-01' AND '2005-01-31'"
  1366. *
  1367. * // single placeholder with array value
  1368. * $text = "WHERE foo IN (?)";
  1369. * $data = array('a', 'b', 'c');
  1370. * $safe = $sql->quoteInto($text, $data);
  1371. * // => "WHERE foo IN ('a', 'b', 'c')"
  1372. *
  1373. * // multiple placeholders and array values
  1374. * $text = "WHERE date >= ? AND foo IN (?)";
  1375. * $data = array('2005-01-01, array('a', 'b', 'c'));
  1376. * $safe = $sql->quoteInto($text, $data);
  1377. * // => "WHERE date >= '2005-01-01' AND foo IN ('a', 'b', 'c')"
  1378. * }}
  1379. *
  1380. * @param string $text The text with placeholder(s).
  1381. *
  1382. * @param mixed $data The data value(s) to quote.
  1383. *
  1384. * @return mixed An SQL-safe quoted value (or string of separated values)
  1385. * placed into the orignal text.
  1386. *
  1387. * @see quote()
  1388. *
  1389. */
  1390. public function quoteInto($text, $data)
  1391. {
  1392. // how many question marks are there?
  1393. $count = substr_count($text, '?');
  1394. if (! $count) {
  1395. // no replacements needed
  1396. return $text;
  1397. }
  1398. // only one replacement?
  1399. if ($count == 1) {
  1400. $data = $this->quote($data);
  1401. $text = str_replace('?', $data, $text);
  1402. return $text;
  1403. }
  1404. // more than one replacement; force values to be an array, then make
  1405. // sure we have enough values to replace all the placeholders.
  1406. settype($data, 'array');
  1407. if (count($data) < $count) {
  1408. // more placeholders than values
  1409. throw $this->_exception('ERR_NOT_ENOUGH_VALUES', array(
  1410. 'text' => $text,
  1411. 'data' => $data,
  1412. ));
  1413. }
  1414. // replace each placeholder with a quoted value
  1415. $offset = 0;
  1416. foreach ($data as $val) {
  1417. // find the next placeholder
  1418. $pos = strpos($text, '?', $offset);
  1419. if ($pos === false) {
  1420. // no more placeholders, exit the data loop
  1421. break;
  1422. }
  1423. // replace this question mark with a quoted value
  1424. $val = $this->quote($val);
  1425. $text = substr_replace($text, $val, $pos, 1);
  1426. // update the offset to move us past the quoted value
  1427. $offset = $pos + strlen($val);
  1428. }
  1429. return $text;
  1430. }
  1431. /**
  1432. *
  1433. * Quote multiple text-and-value pieces.
  1434. *
  1435. * The placeholder is a question-mark; all placeholders will be replaced
  1436. * with the quoted value. For example ...
  1437. *
  1438. * {{code: php
  1439. * $sql = Solar::factory('Solar_Sql');
  1440. *
  1441. * $list = array(
  1442. * "WHERE date > ?" => '2005-01-01',
  1443. * " AND date < ?" => '2005-02-01',
  1444. * " AND type IN(?)" => array('a', 'b', 'c'),
  1445. * );
  1446. * $safe = $sql->quoteMulti($list);
  1447. *
  1448. * // $safe = "WHERE date > '2005-01-02'
  1449. * // AND date < 2005-02-01
  1450. * // AND type IN('a','b','c')"
  1451. * }}
  1452. *
  1453. * @param array $list A series of key-value pairs where the key is
  1454. * the placeholder text and the value is the value to be quoted into
  1455. * it. If the key is an integer, it is assumed that the value is
  1456. * piece of literal text to be used and not quoted.
  1457. *
  1458. * @param string $sep Return the list pieces separated with this string
  1459. * (for example ' AND '), default null.
  1460. *
  1461. * @return string An SQL-safe string composed of the list keys and
  1462. * quoted values.
  1463. *
  1464. */
  1465. public function quoteMulti($list, $sep = null)
  1466. {
  1467. $text = array();
  1468. foreach ((array) $list as $key => $val) {
  1469. if (is_int($key)) {
  1470. // integer $key means a literal phrase and no value to
  1471. // be bound into it
  1472. $text[] = $val;
  1473. } else {
  1474. // string $key means a phrase with a placeholder, and
  1475. // $val should be bound into it.
  1476. $text[] = $this->quoteInto($key, $val);
  1477. }
  1478. }
  1479. // return the condition list
  1480. $result = implode($sep, $text);
  1481. return $result;
  1482. }
  1483. /**
  1484. *
  1485. * Quotes a single identifier name (table, table alias, table column,
  1486. * index, sequence). Ignores empty values.
  1487. *
  1488. * If the name contains ' AS ', this method will separately quote the
  1489. * parts before and after the ' AS '.
  1490. *
  1491. * If the name contains a space, this method will separately quote the
  1492. * parts before and after the space.
  1493. *
  1494. * If the name contains a dot, this method will separately quote the
  1495. * parts before and after the dot.
  1496. *
  1497. * @param string|array $spec The identifier name to quote. If an array,
  1498. * quotes each element in the array as an identifier name.
  1499. *
  1500. * @return string|array The quoted identifier name (or array of names).
  1501. *
  1502. * @see _quoteName()
  1503. *
  1504. */
  1505. public function quoteName($spec)
  1506. {
  1507. if (is_array($spec)) {
  1508. foreach ($spec as $key => $val) {
  1509. $spec[$key] = $this->quoteName($val);
  1510. }
  1511. return $spec;
  1512. }
  1513. // no extraneous spaces
  1514. $spec = trim($spec);
  1515. // `original` AS `alias` ... note the 'rr' in strripos
  1516. $pos = strripos($spec, ' AS ');
  1517. if ($pos) {
  1518. // recurse to allow for "table.col"
  1519. $orig = $this->quoteName(substr($spec, 0, $pos));
  1520. // use as-is
  1521. $alias = $this->_quoteName(substr($spec, $pos + 4));
  1522. return "$orig AS $alias";
  1523. }
  1524. // `original` `alias`
  1525. $pos = strrpos($spec, ' ');
  1526. if ($pos) {
  1527. // recurse to allow for "table.col"
  1528. $orig = $this->quoteName(substr($spec, 0, $pos));
  1529. // use as-is
  1530. $alias = $this->_quoteName(substr($spec, $pos + 1));
  1531. return "$orig $alias";
  1532. }
  1533. // `table`.`column`
  1534. $pos = strrpos($spec, '.');
  1535. if ($pos) {
  1536. // use both as-is
  1537. $table = $this->_quoteName(substr($spec, 0, $pos));
  1538. $col = $this->_quoteName(substr($spec, $pos + 1));
  1539. return "$table.$col";
  1540. }
  1541. // `name`
  1542. return $this->_quoteName($spec);
  1543. }
  1544. /**
  1545. *
  1546. * Quotes an identifier name (table, index, etc); ignores empty values and
  1547. * values of '*'.
  1548. *
  1549. * @param string $name The identifier name to quote.
  1550. *
  1551. * @return string The quoted identifier name.
  1552. *
  1553. * @see quoteName()
  1554. *
  1555. */
  1556. protected function _quoteName($name)
  1557. {
  1558. $name = trim($name);
  1559. if ($name == '*') {
  1560. return $name;
  1561. } else {
  1562. return $this->_ident_quote_prefix
  1563. . $name
  1564. . $this->_ident_quote_suffix;
  1565. }
  1566. }
  1567. /**
  1568. *
  1569. * Quotes all fully-qualified identifier names ("table.col") in a string,
  1570. * typically an SQL snippet for a SELECT clause.
  1571. *
  1572. * Does not quote identifier names that are string literals (i.e., inside
  1573. * single or double quotes).
  1574. *
  1575. * Looks for a trailing ' AS alias' and quotes the alias as well.
  1576. *
  1577. * @param string|array $spec The string in which to quote fully-qualified
  1578. * identifier names to quote. If an array, quotes names in each element
  1579. * in the array.
  1580. *
  1581. * @return string|array The string (or array) with names quoted in it.
  1582. *
  1583. * @see _quoteNamesIn()
  1584. *
  1585. */
  1586. public function quoteNamesIn($spec)
  1587. {
  1588. if (is_array($spec)) {
  1589. foreach ($spec as $key => $val) {
  1590. $spec[$key] = $this->quoteNamesIn($val);
  1591. }
  1592. return $spec;
  1593. }
  1594. // single and double quotes
  1595. $apos =

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