DbBak2Sql::Log()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
* @package      fwolflib
4
* @copyright    Copyright 2006, Fwolf
5
* @author       Fwolf <[email protected]>
6
*/
7
8
9
require_once(dirname(__FILE__) . '/fwolflib.php');
10
require_once('adodb/adodb.inc.php');
11
12
13
/**
14
* Data backup tool, result is sql format.
15
* Test under sybase 11.92 ok.
16
*
17
* @deprecated   Use Fwlib\Db\DbDataExport
18
* @package    fwolflib
19
* @copyright  Copyright 2006, Fwolf
20
* @author     Fwolf <[email protected]>
21
* @since      2006-11-14
22
* @access     public
23
* @version    $Id$
24
*/
25
class DbBak2Sql extends Fwolflib {
0 ignored issues
show
Deprecated Code introduced by
The class Fwolflib has been deprecated with message: Use classes in Fwlib namespace, see PSR-0/1/2

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.

Loading history...
26
	/**
27
	 * Charset of database
28
	 * If charset of db diff from os, do convert when execute sql.
29
	 * @var	string
30
	 * @access	public
31
	 */
32
	var $mCharsetDb = '';
33
34
	/**
35
	 * Charset of operation system
36
	 * @var string
37
	 * @access	public
38
	 * @see	$mCharsetDb
39
	 */
40
	var $mCharsetOs = '';
41
42
	/**
43
	 * Db connection object
44
	 * @var object
45
	 * @access	private
46
	 */
47
	var $mDb;
48
49
	/**
50
	 * Ignore columns
51
	 * These columns will not be trans to sql when processed.
52
	 * @var	array
53
	 * @access	public
54
	 */
55
	var $mIgnoreColumn = array('lasttime');
56
57
	/**
58
	 * Log file
59
	 * Log file to write, this is only filename, path is $this->mTargetPath
60
	 * @var	string
61
	 * @access	public
62
	 * @see	$mTargetPath
63
	 */
64
	var $mLogFile = 'dbbak2sql.log';
65
66
	/**
67
	 * Db server information array
68
	 * 	Array item: host, user, pass, name, type.
69
	 * @var	array
70
	 * @access	private
71
	 */
72
	var $mServer = array();
73
74
	/**
75
	 * Summary text
76
	 * @var string
77
	 * @access public
78
	 */
79
	var $mSummary = '';
80
81
	/**
82
	 * Tables will be backup
83
	 * Include needed, exclude no needed, this is result.
84
	 * @var	array
85
	 * @access	private
86
	 */
87
	var $mTable = array();
88
89
	/**
90
	 * Tables to be exclude from backup task
91
	 * @var array
92
	 * @access	private
93
	 */
94
	var $mTableExclude = array();
95
96
	/**
97
	 * Table need to be group by some cols when backup
98
	 * Usually used when table contains too much rows
99
	 * @var	array
100
	 * @access	private
101
	 */
102
	var $mTableGroupby = array();
103
104
	/**
105
	 * Tables to be include in backup task
106
	 * If not empty, will only backup tables in this list.
107
	 * @var array
108
	 * @access	private
109
	 */
110
	var $mTableInclude = array();
111
112
	/**
113
	 * Where to save exported sql files.
114
	 * @var	string
115
	 * @access	private
116
	 */
117
	var $mTargetPath = '/tmp/dbbak2sql';
118
119
120
	/**
121
	 * Construct function
122
	 * @access public
123
	 * @param	array	$server	Db server information
124
	 */
125
	function __construct($server=array())
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
126
	{
127
		if (!empty($server))
128
			$this->SetDatabase($server);
129
	} // end of func __construct
130
131
132
	/**
133
	 * Do backup to database
134
	 * @access	public
135
	 */
136
	public function Bak()
137
	{
138
		file_put_contents($this->mTargetPath . '/' . $this->mLogFile, '');
139
		$this->GetTableList();
140
		foreach ($this->mTable as $tbl)
141
			$this->BakTable($tbl);
142
		$this->Summary();
143
	} // end of func Bak
144
145
146
	/**
147
	 * Do backup to a single table
148
	 * @access	private
149
	 * @param	string	$tbl Table name
150
	 */
151
	private function BakTable($tbl)
152
	{
153
		// Clear sql file(sql is save to seperated file, not need clear anymore
154
		//file_put_contents($this->mTargetPath . "/$tbl.sql", '');
155
		$this->Log("Begin to backup $tbl, ");
156
157
		$cols = $this->GetTblFields($tbl);
158
159
		// Split sql to 10000 rows per step
160
		$sql_step = 10000;
161
		$sql_offset = 0;
162
		// Rows and Byte count
163
		$done_rows = 0;
164
		$done_bytes = 0;
165
166
		// Get total rows
167
		$sql = "select count(1) as c from $tbl";
168
		$rs = $this->mDb->Execute($sql);
169
		$rowcount = $rs->fields['c'];
170
		$this->Log("Got $rowcount rows: ");
171
172
		// Write rs to sql
173
		// GetInsertSQL failed for unknown reason, manual generate sql
174
		//$sql = $this->mDb->GetInsertSQL($rs, $cols, false, false);
175
		$bakfile = $this->mTargetPath . "/$tbl.sql";
176
		$sql = "truncate table $tbl;\n";
177
		if (true == $this->NeedIdentityInsert())
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
178
			$sql .= "set identity_insert $tbl on;\n";
179
180
		// Backup by groupby will cause two situation:
181
		// 1. one sql file will contain rows diffs with sql_step.
182
		// 2. sql file saved's number sometimes is not continued.
183
184
		// Groupby rules is converted to where clauses
185
		$ar_where = $this->GroupbyRule2WhereSql($tbl);
186
		while ($sql_offset < $rowcount)
187
		{
188
			$this->Log(".");
189
			// Execute sql
190
			// When ar_where is empty, the loop should be end.
191
			// Or groupby is not used.
192
			if (!empty($ar_where))
193
			{
194
				$s_where = array_shift($ar_where);
195
				$sql_select = "select * from $tbl $s_where";
196
				$rs = $this->mDb->Execute($sql_select);
197
			}
198
			else
199
			{
200
				$sql_select = "select * from $tbl";
201
				// This select sql does not need iconv
202
				//if ($this->mCharsetDb != $this->mCharsetOs)
203
				//	$sql = mb_convert_encoding($sql, $this->mCharsetDb, $this->mCharsetOs);
204
				$rs = $this->mDb->SelectLimit($sql_select, $sql_step, $sql_offset);
205
			}
206
			$rs_rows = $rs->RecordCount();
207
			if (0 != $this->mDb->ErrorNo())
208
				$this->Log("\n" . $db->ErrorMsg() . "\n");
0 ignored issues
show
Bug introduced by
The variable $db does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
209
			else
210
			{
211
				$sql .= $this->Rs2Sql($rs, $tbl, $cols);
212
				$done_rows += $rs_rows;
213
			}
214
215
			// Save this step to file
216
			if ($this->mCharsetDb != $this->mCharsetOs)
217
				$sql = mb_convert_encoding($sql, $this->mCharsetOs, $this->mCharsetDb);
218
			// Save to seperated file, first check about how many files will be used.
219
			if ($rowcount > $sql_step)
220
			{
221
				$i = strlen(strval(ceil($rowcount / $sql_step)));
222
				$s = substr(str_repeat('0', $i) . strval(floor($sql_offset / $sql_step)), $i * -1) . '.';
223
			}
224
			else
225
				$s = '';
226
			$bakfile = $this->mTargetPath . "/$tbl.${s}sql";
227
			file_put_contents($bakfile, $sql, FILE_APPEND);
228
			// Prepare for loop
229
			$done_bytes += strlen($sql);
230
			unset($sql);
231
			$sql = '';
232
			//$sql_offset += $sql_step;
233
			$sql_offset += $rs_rows;
234
			unset($rs);
235
		}
236
237
		// End sql, the last one.
238
		if (true == $this->NeedIdentityInsert())
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
239
			$sql .= "set identity_insert $tbl off;\n";
240
		file_put_contents($bakfile, $sql, FILE_APPEND);
241
242
		$this->Log("Saved $done_rows rows, Total size: $done_bytes bytes.\n");
243
244
	} // end of func BakTable
245
246
247
	/**
248
	 * 获得数据库连接
249
	 * @access	private
250
	 * @param	array	$server
251
	 * @return object
252
	 */
253
	private function &DbConn($server)
254
	{
255
		global $ADODB_FETCH_MODE;
256
257
		//ADODB设定
258
		$ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
259
260
		try
261
		{
262
			$conn = &ADONewConnection($server['type']);
263
			//$conn->debug = true;
264
			$conn->Connect($server['host'],
265
				$server['user'],
266
				$server['pass'],
267
				$server['name']);
268
			//针对mysql 4.1以上,UTF8编码的数据库,需要在连接后指定编码
269
			//$conn->Execute('set names "utf8"');
270
			if ('mysql' == $server['type']) $conn->Execute('set names "utf8"');
271
		}
272
		catch (Exception $e)
273
		{
274
			//var_dump($e);
275
			adodb_backtrace($e->getTrace());
276
			//echo $e;
277
			exit();
278
		}
279
		return $conn;
280
	} // end of func DbConn
281
282
283
	/**
284
	 * Retrieve table list from db
285
	 * @access	private
286
	 */
287
	private function GetTableList()
288
	{
289
		if (!empty($this->mTableInclude))
290
			$this->mTable = $this->mTableInclude;
291
		else
292
			// Adodb 4.65 can't read sysbase table list currect,
293
			// Replaced it with 4.93a, it's fine.
294
			$this->mTable = $this->mDb->MetaTables('TABLES');
295
296
		// Compute exclude
297
		foreach ($this->mTableExclude as $tbl)
298
		{
299
			$idx = array_search($tbl, $this->mTable);
300
			if (!(false === $idx))
301
				unset($this->mTable[$idx]);
302
		}
303
304
		// Write Log
305
		$this->Log("Ready for backup " . count($this->mTable) . " tables.\n");
306
	} // end of func GetTableList
307
308
309
	/**
310
	 * Get fields of a table, ignore prefered fields
311
	 * @access	private
312
	 * @param	string	$tbl
313
	 * @return	array
314
	 */
315
	private function GetTblFields($tbl)
316
	{
317
		$rs_cols = $this->mDb->MetaColumns($tbl, false);
318
		//print_r($rs_cols);
319
		$cols = array();
320
		if (!empty($rs_cols))
321
			foreach ($rs_cols as $c)
322
			{
323
				// Ignore some columns ?(timestamp)
324
				if (false == in_array($c->name, $this->mIgnoreColumn))
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
325
					array_push($cols, $c->name);
326
			}
327
		return $cols;
328
	} // end of func GetTblFields
329
330
331
	/**
332
	 * Convert groupby rules to where sql clauses
333
	 * Retrieve data from db by groupby rules, and convert to where sql.
334
	 * Used when backup, where clauses can used directly in select sql
335
	 * @access	private
336
	 * @param	string	$tbl
337
	 * @return	array
338
	 */
339
	private function GroupbyRule2WhereSql($tbl)
340
	{
341
		$ar_where = array();
342
		if (!empty($this->mTableGroupby[$tbl]))
343
		{
344
			$groupby = $this->mTableGroupby[$tbl];
345
			$sql = "select distinct $groupby from $tbl";
346
			$rs = $this->mDb->Execute($sql);
347
348
			// Convert every rows to where sql
349
			$cols = explode(',', $groupby);
350
			$rs_cols = $this->mDb->MetaColumns($tbl, false);
351
			while (!empty($rs) && !$rs->EOF && !empty($cols))
352
			{
353
				$sql = ' WHERE 1=1 ';
354 View Code Duplication
				foreach ($cols as $c)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
355
				{
356
					$val = $this->ParseSqlData($rs->fields[$c], $rs_cols[strtoupper($c)]->type);
357
					$sql .= " and $c=$val ";
358
				}
359
				array_push($ar_where, $sql);
360
				$rs->MoveNext();
361
			}
362
		}
363
		return $ar_where;
364
	} // end of function GroupbyRule2WhereSql
365
366
367
	/**
368
	 * Save log
369
	 * Both log file and summary text is saved to.
370
	 * @access	private
371
	 * @param	string	$log
372
	 */
373
	public function Log ($log) {
374
		$logfile = $this->mTargetPath . '/'  . $this->mLogFile;
375
		file_put_contents($logfile, $log, FILE_APPEND);
376
		echo $log;
377
		$this->mSummary .= $log;
378
	} // end of func Log
379
380
381
	/**
382
	 * Determin if current db driver need set identity_insert tbl on/off
383
	 * @access	private
384
	 * @return	boolean
385
	 */
386
	private function NeedIdentityInsert()
387
	{
388
		$ar_need = array('mssql', 'sybase', 'sybase_ase');
389
		if (true == in_array($this->mServer['type'], $ar_need))
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
390
			return true;
391
		else
392
			return false;
393
	} // end of func NeedIdentityInsert
394
395
396
	/**
397
	 * Parse sql text used in sql value field
398
	 * @access	private
399
	 * @param	string	$val
400
	 * @param	string	$type
401
	 * @return	string
402
	 */
403
	private function ParseSqlData($val, $type)
404
	{
405
		// First, ' -> '' in sybase
406
		//if (true == in_array($this->mServer['type'], array('sybase', 'sybase_ase')))
407
		//	$val = str_replace("'", "''", $val);
408
		// Quote fields of char
409
		$varchar = array('char', 'charn', 'text', 'varchar', 'varchar2', 'varcharn');
410
		if (true == in_array($type, $varchar))
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
411
			$val = '"' . addslashes($val) . '"';
412
413
		// Datetime field
414
		$datestyle = array('date', 'daten', 'datetime', 'datetimn');
415
		if (in_array($type, $datestyle))
416
			if (empty($val))
417
				$val = 'null';
418
			else
419
				$val = '"' . $val . '"';
420
421
		// If a numeric field is null
422
		if (!in_array($type, $varchar) && !in_array($type, $datestyle) && is_null($val))
423
			$val = 'null';
424
425
		return $val;
426
	} // end of func ParseSqlData
427
428
429
	/**
430
	 * Convert ADOdb recordset to sql text
431
	 * @access	private
432
	 * @param	object	$rs
433
	 * @param	string	$tbl
434
	 * @param	array	$cols
435
	 * @return	string
436
	 */
437
	private function Rs2Sql(&$rs, $tbl, $cols=array())
438
	{
439
		if (empty($rs) || $rs->EOF)
440
			return '';
441
		else
442
			{
443
				$sql = '';
444
				if (empty($cols))
445
					$cols = $this->GetTblFields($tbl);
446
				$rs_cols = $this->mDb->MetaColumns($tbl, false);
447
448
				while (!$rs->EOF)
449
				{
450
					// Insert sql begin
451
					$sql_i = "INSERT INTO $tbl (" . implode(',', $cols) . " ) VALUES ( \n";
452
					// Fields data
453
					$ar = array();
454 View Code Duplication
					foreach ($cols as $c)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
455
					{
456
						$val = $rs->fields[$c];
457
						$type = $rs_cols[strtoupper($c)]->type;
458
						array_push($ar, $this->ParseSqlData($val, $type));
459
					}
460
					$sql_i .= implode(',', $ar) . "\n";
461
					// Insert sql end
462
					$sql_i .= ");\n";
463
					$sql .= $sql_i;
464
					// Move cursor
465
					$rs->MoveNext();
466
				}
467
			}
468
		return $sql;
469
	} // end of func Rs2Sql
470
471
472
	/**
473
	 * Accept database information from outside class
474
	 *	Didnot validate data send in.
475
	 *	And connect to db after store infomation.
476
	 * @access	public
477
	 * @var	array	$server	array items: host, user, pass, name, type
478
	 */
479 View Code Duplication
	public function SetDatabase($server)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
480
	{
481
		if (!empty($server) && is_array($server))
482
		{
483
			$this->mServer = $server;
484
			$this->mDb = $this->DbConn($this->mServer);
485
		}
486
	} // end of func SetDatabase
487
488
489
	/**
490
	 * Set tables will not be backup
491
	 * @access public
492
	 * @var	array	$ar
493
	 */
494
	public function SetTableExclude($ar)
495
	{
496
		if (!empty($ar) and is_array($ar))
497
		{
498
			$this->mTableExclude = $ar;
499
		}
500
	} // end of func SetTableExclude
501
502
503
	/**
504
	 * Set table group by rules when backup-select
505
	 * If given cols is empty, it will remove tbl from list need-to-be groupby.
506
	 * Multi cols can be assigned split by ','.
507
	 * @access	public
508
	 * @var	string	$tbl
509
	 * @var	string	$cols
510
	 */
511
	public function SetTableGroupby($tbl, $cols)
512
	{
513
		if (empty($cols))
514
			unset($this->mTableGroupby[$tbl]);
515
		else
516
			$this->mTableGroupby[$tbl] = $cols;
517
	} // end of func SetTableGroupby
518
519
520
	/**
521
	 * Set tables will only be backup
522
	 * @access public
523
	 * @var	array	$ar
524
	 */
525
	public function SetTableInclude($ar)
526
	{
527
		if (!empty($ar) and is_array($ar))
528
		{
529
			$this->mTableInclude = $ar;
530
		}
531
	} // end of func SetTableInclude
532
533
534
	/**
535
	 * Set where to save sql files exported.
536
	 * If directory doesn't exists, create it.
537
	 * @access	public
538
	 * @var	string	$path
539
	 */
540
	public function SetTargetPath($path)
541
	{
542
		$this->mTargetPath = $path;
543
		// Check and create
544
		if (file_exists($path) && !is_dir($path))
545
			die("Target path is a file.");
546
		elseif (!file_exists($path))
547
			mkdir($path, 0700, true);	// Do path end with '/' needed ?
548
	} // end of func SetTargetPath
549
550
	/**
551
	 * Print or write summary text of the whole backup process
552
	 * @access	private
553
	 */
554
	private function Summary()
555
	{
556
		echo $this->mSummary . "\n";
557
	} // end of func Summary
558
559
560
} // end of class DbBak2Sql
561
/*
562
$db = DbConn();
563
564
//打印各市地区编码
565
$sql[2] = 'select substring(bm, 1, 4), mc from dqk where bm like "%00"';
566
567
$sql = $sql[4];
568
$sql = mb_convert_encoding($sql, 'gbk', 'utf8');
569
$rs = $db->Execute($sql);
570
if (0 != $db->ErrorNo())
571
	echo $db->ErrorMsg();
572
$s = '';
573
$ar = $rs->GetArray();
574
575
//输出方式一,直接print
576
//$s = print_r($ar, true);
577
//输出方式二,tab间隔,便于导入
578
foreach ($ar as $key=>$row)
579
{
580
	foreach ($row as $var)
581
		$s .= "$var\t";
582
	$s .= "\n";
583
}
584
585
$s = mb_convert_encoding($s, 'utf8', 'gbk');
586
echo $s;
587
*/
588
?>
0 ignored issues
show
Best Practice introduced by
It is not recommended to use PHP's closing tag ?> in files other than templates.

Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.

A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.

Loading history...
589