AbstractDriver::quote_table()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 7
rs 9.4285
cc 1
eloc 3
nc 1
nop 1
1
<?php
2
/**
3
 * Query
4
 *
5
 * Free Query Builder / Database Abstraction Layer
6
 *
7
 * @package		Query
8
 * @author		Timothy J. Warren
9
 * @copyright	Copyright (c) 2012 - 2015
10
 * @link 		https://github.com/aviat4ion/Query
11
 * @license		http://philsturgeon.co.uk/code/dbad-license
12
 */
13
14
// --------------------------------------------------------------------------
15
16
namespace Query;
17
18
// --------------------------------------------------------------------------
19
20
/**
21
 * Base Database class
22
 *
23
 * Extends PDO to simplify cross-database issues
24
 *
25
 * @package Query
26
 * @subpackage Drivers
27
 */
28
abstract class AbstractDriver extends \PDO implements DriverInterface {
29
30
	/**
31
	 * Reference to the last executed query
32
	 * @var \PDOStatement
33
	 */
34
	protected $statement;
35
36
	/**
37
	 * Character to escape identifiers
38
	 * @var string
39
	 */
40
	protected $escape_char = '"';
41
42
	/**
43
	 * Reference to sql class
44
	 * @var SQLInterface
45
	 */
46
	protected $sql;
47
48
	/**
49
	 * Reference to util class
50
	 * @var AbstractUtil
51
	 */
52
	protected $util;
53
54
	/**
55
	 * Last query executed
56
	 * @var string
57
	 */
58
	protected $last_query;
59
60
	/**
61
	 * Prefix to apply to table names
62
	 * @var string
63
	 */
64
	protected $table_prefix = '';
65
66
	/**
67
	 * Whether the driver supports 'TRUNCATE'
68
	 * @var bool
69
	 */
70
	protected $has_truncate = TRUE;
71
72
	/**
73
	 * PDO constructor wrapper
74
	 *
75
	 * @param string $dsn
76
	 * @param string $username
77
	 * @param string $password
78
	 * @param array $driver_options
79
	 */
80
	public function __construct($dsn, $username=NULL, $password=NULL, array $driver_options=array())
81
	{
82
		// Set PDO to display errors as exceptions, and apply driver options
83
		$driver_options[\PDO::ATTR_ERRMODE] = \PDO::ERRMODE_EXCEPTION;
84
		parent::__construct($dsn, $username, $password, $driver_options);
85
86
		$this->_load_sub_classes();
87
	}
88
89
	// --------------------------------------------------------------------------
90
91
	/**
92
	 * Loads the subclasses for the driver
93
	 *
94
	 * @return void
95
	 */
96
	protected function _load_sub_classes()
97
	{
98
		// Load the sql and util class for the driver
99
		$this_class = get_class($this);
100
		$ns_array = explode("\\", $this_class);
101
		array_pop($ns_array);
102
		$driver = array_pop($ns_array);
103
		$sql_class = "\\Query\\Drivers\\{$driver}\\SQL";
104
		$util_class = "\\Query\\Drivers\\{$driver}\\Util";
105
106
		$this->sql = new $sql_class();
107
		$this->util = new $util_class($this);
108
	}
109
110
	// --------------------------------------------------------------------------
111
112
	/**
113
	 * Allow invoke to work on table object
114
	 *
115
	 * @codeCoverageIgnore
116
	 * @param string $name
117
	 * @param array $args
118
	 * @return mixed
119
	 */
120
	public function __call($name, $args = array())
121
	{
122
		if (
123
			isset($this->$name)
124
			&& is_object($this->$name)
125
			&& method_exists($this->$name, '__invoke')
126
		)
127
		{
128
			return call_user_func_array(array($this->$name, '__invoke'), $args);
129
		}
130
	}
131
132
	// --------------------------------------------------------------------------
133
	// ! Accessors / Mutators
134
	// --------------------------------------------------------------------------
135
136
	/**
137
	 * Get the last sql query exexcuted
138
	 *
139
	 * @return string
140
	 */
141
	public function get_last_query()
142
	{
143
		return $this->last_query;
144
	}
145
146
	// --------------------------------------------------------------------------
147
148
	/**
149
	 * Set the last query sql
150
	 *
151
	 * @param string $query_string
152
	 * @return void
153
	 */
154
	public function set_last_query($query_string)
155
	{
156
		$this->last_query = $query_string;
157
	}
158
159
	// --------------------------------------------------------------------------
160
161
	/**
162
	 * Get the SQL class for the current driver
163
	 *
164
	 * @return SQLInterface
165
	 */
166
	public function get_sql()
167
	{
168
		return $this->sql;
169
	}
170
171
	// --------------------------------------------------------------------------
172
173
	/**
174
	 * Get the Util class for the current driver
175
	 *
176
	 * @return AbstractUtil
177
	 */
178
	public function get_util()
179
	{
180
		return $this->util;
181
	}
182
183
	// --------------------------------------------------------------------------
184
185
	/**
186
	 * Set the common table name prefix
187
	 *
188
	 * @param string $prefix
189
	 * @return void
190
	 */
191
	public function set_table_prefix($prefix)
192
	{
193
		$this->table_prefix = $prefix;
194
	}
195
196
	// --------------------------------------------------------------------------
197
	// ! Concrete functions that can be overridden in child classes
198
	// --------------------------------------------------------------------------
199
200
	/**
201
	 * Simplifies prepared statements for database queries
202
	 *
203
	 * @param string $sql
204
	 * @param array $data
205
	 * @return \PDOStatement | FALSE
206
	 * @throws \InvalidArgumentException
207
	 */
208
	public function prepare_query($sql, $data)
209
	{
210
		// Prepare the sql, save the statement for easy access later
211
		$this->statement = $this->prepare($sql);
212
213
		if( ! (is_array($data) || is_object($data)))
214
		{
215
			throw new \InvalidArgumentException("Invalid data argument");
216
		}
217
218
		// Bind the parameters
219
		foreach($data as $k => $value)
220
		{
221
			// Parameters are 1-based, the data is 0-based
222
			// So, if the key is numeric, add 1
223
			if(is_numeric($k))
224
			{
225
				$k++;
226
			}
227
			$this->statement->bindValue($k, $value);
228
		}
229
230
		return $this->statement;
231
	}
232
233
	// -------------------------------------------------------------------------
234
235
	/**
236
	 * Create and execute a prepared statement with the provided parameters
237
	 *
238
	 * @param string $sql
239
	 * @param array $params
240
	 * @return \PDOStatement
241
	 */
242
	public function prepare_execute($sql, $params)
243
	{
244
		$this->statement = $this->prepare_query($sql, $params);
245
		$this->statement->execute();
246
247
		return $this->statement;
248
	}
249
250
	// -------------------------------------------------------------------------
251
252
	/**
253
	 * Returns number of rows affected by an INSERT, UPDATE, DELETE type query
254
	 *
255
	 * @return int
256
	 */
257
	public function affected_rows()
258
	{
259
		// Return number of rows affected
260
		return $this->statement->rowCount();
261
	}
262
263
	// --------------------------------------------------------------------------
264
265
	/**
266
	 * Prefixes a table if it is not already prefixed
267
	 * @param string $table
268
	 * @return string
269
	 */
270
	public function prefix_table($table)
271
	{
272
		// Add the prefix to the table name
273
		// before quoting it
274
		if ( ! empty($this->table_prefix))
275
		{
276
			// Split identifier by period, will split into:
277
			// database.schema.table OR
278
			// schema.table OR
279
			// database.table OR
280
			// table
281
			$idents = explode('.', $table);
282
			$segments = count($idents);
283
284
			// Quote the last item, and add the database prefix
285
			$idents[$segments - 1] = $this->_prefix(end($idents));
286
287
			// Rejoin
288
			$table = implode('.', $idents);
289
		}
290
291
		return $table;
292
	}
293
294
	// --------------------------------------------------------------------------
295
296
	/**
297
	 * Quote database table name, and set prefix
298
	 *
299
	 * @param string $table
300
	 * @return string
301
	 */
302
	public function quote_table($table)
303
	{
304
		$table = $this->prefix_table($table);
305
306
		// Finally, quote the table
307
		return $this->quote_ident($table);
308
	}
309
310
	// --------------------------------------------------------------------------
311
312
	/**
313
	 * Surrounds the string with the databases identifier escape characters
314
	 *
315
	 * @param mixed $ident
316
	 * @return string
317
	 */
318
	public function quote_ident($ident)
319
	{
320
		if (is_array($ident))
321
		{
322
			return array_map(array($this, __METHOD__), $ident);
323
		}
324
325
		// Handle comma-separated identifiers
326
		if (strpos($ident, ',') !== FALSE)
327
		{
328
			$parts = array_map('mb_trim', explode(',', $ident));
329
			$parts = array_map(array($this, __METHOD__), $parts);
330
			$ident = implode(',', $parts);
331
		}
332
333
		// Split each identifier by the period
334
		$hiers = explode('.', $ident);
335
		$hiers = array_map('mb_trim', $hiers);
336
337
		// Re-compile the string
338
		$raw = implode('.', array_map(array($this, '_quote'), $hiers));
339
340
		// Fix functions
341
		$funcs = array();
342
		preg_match_all("#{$this->escape_char}([a-zA-Z0-9_]+(\((.*?)\))){$this->escape_char}#iu", $raw, $funcs, PREG_SET_ORDER);
343
		foreach($funcs as $f)
344
		{
345
			// Unquote the function
346
			$raw = str_replace($f[0], $f[1], $raw);
347
348
			// Quote the inside identifiers
349
			$raw = str_replace($f[3], $this->quote_ident($f[3]), $raw);
350
		}
351
352
		return $raw;
353
354
	}
355
356
	// -------------------------------------------------------------------------
357
358
	/**
359
	 * Return schemas for databases that list them
360
	 *
361
	 * @return array
362
	 */
363
	public function get_schemas()
364
	{
365
		return NULL;
366
	}
367
368
	// -------------------------------------------------------------------------
369
370
	/**
371
	 * Return list of tables for the current database
372
	 *
373
	 * @return array
374
	 */
375
	public function get_tables()
376
	{
377
		$tables = $this->driver_query('table_list');
378
		natsort($tables);
379
		return $tables;
380
	}
381
382
	// -------------------------------------------------------------------------
383
384
	/**
385
	 * Return list of dbs for the current connection, if possible
386
	 *
387
	 * @return array
388
	 */
389
	public function get_dbs()
390
	{
391
		return $this->driver_query('db_list');
392
	}
393
394
	// -------------------------------------------------------------------------
395
396
	/**
397
	 * Return list of views for the current database
398
	 *
399
	 * @return array
400
	 */
401
	public function get_views()
402
	{
403
		$views = $this->driver_query('view_list');
404
		sort($views);
405
		return $views;
406
	}
407
408
	// -------------------------------------------------------------------------
409
410
	/**
411
	 * Return list of sequences for the current database, if they exist
412
	 *
413
	 * @return array
414
	 */
415
	public function get_sequences()
416
	{
417
		return $this->driver_query('sequence_list');
418
	}
419
420
	// -------------------------------------------------------------------------
421
422
	/**
423
	 * Return list of functions for the current database
424
	 *
425
	 * @return array
426
	 */
427
	public function get_functions()
428
	{
429
		return $this->driver_query('function_list', FALSE);
430
	}
431
432
	// -------------------------------------------------------------------------
433
434
	/**
435
	 * Return list of stored procedures for the current database
436
	 *
437
	 * @return array
438
	 */
439
	public function get_procedures()
440
	{
441
		return $this->driver_query('procedure_list', FALSE);
442
	}
443
444
	// -------------------------------------------------------------------------
445
446
	/**
447
	 * Return list of triggers for the current database
448
	 *
449
	 * @return array
450
	 */
451
	public function get_triggers()
452
	{
453
		return $this->driver_query('trigger_list', FALSE);
454
	}
455
456
	// -------------------------------------------------------------------------
457
458
	/**
459
	 * Retrieves an array of non-user-created tables for
460
	 * the connection/database
461
	 *
462
	 * @return array
463
	 */
464
	public function get_system_tables()
465
	{
466
		return $this->driver_query('system_table_list');
467
	}
468
469
	// --------------------------------------------------------------------------
470
471
	/**
472
	 * Retrieve column information for the current database table
473
	 *
474
	 * @param string $table
475
	 * @return array
476
	 */
477
	public function get_columns($table)
478
	{
479
		return $this->driver_query($this->get_sql()->column_list($this->prefix_table($table)), FALSE);
480
	}
481
482
	// --------------------------------------------------------------------------
483
484
	/**
485
	 * Retrieve foreign keys for the table
486
	 *
487
	 * @param string $table
488
	 * @return array
489
	 */
490
	public function get_fks($table)
491
	{
492
		return $this->driver_query($this->get_sql()->fk_list($table), FALSE);
493
	}
494
495
	// --------------------------------------------------------------------------
496
497
	/**
498
	 * Retrieve indexes for the table
499
	 *
500
	 * @param string $table
501
	 * @return array
502
	 */
503
	public function get_indexes($table)
504
	{
505
		return $this->driver_query($this->get_sql()->index_list($this->prefix_table($table)), FALSE);
506
	}
507
508
	// --------------------------------------------------------------------------
509
510
	/**
511
	 * Retrieve list of data types for the database
512
	 *
513
	 * @return array
514
	 */
515
	public function get_types()
516
	{
517
		return $this->driver_query('type_list', FALSE);
518
	}
519
520
	// -------------------------------------------------------------------------
521
522
	/**
523
	 * Method to simplify retrieving db results for meta-data queries
524
	 *
525
	 * @param string|array|null $query
526
	 * @param bool $filtered_index
527
	 * @return array
528
	 */
529
	public function driver_query($query, $filtered_index=TRUE)
530
	{
531
		// Call the appropriate method, if it exists
532
		if (is_string($query) && method_exists($this->sql, $query))
533
		{
534
			$query = $this->get_sql()->$query();
535
		}
536
537
		// Return if the values are returned instead of a query,
538
		// or if the query doesn't apply to the driver
539
		if ( ! is_string($query))
540
		{
541
			return $query;
542
		}
543
544
		// Run the query!
545
		$res = $this->query($query);
546
547
		$flag = ($filtered_index) ? \PDO::FETCH_NUM : \PDO::FETCH_ASSOC;
548
		$all = $res->fetchAll($flag);
549
550
		return ($filtered_index) ? \db_filter($all, 0) : $all;
551
	}
552
553
	// --------------------------------------------------------------------------
554
555
	/**
556
	 * Return the number of rows returned for a SELECT query
557
	 *
558
	 * @see http://us3.php.net/manual/en/pdostatement.rowcount.php#87110
559
	 * @return int
560
	 */
561
	public function num_rows()
562
	{
563
		$regex = '/^SELECT\s+(?:ALL\s+|DISTINCT\s+)?(?:.*?)\s+FROM\s+(.*)$/i';
564
		$output = array();
565
566
		if (preg_match($regex, $this->last_query, $output) > 0)
567
		{
568
			$stmt = $this->query("SELECT COUNT(*) FROM {$output[1]}");
569
			return (int) $stmt->fetchColumn();
570
		}
571
572
		return NULL;
573
	}
574
575
	// --------------------------------------------------------------------------
576
577
	/**
578
	 * Create sql for batch insert
579
	 *
580
	 * @param string $table
581
	 * @param array $data
582
	 * @return null|array<string|array|null>
583
	 */
584
	public function insert_batch($table, $data=array())
585
	{
586
		$first_row = current($data);
587
		if ( ! is_array($first_row))
588
		{
589
			return NULL;
590
		}
591
592
		// Values for insertion
593
		$vals = array();
594
		foreach($data as $group)
595
		{
596
			$vals = array_merge($vals, array_values($group));
597
		}
598
		$table = $this->quote_table($table);
599
		$fields = array_keys($first_row);
600
601
		$sql = "INSERT INTO {$table} ("
602
			. implode(',', $this->quote_ident($fields))
603
			. ") VALUES ";
604
605
		// Create the placeholder groups
606
		$params = array_fill(0, count($fields), '?');
607
		$param_string = "(" . implode(',', $params) . ")";
608
		$param_list = array_fill(0, count($data), $param_string);
609
610
		// Append the placeholder groups to the query
611
		$sql .= implode(',', $param_list);
612
613
		return array($sql, $vals);
614
	}
615
616
	// --------------------------------------------------------------------------
617
618
	/**
619
	 * Helper method for quote_ident
620
	 *
621
	 * @param mixed $str
622
	 * @return mixed
623
	 */
624
	public function _quote($str)
625
	{
626
		// Check that the current value is a string,
627
		// and is not already quoted before quoting
628
		// that value, otherwise, return the original value
629
		return (
630
			is_string($str)
631
			&& strpos($str, $this->escape_char) !== 0
632
			&& strrpos($str, $this->escape_char) !== 0
633
		)
634
			? "{$this->escape_char}{$str}{$this->escape_char}"
635
			: $str;
636
637
	}
638
639
	// --------------------------------------------------------------------------
640
641
	/**
642
	 * Sets the table prefix on the passed string
643
	 *
644
	 * @param string $str
645
	 * @return string
646
	 */
647
	protected function _prefix($str)
648
	{
649
		// Don't prefix an already prefixed table
650
		if (strpos($str, $this->table_prefix) !== FALSE)
651
		{
652
			return $str;
653
		}
654
655
		return $this->table_prefix.$str;
656
	}
657
658
	// -------------------------------------------------------------------------
659
660
	/**
661
	 * Empty the passed table
662
	 *
663
	 * @param string $table
664
	 * @return \PDOStatement
665
	 */
666
	public function truncate($table)
667
	{
668
		$sql = ($this->has_truncate)
669
			? 'TRUNCATE '
670
			: 'DELETE FROM ';
671
672
		$sql .= $this->quote_table($table);
673
674
		$this->statement = $this->query($sql);
675
		return $this->statement;
676
	}
677
678
}
679
// End of db_pdo.php