1
|
|
|
<?php declare(strict_types=1); |
2
|
|
|
/** |
3
|
|
|
* Query |
4
|
|
|
* |
5
|
|
|
* SQL Query Builder / Database Abstraction Layer |
6
|
|
|
* |
7
|
|
|
* PHP version 7 |
8
|
|
|
* |
9
|
|
|
* @package Query |
10
|
|
|
* @author Timothy J. Warren <[email protected]> |
11
|
|
|
* @copyright 2012 - 2016 Timothy J. Warren |
12
|
|
|
* @license http://www.opensource.org/licenses/mit-license.html MIT License |
13
|
|
|
* @link https://git.timshomepage.net/aviat4ion/Query |
14
|
|
|
*/ |
15
|
|
|
namespace Query\Drivers\Firebird; |
16
|
|
|
|
17
|
|
|
use PDO; |
18
|
|
|
use PDOException; |
19
|
|
|
use Query\Drivers\AbstractDriver; |
20
|
|
|
use Query\Drivers\DriverInterface; |
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* Firebird Database class |
24
|
|
|
* |
25
|
|
|
* PDO-firebird isn't stable, so this is a wrapper of the fbird_ public functions. |
26
|
|
|
* |
27
|
|
|
*/ |
28
|
|
|
class Driver extends AbstractDriver implements DriverInterface { |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* Reference to the resource returned by |
32
|
|
|
* the last query executed |
33
|
|
|
* |
34
|
|
|
* @var resource |
35
|
|
|
*/ |
36
|
|
|
protected $statementLink = NULL; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* Reference to the current transaction |
40
|
|
|
* |
41
|
|
|
* @var resource |
42
|
|
|
*/ |
43
|
|
|
protected $trans = NULL; |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* Reference to the connection resource |
47
|
|
|
* |
48
|
|
|
* @var resource |
49
|
|
|
*/ |
50
|
|
|
protected $conn = NULL; |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* Reference to the service resource |
54
|
|
|
* |
55
|
|
|
* @var resource |
56
|
|
|
*/ |
57
|
|
|
protected $service = NULL; |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* Firebird doesn't have the truncate keyword |
61
|
|
|
* |
62
|
|
|
* @var boolean |
63
|
|
|
*/ |
64
|
|
|
protected $hasTruncate = FALSE; |
65
|
|
|
|
66
|
|
|
/** |
67
|
|
|
* Open the link to the database |
68
|
|
|
* |
69
|
|
|
* @param string $dbpath |
70
|
|
|
* @param string $user |
71
|
|
|
* @param string $pass |
72
|
|
|
* @param array $options |
73
|
|
|
* @throws PDOException |
74
|
|
|
*/ |
75
|
|
|
public function __construct($dbpath, $user='SYSDBA', $pass='masterkey', array $options = []) |
76
|
|
|
{ |
77
|
|
|
$connectFunction = (isset($options[PDO::ATTR_PERSISTENT]) && $options[PDO::ATTR_PERSISTENT]) |
78
|
|
|
? '\\fbird_pconnect' |
79
|
|
|
: '\\fbird_connect'; |
80
|
|
|
|
81
|
|
|
$this->conn = $connectFunction($dbpath, $user, $pass, 'utf-8', 0); |
82
|
|
|
$this->service = \fbird_service_attach('localhost', $user, $pass); |
83
|
|
|
|
84
|
|
|
// Throw an exception to make this match other pdo classes |
85
|
|
|
if ( ! \is_resource($this->conn)) |
86
|
|
|
{ |
87
|
|
|
throw new PDOException(\fbird_errmsg(), \fbird_errcode(), NULL); |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
// Load these classes here because this |
91
|
|
|
// driver does not call the constructor |
92
|
|
|
// of AbstractDriver, which defines these |
93
|
|
|
// class variables for the other drivers |
94
|
|
|
$this->_loadSubClasses(); |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
/** |
98
|
|
|
* Cleanup some loose ends |
99
|
|
|
* @codeCoverageIgnore |
100
|
|
|
*/ |
101
|
|
|
public function __destruct() |
102
|
|
|
{ |
103
|
|
|
\fbird_service_detach($this->service); |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* Return service handle |
108
|
|
|
* |
109
|
|
|
* @return resource |
110
|
|
|
*/ |
111
|
|
|
public function getService() |
112
|
|
|
{ |
113
|
|
|
return $this->service; |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
/** |
117
|
|
|
* Execute an sql statement and return number of affected rows |
118
|
|
|
* |
119
|
|
|
* @param string $sql |
120
|
|
|
* @return int |
121
|
|
|
*/ |
122
|
|
|
public function exec($sql) |
123
|
|
|
{ |
124
|
|
|
return NULL; |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
/** |
128
|
|
|
* Implement for compatibility with PDO |
129
|
|
|
* |
130
|
|
|
* @param int $attribute |
131
|
|
|
* @return mixed |
132
|
|
|
*/ |
133
|
|
|
public function getAttribute($attribute) |
134
|
|
|
{ |
135
|
|
|
return NULL; |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
/** |
139
|
|
|
* Return whether the current statement is in a transaction |
140
|
|
|
* |
141
|
|
|
* @return bool |
142
|
|
|
*/ |
143
|
|
|
public function inTransaction() |
144
|
|
|
{ |
145
|
|
|
return ! is_null($this->trans); |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
/** |
149
|
|
|
* Returns the last value of the specified generator |
150
|
|
|
* |
151
|
|
|
* @param string $name |
152
|
|
|
* @return mixed |
153
|
|
|
*/ |
154
|
|
|
public function lastInsertId($name = NULL) |
155
|
|
|
{ |
156
|
|
|
return \fbird_gen_id($name, 0, $this->conn); |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
/** |
160
|
|
|
* Wrapper public function to better match PDO |
161
|
|
|
* |
162
|
|
|
* @param string $sql |
163
|
|
|
* @return Result |
164
|
|
|
* @throws PDOException |
165
|
|
|
*/ |
166
|
|
|
public function query($sql = '') |
167
|
|
|
{ |
168
|
|
|
if (empty($sql)) |
169
|
|
|
{ |
170
|
|
|
throw new PDOException("Query method requires an sql query!", 0, NULL); |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
$this->statementLink = (isset($this->trans)) |
174
|
|
|
? \fbird_query($this->trans, $sql) |
175
|
|
|
: \fbird_query($this->conn, $sql); |
176
|
|
|
|
177
|
|
|
// Throw the error as a exception |
178
|
|
|
$errString = \fbird_errmsg() . "Last query:" . $this->getLastQuery(); |
179
|
|
|
if ($this->statementLink === FALSE) |
180
|
|
|
{ |
181
|
|
|
throw new PDOException($errString, \fbird_errcode(), NULL); |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
$this->statement = new Result($this->statementLink, $this); |
185
|
|
|
|
186
|
|
|
return $this->statement; |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
/** |
190
|
|
|
* Emulate PDO prepare |
191
|
|
|
* |
192
|
|
|
* @param string $query |
193
|
|
|
* @param array $options |
194
|
|
|
* @return Result |
195
|
|
|
* @throws PDOException |
196
|
|
|
*/ |
197
|
|
|
public function prepare($query, $options=[]) |
198
|
|
|
{ |
199
|
|
|
$this->statementLink = \fbird_prepare($this->conn, $query); |
200
|
|
|
|
201
|
|
|
// Throw the error as an exception |
202
|
|
|
if ($this->statementLink === FALSE) |
203
|
|
|
{ |
204
|
|
|
throw new PDOException(\fbird_errmsg(), \fbird_errcode(), NULL); |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
$this->statement = new Result($this->statementLink, $this); |
208
|
|
|
|
209
|
|
|
return $this->statement; |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
/** |
213
|
|
|
* Start a database transaction |
214
|
|
|
* |
215
|
|
|
* @return boolean|null |
216
|
|
|
*/ |
217
|
|
|
public function beginTransaction() |
218
|
|
|
{ |
219
|
|
|
return (($this->trans = \fbird_trans($this->conn)) !== NULL) ? TRUE : NULL; |
220
|
|
|
} |
221
|
|
|
|
222
|
|
|
/** |
223
|
|
|
* Commit a database transaction |
224
|
|
|
* |
225
|
|
|
* @return bool |
226
|
|
|
*/ |
227
|
|
|
public function commit() |
228
|
|
|
{ |
229
|
|
|
$res = \fbird_commit($this->trans); |
230
|
|
|
$this->trans = NULL; |
231
|
|
|
return $res; |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
/** |
235
|
|
|
* Rollback a transaction |
236
|
|
|
* |
237
|
|
|
* @return bool |
238
|
|
|
*/ |
239
|
|
|
public function rollBack() |
240
|
|
|
{ |
241
|
|
|
$res = \fbird_rollback($this->trans); |
242
|
|
|
$this->trans = NULL; |
243
|
|
|
return $res; |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
/** |
247
|
|
|
* Set a connection attribute |
248
|
|
|
* @param int $attribute |
249
|
|
|
* @param mixed $value |
250
|
|
|
* @return bool |
251
|
|
|
*/ |
252
|
|
|
public function setAttribute($attribute, $value) |
253
|
|
|
{ |
254
|
|
|
return FALSE; |
255
|
|
|
} |
256
|
|
|
|
257
|
|
|
/** |
258
|
|
|
* Prepare and execute a query |
259
|
|
|
* |
260
|
|
|
* @param string $sql |
261
|
|
|
* @param array $args |
262
|
|
|
* @return Result |
263
|
|
|
*/ |
264
|
|
|
public function prepareExecute($sql, $args) |
265
|
|
|
{ |
266
|
|
|
$query = $this->prepare($sql); |
267
|
|
|
|
268
|
|
|
// Set the statement in the class variable for easy later access |
269
|
|
|
$this->statementLink =& $query; |
270
|
|
|
|
271
|
|
|
return $query->execute($args); |
272
|
|
|
} |
273
|
|
|
|
274
|
|
|
/** |
275
|
|
|
* Method to emulate PDO->quote |
276
|
|
|
* |
277
|
|
|
* @param string $str |
278
|
|
|
* @param int $paramType |
279
|
|
|
* @return string |
280
|
|
|
*/ |
281
|
|
|
public function quote($str, $paramType = PDO::PARAM_STR) |
282
|
|
|
{ |
283
|
|
|
if(is_numeric($str)) |
284
|
|
|
{ |
285
|
|
|
return $str; |
|
|
|
|
286
|
|
|
} |
287
|
|
|
|
288
|
|
|
return "'".str_replace("'", "''", $str)."'"; |
289
|
|
|
} |
290
|
|
|
|
291
|
|
|
/** |
292
|
|
|
* Method to emulate PDO->errorInfo / PDOStatement->errorInfo |
293
|
|
|
* |
294
|
|
|
* @return array |
295
|
|
|
*/ |
296
|
|
|
public function errorInfo() |
297
|
|
|
{ |
298
|
|
|
$code = \fbird_errcode(); |
299
|
|
|
$msg = \fbird_errmsg(); |
300
|
|
|
|
301
|
|
|
return [0, $code, $msg]; |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
/** |
305
|
|
|
* Method to emulate PDO->errorCode |
306
|
|
|
* |
307
|
|
|
* @return array |
308
|
|
|
*/ |
309
|
|
|
public function errorCode() |
310
|
|
|
{ |
311
|
|
|
return \fbird_errcode(); |
312
|
|
|
} |
313
|
|
|
|
314
|
|
|
/** |
315
|
|
|
* Bind a prepared query with arguments for executing |
316
|
|
|
* |
317
|
|
|
* @param string $sql |
318
|
|
|
* @param array $params |
319
|
|
|
* @return NULL |
320
|
|
|
*/ |
321
|
|
|
public function prepareQuery($sql, $params) |
322
|
|
|
{ |
323
|
|
|
// You can't bind query statements before execution with |
324
|
|
|
// the firebird database |
325
|
|
|
return NULL; |
326
|
|
|
} |
327
|
|
|
|
328
|
|
|
/** |
329
|
|
|
* Create sql for batch insert |
330
|
|
|
* |
331
|
|
|
* @param string $table |
332
|
|
|
* @param array $data |
333
|
|
|
* @return array |
334
|
|
|
*/ |
335
|
|
|
public function insertBatch($table, $data=[]) |
336
|
|
|
{ |
337
|
|
|
// Each member of the data array needs to be an array |
338
|
|
|
if ( ! is_array(current($data))) |
339
|
|
|
{ |
340
|
|
|
return NULL; |
341
|
|
|
} |
342
|
|
|
|
343
|
|
|
// Start the block of sql statements |
344
|
|
|
$sql = "EXECUTE BLOCK AS BEGIN\n"; |
345
|
|
|
|
346
|
|
|
$table = $this->quoteTable($table); |
347
|
|
|
$fields = \array_keys(\current($data)); |
348
|
|
|
|
349
|
|
|
$insertTemplate = "INSERT INTO {$table} (" |
350
|
|
|
. implode(',', $this->quoteIdent($fields)) |
351
|
|
|
. ") VALUES ("; |
352
|
|
|
|
353
|
|
View Code Duplication |
foreach($data as $item) |
|
|
|
|
354
|
|
|
{ |
355
|
|
|
// Quote string values |
356
|
|
|
$vals = array_map([$this, 'quote'], $item); |
357
|
|
|
|
358
|
|
|
// Add the values in the sql |
359
|
|
|
$sql .= $insertTemplate . implode(', ', $vals) . ");\n"; |
360
|
|
|
} |
361
|
|
|
|
362
|
|
|
// End the block of SQL statements |
363
|
|
|
$sql .= "END"; |
364
|
|
|
|
365
|
|
|
// Return a null array value so the query is run as it is, |
366
|
|
|
// not as a prepared statement, because a prepared statement |
367
|
|
|
// doesn't work for this type of query in Firebird. |
368
|
|
|
return [$sql, NULL]; |
|
|
|
|
369
|
|
|
} |
370
|
|
|
} |
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.