1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* @package fwolflib |
4
|
|
|
* @subpackage class |
5
|
|
|
* @copyright Copyright 2006-2009, Fwolf |
6
|
|
|
* @author Fwolf <[email protected]> |
7
|
|
|
*/ |
8
|
|
|
|
9
|
|
|
|
10
|
|
|
require_once(dirname(__FILE__) . '/fwolflib.php'); |
11
|
|
|
require_once(FWOLFLIB . 'class/adodb.php'); |
12
|
|
|
require_once(FWOLFLIB . 'func/env.php'); |
13
|
|
|
|
14
|
|
|
|
15
|
|
|
//// From fwolflib r12, Don't modify it outside fwolflib ! //// |
16
|
|
|
|
17
|
|
|
/** |
18
|
|
|
* Database mantance & update tools |
19
|
|
|
* Used in develop or sync multi databases' structure |
20
|
|
|
* |
21
|
|
|
* It use a table to 'remember' every update done or to be done to db |
22
|
|
|
* and, all update to db here is defined to 'sql' lang format, |
23
|
|
|
* I hope this can meets all my needs. |
24
|
|
|
* |
25
|
|
|
* All updates MUST be done by step order, so when a last-update-id |
26
|
|
|
* is set, it's consided all updates before last-update-id is done. |
27
|
|
|
* |
28
|
|
|
* Additional tools do similar task: |
29
|
|
|
* http://xml2ddl.berlios.de/ |
30
|
|
|
* |
31
|
|
|
* @deprecated Use Fwlib\Db\SyncDbSchema |
32
|
|
|
* @package fwolflib |
33
|
|
|
* @subpackage class |
34
|
|
|
* @copyright Copyright 2006-2009, Fwolf |
35
|
|
|
* @author Fwolf <[email protected]> |
36
|
|
|
* @since 2006-12-10 |
37
|
|
|
* @version $Id$ |
38
|
|
|
*/ |
39
|
|
|
class DbUpdater extends Fwolflib { |
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* Db server information array |
42
|
|
|
* Array item: type, host, user, pass, name. |
43
|
|
|
* @var array |
44
|
|
|
*/ |
45
|
|
|
private $aServer = array(); |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* Last update id |
49
|
|
|
* @var int |
50
|
|
|
*/ |
51
|
|
|
public $iLastId = 0; |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* Last done update id |
55
|
|
|
* @var int |
56
|
|
|
*/ |
57
|
|
|
public $iLastDoneId = 0; |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* Charset of database |
61
|
|
|
* If charset of db diff from os, do convert when execute sql. |
62
|
|
|
* @var string |
63
|
|
|
*/ |
64
|
|
|
public $sCharsetDb = ''; |
65
|
|
|
|
66
|
|
|
/** |
67
|
|
|
* Charset of operation system |
68
|
|
|
* @var string |
69
|
|
|
* @see $sCharsetDb |
70
|
|
|
*/ |
71
|
|
|
public $sCharsetOs = ''; |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* Summary text |
75
|
|
|
* @var string |
76
|
|
|
*/ |
77
|
|
|
public $sSummary = ''; |
78
|
|
|
|
79
|
|
|
/** |
80
|
|
|
* Table to save modifications and logs |
81
|
|
|
* |
82
|
|
|
* If u want to change log table name in use(had log some sql already) |
83
|
|
|
* remember to rename table in database also. |
84
|
|
|
* @var string |
85
|
|
|
*/ |
86
|
|
|
public $sTblLog = 'log_dbupdater'; |
87
|
|
|
|
88
|
|
|
|
89
|
|
|
/** |
90
|
|
|
* Db connection object |
91
|
|
|
* @var object |
92
|
|
|
*/ |
93
|
|
|
public $oDb; |
94
|
|
|
|
95
|
|
|
/** |
96
|
|
|
* Construct function |
97
|
|
|
* @access public |
98
|
|
|
* @param array $server Db server information |
|
|
|
|
99
|
|
|
*/ |
100
|
|
|
function __construct($dbserver=array()) |
|
|
|
|
101
|
|
|
{ |
102
|
|
|
if (!empty($dbserver)) |
103
|
|
|
$this->SetDatabase($dbserver); |
104
|
|
|
|
105
|
|
|
// Check and install log table |
106
|
|
|
$this->CheckLogTbl(); |
107
|
|
|
} // end of func construct |
108
|
|
|
|
109
|
|
|
|
110
|
|
|
/** |
111
|
|
|
* Check and install log table if not exists |
112
|
|
|
* @access public |
113
|
|
|
*/ |
114
|
|
|
public function CheckLogTbl() |
115
|
|
|
{ |
116
|
|
|
$logtbl_not_exists = false; |
117
|
|
|
if ('sybase' == $this->aServer['type'] || |
118
|
|
|
'sybase_ase' == $this->aServer['type']) |
119
|
|
|
{ |
120
|
|
|
$sql = "select count(1) as c from sysobjects where name = '$this->sTblLog' and type = 'U'"; |
121
|
|
|
$rs = $this->oDb->Execute($sql); |
122
|
|
|
if (0 == $rs->fields['c']) |
123
|
|
|
$logtbl_not_exists = true; |
124
|
|
|
} |
125
|
|
|
elseif ('mysql' == $this->aServer['type'] || |
126
|
|
|
'mysqli' == $this->aServer['type']) |
127
|
|
|
{ |
128
|
|
|
$sql = "SHOW TABLES LIKE '$this->sTblLog'"; |
129
|
|
|
$rs = $this->oDb->Execute($sql); |
130
|
|
|
if (0 == $rs->RowCount()) |
131
|
|
|
$logtbl_not_exists = true; |
132
|
|
|
} |
133
|
|
|
else |
134
|
|
|
{ |
135
|
|
|
$sql = "select 1 from $this->sTblLog"; |
136
|
|
|
$rs = $this->oDb->Execute($sql); |
|
|
|
|
137
|
|
|
if (0 == $this->oDb->ErrorNo()) |
138
|
|
|
$logtbl_not_exists = true; |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
if (true == $logtbl_not_exists) |
|
|
|
|
142
|
|
|
{ |
143
|
|
|
// Table doesn't exist, create it |
144
|
|
|
// 'sql' is a reserved word, so sqtext is used. |
145
|
|
|
// SQL for Create table diffs from several db |
146
|
|
|
if ('sybase' == $this->aServer['type'] || 'sybase_ase' == $this->aServer['type']) |
147
|
|
|
$sql = " |
148
|
|
|
CREATE TABLE $this->sTblLog ( |
149
|
|
|
id numeric(8) NOT NULL, |
150
|
|
|
comment varchar(200) NOT NULL, |
151
|
|
|
done int default 0, -- 0:not do, -1:error, 1:done ok |
152
|
|
|
sqltext text, |
153
|
|
|
ts timestamp NOT NULL, |
154
|
|
|
constraint PK_$this->sTblLog PRIMARY KEY (id) |
155
|
|
|
) |
156
|
|
|
"; |
157
|
|
|
else |
158
|
|
|
// :DELETED: ) ENGINE=MyISAM AUTO_INCREMENT=1 |
159
|
|
|
$sql = " |
160
|
|
|
CREATE TABLE $this->sTblLog ( |
161
|
|
|
id int(8) NOT NULL auto_increment, |
162
|
|
|
comment varchar(200) NOT NULL, |
163
|
|
|
done tinyint(1) default 0, -- 0:not do, -1:error, 1:done ok |
164
|
|
|
sqltext text, |
165
|
|
|
ts timestamp NOT NULL default CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
166
|
|
|
PRIMARY KEY (id) |
167
|
|
|
) AUTO_INCREMENT=1 |
168
|
|
|
"; |
169
|
|
|
$this->oDb->Execute($sql); |
170
|
|
|
if (0 < $this->oDb->ErrorNo()) |
171
|
|
|
{ |
172
|
|
|
$this->Log($this->oDb->ErrorNo() . ' - ' . $this->oDb->ErrorMsg() . "\n"); |
173
|
|
|
$this->Summary(); |
174
|
|
|
die("Log table $this->sTblLog doesn't exists and create fail.\n"); |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
// Log table create information |
178
|
|
|
$this->Log("Log table $this->sTblLog doesn't exists, create it, done.\n"); |
179
|
|
|
} |
180
|
|
|
else |
181
|
|
|
{ |
182
|
|
|
// Log table exist information |
183
|
|
|
$this->Log("Log table $this->sTblLog already exists.\n"); |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
// Get last-done-id for later usage |
187
|
|
|
$this->GetLastDoneId(); |
188
|
|
|
} // end of func CheckLogTbl |
189
|
|
|
|
190
|
|
|
|
191
|
|
|
/** |
192
|
|
|
* 获得数据库连接 |
193
|
|
|
* @param array $server |
194
|
|
|
* @return object |
195
|
|
|
*/ |
196
|
|
|
private function &DbConn($server) |
197
|
|
|
{ |
198
|
|
|
$conn = new Adodb($server); |
|
|
|
|
199
|
|
|
$conn->Connect(); |
200
|
|
|
return $conn; |
201
|
|
|
} // end of func DbConn |
202
|
|
|
|
203
|
|
|
|
204
|
|
|
/** |
205
|
|
|
* Del error record when last done. |
206
|
|
|
* |
207
|
|
|
* So it can rewrite/update these record in db. |
208
|
|
|
* Only del failed sql is not enough, |
209
|
|
|
* you need del all sql start from the failed ONE. |
210
|
|
|
*/ |
211
|
|
|
public function DelErrorSql() { |
212
|
|
|
$sql = "SELECT id FROM {$this->sTblLog} WHERE done=-1 ORDER BY id ASC LIMIT 1"; |
213
|
|
|
$rs = $this->oDb->Execute($sql); |
214
|
|
|
if (!empty($rs) && (0 < $rs->RecordCount())) { |
215
|
|
|
// Del sql after it |
216
|
|
|
$id = $rs->fields['id']; |
217
|
|
|
$sql = "DELETE FROM {$this->sTblLog} WHERE id >= $id"; |
218
|
|
|
$rs = $this->oDb->Execute($sql); |
|
|
|
|
219
|
|
|
$i = $this->oDb->Affected_Rows(); |
220
|
|
|
// $i should > 0 |
221
|
|
|
$this->Log("Clear $i sql start from failed sql $id.\n"); |
222
|
|
|
} |
223
|
|
|
} // end of func DelErrorSql |
224
|
|
|
|
225
|
|
|
|
226
|
|
|
/** |
227
|
|
|
* Do updates according to update-records in log table |
228
|
|
|
*/ |
229
|
|
|
public function DoUpdate() |
230
|
|
|
{ |
231
|
|
|
$sql = "SELECT id, sqltext FROM $this->sTblLog where done<>1 order by id asc"; |
232
|
|
|
$rs = $this->oDb->Execute($sql); |
233
|
|
|
if ($rs->EOF) |
234
|
|
|
{ |
235
|
|
|
$this->Log("No un-done update to do.\n"); |
236
|
|
|
} |
237
|
|
|
else |
238
|
|
|
{ |
239
|
|
|
// Do these updates one by one |
240
|
|
|
$ar = $rs->GetArray(); |
241
|
|
|
$i_total = count($ar); |
242
|
|
|
for ($i=0; $i<$i_total; $i++) |
243
|
|
|
{ |
244
|
|
|
$id = $ar[$i]['id']; |
245
|
|
|
$sqltext = stripslashes($ar[$i]['sqltext']); |
246
|
|
|
// Do on update |
247
|
|
|
// Cancel transaction because some ddl sql can't use in trans. |
248
|
|
|
//$this->oDb->StartTrans(); |
249
|
|
|
$this->oDb->Execute($sqltext); |
250
|
|
|
|
251
|
|
|
// Bad sybase support, select db will got errormsg |
252
|
|
|
// Avoid sybase errormsg like: Changed database context to 'jygl' |
253
|
|
|
if ((0 == strlen($this->oDb->ErrorMsg()) && 0 == $this->oDb->ErrorNo()) || ('Changed database context t' == substr($this->oDb->ErrorMsg(), 0, 26))) |
254
|
|
|
{ |
255
|
|
|
$this->Log("Update id $id done successful.\n"); |
256
|
|
|
$this->SetUpdateDone($id, 1); |
257
|
|
|
//$this->oDb->CompleteTrans(); |
258
|
|
|
} |
259
|
|
|
else |
260
|
|
|
{ |
261
|
|
|
$this->Log("Update id $id done failed.\n"); |
262
|
|
|
$this->Log($this->oDb->ErrorNo() . '-' . $this->oDb->ErrorMsg() . "\n"); |
263
|
|
|
//$this->oDb->CompleteTrans(); |
264
|
|
|
$this->SetUpdateDone($id, -1); |
265
|
|
|
$this->Summary(true); |
266
|
|
|
die("Doing update aborted because of failed.\n"); |
267
|
|
|
} |
268
|
|
|
} |
269
|
|
|
// Log |
270
|
|
|
$this->Log("Total $i/$i_total updates done.\n"); |
271
|
|
|
} |
272
|
|
|
return true; |
273
|
|
|
} // end of func DoUpdate |
274
|
|
|
|
275
|
|
|
|
276
|
|
|
/** |
277
|
|
|
* Get last update id, whether done or not |
278
|
|
|
* @access public |
279
|
|
|
* @return int |
280
|
|
|
*/ |
281
|
|
View Code Duplication |
public function GetLastId() |
|
|
|
|
282
|
|
|
{ |
283
|
|
|
$sql = "select id from $this->sTblLog order by id desc"; |
284
|
|
|
$rs = $this->oDb->SelectLimit($sql, 1); |
285
|
|
|
if ($rs->EOF) |
286
|
|
|
$id = 0; |
287
|
|
|
else |
288
|
|
|
$id = $rs->fields['id']; |
289
|
|
|
$this->iLastId = $id; |
290
|
|
|
return $id; |
291
|
|
|
} // end of func GetLastId |
292
|
|
|
|
293
|
|
|
|
294
|
|
|
/** |
295
|
|
|
* Get last done update id |
296
|
|
|
* @access public |
297
|
|
|
* @return int |
298
|
|
|
*/ |
299
|
|
View Code Duplication |
public function GetLastDoneId() |
|
|
|
|
300
|
|
|
{ |
301
|
|
|
$sql = "select id from $this->sTblLog where done=1 order by id desc"; |
302
|
|
|
$rs = $this->oDb->SelectLimit($sql, 1); |
303
|
|
|
if ($rs->EOF) |
304
|
|
|
{ |
305
|
|
|
$id = 0; |
306
|
|
|
} |
307
|
|
|
else |
308
|
|
|
{ |
309
|
|
|
$id = $rs->fields['id']; |
310
|
|
|
} |
311
|
|
|
$this->iLastDoneId = $id; |
312
|
|
|
return $id; |
313
|
|
|
} // end of func GetLastDoneId |
314
|
|
|
|
315
|
|
|
|
316
|
|
|
/** |
317
|
|
|
* Return if an update is already done |
318
|
|
|
* Use cached $iLastDoneId if it's non-zero, doesn't retrieve id from db. |
319
|
|
|
* @access public |
320
|
|
|
* @param int $id |
321
|
|
|
* @return boolean |
322
|
|
|
* @see $iLastDoneId |
323
|
|
|
* @see GetLastDoneId() |
324
|
|
|
*/ |
325
|
|
|
public function IfDone($id) |
326
|
|
|
{ |
327
|
|
|
if (0 == $this->iLastDoneId) |
328
|
|
|
$this->GetLastDoneId(); |
329
|
|
|
return ($id <= $this->iLastDoneId); |
330
|
|
|
} // end of func IfDone |
331
|
|
|
|
332
|
|
|
|
333
|
|
|
/* |
334
|
|
|
* Save log |
335
|
|
|
* @access private |
336
|
|
|
* @param string $log |
337
|
|
|
*/ |
338
|
|
|
public function Log($log) |
339
|
|
|
{ |
340
|
|
|
$this->sSummary .= $log; |
341
|
|
|
} // end of func Log |
342
|
|
|
|
343
|
|
|
|
344
|
|
|
/** |
345
|
|
|
* Accept database information from outside class |
346
|
|
|
* Didnot validate data send in. |
347
|
|
|
* And connect to db after store infomation. |
348
|
|
|
* @access public |
349
|
|
|
* @var array $server array items: type, host, user, pass, name |
350
|
|
|
*/ |
351
|
|
View Code Duplication |
public function SetDatabase($server) |
|
|
|
|
352
|
|
|
{ |
353
|
|
|
if (!empty($server) && is_array($server)) |
354
|
|
|
{ |
355
|
|
|
$this->aServer = $server; |
356
|
|
|
$this->oDb = &$this->DbConn($this->aServer); |
357
|
|
|
} |
358
|
|
|
} // end of func SetDatabase |
359
|
|
|
|
360
|
|
|
|
361
|
|
|
/** |
362
|
|
|
* Set a update step, but doesn't execute it |
363
|
|
|
* Use $iLastId if it's non-zero. |
364
|
|
|
* @access public |
365
|
|
|
* @param int $id |
366
|
|
|
* @param string $comment |
367
|
|
|
* @param string $sqltext |
368
|
|
|
* @see $iLastId |
369
|
|
|
*/ |
370
|
|
|
public function SetUpdate($id, $comment, $sqltext) |
371
|
|
|
{ |
372
|
|
|
if (0 == $this->iLastId) |
373
|
|
|
$this->GetLastId(); |
374
|
|
|
// Update can't be recoverd, so only worked when $id > $iLastId |
375
|
|
|
// And notice that update id MUST be assigned order ASC. |
376
|
|
|
if ($id > $this->iLastId) |
377
|
|
|
{ |
378
|
|
|
$comment = addslashes($comment); |
379
|
|
|
$sqltext = addslashes($sqltext); |
380
|
|
|
$sql = "INSERT INTO $this->sTblLog (id, comment, sqltext) VALUES ($id, '$comment', '$sqltext')"; |
381
|
|
|
// Check if iconv for sqltext is needed |
382
|
|
|
if ($this->sCharsetDb != $this->sCharsetOs) |
383
|
|
|
$sql = mb_convert_encoding($sql, $this->sCharsetDb, $this->sCharsetOs); |
384
|
|
|
// |
385
|
|
|
$this->oDb->Execute($sql); |
386
|
|
|
if (0 != $this->oDb->ErrorNo()) |
387
|
|
|
{ |
388
|
|
|
$this->Log($this->oDb->ErrorNo() . '-' . $this->oDb->ErrorMsg() . "\n"); |
389
|
|
|
$this->Summary(true); |
390
|
|
|
die("Set update failed.\n"); |
391
|
|
|
} |
392
|
|
|
else |
393
|
|
|
{ |
394
|
|
|
$this->iLastId ++; |
395
|
|
|
$this->Log("Update id $id saved.\n"); |
396
|
|
|
} |
397
|
|
|
} |
398
|
|
|
} // end of func SetUpdate |
399
|
|
|
|
400
|
|
|
|
401
|
|
|
/** |
402
|
|
|
* Set a update record's status done or not or failed |
403
|
|
|
* Didn't validate $id or $status. |
404
|
|
|
* @param int $id Update id |
405
|
|
|
* @param int $status Update id's status(0/1/-1) |
406
|
|
|
*/ |
407
|
|
|
private function SetUpdateDone($id, $status) |
408
|
|
|
{ |
409
|
|
|
if (-1 == $status) |
410
|
|
|
$this->Log("Error when do update $id, {$this->oDb->ErrorNo()}:{$this->oDb->ErrorMsg()}\n"); |
411
|
|
|
$sql = "UPDATE $this->sTblLog set done=$status where id=$id"; |
412
|
|
|
$this->oDb->Execute($sql); |
413
|
|
|
//if (0 == $this->oDb->ErrorNo() && 0 == strlen($this->oDb->ErrorMsg())) |
414
|
|
|
if ((0 == strlen($this->oDb->ErrorMsg()) && 0 == $this->oDb->ErrorNo()) || ('Changed database context t' == substr($this->oDb->ErrorMsg(), 0, 26))) |
415
|
|
|
$this->Log("Update id $id's done is set to $status.\n"); |
416
|
|
|
else |
417
|
|
|
die("Failed when set update id $id's done status.({$this->oDb->ErrorNo()}:{$this->oDb->ErrorMsg()})\n"); |
418
|
|
|
} // end of func SetUpdateDone |
419
|
|
|
|
420
|
|
|
|
421
|
|
|
/** |
422
|
|
|
* Return summary text of the whole backup process |
423
|
|
|
* @param boolean print |
424
|
|
|
* @return string |
425
|
|
|
*/ |
426
|
|
|
public function Summary($print = false) |
427
|
|
|
{ |
428
|
|
|
$s = ''; |
|
|
|
|
429
|
|
|
if (true == IsCli()) |
|
|
|
|
430
|
|
|
$s = $this->sSummary . "\n"; |
431
|
|
|
else |
432
|
|
|
$s = nl2br($this->sSummary); |
433
|
|
|
|
434
|
|
|
if (true == $print) |
|
|
|
|
435
|
|
|
echo $s; |
436
|
|
|
return $s; |
437
|
|
|
} // end of func Summary |
438
|
|
|
|
439
|
|
|
|
440
|
|
|
} // end of class DbUpdater |
441
|
|
|
?> |
|
|
|
|
442
|
|
|
|
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.