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)) { |
|
|
|
|
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)) { |
|
|
|
|
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)) { |
|
|
|
|
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) |
|
|
|
|
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
|
|
|
|
This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.