This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | // Set include path in __construct |
||
3 | //require_once('adodb/adodb.inc.php'); |
||
4 | require_once(dirname(__FILE__) . '/fwolflib.php'); |
||
5 | require_once(dirname(__FILE__) . '/sql_generator.php'); |
||
6 | require_once(dirname(__FILE__) . '/../func/ecl.php'); |
||
7 | require_once(dirname(__FILE__) . '/../func/string.php'); |
||
8 | |||
9 | |||
10 | /** |
||
11 | * Extended ADODB class |
||
12 | * |
||
13 | * Include all ADODB had, and add a little others. |
||
14 | * |
||
15 | * Piror use this class' method and property, if the get/set/call target |
||
16 | * is not exists, use original ADODB's, this can be done by php's mechematic |
||
17 | * of overload __call __get __set. |
||
18 | * |
||
19 | * 这似乎是extend ADODB的一种比较好的方式,比官方网站文档上给的按不同数据库来继承子类的方式, |
||
20 | * 我认为要更方便一些。缺点是没有对RecordSet对象进行处理。 |
||
21 | * |
||
22 | * Adodb for Sybase bug: |
||
23 | * Affected_Rows() in windows 2003 不可用,httpd 进程会出错中止 |
||
24 | * |
||
25 | * 执行sql查询的系列更改中,限定系统/HTML/PHP使用$sSysCharset指定的编码,涉及函数列在__call()中, |
||
26 | * 但一些通过数组等其它方式传递参数的ADODB方法仍然无法通过这种方式实现sql编码自动转换。 |
||
27 | * |
||
28 | * 执行返回的数据还是需要转码的,不过返回数据的种类太多,放在应用中实现更简单一些,这里不自动执行, |
||
29 | * 只提供一个EncodingConvert方法供用户调用。 |
||
30 | * |
||
31 | * @deprecated Use Fwlib\Bridge\Adodb |
||
32 | * @package fwolflib |
||
33 | * @subpackage class |
||
34 | * @copyright Copyright 2008-2012, Fwolf |
||
35 | * @author Fwolf <[email protected]> |
||
36 | * @since 2008-04-08 |
||
37 | */ |
||
38 | class Adodb extends Fwolflib { |
||
39 | |||
40 | /** |
||
41 | * Real ADODB connection object |
||
42 | * @var object |
||
43 | */ |
||
44 | protected $__conn = null; |
||
45 | |||
46 | /** |
||
47 | * Db profile |
||
48 | * @var array |
||
49 | */ |
||
50 | public $aDbProfile = null; |
||
51 | |||
52 | /** |
||
53 | * Table schema |
||
54 | * |
||
55 | * array( |
||
56 | * col -> // ADOFieldObject Object, not Array ! |
||
57 | * [name] => ts |
||
58 | * [max_length] => -1 |
||
59 | * [type] => timestamp |
||
60 | * [scale] => |
||
61 | * [not_null] => 1 |
||
62 | * [primary_key] => |
||
63 | * [auto_increment] => |
||
64 | * [binary] => |
||
65 | * [unsigned] => |
||
66 | * [zerofill] => |
||
67 | * [has_default] => 1 |
||
68 | * [default_value] => CURRENT_TIMESTAMP |
||
69 | * ) |
||
70 | * ) |
||
71 | * @var array |
||
72 | */ |
||
73 | public $aMetaColumn = array(); |
||
74 | |||
75 | /** |
||
76 | * Table column name array, index is upper case of column name |
||
77 | * |
||
78 | * eg: array( |
||
79 | * 'COLUMN' => 'column', |
||
80 | * ) |
||
81 | * @var array |
||
82 | */ |
||
83 | public $aMetaColumnName = array(); |
||
84 | |||
85 | /** |
||
86 | * Primary key columns of table |
||
87 | * |
||
88 | * array( |
||
89 | * tbl_name -> 'col_pk', |
||
90 | * tbl_name -> array(pk_col1, pk_col2), |
||
91 | * ) |
||
92 | * @var array |
||
93 | */ |
||
94 | public $aMetaPrimaryKey = array(); |
||
95 | |||
96 | /** |
||
97 | * Sql generator object |
||
98 | * @var object |
||
99 | */ |
||
100 | protected $oSg; |
||
101 | |||
102 | /** |
||
103 | * Error msg |
||
104 | * @var string |
||
105 | */ |
||
106 | public $sErrorMsg = ''; |
||
107 | |||
108 | /** |
||
109 | * System charset |
||
110 | * |
||
111 | * In common, this is your php script/operation system charset |
||
112 | * @var string |
||
113 | */ |
||
114 | public $sSysCharset = 'utf8'; |
||
115 | |||
116 | |||
117 | /** |
||
118 | * construct |
||
119 | * |
||
120 | * <code> |
||
121 | * $dbprofile = array(type, host, user, pass, name, lang); |
||
122 | * type is mysql/sybase_ase etc, |
||
123 | * name is dbname to select, |
||
124 | * lang is db server charset. |
||
125 | * </code> |
||
126 | * @var param array $dbprofile |
||
127 | * @var param string $path_adodb Include path of original ADODB |
||
128 | */ |
||
129 | public function __construct ($dbprofile, $path_adodb = '') { |
||
130 | parent::__construct(); |
||
131 | |||
132 | // Include original adodb lib |
||
133 | if (empty($path_adodb)) |
||
134 | $path_adodb = 'adodb/adodb.inc.php'; |
||
135 | require_once($path_adodb); |
||
136 | |||
137 | $this->aDbProfile = $dbprofile; |
||
138 | $this->__conn = ADONewConnection($dbprofile['type']); |
||
139 | |||
140 | // Sql generator object |
||
141 | $this->oSg = new SqlGenerator($this); |
||
142 | } // end of class __construct |
||
143 | |||
144 | |||
145 | /** |
||
146 | * Overload __call, redirect method call to adodb |
||
147 | * |
||
148 | * @var string $name Method name |
||
149 | * @var array $arg Method argument |
||
150 | * @global int $i_db_query_times |
||
151 | * @return mixed |
||
152 | */ |
||
153 | public function __call ($name, $arg) { |
||
154 | // Before call, convert $sql encoding first |
||
155 | if ($this->sSysCharset != $this->aDbProfile['lang']) { |
||
156 | // Method list by ADODB doc order |
||
157 | // $sql is the 1st param |
||
158 | if (in_array($name, array('Execute', |
||
159 | 'CacheExecute', |
||
160 | 'SelectLimit', |
||
161 | 'CacheSelectLimit', |
||
162 | 'Prepare', |
||
163 | 'PrepareSP', |
||
164 | 'GetOne', |
||
165 | 'GetRow', |
||
166 | 'GetAll', |
||
167 | 'GetCol', |
||
168 | 'CacheGetOne', |
||
169 | 'CacheGetRow', |
||
170 | 'CacheGetAll', |
||
171 | 'CacheGetCol', |
||
172 | 'GetAssoc', |
||
173 | 'CacheGetAssoc', |
||
174 | 'ExecuteCursor', |
||
175 | ))) |
||
176 | $arg[0] = mb_convert_encoding($arg[0], $this->aDbProfile['lang'], $this->sSysCharset); |
||
177 | |||
178 | // $sql is the 2nd param |
||
179 | if (in_array($name, array('CacheExecute', |
||
180 | 'CacheSelectLimit', |
||
181 | 'CacheGetOne', |
||
182 | 'CacheGetRow', |
||
183 | 'CacheGetAll', |
||
184 | 'CacheGetCol', |
||
185 | 'CacheGetAssoc', |
||
186 | ))) |
||
187 | $arg[1] = mb_convert_encoding($arg[1], $this->aDbProfile['lang'], $this->sSysCharset); |
||
188 | } |
||
189 | |||
190 | // Count db query times |
||
191 | // Use global var so multi Adodb object can be included in count. |
||
192 | // (Done in func now) |
||
193 | // Use standalone func to can be easy extend by sub class. |
||
194 | View Code Duplication | if (in_array($name, array( |
|
195 | 'Execute', 'SelectLimit', 'GetOne', 'GetRow', 'GetAll', |
||
196 | 'GetCol', 'GetAssoc', 'ExecuteCursor' |
||
197 | ))) |
||
198 | $this->CountDbQueryTimes(); |
||
199 | |||
200 | return call_user_func_array(array($this->__conn, $name), $arg); |
||
201 | } // end of func __call |
||
202 | |||
203 | |||
204 | /** |
||
205 | * Overload __get, redirect method call to adodb |
||
206 | * |
||
207 | * @param string $name |
||
208 | * @return mixed |
||
209 | */ |
||
210 | public function __get ($name) { |
||
211 | return $this->__conn->$name; |
||
212 | } // end of func __get |
||
213 | |||
214 | |||
215 | /** |
||
216 | * Overload __set, redirect method call to adodb |
||
217 | * |
||
218 | * @param string $name |
||
219 | * @param mixed $val |
||
220 | */ |
||
221 | public function __set ($name, $val) { |
||
222 | $this->__conn->$name = $val; |
||
223 | } // end of func __set |
||
224 | |||
225 | |||
226 | /** |
||
227 | } |
||
228 | * Connect, Add mysql 'set names utf8' |
||
229 | * |
||
230 | * <code> |
||
231 | * Obmit params(dbprofile was set in __construct): |
||
232 | * param $argHostname Host to connect to |
||
233 | * param $argUsername Userid to login |
||
234 | * param $argPassword Associated password |
||
235 | * param $argDatabaseName database |
||
236 | * </code> |
||
237 | * @param $forcenew Force new connection |
||
238 | * @return boolean |
||
239 | */ |
||
240 | public function Connect ($forcenew = false) { |
||
241 | // Mysqli doesn't allow port in host, grab it out and set |
||
242 | View Code Duplication | if ('mysqli' == strtolower($this->__conn->databaseType)) { |
|
243 | $ar = array(); |
||
244 | $i = preg_match('/:(\d+)$/', $this->aDbProfile['host'], $ar); |
||
245 | if (0 < $i) { |
||
246 | $this->__conn->port = $ar[1]; |
||
247 | $this->aDbProfile['host'] = preg_replace('/:(\d+)$/', '' |
||
248 | , $this->aDbProfile['host']); |
||
249 | } |
||
250 | } |
||
251 | |||
252 | |||
253 | try { |
||
254 | // Disable error display tempratory |
||
255 | $s = ini_get("display_errors"); |
||
256 | ini_set("display_errors", "0"); |
||
257 | |||
258 | // Sybase will echo 'change to master' warning msg |
||
259 | // :THINK: Will this problem solved if we drop default |
||
260 | // database master from sa user ? |
||
261 | if ($this->IsDbSybase()) { |
||
262 | $rs = $this->__conn->Connect($this->aDbProfile['host'], |
||
263 | $this->aDbProfile['user'], |
||
264 | $this->aDbProfile['pass'], |
||
265 | $this->aDbProfile['name'], |
||
266 | $forcenew); |
||
267 | } |
||
268 | else { |
||
269 | $rs = $this->__conn->Connect($this->aDbProfile['host'], |
||
270 | $this->aDbProfile['user'], |
||
271 | $this->aDbProfile['pass'], |
||
272 | $this->aDbProfile['name'], |
||
273 | $forcenew); |
||
274 | } |
||
275 | |||
276 | // Recover original error display setting |
||
277 | ini_set("display_errors", $s); |
||
278 | |||
279 | if (empty($rs)) { |
||
280 | throw new Exception('Db connect fail, please check php errorlog.', -1); |
||
281 | } |
||
282 | } catch (Exception $e) { |
||
283 | // Get error trace message |
||
284 | $i_ob = ob_get_level(); |
||
285 | if (0 != $i_ob) |
||
286 | $s_t = ob_get_clean(); |
||
287 | |||
288 | ob_start(); |
||
289 | adodb_backtrace($e->getTrace()); |
||
290 | $s_trace = ob_get_clean(); |
||
291 | |||
292 | if (0 != $i_ob) { |
||
293 | // Maybe cause error if output handle of ob_start used before |
||
294 | ob_start(); |
||
295 | echo $s_t; |
||
296 | } |
||
297 | |||
298 | |||
299 | // Log error |
||
300 | $s_trace = "======== Adodb db connect error\n" |
||
301 | . str_replace(' ', '>', strip_tags($s_trace)) |
||
302 | . $this->ErrorMsg() . "\n"; |
||
303 | $this->sErrorMsg = $s_trace; |
||
304 | error_log($s_trace); |
||
305 | |||
306 | /* |
||
307 | // Print error |
||
308 | $this->sErrorMsg = 'Error, code ' |
||
309 | . $e->getCode() |
||
310 | . ', msg: ' . $e->getMessage(); |
||
311 | |||
312 | // Log error |
||
313 | $s_trace = str_replace(' ', '>', strip_tags($s_trace)); |
||
314 | error_log(''); |
||
315 | error_log('======== Adodb db connect error'); |
||
316 | error_log("\n" . $s_trace); |
||
317 | error_log($this->sErrorMsg); |
||
318 | //error_log(''); |
||
319 | */ |
||
320 | |||
321 | //var_dump($e); |
||
322 | //echo $e; |
||
323 | if (0 != $i_ob) { |
||
324 | ob_end_flush(); |
||
325 | } |
||
326 | //exit(); |
||
327 | return false; |
||
328 | } |
||
329 | |||
330 | // 针对mysql 4.1以上,UTF8编码的数据库,需要在连接后指定编码 |
||
331 | // Can also use $this->aDbProfile['type'] |
||
332 | // mysql, mysqli |
||
333 | if ($this->IsDbMysql()) { |
||
334 | $this->__conn->Execute('set names "' . str_replace('utf-8', 'utf8', $this->aDbProfile['lang']) . '"'); |
||
335 | } |
||
336 | |||
337 | //return $rs; |
||
338 | return true; |
||
339 | } // end of func Connect |
||
340 | |||
341 | |||
342 | /** |
||
343 | * Count how many db query have executed |
||
344 | * |
||
345 | * This function can be extend by subclass if you want to count on multi db objects. |
||
346 | * |
||
347 | * Can't count in Adodb::property, because need display is done by Controler, |
||
348 | * which will call View, but Adodb is property of Module, |
||
349 | * so we can only use global vars to save this value. |
||
350 | * @global int $i_db_query_times |
||
351 | */ |
||
352 | protected function CountDbQueryTimes () { |
||
353 | global $i_db_query_times; |
||
354 | $i_db_query_times ++; |
||
355 | } // end of func CountDbQueryTimes |
||
356 | |||
357 | |||
358 | /** |
||
359 | * Delete rows by condition user given |
||
360 | * |
||
361 | * @param string $tbl |
||
362 | * @param string $cond Condition, can be where, having etc, raw sql string, not null. |
||
363 | * @return int -1 error/0 not found/N > 0 number of rows |
||
364 | */ |
||
365 | View Code Duplication | public function DelRow ($tbl, $cond) { |
|
366 | $cond = trim($cond); |
||
367 | if (empty($cond)) |
||
368 | return -1; |
||
369 | $this->PExecute($this->GenSql(array( |
||
370 | 'DELETE' => $tbl, |
||
371 | )) . ' ' . $cond); |
||
372 | if (0 != $this->ErrorNo()) |
||
373 | // Execute error |
||
374 | return -1; |
||
375 | else |
||
376 | return $this->Affected_Rows(); |
||
377 | } // end of func DelRow |
||
378 | |||
379 | |||
380 | /** |
||
381 | * Convert recordset(simple array) or other string |
||
382 | * from db encoding to system encoding |
||
383 | * |
||
384 | * Use recursive mechanism, beware of loop hole. |
||
385 | * @param mixed &$s Source to convert |
||
386 | * @return mixed |
||
387 | */ |
||
388 | View Code Duplication | public function EncodingConvert (&$s) { |
|
389 | if (is_array($s) && !empty($s)) { |
||
390 | foreach ($s as &$val) |
||
391 | $this->EncodingConvert($val); |
||
392 | unset($val); |
||
393 | } |
||
394 | |||
395 | if (is_string($s)) { |
||
396 | if ($this->sSysCharset != $this->aDbProfile['lang']) |
||
397 | $s = mb_convert_encoding($s, $this->sSysCharset, $this->aDbProfile['lang']); |
||
398 | } |
||
399 | return $s; |
||
400 | } // end of func EncodingConvert |
||
401 | |||
402 | |||
403 | /** |
||
404 | * Convert data encoding |
||
405 | * from system(usually utf-8) to db |
||
406 | * |
||
407 | * Use recursive mechanism, beware of loop hole. |
||
408 | * @param mixed &$s Source to convert |
||
409 | * @return mixed |
||
410 | */ |
||
411 | View Code Duplication | public function EncodingConvertReverse (&$s) { |
|
412 | if (is_array($s) && !empty($s)) { |
||
413 | foreach ($s as &$val) |
||
414 | $this->EncodingConvertReverse($val); |
||
415 | unset($val); |
||
416 | } |
||
417 | |||
418 | if (is_string($s)) { |
||
419 | if ($this->sSysCharset != $this->aDbProfile['lang']) |
||
420 | $s = mb_convert_encoding($s, $this->aDbProfile['lang'], $this->sSysCharset); |
||
421 | } |
||
422 | return $s; |
||
423 | } // end of func EncodingConvertReverse |
||
424 | |||
425 | |||
426 | /** |
||
427 | * Generate SQL then exec it |
||
428 | * |
||
429 | * @param array $ar_sql Same as GenSql() |
||
430 | * @return object |
||
431 | * @see GenSql() |
||
432 | */ |
||
433 | public function ExecuteGenSql ($ar_sql) { |
||
434 | return $this->Execute($this->GenSql($ar_sql)); |
||
435 | } // end of func ExecuteGenSql |
||
436 | |||
437 | |||
438 | /** |
||
439 | * Find name of timestamp column of a table |
||
440 | * |
||
441 | * @param $tbl Table name |
||
442 | * @return string |
||
443 | */ |
||
444 | public function FindColTs ($tbl) { |
||
445 | $ar_col = $this->GetMetaColumn($tbl); |
||
446 | if (empty($ar_col)) |
||
447 | return ''; |
||
448 | |||
449 | if ($this->IsDbSybase()) { |
||
450 | // Sybase's timestamp column must be lower cased |
||
451 | // Can name as others, but name as 'timestamp' will auto got )timestamp) type. |
||
452 | /* |
||
453 | if (isset($ar_col['timestamp'])) |
||
454 | return 'timestamp'; |
||
455 | else |
||
456 | return ''; |
||
457 | */ |
||
458 | // New way: |
||
459 | // http://bbs.chinaunix.net/archiver/tid-930729.html |
||
460 | $rs = $this->ExecuteGenSql(array( |
||
461 | 'SELECT' => array( |
||
462 | 'name' => 'a.name', |
||
463 | 'length' => 'a.length', |
||
464 | 'usertype' => 'a.usertype', |
||
465 | 'type' => 'b.name', |
||
466 | 'tableName' => 'c.name', |
||
467 | ), |
||
468 | 'FROM' => array( |
||
469 | 'a' => 'syscolumns', |
||
470 | 'b' => 'systypes', |
||
471 | 'c' => 'sysobjects', |
||
472 | ), |
||
473 | 'WHERE' => array( |
||
474 | "a.id = c.id", |
||
475 | 'a.type = b.type', |
||
476 | 'a.usertype = b.usertype', |
||
477 | 'b.type = 37', |
||
478 | 'b.usertype = 80', |
||
479 | ), |
||
480 | )); |
||
481 | View Code Duplication | while (!empty($rs) && !$rs->EOF) { |
|
482 | if ($tbl == $rs->fields['tableName']) { |
||
483 | return $rs->fields['name']; |
||
484 | } |
||
485 | $rs->MoveNext(); |
||
486 | } |
||
487 | |||
488 | return ''; |
||
489 | //select a.name,a.length,a.usertype,b.name AS type from syscolumns a ,systypes b |
||
490 | //where id = object_id('ztb_yh') and a.type=b.type and a.usertype = b.usertype |
||
491 | |||
492 | } |
||
493 | View Code Duplication | elseif ($this->IsDbMysql()) { |
|
494 | // Check 'type' |
||
495 | foreach ($ar_col as $k => $v) |
||
496 | if (isset($v->type) && 'timestamp' == $v->type) |
||
497 | return $k; |
||
498 | } |
||
499 | else { |
||
500 | die("FindColTs not implemented!\n"); |
||
501 | } |
||
502 | } // end of func FindColTs |
||
503 | |||
504 | |||
505 | /** |
||
506 | * Generate SQL statement |
||
507 | * |
||
508 | * User should avoid use SELECT/UPDATE/INSERT/DELETE simultaneously. |
||
509 | * |
||
510 | * Generate order by SQL statement format order. |
||
511 | * |
||
512 | * UPDATE/INSERT/DELETE is followed by [TBL_NAME], |
||
513 | * so need not use FROM. |
||
514 | * @param array $ar_sql Array(select=>..., from=>...) |
||
515 | * @return string |
||
516 | * @see SqlGenerator |
||
517 | */ |
||
518 | public function GenSql ($ar_sql) { |
||
519 | // Changed to use SqlGenerator |
||
520 | if (!empty($ar_sql)) { |
||
521 | return $this->oSg->GetSql($ar_sql); |
||
522 | } |
||
523 | else |
||
524 | return ''; |
||
525 | } // end of func GenSql |
||
526 | |||
527 | |||
528 | /** |
||
529 | * Generate SQL statement for Prepare |
||
530 | * |
||
531 | * value -> ? or :name, and quote chars removed |
||
532 | * @param array $ar_sql Same as GenSql() |
||
533 | * @return string |
||
534 | * @see GenSql() |
||
535 | * @see SqlGenerator |
||
536 | */ |
||
537 | public function GenSqlPrepare ($ar_sql) { |
||
538 | if (!empty($ar_sql)) |
||
539 | return $this->oSg->GetSqlPrepare($ar_sql); |
||
540 | else |
||
541 | return ''; |
||
542 | } // end of func GenSqlPrepare |
||
543 | |||
544 | |||
545 | /** |
||
546 | * Get data from single table using PK |
||
547 | * |
||
548 | * $m_pk, $col, $col_pk support string split by ',' or array, like: |
||
549 | * 1. 'val' |
||
550 | * 2. 'val1, val2' |
||
551 | * 3. array('val1', 'val2') |
||
552 | * |
||
553 | * '*' can be used for $col, means all cols in table, this way can't use |
||
554 | * cache, not recommend. |
||
555 | * |
||
556 | * Notice: $col must indexed by number start from 0. |
||
557 | * |
||
558 | * Also, this function can be used to retrieve data from a table with |
||
559 | * single unique index, by assigning $col_pk to non-PK column. |
||
560 | * |
||
561 | * @param string $s_tbl |
||
562 | * @param mixed $m_pk PK value |
||
563 | * @param mixed $col Cols need to retrieve. |
||
564 | * @param mixed $col_pk PK column name, NULL to auto get. |
||
565 | * @return mixed Single/array, NULL if error occur. |
||
566 | */ |
||
567 | public function GetDataByPk ($s_tbl, $m_pk, $col = NULL, $col_pk = NULL) { |
||
568 | // Treat PK col |
||
569 | if (empty($col_pk)) { |
||
570 | $col_pk = $this->GetMetaPrimaryKey($s_tbl); |
||
571 | } |
||
572 | |||
573 | // PK and col name all convert to array |
||
574 | View Code Duplication | if (!is_array($m_pk)) { |
|
575 | if (is_string($m_pk)) |
||
576 | $m_pk = StrToArray($m_pk, ','); |
||
577 | else |
||
578 | $m_pk = array($m_pk); |
||
579 | } |
||
580 | View Code Duplication | if (!is_array($col_pk)) { |
|
581 | if (is_string($col_pk)) |
||
582 | $col_pk = StrToArray($col_pk, ','); |
||
583 | else |
||
584 | $col_pk = array($col_pk); |
||
585 | } |
||
586 | |||
587 | // $col_pk need to be array same count with $m_pk |
||
588 | if (count($m_pk) != count($col_pk)) { |
||
589 | $this->Log('PK value and column not match.', 4); |
||
590 | return NULL; |
||
591 | } |
||
592 | |||
593 | // Treat col |
||
594 | if (empty($col)) |
||
595 | $col = '*'; |
||
596 | if ('*' == $col) |
||
597 | // Drop uppercased index |
||
598 | $col = array_values($this->GetMetaColumnName($s_tbl)); |
||
599 | View Code Duplication | if (!is_array($col)) { |
|
600 | if (is_string($col)) |
||
601 | // String split by ',', style 'col AS col_alias' allowed |
||
602 | $col = StrToArray($col, ','); |
||
603 | else |
||
604 | $col = array($col); |
||
605 | } |
||
606 | |||
607 | // $m_pk, $col, $col_pk all converted to array |
||
608 | |||
609 | // Retrieve from db |
||
610 | $ar_sql = array( |
||
611 | 'SELECT' => $col, |
||
612 | 'FROM' => $s_tbl, |
||
613 | 'LIMIT' => 1, |
||
614 | ); |
||
615 | while (!empty($m_pk)) { |
||
616 | $s_col_pk = array_shift($col_pk); |
||
617 | $ar_sql['WHERE'][] = $s_col_pk . ' = ' |
||
618 | . $this->QuoteValue($s_tbl, $s_col_pk, array_shift($m_pk)); |
||
619 | unset($s_col_pk); |
||
620 | } |
||
621 | $rs = $this->ExecuteGenSql($ar_sql); |
||
622 | $ar_rs = array(); |
||
623 | if (!empty($rs) && !$rs->EOF) { |
||
624 | $ar_rs = $rs->GetRowAssoc(false); |
||
625 | } |
||
626 | |||
627 | // Return value |
||
628 | if (empty($ar_rs)) |
||
629 | return NULL; |
||
630 | else { |
||
631 | if (1 == count($ar_rs)) |
||
632 | return array_pop($ar_rs); |
||
633 | else |
||
634 | return $ar_rs; |
||
635 | } |
||
636 | } // end of func GetDataByPk |
||
637 | |||
638 | |||
639 | /** |
||
640 | * Get table schema |
||
641 | * |
||
642 | * @param string $table |
||
643 | * @param boolean $forcenew Force to retrieve instead of read from cache |
||
644 | * @return array |
||
645 | * @see $aMetaColumn |
||
646 | */ |
||
647 | public function GetMetaColumn ($table, $forcenew = false) { |
||
648 | if (!isset($this->aMetaColumn[$table]) || (true == $forcenew)) { |
||
0 ignored issues
–
show
|
|||
649 | $this->aMetaColumn[$table] = $this->MetaColumns($table); |
||
650 | |||
651 | // Convert columns to native case |
||
652 | $col_name = $this->GetMetaColumnName($table); |
||
653 | // $col_name = array(COLUMN => column), $c is UPPER CASED |
||
654 | foreach ($this->aMetaColumn[$table] as $c => $ar) { |
||
655 | $this->aMetaColumn[$table][$col_name[$c]] = $ar; |
||
656 | unset($this->aMetaColumn[$table][$c]); |
||
657 | } |
||
658 | // Fix: sybase db display timestamp column as varbinary |
||
659 | View Code Duplication | if ($this->IsDbSybase()) { |
|
660 | $s = $this->FindColTs($table); |
||
661 | if (!empty($s)) |
||
662 | $this->aMetaColumn[$table][$s]->type = 'timestamp'; |
||
663 | } |
||
664 | //print_r($this->aMetaColumn); |
||
665 | } |
||
666 | return $this->aMetaColumn[$table]; |
||
667 | } // end of func GetMetaColumn |
||
668 | |||
669 | |||
670 | /** |
||
671 | * Get table column name |
||
672 | * |
||
673 | * @param string $table |
||
674 | * @param boolean $forcenew Force to retrieve instead of read from cache |
||
675 | * @return array |
||
676 | * @see $aMetaColumnName |
||
677 | */ |
||
678 | public function GetMetaColumnName ($table, $forcenew = false) { |
||
679 | View Code Duplication | if (!isset($this->aMetaColumnName[$table]) || (true == $forcenew)) { |
|
0 ignored issues
–
show
|
|||
680 | $this->aMetaColumnName[$table] = $this->MetaColumnNames($table); |
||
681 | } |
||
682 | return $this->aMetaColumnName[$table]; |
||
683 | } // end of func GetMetaColumnName |
||
684 | |||
685 | |||
686 | /** |
||
687 | * Get primary key column of a table |
||
688 | * |
||
689 | * @param string $table |
||
690 | * @param boolean $forcenew Force to retrieve instead of read from cache |
||
691 | * @return mixed Single string value or array when primary key contain multi columns. |
||
692 | * @see $aMetaPrimaryKey |
||
693 | */ |
||
694 | public function GetMetaPrimaryKey ($table, $forcenew = false) { |
||
695 | if (!isset($this->aMetaPrimaryKey[$table]) || (true == $forcenew)) { |
||
0 ignored issues
–
show
|
|||
696 | // Find using Adodb first |
||
697 | $ar = $this->MetaPrimaryKeys($table); |
||
698 | if (false == $ar || empty($ar)) { |
||
699 | // Adodb not support, find by hand |
||
700 | // Sybase |
||
701 | // keys1、keys2、keys3的描述不清,应该是: |
||
702 | // select name ,keycnt |
||
703 | // ,index_col(YourTableName,indid,1) --主键中的第一列 |
||
704 | // ,index_col(YourTableName,indid,2) --主键中的第二列,如果有的话 |
||
705 | // from sysindexes |
||
706 | // where status & 2048=2048 |
||
707 | // and id=object_id(YourTableName) |
||
708 | // 主键涉及的列的数量在keycnt中。如果主键索引不是簇集索引(由status中的0x10位决定)的话,则为keycnt-1。 |
||
709 | // http://topic.csdn.net/t/20030117/17/1369396.html |
||
710 | // 根据这种方法,目前好像只能用于主键包含三个以下字段的情况? |
||
711 | // 已测试过主键包含两个字段的情况下能取出来 |
||
712 | /* |
||
713 | select name, keycnt, index_col('sgqyjbqk', indid, 1) |
||
714 | , index_col('sgqyjbqk', indid, 2) |
||
715 | , index_col('sgqyjbqk', indid, 3) |
||
716 | from sysindexes |
||
717 | where status & 2048 = 2048 |
||
718 | and id = object_id('sgqyjbqk') |
||
719 | */ |
||
720 | View Code Duplication | if ($this->IsDbSybase()) { |
|
721 | $rs = $this->PExecuteGenSql(array( |
||
722 | 'select' => array( |
||
723 | 'name' => 'a.name', |
||
724 | 'keycnt' => 'a.keycnt', |
||
725 | 'k1' => "index_col('$table', indid, 1)", |
||
726 | 'k2' => "index_col('$table', indid, 2)", |
||
727 | 'k3' => "index_col('$table', indid, 3)", |
||
728 | ), |
||
729 | 'from' => array( |
||
730 | 'a' => 'sysindexes', |
||
731 | 'b' => 'sysobjects', |
||
732 | ), |
||
733 | 'where' => array( |
||
734 | 'a.status & 2048 = 2048 ', |
||
735 | "b.name = '$table'", |
||
736 | "a.id = b.id" |
||
737 | ) |
||
738 | )); |
||
739 | if (true == $rs && 0 < $rs->RowCount()) { |
||
740 | // Got |
||
741 | $ar = array($rs->fields['k1']); |
||
742 | if (!empty($rs->fields['k2'])) |
||
743 | $ar[] = $rs->fields['k2']; |
||
744 | if (!empty($rs->fields['k3'])) |
||
745 | $ar[] = $rs->fields['k3']; |
||
746 | } |
||
747 | else { |
||
748 | // Table have no primary key |
||
749 | $ar = ''; |
||
750 | } |
||
751 | } |
||
752 | } |
||
753 | |||
754 | // Convert columns to native case |
||
755 | if (!empty($ar)) { |
||
756 | $col_name = $this->GetMetaColumnName($table); |
||
757 | // $col_name = array(COLUMN => column), $c is UPPER CASED |
||
758 | foreach ($ar as $idx => &$col) { |
||
759 | if ($col != $col_name[strtoupper($col)]) { |
||
760 | unset($ar[$idx]); |
||
761 | $ar[] = $col_name[strtoupper($col)]; |
||
762 | } |
||
763 | } |
||
764 | unset($col); |
||
765 | } |
||
766 | |||
767 | if (is_array($ar) && 1 == count($ar)) |
||
768 | // Only 1 primary key column |
||
769 | $ar = $ar[0]; |
||
770 | |||
771 | // Set to cache |
||
772 | if (!empty($ar)) |
||
773 | $this->aMetaPrimaryKey[$table] = $ar; |
||
774 | } |
||
775 | if (isset($this->aMetaPrimaryKey[$table])) |
||
776 | return $this->aMetaPrimaryKey[$table]; |
||
777 | else |
||
778 | return ''; |
||
779 | } // end of func GetMetaPrimaryKey |
||
780 | |||
781 | |||
782 | /** |
||
783 | * Get rows count by condition user given |
||
784 | * |
||
785 | * @param string $tbl |
||
786 | * @param string $cond Condition, can be where, having etc, raw sql string. |
||
787 | * @return int -1: error/N >= 0: number of rows |
||
788 | */ |
||
789 | View Code Duplication | public function GetRowCount ($tbl, $cond = '') { |
|
790 | $rs = $this->PExecute($this->GenSql(array( |
||
791 | 'SELECT' => array('c' => 'count(1)'), |
||
792 | 'FROM' => $tbl, |
||
793 | )) . ' ' . $cond); |
||
794 | if (false == $rs || 0 != $this->ErrorNo() |
||
795 | || 0 == $rs->RowCount()) |
||
796 | // Execute error |
||
797 | return -1; |
||
798 | else |
||
799 | return $rs->fields['c']; |
||
800 | } // end of func GetRowCount |
||
801 | |||
802 | |||
803 | /** |
||
804 | * Get delimiter between SQL for various db |
||
805 | * |
||
806 | * @return string |
||
807 | */ |
||
808 | public function GetSqlDelimiter () { |
||
809 | if ($this->IsDbMysql()) |
||
810 | return ";\n"; |
||
811 | elseif ($this->IsDbSybase()) |
||
812 | return "\n"; |
||
813 | else { |
||
814 | $this->Log('GetSqlDelimiter() for this kind of db not implement.' |
||
815 | , 5); |
||
816 | return "\n"; |
||
817 | } |
||
818 | } // end of func GetSqlDelimiter |
||
819 | |||
820 | |||
821 | /** |
||
822 | * Get SQL: begin transaction |
||
823 | * |
||
824 | * @return string |
||
825 | */ |
||
826 | public function GetSqlTransBegin () { |
||
827 | if ($this->IsDbMysql()) |
||
828 | return 'START TRANSACTION' . $this->GetSqlDelimiter(); |
||
829 | else |
||
830 | return 'BEGIN TRANSACTION' . $this->GetSqlDelimiter(); |
||
831 | } // end of func GetSqlTransBegin |
||
832 | |||
833 | |||
834 | /** |
||
835 | * Get SQL: commit transaction |
||
836 | * |
||
837 | * @return string |
||
838 | */ |
||
839 | public function GetSqlTransCommit () { |
||
840 | return 'COMMIT' . $this->GetSqlDelimiter(); |
||
841 | } // end of func GetSqlTransCommit |
||
842 | |||
843 | |||
844 | /** |
||
845 | * Get SQL: rollback transaction |
||
846 | * |
||
847 | * @return string |
||
848 | */ |
||
849 | public function GetSqlTransRollback () { |
||
850 | return 'ROLLBACK' . $this->GetSqlDelimiter(); |
||
851 | } // end of func GetSqlTransRollback |
||
852 | |||
853 | |||
854 | /** |
||
855 | * If current db is a mysql db. |
||
856 | * |
||
857 | * @return boolean |
||
858 | */ |
||
859 | public function IsDbMysql () { |
||
860 | return ('mysql' == substr($this->__conn->databaseType, 0, 5)); |
||
861 | } // end of func IsDbMysql |
||
862 | |||
863 | |||
864 | /** |
||
865 | * If current db is a sybase db. |
||
866 | * |
||
867 | * @return boolean |
||
868 | */ |
||
869 | public function IsDbSybase () { |
||
870 | return ('sybase' == substr($this->aDbProfile['type'], 0, 6)) || |
||
871 | ('pdo_sybase' == substr($this->aDbProfile['type'], 0, 10)); |
||
872 | } |
||
873 | |||
874 | |||
875 | /** |
||
876 | * Is timestamp column's value is unique |
||
877 | * |
||
878 | * @return boolean |
||
879 | */ |
||
880 | public function IsTsUnique () { |
||
881 | if ('sybase' == $this->IsDbSybase()) |
||
882 | return true; |
||
883 | else |
||
884 | // Mysql |
||
885 | return false; |
||
886 | } // end of func IsTsUnique |
||
887 | |||
888 | |||
889 | /** |
||
890 | * Prepare and execute sql |
||
891 | * |
||
892 | * @param string $sql |
||
893 | * @param array $inputarr Optional parameters in sql |
||
894 | * @return object |
||
895 | */ |
||
896 | public function PExecute ($sql, $inputarr = false) { |
||
897 | $stmt = $this->Prepare($sql); |
||
898 | $this->BeginTrans(); |
||
899 | $rs = $this->Execute($stmt, $inputarr); |
||
900 | View Code Duplication | if (0 != $this->ErrorNo()) { |
|
901 | // Log to error log file |
||
902 | error_log('ErrorNo: ' . $this->ErrorNo() |
||
903 | . "\nErrorMsg: " . $this->ErrorMsg() |
||
904 | ); |
||
905 | $this->RollbackTrans(); |
||
906 | return -1; |
||
907 | } |
||
908 | $this->CommitTrans(); |
||
909 | return $rs; |
||
910 | } // end of PExecute |
||
911 | |||
912 | |||
913 | /** |
||
914 | * Generate, prepare and exec SQL |
||
915 | * |
||
916 | * @param array $ar_sql Same as GenSql() |
||
917 | * @param array $inputarr Optional parameters in sql |
||
918 | * @return object |
||
919 | * @see GenSql() |
||
920 | */ |
||
921 | public function PExecuteGenSql ($ar_sql, $inputarr = false) { |
||
922 | return $this->PExecute($this->GenSqlPrepare($ar_sql), $inputarr); |
||
923 | } // end of func PExecuteGenSql |
||
924 | |||
925 | |||
926 | /** |
||
927 | * Smarty quote string in sql, by check columns type |
||
928 | * |
||
929 | * @param string $table |
||
930 | * @param string $column |
||
931 | * @param mixed $val |
||
932 | * @return string |
||
933 | */ |
||
934 | View Code Duplication | public function QuoteValue ($table, $column, $val) { |
|
935 | $this->GetMetaColumn($table); |
||
936 | if (!isset($this->aMetaColumn[$table][$column]->type)) { |
||
937 | error_log("Column to quote not exists($table.$column).\n"); |
||
938 | // Return quoted value for safety |
||
939 | $val = stripslashes($val); |
||
940 | return $this->qstr($val, false); |
||
941 | } |
||
942 | |||
943 | //print_r($this->aMetaColumn[$table][$column]); |
||
944 | $type = $this->aMetaColumn[$table][$column]->type; |
||
945 | //var_dump($type); |
||
946 | if (in_array($type, array( |
||
947 | 'bigint', |
||
948 | 'bit', |
||
949 | 'decimal', |
||
950 | 'double', |
||
951 | 'float', |
||
952 | 'int', |
||
953 | 'intn', // Sybase - tinyint |
||
954 | 'mediumint', |
||
955 | 'numeric', |
||
956 | 'numericn', // Sybase - numeric |
||
957 | 'real', |
||
958 | 'smallint', |
||
959 | 'tinyint', |
||
960 | ))) |
||
961 | // Need not quote, output directly |
||
962 | return $val; |
||
963 | // Sybase timestamp |
||
964 | //elseif ($this->IsDbSybase() && 'varbinary' == $type && 'timestamp' == $column) |
||
965 | elseif ($this->IsDbSybase() && 'timestamp' == $type) |
||
966 | return '0x' . $val; |
||
967 | else { |
||
968 | // Need quote, use db's quote method |
||
969 | $val = stripslashes($val); |
||
970 | return $this->qstr($val, false); |
||
971 | } |
||
972 | } // end of func GenSqlQuote |
||
973 | |||
974 | |||
975 | /** |
||
976 | * If a table exists in db ? |
||
977 | * |
||
978 | * @param string $tbl |
||
979 | * @return boolean |
||
980 | */ |
||
981 | View Code Duplication | public function TblExists ($tbl) { |
|
982 | if ($this->IsDbSybase()) { |
||
983 | $sql = "select count(1) as c from sysobjects where name = '$tbl' and type = 'U'"; |
||
984 | $rs = $this->Execute($sql); |
||
985 | return (0 != $rs->fields['c']); |
||
986 | } |
||
987 | elseif ($this->IsDbMysql()) { |
||
988 | $sql = "SHOW TABLES LIKE '$tbl'"; |
||
989 | $rs = $this->Execute($sql); |
||
990 | return (0 != $rs->RowCount()); |
||
991 | } |
||
992 | else { |
||
993 | // :NOTICE: Un-tested method |
||
994 | $sql = "select 1 from $tbl"; |
||
995 | $rs = $this->Execute($sql); |
||
996 | return (0 == $this->ErrorNo()); |
||
997 | } |
||
998 | } // end of func TblExists |
||
999 | |||
1000 | |||
1001 | /** |
||
1002 | * Smart write data row(s) to table |
||
1003 | * |
||
1004 | * Will auto check row existence, and decide to use INSERT or UPDATE, |
||
1005 | * so PRIMARY KEY column must include in $data array. |
||
1006 | * Also, table must have primary key defined. |
||
1007 | * @param string $tbl Table which rows to write to |
||
1008 | * @param array $data Row(s) data, only one row(1-dim array, index is column name) |
||
1009 | * or some rows(2-dim array, index layer 1 MUST be number and |
||
1010 | * will not write to db). |
||
1011 | * @param string $mode A auto detect/U update/I insert, ignore case. |
||
1012 | * If you assign some rows, it's better not to set this to 0, |
||
1013 | * because it will only detect by the FIRST row data. |
||
1014 | * @return int Number of inserted or updated rows, -1 means some error, |
||
1015 | * 0 and upper are normal result. |
||
1016 | */ |
||
1017 | public function Write ($tbl, $data, $mode = 'A') { |
||
1018 | // Find primary key column first |
||
1019 | $pk = $this->GetMetaPrimaryKey($tbl); |
||
1020 | |||
1021 | // Convert single row data to multi row mode |
||
1022 | if (!isset($data[0])) |
||
1023 | $data = array(0 => $data); |
||
1024 | // Convert primary key to array if it's single string now |
||
1025 | if (!is_array($pk)) |
||
1026 | $pk = array(0 => $pk); |
||
1027 | |||
1028 | // Columns in $data |
||
1029 | $ar_cols = array_keys($data[0]); |
||
1030 | // Check if primary key is assigned in $data |
||
1031 | $b_data_ok = true; |
||
1032 | foreach ($pk as $key) |
||
1033 | if (!in_array($key, $ar_cols)) |
||
1034 | $b_data_ok = false; |
||
1035 | // If no primary key column in $data, return -1 |
||
1036 | if (false == $b_data_ok) |
||
0 ignored issues
–
show
|
|||
1037 | return -1; |
||
1038 | |||
1039 | $mode = strtoupper($mode); |
||
1040 | // Consider mode if user not assigned |
||
1041 | View Code Duplication | if ('A' == $mode) { |
|
1042 | $s_where = ' WHERE '; |
||
1043 | foreach ($pk as $key) |
||
1044 | $s_where .= " $key = " . $this->QuoteValue($tbl, $key, $data[0][$key]) |
||
1045 | . ' AND '; |
||
1046 | $s_where = substr($s_where, 0, strlen($s_where) - 5); |
||
1047 | if (0 < $this->GetRowCount($tbl, $s_where)) |
||
1048 | $mode = 'U'; |
||
1049 | else |
||
1050 | $mode = 'I'; |
||
1051 | } |
||
1052 | |||
1053 | // Do batch update or insert, prepare stmt first |
||
1054 | $sql = ''; |
||
1055 | View Code Duplication | if ('U' == $mode) { |
|
1056 | $ar_conf = array( |
||
1057 | 'UPDATE' => $tbl, |
||
1058 | 'LIMIT' => 1, |
||
1059 | ); |
||
1060 | foreach ($pk as $key) { |
||
1061 | // Primary key need remove from 'SET' clause |
||
1062 | // Actual value will assign later, do quote then. |
||
1063 | // :NOTICE: Remember to put pk data to end of row data when assign, |
||
1064 | // because where clause is after set clause. |
||
1065 | $ar_conf['WHERE'][] = "$key = " |
||
1066 | . $this->Param($key); |
||
1067 | unset($ar_cols[array_search($key, $ar_cols)]); |
||
1068 | } |
||
1069 | // Convert array $ar_cols with to prepare param |
||
1070 | $ar_set = array(); |
||
1071 | foreach ($ar_cols as $key) |
||
1072 | $ar_set[$key] = $this->Param($key); |
||
1073 | // Fin, assign 'SET' clause |
||
1074 | $ar_conf['SET'] = $ar_set; |
||
1075 | } |
||
1076 | elseif ('I' == $mode) { |
||
1077 | $ar_set = array(); |
||
1078 | foreach ($ar_cols as $key) { |
||
1079 | $ar_set[$key] = $this->Param($key); |
||
1080 | } |
||
1081 | $ar_conf = array( |
||
1082 | 'INSERT' => $tbl, |
||
1083 | 'VALUES' => $ar_set, |
||
1084 | ); |
||
1085 | } |
||
1086 | $sql = $this->GenSqlPrepare($ar_conf); |
||
1087 | |||
1088 | /* Treat moved to SqlGenerator |
||
1089 | //$sql = $this->GenSql($ar_conf); |
||
1090 | // Remove duplicate ' in sql add by SqlGenerator, |
||
1091 | // Execute after Prepare will auto recoginize variant type and quote, |
||
1092 | // but notice, it's VAR TYPE and NOT DB COLUMN TYPE. |
||
1093 | // replaceQuote: The string used to escape quotes. Eg. double single-quotes for |
||
1094 | // Microsoft SQL, and backslash-quote for MySQL. Used by qstr. |
||
1095 | if ("''" == $this->replaceQuote) |
||
1096 | $s_quote = "'"; |
||
1097 | else |
||
1098 | $s_quote = $this->replaceQuote; |
||
1099 | $sql = preg_replace( |
||
1100 | "/ {$s_quote}([\?\:\w\-_]+){$s_quote}([, ])/i", |
||
1101 | " $1$2", $sql); |
||
1102 | */ |
||
1103 | |||
1104 | if (!empty($sql)) { |
||
1105 | // Do prepare |
||
1106 | $stmt = $this->Prepare($sql); |
||
1107 | // Execute |
||
1108 | View Code Duplication | if ('U' == $mode) { |
|
1109 | foreach ($data as &$row) { |
||
1110 | // Change pk's value position when update mode |
||
1111 | foreach ($pk as $key) { |
||
1112 | $v = $row[$key]; |
||
1113 | unset($row[$key]); |
||
1114 | $row[$key] = $v; |
||
1115 | } |
||
1116 | } |
||
1117 | unset($row); |
||
1118 | } |
||
1119 | // Now, finanly, actual write data |
||
1120 | // Auto convert encoding ? |
||
1121 | // Use of prepare we must convert $data manually, because $data is not sql. |
||
1122 | $this->EncodingConvert($data); |
||
1123 | try { |
||
1124 | $this->Execute($stmt, $data); |
||
1125 | } |
||
1126 | catch (Exception $e) { |
||
1127 | // Show error message ? |
||
1128 | $this->RollbackTrans(); |
||
1129 | return -1; |
||
1130 | } |
||
1131 | // Any error ? |
||
1132 | View Code Duplication | if (0 != $this->ErrorNo()) { |
|
1133 | // Log to error log file |
||
1134 | error_log('ErrorNo: ' . $this->ErrorNo() |
||
1135 | . "\nErrorMsg: " . $this->ErrorMsg() |
||
1136 | ); |
||
1137 | $this->RollbackTrans(); |
||
1138 | return -1; |
||
1139 | } |
||
1140 | else { |
||
1141 | $this->CommitTrans(); |
||
1142 | return count($data); |
||
1143 | } |
||
1144 | } |
||
1145 | else |
||
1146 | return -1; |
||
1147 | } // end of func Write |
||
1148 | |||
1149 | |||
1150 | } // end of class Adodb |
||
1151 | ?> |
||
1152 |
When comparing two booleans, it is generally considered safer to use the strict comparison operator.