PageRenderTime 50ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/Genv/Db/Adapter.php

https://gitlab.com/winiceo/levengit
PHP | 1099 lines | 764 code | 151 blank | 184 comment | 67 complexity | 1b053e3bce597cc95a9eff00c8ceeb67 MD5 | raw file
  1. <?php
  2. /**
  3. * Genv数据库适配器
  4. * 定义通用的数据库适配接口
  5. * 改编自 ezsql;
  6. *
  7. */
  8. define('EZSQL_VERSION','2.17');
  9. define('OBJECT','OBJECT',true);
  10. define('ARRAY_A','ARRAY_A',true);
  11. define('ARRAY_N','ARRAY_N',true);
  12. define('DB_COLLATE', '');
  13. abstract class Genv_Db_Adapter extends Genv_Base {
  14. protected $_Genv_Sql_Adapter = array(
  15. 'host' => null,
  16. 'port' => null,
  17. 'sock' => null,
  18. 'user' => null,
  19. 'pass' => null,
  20. 'name' => null,
  21. 'profiling' => false,
  22. 'cache' => array('adapter' => 'Genv_Cache_Adapter_Var'),
  23. );
  24. var $trace = false; // same as $debug_all
  25. var $debug_all = false; // same as $trace
  26. var $debug_called = false;
  27. var $vardump_called = false;
  28. var $show_errors = true;
  29. var $num_queries = 0;
  30. var $last_query = null;
  31. var $queries = array();
  32. var $last_error = null;
  33. var $col_info = null;
  34. var $captured_errors = array();
  35. var $cache_dir = false;
  36. var $cache_queries = false;
  37. var $cache_inserts = false;
  38. var $use_disk_cache = false;
  39. var $cache_timeout = 24; // hours
  40. var $timers = array();
  41. var $total_query_time = 0;
  42. var $db_connect_time = 0;
  43. var $trace_log = array();
  44. var $use_trace_log = false;
  45. var $sql_log_file = false;
  46. var $do_profile = false;
  47. var $profile_times = array();
  48. // == TJH == default now needed for echo of debug function
  49. var $debug_echo_is_on = true;
  50. var $field_types = array();
  51. protected $_ident_quote_prefix = null;
  52. protected $_ident_quote_suffix = null;
  53. protected function _postConstruct(){
  54. parent::_postConstruct();
  55. }
  56. /**********************************************************************
  57. * Print SQL/DB error - over-ridden by specific DB class
  58. */
  59. function register_error($err_str)
  60. {
  61. // Keep track of last error
  62. $this->last_error = $err_str;
  63. // Capture all errors to an error array no matter what happens
  64. $this->captured_errors[] = array
  65. (
  66. 'error_str' => $err_str,
  67. 'query' => $this->last_query
  68. );
  69. }
  70. /**********************************************************************
  71. * Turn error handling on or off..
  72. */
  73. function show_errors()
  74. {
  75. $this->show_errors = true;
  76. }
  77. function hide_errors()
  78. {
  79. $this->show_errors = false;
  80. }
  81. /**********************************************************************
  82. * Kill cached query results
  83. */
  84. function flush()
  85. {
  86. // Get rid of these
  87. $this->last_result = null;
  88. $this->col_info = null;
  89. $this->last_query = null;
  90. $this->from_disk_cache = false;
  91. }
  92. /**********************************************************************
  93. * Get one variable from the DB - see docs for more detail
  94. */
  95. function get_var($query=null,$x=0,$y=0)
  96. {
  97. // Log how the function was called
  98. $this->func_call = "\$db->get_var(\"$query\",$x,$y)";
  99. // If there is a query then perform it if not then use cached results..
  100. if ( $query )
  101. {
  102. $this->query($query);
  103. }
  104. // Extract var out of cached results based x,y vals
  105. if ( $this->last_result[$y] )
  106. {
  107. $values = array_values(get_object_vars($this->last_result[$y]));
  108. }
  109. // If there is a value return it else return null
  110. return (isset($values[$x]) && $values[$x]!=='')?$values[$x]:null;
  111. }
  112. /**********************************************************************
  113. * Get one row from the DB - see docs for more detail
  114. */
  115. function get_row($query=null,$output=ARRAY_A,$y=0)
  116. {
  117. // Log how the function was called
  118. $this->func_call = "\$db->get_row(\"$query\",$output,$y)";
  119. // If there is a query then perform it if not then use cached results..
  120. if ( $query )
  121. {
  122. $this->query($query);
  123. }
  124. // If the output is an object then return object using the row offset..
  125. if ( $output == OBJECT )
  126. {
  127. return $this->last_result[$y]?$this->last_result[$y]:null;
  128. }
  129. // If the output is an associative array then return row as such..
  130. elseif ( $output == ARRAY_A )
  131. {
  132. return $this->last_result[$y]?get_object_vars($this->last_result[$y]):null;
  133. }
  134. // If the output is an numerical array then return row as such..
  135. elseif ( $output == ARRAY_N )
  136. {
  137. return $this->last_result[$y]?array_values(get_object_vars($this->last_result[$y])):null;
  138. }
  139. // If invalid output type was specified..
  140. else
  141. {
  142. $this->print_error(" \$db->get_row(string query, output type, int offset) -- Output type must be one of: OBJECT, ARRAY_A, ARRAY_N");
  143. }
  144. }
  145. /**********************************************************************
  146. * Function to get 1 column from the cached result set based in X index
  147. * see docs for usage and info
  148. */
  149. function get_col($query=null,$x=0)
  150. {
  151. $new_array = array();
  152. // If there is a query then perform it if not then use cached results..
  153. if ( $query )
  154. {
  155. $this->query($query);
  156. }
  157. // Extract the column values
  158. for ( $i=0; $i < count($this->last_result); $i++ )
  159. {
  160. $new_array[$i] = $this->get_var(null,$x,$i);
  161. }
  162. return $new_array;
  163. }
  164. /**********************************************************************
  165. * Return the the query as a result set - see docs for more details
  166. */
  167. function get_results($query=null, $output = ARRAY_A)
  168. {
  169. // Log how the function was called
  170. $this->func_call = "\$db->get_results(\"$query\", $output)";
  171. // If there is a query then perform it if not then use cached results..
  172. if ( $query )
  173. {
  174. $this->query($query);
  175. }
  176. // Send back array of objects. Each row is an object
  177. if ( $output == OBJECT )
  178. {
  179. return $this->last_result;
  180. }
  181. elseif ( $output == ARRAY_A || $output == ARRAY_N )
  182. {
  183. if ( $this->last_result )
  184. {
  185. $i=0;
  186. foreach( $this->last_result as $row )
  187. {
  188. $new_array[$i] = get_object_vars($row);
  189. if ( $output == ARRAY_N )
  190. {
  191. $new_array[$i] = array_values($new_array[$i]);
  192. }
  193. $i++;
  194. }
  195. return $new_array;
  196. }
  197. else
  198. {
  199. return null;
  200. }
  201. }
  202. }
  203. /**********************************************************************
  204. * Function to get column meta data info pertaining to the last query
  205. * see docs for more info and usage
  206. */
  207. function get_col_info($info_type="name",$col_offset=-1)
  208. {
  209. if ( $this->col_info )
  210. {
  211. if ( $col_offset == -1 )
  212. {
  213. $i=0;
  214. foreach($this->col_info as $col )
  215. {
  216. $new_array[$i] = $col->{$info_type};
  217. $i++;
  218. }
  219. return $new_array;
  220. }
  221. else
  222. {
  223. return $this->col_info[$col_offset]->{$info_type};
  224. }
  225. }
  226. }
  227. /**********************************************************************
  228. * store_cache
  229. */
  230. function store_cache($query,$is_insert)
  231. {
  232. // The would be cache file for this query
  233. $cache_file = $this->cache_dir.'/'.md5($query);
  234. // disk caching of queries
  235. if ( $this->use_disk_cache && ( $this->cache_queries && ! $is_insert ) || ( $this->cache_inserts && $is_insert ))
  236. {
  237. if ( ! is_dir($this->cache_dir) )
  238. {
  239. $this->register_error("Could not open cache dir: $this->cache_dir");
  240. $this->show_errors ? trigger_error("Could not open cache dir: $this->cache_dir",E_USER_WARNING) : null;
  241. }
  242. else
  243. {
  244. // Cache all result values
  245. $result_cache = array
  246. (
  247. 'col_info' => $this->col_info,
  248. 'last_result' => $this->last_result,
  249. 'num_rows' => $this->num_rows,
  250. 'return_value' => $this->num_rows,
  251. );
  252. error_log ( serialize($result_cache), 3, $cache_file);
  253. }
  254. }
  255. }
  256. /**********************************************************************
  257. * get_cache
  258. */
  259. function get_cache($query)
  260. {
  261. // The would be cache file for this query
  262. $cache_file = $this->cache_dir.'/'.md5($query);
  263. // Try to get previously cached version
  264. if ( $this->use_disk_cache && file_exists($cache_file) )
  265. {
  266. // Only use this cache file if less than 'cache_timeout' (hours)
  267. if ( (time() - filemtime($cache_file)) > ($this->cache_timeout*3600) )
  268. {
  269. unlink($cache_file);
  270. }
  271. else
  272. {
  273. $result_cache = unserialize(file_get_contents($cache_file));
  274. $this->col_info = $result_cache['col_info'];
  275. $this->last_result = $result_cache['last_result'];
  276. $this->num_rows = $result_cache['num_rows'];
  277. $this->from_disk_cache = true;
  278. // If debug ALL queries
  279. $this->trace || $this->debug_all ? $this->debug() : null ;
  280. return $result_cache['return_value'];
  281. }
  282. }
  283. }
  284. /**********************************************************************
  285. * Dumps the contents of any input variable to screen in a nicely
  286. * formatted and easy to understand way - any type: Object, Var or Array
  287. */
  288. function vardump($mixed='')
  289. {
  290. // Start outup buffering
  291. ob_start();
  292. echo "<p><table><tr><td bgcolor=ffffff><blockquote><font color=000090>";
  293. echo "<pre><font face=arial>";
  294. if ( ! $this->vardump_called )
  295. {
  296. echo "<font color=800080><b>ezSQL</b> (v".EZSQL_VERSION.") <b>Variable Dump..</b></font>\n\n";
  297. }
  298. $var_type = gettype ($mixed);
  299. print_r(($mixed?$mixed:"<font color=red>No Value / False</font>"));
  300. echo "\n\n<b>Type:</b> " . ucfirst($var_type) . "\n";
  301. echo "<b>Last Query</b> [$this->num_queries]<b>:</b> ".($this->last_query?$this->last_query:"NULL")."\n";
  302. echo "<b>Last Function Call:</b> " . ($this->func_call?$this->func_call:"None")."\n";
  303. echo "<b>Last Rows Returned:</b> ".count($this->last_result)."\n";
  304. echo "</font></pre></font></blockquote></td></tr></table>";
  305. echo "\n<hr size=1 noshade color=dddddd>";
  306. // Stop output buffering and capture debug HTML
  307. $html = ob_get_contents();
  308. ob_end_clean();
  309. // Only echo output if it is turned on
  310. if ( $this->debug_echo_is_on )
  311. {
  312. echo $html;
  313. }
  314. $this->vardump_called = true;
  315. return $html;
  316. }
  317. /**********************************************************************
  318. * Alias for the above function
  319. */
  320. function dumpvar($mixed)
  321. {
  322. $this->vardump($mixed);
  323. }
  324. /**********************************************************************
  325. * Displays the last query string that was sent to the database & a
  326. * table listing results (if there were any).
  327. * (abstracted into a seperate file to save server overhead).
  328. */
  329. function debug($print_to_screen=true)
  330. {
  331. // Start outup buffering
  332. ob_start();
  333. echo "<blockquote>";
  334. // Only show ezSQL credits once..
  335. if ( ! $this->debug_called )
  336. {
  337. echo "<font color=800080 face=arial size=2><b>ezSQL</b> (v".EZSQL_VERSION.") <b>Debug..</b></font><p>\n";
  338. }
  339. if ( $this->last_error )
  340. {
  341. echo "<font face=arial size=2 color=000099><b>Last Error --</b> [<font color=000000><b>$this->last_error</b></font>]<p>";
  342. }
  343. if ( $this->from_disk_cache )
  344. {
  345. echo "<font face=arial size=2 color=000099><b>Results retrieved from disk cache</b></font><p>";
  346. }
  347. echo "<font face=arial size=2 color=000099><b>Query</b> [$this->num_queries] <b>--</b> ";
  348. echo "[<font color=000000><b>$this->last_query</b></font>]</font><p>";
  349. echo "<font face=arial size=2 color=000099><b>Query Result..</b></font>";
  350. echo "<blockquote>";
  351. if ( $this->col_info )
  352. {
  353. // =====================================================
  354. // Results top rows
  355. echo "<table cellpadding=5 cellspacing=1 bgcolor=555555>";
  356. echo "<tr bgcolor=eeeeee><td nowrap valign=bottom><font color=555599 face=arial size=2><b>(row)</b></font></td>";
  357. for ( $i=0; $i < count($this->col_info); $i++ )
  358. {
  359. echo "<td nowrap align=left valign=top><font size=1 color=555599 face=arial>{$this->col_info[$i]->type} {$this->col_info[$i]->max_length}</font><br><span style='font-family: arial; font-size: 10pt; font-weight: bold;'>{$this->col_info[$i]->name}</span></td>";
  360. }
  361. echo "</tr>";
  362. // ======================================================
  363. // print main results
  364. if ( $this->last_result )
  365. {
  366. $i=0;
  367. foreach ( $this->get_results(null,ARRAY_N) as $one_row )
  368. {
  369. $i++;
  370. echo "<tr bgcolor=ffffff><td bgcolor=eeeeee nowrap align=middle><font size=2 color=555599 face=arial>$i</font></td>";
  371. foreach ( $one_row as $item )
  372. {
  373. echo "<td nowrap><font face=arial size=2>$item</font></td>";
  374. }
  375. echo "</tr>";
  376. }
  377. } // if last result
  378. else
  379. {
  380. echo "<tr bgcolor=ffffff><td colspan=".(count($this->col_info)+1)."><font face=arial size=2>No Results</font></td></tr>";
  381. }
  382. echo "</table>";
  383. } // if col_info
  384. else
  385. {
  386. echo "<font face=arial size=2>No Results</font>";
  387. }
  388. echo "</blockquote></blockquote> <hr noshade color=dddddd size=1>";
  389. // Stop output buffering and capture debug HTML
  390. $html = ob_get_contents();
  391. ob_end_clean();
  392. // Only echo output if it is turned on
  393. if ( $this->debug_echo_is_on && $print_to_screen)
  394. {
  395. echo $html;
  396. }
  397. $this->debug_called = true;
  398. return $html;
  399. }
  400. /**********************************************************************
  401. * Timer related functions
  402. */
  403. function timer_get_cur()
  404. {
  405. list($usec, $sec) = explode(" ",microtime());
  406. return ((float)$usec + (float)$sec);
  407. }
  408. function timer_start($timer_name)
  409. {
  410. $this->timers[$timer_name] = $this->timer_get_cur();
  411. }
  412. function timer_elapsed($timer_name)
  413. {
  414. return round($this->timer_get_cur() - $this->timers[$timer_name],2);
  415. }
  416. function timer_update_global($timer_name)
  417. {
  418. if ( $this->do_profile )
  419. {
  420. $this->profile_times[] = array
  421. (
  422. 'query' => $this->last_query,
  423. 'time' => $this->timer_elapsed($timer_name)
  424. );
  425. }
  426. $this->total_query_time += $this->timer_elapsed($timer_name);
  427. }
  428. public function get_set($data,$format=null){
  429. $formats = $format = (array) $format;
  430. $fields = array_keys( $data );
  431. $formatted_fields = array();
  432. foreach ( $fields as $field ) {
  433. if ( !empty( $format ) )
  434. $form = ( $form = array_shift( $formats ) ) ? $form : $format[0];
  435. elseif ( isset( $this->field_types[$field] ) )
  436. $form = $this->field_types[$field];
  437. else
  438. $form = '%s';
  439. $formatted_fields[] ="`$field` = {$form}";// '`' . $this->escape($field). '`='.$form.'';
  440. }
  441. $values=array_values($data);
  442. $formatted_values = array();
  443. foreach ( $values as $val ) {
  444. if ( $val === 'true' ) $val = 1;
  445. if ( $val === 'false' ) $val = 0;
  446. if ( $val == 'NOW()' ){
  447. $val = $this->escape($val) ;
  448. }else{
  449. $val = $this->escape($val) ;
  450. }
  451. $formatted_values[]=$val;
  452. }
  453. if (sizeof($formatted_fields) != sizeof($formatted_values)) {
  454. return false;
  455. }
  456. return $this->prepare(implode(' , ',$formatted_fields), $formatted_values);
  457. }
  458. /**
  459. * Insert a row into a table.
  460. *
  461. * <code>
  462. * wpdb::insert( 'table', array( 'column' => 'foo', 'field' => 'bar' ) )
  463. * wpdb::insert( 'table', array( 'column' => 'foo', 'field' => 1337 ), array( '%s', '%d' ) )
  464. * </code>
  465. *
  466. * @since 2.5.0
  467. * @see wpdb::prepare()
  468. * @see wpdb::$field_types
  469. * @see wp_set_wpdb_vars()
  470. *
  471. * @param string $table table name
  472. * @param array $data Data to insert (in column => value pairs). Both $data columns and $data values should be "raw" (neither should be SQL escaped).
  473. * @param array|string $format Optional. An array of formats to be mapped to each of the value in $data. If string, that format will be used for all of the values in $data.
  474. * A format is one of '%d', '%f', '%s' (integer, float, string). If omitted, all values in $data will be treated as strings unless otherwise specified in wpdb::$field_types.
  475. * @return int|false The number of rows inserted, or false on error.
  476. */
  477. function insert( $table, $data, $format = null ) {
  478. return $this->_insert_replace_helper( $table, $data, $format, 'INSERT' );
  479. }
  480. /**
  481. * Replace a row into a table.
  482. *
  483. * <code>
  484. * wpdb::replace( 'table', array( 'column' => 'foo', 'field' => 'bar' ) )
  485. * wpdb::replace( 'table', array( 'column' => 'foo', 'field' => 1337 ), array( '%s', '%d' ) )
  486. * </code>
  487. *
  488. * @since 3.0.0
  489. * @see wpdb::prepare()
  490. * @see wpdb::$field_types
  491. * @see wp_set_wpdb_vars()
  492. *
  493. * @param string $table table name
  494. * @param array $data Data to insert (in column => value pairs). Both $data columns and $data values should be "raw" (neither should be SQL escaped).
  495. * @param array|string $format Optional. An array of formats to be mapped to each of the value in $data. If string, that format will be used for all of the values in $data.
  496. * A format is one of '%d', '%f', '%s' (integer, float, string). If omitted, all values in $data will be treated as strings unless otherwise specified in wpdb::$field_types.
  497. * @return int|false The number of rows affected, or false on error.
  498. */
  499. function replace( $table, $data, $format = null ) {
  500. return $this->_insert_replace_helper( $table, $data, $format, 'REPLACE' );
  501. }
  502. function _insert_replace_helper( $table, $data, $format = null, $type = 'INSERT' ) {
  503. if ( ! in_array( strtoupper( $type ), array( 'REPLACE', 'INSERT' ) ) )
  504. return false;
  505. $table = $this->quoteName($table);
  506. $sql = "{$type} INTO $table SET ";
  507. $sql.=$this->get_set($data,$format);
  508. return $this->query( $sql );
  509. }
  510. /**
  511. * Update a row in the table
  512. *
  513. * <code>
  514. * wpdb::update( 'table', array( 'column' => 'foo', 'field' => 'bar' ), array( 'ID' => 1 ) )
  515. * wpdb::update( 'table', array( 'column' => 'foo', 'field' => 1337 ), array( 'ID' => 1 ), array( '%s', '%d' ), array( '%d' ) )
  516. * </code>
  517. *
  518. * @since 2.5.0
  519. * @see wpdb::prepare()
  520. * @see wpdb::$field_types
  521. * @see wp_set_wpdb_vars()
  522. *
  523. * @param string $table table name
  524. * @param array $data Data to update (in column => value pairs). Both $data columns and $data values should be "raw" (neither should be SQL escaped).
  525. * @param array $where A named array of WHERE clauses (in column => value pairs). Multiple clauses will be joined with ANDs. Both $where columns and $where values should be "raw".
  526. * @param array|string $format Optional. An array of formats to be mapped to each of the values in $data. If string, that format will be used for all of the values in $data.
  527. * A format is one of '%d', '%f', '%s' (integer, float, string). If omitted, all values in $data will be treated as strings unless otherwise specified in wpdb::$field_types.
  528. * @param array|string $where_format Optional. An array of formats to be mapped to each of the values in $where. If string, that format will be used for all of the items in $where. A format is one of '%d', '%f', '%s' (integer, float, string). If omitted, all values in $where will be treated as strings.
  529. * @return int|false The number of rows updated, or false on error.
  530. */
  531. public function update($table, $data, $where,$format=null){
  532. // the base statement
  533. $table = $this->quoteName($table);
  534. $sql = "UPDATE $table SET ";
  535. $sql.=$this->get_set($data,$format);
  536. $sql.=' where '.$where;
  537. return $this->query($sql);
  538. }
  539. public function delete($table, $where){
  540. $table = $this->quoteName($table);
  541. //$where=$options['where'];
  542. $result = $this->query("DELETE FROM $table WHERE $where");
  543. return $result;
  544. }
  545. /**
  546. * Retrieve the name of the function that called wpdb.
  547. *
  548. * Searches up the list of functions until it reaches
  549. * the one that would most logically had called this method.
  550. *
  551. * @since 2.5.0
  552. *
  553. * @return string The name of the calling function
  554. */
  555. function get_caller() {
  556. $trace = array_reverse( debug_backtrace() );
  557. $caller = array();
  558. foreach ( $trace as $call ) {
  559. if ( isset( $call['class'] ) && __CLASS__ == $call['class'] )
  560. continue; // Filter out wpdb calls.
  561. $caller[] = isset( $call['class'] ) ? "{$call['class']}->{$call['function']}" : $call['function'];
  562. }
  563. return join( ', ', $caller );
  564. }
  565. /*---------------------------------------*/
  566. function logs($sql){
  567. $config = array(
  568. 'adapter' => 'Genv_Log_Adapter_File',
  569. 'events' => '*',
  570. 'file' => Genv_Config::get('Genv','appname').'/Data/Logs/sql.log.txt',
  571. );
  572. $log = Genv::factory('Genv_Log', $config);
  573. $log->save($sql,'','');
  574. }
  575. public function quoteInto($text, $data){
  576. // how many question marks are there?
  577. $count = substr_count($text, '?');
  578. if (! $count) {
  579. // no replacements needed
  580. return $text;
  581. }
  582. // only one replacement?
  583. if ($count == 1) {
  584. $data = $this->quote($data);
  585. $text = str_replace('?', $data, $text);
  586. return $text;
  587. }
  588. // more than one replacement; force values to be an array, then make
  589. // sure we have enough values to replace all the placeholders.
  590. settype($data, 'array');
  591. if (count($data) < $count) {
  592. // more placeholders than values
  593. throw $this->_exception('ERR_NOT_ENOUGH_VALUES', array(
  594. 'text' => $text,
  595. 'data' => $data,
  596. ));
  597. }
  598. // replace each placeholder with a quoted value
  599. $offset = 0;
  600. foreach ($data as $val) {
  601. // find the next placeholder
  602. $pos = strpos($text, '?', $offset);
  603. if ($pos === false) {
  604. // no more placeholders, exit the data loop
  605. break;
  606. }
  607. // replace this question mark with a quoted value
  608. $val = $this->quote($val);
  609. $text = substr_replace($text, $val, $pos, 1);
  610. // update the offset to move us past the quoted value
  611. $offset = $pos + strlen($val);
  612. }
  613. return $text;
  614. }
  615. public function quoteName($spec)
  616. {
  617. if (is_array($spec)) {
  618. foreach ($spec as $key => $val) {
  619. $spec[$key] = $this->quoteName($val);
  620. }
  621. return $spec;
  622. }
  623. // no extraneous spaces
  624. $spec = trim($spec);
  625. // `original` AS `alias` ... note the 'rr' in strripos
  626. $pos = strripos($spec, ' AS ');
  627. if ($pos) {
  628. // recurse to allow for "table.col"
  629. $orig = $this->quoteName(substr($spec, 0, $pos));
  630. // use as-is
  631. $alias = $this->_quoteName(substr($spec, $pos + 4));
  632. return "$orig AS $alias";
  633. }
  634. // `original` `alias`
  635. $pos = strrpos($spec, ' ');
  636. if ($pos) {
  637. // recurse to allow for "table.col"
  638. $orig = $this->quoteName(substr($spec, 0, $pos));
  639. // use as-is
  640. $alias = $this->_quoteName(substr($spec, $pos + 1));
  641. return "$orig $alias";
  642. }
  643. // `table`.`column`
  644. $pos = strrpos($spec, '.');
  645. if ($pos) {
  646. // use both as-is
  647. $table = $this->_quoteName(substr($spec, 0, $pos));
  648. $col = $this->_quoteName(substr($spec, $pos + 1));
  649. return "$table.$col";
  650. }
  651. // `name`
  652. return $this->_quoteName($spec);
  653. }
  654. protected function _quoteName($name)
  655. {
  656. $name = trim($name);
  657. if ($name == '*') {
  658. return $name;
  659. } else {
  660. return $this->_ident_quote_prefix
  661. . $name
  662. . $this->_ident_quote_suffix;
  663. }
  664. }
  665. public function quoteNamesIn($spec)
  666. {
  667. if (is_array($spec)) {
  668. foreach ($spec as $key => $val) {
  669. $spec[$key] = $this->quoteNamesIn($val);
  670. }
  671. return $spec;
  672. }
  673. // single and double quotes
  674. $apos = "'";
  675. $quot = '"';
  676. // look for ', ", \', or \" in the string.
  677. // match closing quotes against the same number of opening quotes.
  678. $list = preg_split(
  679. "/(($apos+|$quot+|\\$apos+|\\$quot+).*?\\2)/",
  680. $spec,
  681. -1,
  682. PREG_SPLIT_DELIM_CAPTURE
  683. );
  684. // concat the pieces back together, quoting names as we go.
  685. $spec = null;
  686. $last = count($list) - 1;
  687. foreach ($list as $key => $val) {
  688. // skip elements 2, 5, 8, 11, etc. as artifacts of the back-
  689. // referenced split; these are the trailing/ending quote
  690. // portions, and already included in the previous element.
  691. // this is the same as every third element from zero.
  692. if (($key+1) % 3 == 0) {
  693. continue;
  694. }
  695. // is there an apos or quot anywhere in the part?
  696. $is_string = strpos($val, $apos) !== false ||
  697. strpos($val, $quot) !== false;
  698. if ($is_string) {
  699. // string literal
  700. $spec .= $val;
  701. } else {
  702. // sql language.
  703. // look for an AS alias if this is the last element.
  704. if ($key == $last) {
  705. // note the 'rr' in strripos
  706. $pos = strripos($val, ' AS ');
  707. if ($pos) {
  708. // quote the alias name directly
  709. $alias = $this->_quoteName(substr($val, $pos + 4));
  710. $val = substr($val, 0, $pos) . " AS $alias";
  711. }
  712. }
  713. // now quote names in the language.
  714. $spec .= $this->_quoteNamesIn($val);
  715. }
  716. }
  717. // done!
  718. return $spec;
  719. }
  720. protected function _quoteNamesIn($text)
  721. {
  722. $word = "[a-z_][a-z0-9_]+";
  723. $find = "/(\\b)($word)\\.($word)(\\b)/i";
  724. $repl = '$1'
  725. . $this->_ident_quote_prefix
  726. . '$2'
  727. . $this->_ident_quote_suffix
  728. . '.'
  729. . $this->_ident_quote_prefix
  730. . '$3'
  731. . $this->_ident_quote_suffix
  732. . '$4'
  733. ;
  734. $text = preg_replace($find, $repl, $text);
  735. return $text;
  736. }
  737. public function fetchSql($spec,$data=array()){
  738. // build the statement from its component parts if needed
  739. if (is_array($spec)) {
  740. // dump($spec);
  741. // echo $this->_select($spec,$data);
  742. return $this->_select($spec,$data);
  743. } else {
  744. return $spec;
  745. }
  746. }
  747. public function fetchValue($spec, $data = array())
  748. {
  749. if (is_array($spec)) {
  750. // automatically limit to the first row only,
  751. // but leave the offset alone.
  752. $spec['limit']['count'] = 1;
  753. }
  754. $sql= $this->fetchSql($spec,$data);
  755. $result = $this->get_row($sql);
  756. return $result['genvid'];
  757. // return $result->fetchColumn(0);
  758. }
  759. protected function _select($parts,$data){
  760. // buid the statment
  761. if (empty($parts['compound'])) {
  762. $stmt = $this->_selectSingle($parts);
  763. } else {
  764. $stmt = $this->_selectCompound($parts);
  765. }
  766. // modify per adapter
  767. $this->_modSelect($stmt, $parts);
  768. foreach($data as $key=>$v){
  769. $stmt=str_replace(":$key",$this->quoteValue($v), $stmt);
  770. }
  771. //dump($stmt);
  772. if($data){
  773. $stmt=vsprintf($stmt, array_map(array($this, 'quoteValue'), $data));
  774. }
  775. return $stmt;
  776. }
  777. protected function _selectSingle($parts)
  778. {
  779. $default = array(
  780. 'distinct' => null,
  781. 'cols' => array(),
  782. 'from' => array(),
  783. 'join' => array(),
  784. 'where' => array(),
  785. 'group' => array(),
  786. 'having' => array(),
  787. 'order' => array(),
  788. );
  789. $parts = array_merge($default, $parts);
  790. // is this a SELECT or SELECT DISTINCT?
  791. if ($parts['distinct']) {
  792. $stmt = "SELECT DISTINCT\n ";
  793. } else {
  794. $stmt = "SELECT\n ";
  795. }
  796. // add columns
  797. $stmt .= implode(",\n ", $parts['cols']) . "\n";
  798. // from these tables
  799. $stmt .= $this->_selectSingleFrom($parts['from']);
  800. // joined to these tables
  801. if ($parts['join']) {
  802. $list = array();
  803. foreach ($parts['join'] as $join) {
  804. $tmp = '';
  805. // add the type (LEFT, INNER, etc)
  806. if (! empty($join['type'])) {
  807. $tmp .= $join['type'] . ' ';
  808. }
  809. // add the table name and condition
  810. $tmp .= 'JOIN ' . $join['name'];
  811. $tmp .= ' ON ' . $join['cond'];
  812. // add to the list
  813. $list[] = $tmp;
  814. }
  815. // add the list of all joins
  816. $stmt .= implode("\n", $list) . "\n";
  817. }
  818. // with these where conditions
  819. if ($parts['where']) {
  820. $stmt .= "WHERE\n ";
  821. $stmt .= implode("\n ", $parts['where']) . "\n";
  822. }
  823. // grouped by these columns
  824. if ($parts['group']) {
  825. $stmt .= "GROUP BY\n ";
  826. $stmt .= implode(",\n ", $parts['group']) . "\n";
  827. }
  828. // having these conditions
  829. if ($parts['having']) {
  830. $stmt .= "HAVING\n ";
  831. $stmt .= implode("\n ", $parts['having']) . "\n";
  832. }
  833. // ordered by these columns
  834. if ($parts['order']) {
  835. $stmt .= "ORDER BY\n ";
  836. $stmt .= implode(",\n ", $parts['order']) . "\n";
  837. }
  838. // done!
  839. return $stmt;
  840. }
  841. protected function _selectSingleFrom($from){
  842. return "FROM\n "
  843. . implode(",\n ", $from)
  844. . "\n";
  845. }
  846. protected function _selectCompound($parts)
  847. {
  848. // the select statement to build up
  849. $stmt = '';
  850. // default parts of each 'compound' element
  851. $default = array(
  852. 'type' => null, // 'UNION', 'UNION ALL', etc.
  853. 'spec' => null, // array or string for the SELECT statement
  854. );
  855. // combine the compound elements
  856. foreach ((array) $parts['compound'] as $compound) {
  857. // make sure we have the default elements
  858. $compound = array_merge($default, $compound);
  859. // is it an array of select parts?
  860. if (is_array($compound['spec'])) {
  861. // yes, build a select string from them
  862. $select = $this->_select($compound['spec']);
  863. } else {
  864. // no, assume it's already a select string
  865. $select = $compound['spec'];
  866. }
  867. // do we need to add the compound type?
  868. // note that the first compound type will be ignored.
  869. if ($stmt) {
  870. $stmt .= strtoupper($compound['type']) . "\n";
  871. }
  872. // now add the select itself
  873. $stmt .= "(" . $select . ")\n";
  874. }
  875. // add any overall order
  876. if (! empty($parts['order'])) {
  877. $stmt .= "ORDER BY\n ";
  878. $stmt .= implode(",\n ", $parts['order']) . "\n";
  879. }
  880. // done!
  881. return $stmt;
  882. }
  883. protected function _modSelect(&$stmt, &$parts){
  884. // determine count
  885. $count = ! empty($parts['limit']['count'])
  886. ? (int) $parts['limit']['count']
  887. : 0;
  888. // determine offset
  889. $offset = ! empty($parts['limit']['offset'])
  890. ? (int) $parts['limit']['offset']
  891. : 0;
  892. // add the count and offset
  893. if ($count > 0) {
  894. $stmt .= "LIMIT $count";
  895. if ($offset > 0) {
  896. $stmt .= " OFFSET $offset";
  897. }
  898. }
  899. }
  900. }