1 /*
2 +----------------------------------------------------------------------+
3 | Copyright (c) The PHP Group |
4 +----------------------------------------------------------------------+
5 | This source file is subject to version 3.01 of the PHP license, |
6 | that is bundled with this package in the file LICENSE, and is |
7 | available through the world-wide-web at the following url: |
8 | http://www.php.net/license/3_01.txt |
9 | If you did not receive a copy of the PHP license and are unable to |
10 | obtain it through the world-wide-web, please send a note to |
11 | license@php.net so we can mail you a copy immediately. |
12 +----------------------------------------------------------------------+
13 | Author: Ard Biesheuvel <abies@php.net> |
14 +----------------------------------------------------------------------+
15 */
16
17 #ifdef HAVE_CONFIG_H
18 #include "config.h"
19 #endif
20
21 #define _GNU_SOURCE
22
23 #include "php.h"
24 #include "zend_exceptions.h"
25 #include "php_ini.h"
26 #include "ext/standard/info.h"
27 #include "pdo/php_pdo.h"
28 #include "pdo/php_pdo_driver.h"
29 #include "php_pdo_firebird.h"
30 #include "php_pdo_firebird_int.h"
31
32 static int firebird_alloc_prepare_stmt(pdo_dbh_t*, const char*, size_t, XSQLDA*, isc_stmt_handle*,
33 HashTable*);
34
35 const char CHR_LETTER = 1;
36 const char CHR_DIGIT = 2;
37 const char CHR_IDENT = 4;
38 const char CHR_QUOTE = 8;
39 const char CHR_WHITE = 16;
40 const char CHR_HEX = 32;
41 const char CHR_INTRODUCER = 64;
42
43 static const char classes_array[] = {
44 /* 000 */ 0,
45 /* 001 */ 0,
46 /* 002 */ 0,
47 /* 003 */ 0,
48 /* 004 */ 0,
49 /* 005 */ 0,
50 /* 006 */ 0,
51 /* 007 */ 0,
52 /* 008 */ 0,
53 /* 009 */ 16, /* CHR_WHITE */
54 /* 010 */ 16, /* CHR_WHITE */
55 /* 011 */ 0,
56 /* 012 */ 0,
57 /* 013 */ 16, /* CHR_WHITE */
58 /* 014 */ 0,
59 /* 015 */ 0,
60 /* 016 */ 0,
61 /* 017 */ 0,
62 /* 018 */ 0,
63 /* 019 */ 0,
64 /* 020 */ 0,
65 /* 021 */ 0,
66 /* 022 */ 0,
67 /* 023 */ 0,
68 /* 024 */ 0,
69 /* 025 */ 0,
70 /* 026 */ 0,
71 /* 027 */ 0,
72 /* 028 */ 0,
73 /* 029 */ 0,
74 /* 030 */ 0,
75 /* 031 */ 0,
76 /* 032 */ 16, /* CHR_WHITE */
77 /* 033 ! */ 0,
78 /* 034 " */ 8, /* CHR_QUOTE */
79 /* 035 # */ 0,
80 /* 036 $ */ 4, /* CHR_IDENT */
81 /* 037 % */ 0,
82 /* 038 & */ 0,
83 /* 039 ' */ 8, /* CHR_QUOTE */
84 /* 040 ( */ 0,
85 /* 041 ) */ 0,
86 /* 042 * */ 0,
87 /* 043 + */ 0,
88 /* 044 , */ 0,
89 /* 045 - */ 0,
90 /* 046 . */ 0,
91 /* 047 / */ 0,
92 /* 048 0 */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */
93 /* 049 1 */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */
94 /* 050 2 */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */
95 /* 051 3 */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */
96 /* 052 4 */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */
97 /* 053 5 */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */
98 /* 054 6 */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */
99 /* 055 7 */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */
100 /* 056 8 */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */
101 /* 057 9 */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */
102 /* 058 : */ 0,
103 /* 059 ; */ 0,
104 /* 060 < */ 0,
105 /* 061 = */ 0,
106 /* 062 > */ 0,
107 /* 063 ? */ 0,
108 /* 064 @ */ 0,
109 /* 065 A */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */
110 /* 066 B */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */
111 /* 067 C */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */
112 /* 068 D */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */
113 /* 069 E */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */
114 /* 070 F */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */
115 /* 071 G */ 5, /* CHR_LETTER | CHR_IDENT */
116 /* 072 H */ 5, /* CHR_LETTER | CHR_IDENT */
117 /* 073 I */ 5, /* CHR_LETTER | CHR_IDENT */
118 /* 074 J */ 5, /* CHR_LETTER | CHR_IDENT */
119 /* 075 K */ 5, /* CHR_LETTER | CHR_IDENT */
120 /* 076 L */ 5, /* CHR_LETTER | CHR_IDENT */
121 /* 077 M */ 5, /* CHR_LETTER | CHR_IDENT */
122 /* 078 N */ 5, /* CHR_LETTER | CHR_IDENT */
123 /* 079 O */ 5, /* CHR_LETTER | CHR_IDENT */
124 /* 080 P */ 5, /* CHR_LETTER | CHR_IDENT */
125 /* 081 Q */ 5, /* CHR_LETTER | CHR_IDENT */
126 /* 082 R */ 5, /* CHR_LETTER | CHR_IDENT */
127 /* 083 S */ 5, /* CHR_LETTER | CHR_IDENT */
128 /* 084 T */ 5, /* CHR_LETTER | CHR_IDENT */
129 /* 085 U */ 5, /* CHR_LETTER | CHR_IDENT */
130 /* 086 V */ 5, /* CHR_LETTER | CHR_IDENT */
131 /* 087 W */ 5, /* CHR_LETTER | CHR_IDENT */
132 /* 088 X */ 5, /* CHR_LETTER | CHR_IDENT */
133 /* 089 Y */ 5, /* CHR_LETTER | CHR_IDENT */
134 /* 090 Z */ 5, /* CHR_LETTER | CHR_IDENT */
135 /* 091 [ */ 0,
136 /* 092 \ */ 0,
137 /* 093 ] */ 0,
138 /* 094 ^ */ 0,
139 /* 095 _ */ 68, /* CHR_IDENT | CHR_INTRODUCER */
140 /* 096 ` */ 0,
141 /* 097 a */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */
142 /* 098 b */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */
143 /* 099 c */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */
144 /* 100 d */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */
145 /* 101 e */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */
146 /* 102 f */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */
147 /* 103 g */ 5, /* CHR_LETTER | CHR_IDENT */
148 /* 104 h */ 5, /* CHR_LETTER | CHR_IDENT */
149 /* 105 i */ 5, /* CHR_LETTER | CHR_IDENT */
150 /* 106 j */ 5, /* CHR_LETTER | CHR_IDENT */
151 /* 107 k */ 5, /* CHR_LETTER | CHR_IDENT */
152 /* 108 l */ 5, /* CHR_LETTER | CHR_IDENT */
153 /* 109 m */ 5, /* CHR_LETTER | CHR_IDENT */
154 /* 110 n */ 5, /* CHR_LETTER | CHR_IDENT */
155 /* 111 o */ 5, /* CHR_LETTER | CHR_IDENT */
156 /* 112 p */ 5, /* CHR_LETTER | CHR_IDENT */
157 /* 113 q */ 5, /* CHR_LETTER | CHR_IDENT */
158 /* 114 r */ 5, /* CHR_LETTER | CHR_IDENT */
159 /* 115 s */ 5, /* CHR_LETTER | CHR_IDENT */
160 /* 116 t */ 5, /* CHR_LETTER | CHR_IDENT */
161 /* 117 u */ 5, /* CHR_LETTER | CHR_IDENT */
162 /* 118 v */ 5, /* CHR_LETTER | CHR_IDENT */
163 /* 119 w */ 5, /* CHR_LETTER | CHR_IDENT */
164 /* 120 x */ 5, /* CHR_LETTER | CHR_IDENT */
165 /* 121 y */ 5, /* CHR_LETTER | CHR_IDENT */
166 /* 122 z */ 5, /* CHR_LETTER | CHR_IDENT */
167 /* 123 { */ 5, /* CHR_LETTER | CHR_IDENT */
168 /* 124 | */ 0,
169 /* 125 } */ 5, /* CHR_LETTER | CHR_IDENT */
170 /* 126 ~ */ 0,
171 /* 127 */ 0
172 };
173
classes(char idx)174 inline char classes(char idx)
175 {
176 if (idx > 127) return 0;
177 return classes_array[idx];
178 }
179
180 typedef enum {
181 ttNone,
182 ttWhite,
183 ttComment,
184 ttBrokenComment,
185 ttString,
186 ttParamMark,
187 ttIdent,
188 ttOther
189 } FbTokenType;
190
getToken(const char ** begin,const char * end)191 static FbTokenType getToken(const char** begin, const char* end)
192 {
193 FbTokenType ret = ttNone;
194 const char* p = *begin;
195
196 char c = *p++;
197 switch (c)
198 {
199 case ':':
200 case '?':
201 ret = ttParamMark;
202 break;
203
204 case '\'':
205 case '"':
206 while (p < end)
207 {
208 if (*p++ == c)
209 {
210 ret = ttString;
211 break;
212 }
213 }
214 break;
215
216 case '/':
217 if (p < end && *p == '*')
218 {
219 ret = ttBrokenComment;
220 p++;
221 while (p < end)
222 {
223 if (*p++ == '*' && p < end && *p == '/')
224 {
225 p++;
226 ret = ttComment;
227 break;
228 }
229 }
230 }
231 else {
232 ret = ttOther;
233 }
234 break;
235
236 case '-':
237 if (p < end && *p == '-')
238 {
239 while (++p < end)
240 {
241 if (*p == '\r')
242 {
243 p++;
244 if (p < end && *p == '\n')
245 p++;
246 break;
247 }
248 else if (*p == '\n')
249 break;
250 }
251
252 ret = ttComment;
253 }
254 else
255 ret = ttOther;
256 break;
257
258 default:
259 if (classes(c) & CHR_DIGIT)
260 {
261 while (p < end && (classes(*p) & CHR_DIGIT))
262 p++;
263 ret = ttOther;
264 }
265 else if (classes(c) & CHR_IDENT)
266 {
267 while (p < end && (classes(*p) & CHR_IDENT))
268 p++;
269 ret = ttIdent;
270 }
271 else if (classes(c) & CHR_WHITE)
272 {
273 while (p < end && (classes(*p) & CHR_WHITE))
274 p++;
275 ret = ttWhite;
276 }
277 else
278 {
279 while (p < end && !(classes(*p) & (CHR_DIGIT | CHR_IDENT | CHR_WHITE)) &&
280 (*p != '/') && (*p != '-') && (*p != ':') && (*p != '?') &&
281 (*p != '\'') && (*p != '"'))
282 {
283 p++;
284 }
285 ret = ttOther;
286 }
287 }
288
289 *begin = p;
290 return ret;
291 }
292
preprocess(const char * sql,int sql_len,char * sql_out,HashTable * named_params)293 int preprocess(const char* sql, int sql_len, char* sql_out, HashTable* named_params)
294 {
295 zend_bool passAsIs = 1, execBlock = 0;
296 zend_long pindex = -1;
297 char pname[254], ident[253], ident2[253];
298 unsigned int l;
299 const char* p = sql, * end = sql + sql_len;
300 const char* start = p;
301 FbTokenType tok = getToken(&p, end);
302
303 const char* i = start;
304 while (p < end && (tok == ttComment || tok == ttWhite))
305 {
306 i = p;
307 tok = getToken(&p, end);
308 }
309
310 if (p >= end || tok != ttIdent)
311 {
312 /* Execute statement preprocess SQL error */
313 /* Statement expected */
314 return 0;
315 }
316 /* skip leading comments ?? */
317 start = i;
318 l = p - i;
319 /* check the length of the identifier */
320 /* in Firebird 4.0 it is 63 characters, in previous versions 31 bytes */
321 if (l > 252) {
322 return 0;
323 }
324 strncpy(ident, i, l);
325 ident[l] = '\0';
326 if (!strcasecmp(ident, "EXECUTE"))
327 {
328 /* For EXECUTE PROCEDURE and EXECUTE BLOCK statements, named parameters must be processed. */
329 /* However, in EXECUTE BLOCK this is done in a special way. */
330 const char* i2 = p;
331 tok = getToken(&p, end);
332 while (p < end && (tok == ttComment || tok == ttWhite))
333 {
334 i2 = p;
335 tok = getToken(&p, end);
336 }
337 if (p >= end || tok != ttIdent)
338 {
339 /* Execute statement preprocess SQL error */
340 /* Statement expected */
341 return 0;
342 }
343 l = p - i2;
344 /* check the length of the identifier */
345 /* in Firebird 4.0 it is 63 characters, in previous versions 31 bytes */
346 if (l > 252) {
347 return 0;
348 }
349 strncpy(ident2, i2, l);
350 ident2[l] = '\0';
351 execBlock = !strcasecmp(ident2, "BLOCK");
352 passAsIs = 0;
353 }
354 else
355 {
356 /* Named parameters must be processed in the INSERT, UPDATE, DELETE, MERGE statements. */
357 /* If CTEs are present in the query, they begin with the WITH keyword. */
358 passAsIs = strcasecmp(ident, "INSERT") && strcasecmp(ident, "UPDATE") &&
359 strcasecmp(ident, "DELETE") && strcasecmp(ident, "MERGE") &&
360 strcasecmp(ident, "SELECT") && strcasecmp(ident, "WITH");
361 }
362
363 if (passAsIs)
364 {
365 strcpy(sql_out, sql);
366 return 1;
367 }
368
369 strncat(sql_out, start, p - start);
370
371 while (p < end)
372 {
373 start = p;
374 tok = getToken(&p, end);
375 switch (tok)
376 {
377 case ttParamMark:
378 tok = getToken(&p, end);
379 if (tok == ttIdent /*|| tok == ttString*/)
380 {
381 ++pindex;
382 l = p - start;
383 /* check the length of the identifier */
384 /* in Firebird 4.0 it is 63 characters, in previous versions 31 bytes */
385 /* + symbol ":" */
386 if (l > 253) {
387 return 0;
388 }
389 strncpy(pname, start, l);
390 pname[l] = '\0';
391
392 if (named_params) {
393 zval tmp;
394 ZVAL_LONG(&tmp, pindex);
395 zend_hash_str_update(named_params, pname, l, &tmp);
396 }
397
398 strcat(sql_out, "?");
399 }
400 else
401 {
402 if (strncmp(start, "?", 1)) {
403 /* Execute statement preprocess SQL error */
404 /* Parameter name expected */
405 return 0;
406 }
407 ++pindex;
408 strncat(sql_out, start, p - start);
409 }
410 break;
411
412 case ttIdent:
413 if (execBlock)
414 {
415 /* In the EXECUTE BLOCK statement, processing must be */
416 /* carried out up to the keyword AS. */
417 l = p - start;
418 /* check the length of the identifier */
419 /* in Firebird 4.0 it is 63 characters, in previous versions 31 bytes */
420 if (l > 252) {
421 return 0;
422 }
423 strncpy(ident, start, l);
424 ident[l] = '\0';
425 if (!strcasecmp(ident, "AS"))
426 {
427 strncat(sql_out, start, end - start);
428 return 1;
429 }
430 }
431
432 case ttWhite:
433 case ttComment:
434 case ttString:
435 case ttOther:
436 strncat(sql_out, start, p - start);
437 break;
438
439 case ttBrokenComment:
440 {
441 /* Execute statement preprocess SQL error */
442 /* Unclosed comment found near ''@1'' */
443 return 0;
444 }
445 break;
446
447
448 case ttNone:
449 /* Execute statement preprocess SQL error */
450 return 0;
451 break;
452 }
453 }
454 return 1;
455 }
456
457 /* map driver specific error message to PDO error */
_firebird_error(pdo_dbh_t * dbh,pdo_stmt_t * stmt,char const * file,zend_long line)458 void _firebird_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, char const *file, zend_long line) /* {{{ */
459 {
460 pdo_error_type *const error_code = stmt ? &stmt->error_code : &dbh->error_code;
461
462 strcpy(*error_code, "HY000");
463 }
464 /* }}} */
465
466 #define RECORD_ERROR(dbh) _firebird_error(dbh, NULL, __FILE__, __LINE__)
467
468 /* called by PDO to close a db handle */
firebird_handle_closer(pdo_dbh_t * dbh)469 static int firebird_handle_closer(pdo_dbh_t *dbh) /* {{{ */
470 {
471 pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
472
473 if (dbh->in_txn) {
474 if (dbh->auto_commit) {
475 if (isc_commit_transaction(H->isc_status, &H->tr)) {
476 RECORD_ERROR(dbh);
477 }
478 } else {
479 if (isc_rollback_transaction(H->isc_status, &H->tr)) {
480 RECORD_ERROR(dbh);
481 }
482 }
483 }
484
485 if (isc_detach_database(H->isc_status, &H->db)) {
486 RECORD_ERROR(dbh);
487 }
488
489 if (H->date_format) {
490 efree(H->date_format);
491 }
492 if (H->time_format) {
493 efree(H->time_format);
494 }
495 if (H->timestamp_format) {
496 efree(H->timestamp_format);
497 }
498
499 pefree(H, dbh->is_persistent);
500
501 return 0;
502 }
503 /* }}} */
504
505 /* called by PDO to prepare an SQL query */
firebird_handle_preparer(pdo_dbh_t * dbh,const char * sql,size_t sql_len,pdo_stmt_t * stmt,zval * driver_options)506 static int firebird_handle_preparer(pdo_dbh_t *dbh, const char *sql, size_t sql_len, /* {{{ */
507 pdo_stmt_t *stmt, zval *driver_options)
508 {
509 pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
510 pdo_firebird_stmt *S = NULL;
511 HashTable *np;
512
513 do {
514 isc_stmt_handle s = PDO_FIREBIRD_HANDLE_INITIALIZER;
515 XSQLDA num_sqlda;
516 static char const info[] = { isc_info_sql_stmt_type };
517 char result[8];
518
519 num_sqlda.version = PDO_FB_SQLDA_VERSION;
520 num_sqlda.sqln = 1;
521
522 ALLOC_HASHTABLE(np);
523 zend_hash_init(np, 8, NULL, NULL, 0);
524
525 /* allocate and prepare statement */
526 if (!firebird_alloc_prepare_stmt(dbh, sql, sql_len, &num_sqlda, &s, np)) {
527 break;
528 }
529
530 /* allocate a statement handle struct of the right size (struct out_sqlda is inlined) */
531 S = ecalloc(1, sizeof(*S)-sizeof(XSQLDA) + XSQLDA_LENGTH(num_sqlda.sqld));
532 S->H = H;
533 S->stmt = s;
534 S->fetch_buf = ecalloc(1,sizeof(char*) * num_sqlda.sqld);
535 S->out_sqlda.version = PDO_FB_SQLDA_VERSION;
536 S->out_sqlda.sqln = stmt->column_count = num_sqlda.sqld;
537 S->named_params = np;
538
539 /* determine the statement type */
540 if (isc_dsql_sql_info(H->isc_status, &s, sizeof(info), const_cast(info), sizeof(result),
541 result)) {
542 break;
543 }
544 S->statement_type = result[3];
545
546 /* fill the output sqlda with information about the prepared query */
547 if (isc_dsql_describe(H->isc_status, &s, PDO_FB_SQLDA_VERSION, &S->out_sqlda)) {
548 RECORD_ERROR(dbh);
549 break;
550 }
551
552 /* allocate the input descriptors */
553 if (isc_dsql_describe_bind(H->isc_status, &s, PDO_FB_SQLDA_VERSION, &num_sqlda)) {
554 break;
555 }
556
557 if (num_sqlda.sqld) {
558 S->in_sqlda = ecalloc(1,XSQLDA_LENGTH(num_sqlda.sqld));
559 S->in_sqlda->version = PDO_FB_SQLDA_VERSION;
560 S->in_sqlda->sqln = num_sqlda.sqld;
561
562 if (isc_dsql_describe_bind(H->isc_status, &s, PDO_FB_SQLDA_VERSION, S->in_sqlda)) {
563 break;
564 }
565 }
566
567 stmt->driver_data = S;
568 stmt->methods = &firebird_stmt_methods;
569 stmt->supports_placeholders = PDO_PLACEHOLDER_POSITIONAL;
570
571 return 1;
572
573 } while (0);
574
575 RECORD_ERROR(dbh);
576
577 zend_hash_destroy(np);
578 FREE_HASHTABLE(np);
579
580 if (S) {
581 if (S->in_sqlda) {
582 efree(S->in_sqlda);
583 }
584 efree(S);
585 }
586
587 return 0;
588 }
589 /* }}} */
590
591 /* called by PDO to execute a statement that doesn't produce a result set */
firebird_handle_doer(pdo_dbh_t * dbh,const char * sql,size_t sql_len)592 static zend_long firebird_handle_doer(pdo_dbh_t *dbh, const char *sql, size_t sql_len) /* {{{ */
593 {
594 pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
595 isc_stmt_handle stmt = PDO_FIREBIRD_HANDLE_INITIALIZER;
596 static char const info_count[] = { isc_info_sql_records };
597 char result[64];
598 int ret = 0;
599 XSQLDA in_sqlda, out_sqlda;
600
601 /* TODO no placeholders in exec() for now */
602 in_sqlda.version = out_sqlda.version = PDO_FB_SQLDA_VERSION;
603 in_sqlda.sqld = out_sqlda.sqld = 0;
604 out_sqlda.sqln = 1;
605
606 /* allocate and prepare statement */
607 if (!firebird_alloc_prepare_stmt(dbh, sql, sql_len, &out_sqlda, &stmt, 0)) {
608 return -1;
609 }
610
611 /* execute the statement */
612 if (isc_dsql_execute2(H->isc_status, &H->tr, &stmt, PDO_FB_SQLDA_VERSION, &in_sqlda, &out_sqlda)) {
613 RECORD_ERROR(dbh);
614 ret = -1;
615 goto free_statement;
616 }
617
618 /* find out how many rows were affected */
619 if (isc_dsql_sql_info(H->isc_status, &stmt, sizeof(info_count), const_cast(info_count),
620 sizeof(result), result)) {
621 RECORD_ERROR(dbh);
622 ret = -1;
623 goto free_statement;
624 }
625
626 if (result[0] == isc_info_sql_records) {
627 unsigned i = 3, result_size = isc_vax_integer(&result[1],2);
628
629 if (result_size > sizeof(result)) {
630 ret = -1;
631 goto free_statement;
632 }
633 while (result[i] != isc_info_end && i < result_size) {
634 short len = (short)isc_vax_integer(&result[i+1],2);
635 /* bail out on bad len */
636 if (len != 1 && len != 2 && len != 4) {
637 ret = -1;
638 goto free_statement;
639 }
640 if (result[i] != isc_info_req_select_count) {
641 ret += isc_vax_integer(&result[i+3],len);
642 }
643 i += len+3;
644 }
645 }
646
647 /* commit if we're in auto_commit mode */
648 if (dbh->auto_commit && isc_commit_retaining(H->isc_status, &H->tr)) {
649 RECORD_ERROR(dbh);
650 }
651
652 free_statement:
653
654 if (isc_dsql_free_statement(H->isc_status, &stmt, DSQL_drop)) {
655 RECORD_ERROR(dbh);
656 }
657
658 return ret;
659 }
660 /* }}} */
661
662 /* called by the PDO SQL parser to add quotes to values that are copied into SQL */
firebird_handle_quoter(pdo_dbh_t * dbh,const char * unquoted,size_t unquotedlen,char ** quoted,size_t * quotedlen,enum pdo_param_type paramtype)663 static int firebird_handle_quoter(pdo_dbh_t *dbh, const char *unquoted, size_t unquotedlen, /* {{{ */
664 char **quoted, size_t *quotedlen, enum pdo_param_type paramtype)
665 {
666 int qcount = 0;
667 char const *co, *l, *r;
668 char *c;
669
670 if (!unquotedlen) {
671 *quotedlen = 2;
672 *quoted = emalloc(*quotedlen+1);
673 strcpy(*quoted, "''");
674 return 1;
675 }
676
677 /* Firebird only requires single quotes to be doubled if string lengths are used */
678 /* count the number of ' characters */
679 for (co = unquoted; (co = strchr(co,'\'')); qcount++, co++);
680
681 *quotedlen = unquotedlen + qcount + 2;
682 *quoted = c = emalloc(*quotedlen+1);
683 *c++ = '\'';
684
685 /* foreach (chunk that ends in a quote) */
686 for (l = unquoted; (r = strchr(l,'\'')); l = r+1) {
687 strncpy(c, l, r-l+1);
688 c += (r-l+1);
689 /* add the second quote */
690 *c++ = '\'';
691 }
692
693 /* copy the remainder */
694 strncpy(c, l, *quotedlen-(c-*quoted)-1);
695 (*quoted)[*quotedlen-1] = '\'';
696 (*quoted)[*quotedlen] = '\0';
697
698 return 1;
699 }
700 /* }}} */
701
702 /* called by PDO to start a transaction */
firebird_handle_begin(pdo_dbh_t * dbh)703 static int firebird_handle_begin(pdo_dbh_t *dbh) /* {{{ */
704 {
705 pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
706 char tpb[8] = { isc_tpb_version3 }, *ptpb = tpb+1;
707 #ifdef abies_0
708 if (dbh->transaction_flags & PDO_TRANS_ISOLATION_LEVEL) {
709 if (dbh->transaction_flags & PDO_TRANS_READ_UNCOMMITTED) {
710 /* this is a poor fit, but it's all we have */
711 *ptpb++ = isc_tpb_read_committed;
712 *ptpb++ = isc_tpb_rec_version;
713 dbh->transaction_flags &= ~(PDO_TRANS_ISOLATION_LEVEL^PDO_TRANS_READ_UNCOMMITTED);
714 } else if (dbh->transaction_flags & PDO_TRANS_READ_COMMITTED) {
715 *ptpb++ = isc_tpb_read_committed;
716 *ptpb++ = isc_tpb_no_rec_version;
717 dbh->transaction_flags &= ~(PDO_TRANS_ISOLATION_LEVEL^PDO_TRANS_READ_COMMITTED);
718 } else if (dbh->transaction_flags & PDO_TRANS_REPEATABLE_READ) {
719 *ptpb++ = isc_tpb_concurrency;
720 dbh->transaction_flags &= ~(PDO_TRANS_ISOLATION_LEVEL^PDO_TRANS_REPEATABLE_READ);
721 } else {
722 *ptpb++ = isc_tpb_consistency;
723 dbh->transaction_flags &= ~(PDO_TRANS_ISOLATION_LEVEL^PDO_TRANS_SERIALIZABLE);
724 }
725 }
726
727 if (dbh->transaction_flags & PDO_TRANS_ACCESS_MODE) {
728 if (dbh->transaction_flags & PDO_TRANS_READONLY) {
729 *ptpb++ = isc_tpb_read;
730 dbh->transaction_flags &= ~(PDO_TRANS_ACCESS_MODE^PDO_TRANS_READONLY);
731 } else {
732 *ptpb++ = isc_tpb_write;
733 dbh->transaction_flags &= ~(PDO_TRANS_ACCESS_MODE^PDO_TRANS_READWRITE);
734 }
735 }
736
737 if (dbh->transaction_flags & PDO_TRANS_CONFLICT_RESOLUTION) {
738 if (dbh->transaction_flags & PDO_TRANS_RETRY) {
739 *ptpb++ = isc_tpb_wait;
740 dbh->transaction_flags &= ~(PDO_TRANS_CONFLICT_RESOLUTION^PDO_TRANS_RETRY);
741 } else {
742 *ptpb++ = isc_tpb_nowait;
743 dbh->transaction_flags &= ~(PDO_TRANS_CONFLICT_RESOLUTION^PDO_TRANS_ABORT);
744 }
745 }
746 #endif
747 if (isc_start_transaction(H->isc_status, &H->tr, 1, &H->db, (unsigned short)(ptpb-tpb), tpb)) {
748 RECORD_ERROR(dbh);
749 return 0;
750 }
751 return 1;
752 }
753 /* }}} */
754
755 /* called by PDO to commit a transaction */
firebird_handle_commit(pdo_dbh_t * dbh)756 static int firebird_handle_commit(pdo_dbh_t *dbh) /* {{{ */
757 {
758 pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
759
760 if (isc_commit_transaction(H->isc_status, &H->tr)) {
761 RECORD_ERROR(dbh);
762 return 0;
763 }
764 return 1;
765 }
766 /* }}} */
767
768 /* called by PDO to rollback a transaction */
firebird_handle_rollback(pdo_dbh_t * dbh)769 static int firebird_handle_rollback(pdo_dbh_t *dbh) /* {{{ */
770 {
771 pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
772
773 if (isc_rollback_transaction(H->isc_status, &H->tr)) {
774 RECORD_ERROR(dbh);
775 return 0;
776 }
777 return 1;
778 }
779 /* }}} */
780
781 /* used by prepare and exec to allocate a statement handle and prepare the SQL */
firebird_alloc_prepare_stmt(pdo_dbh_t * dbh,const char * sql,size_t sql_len,XSQLDA * out_sqlda,isc_stmt_handle * s,HashTable * named_params)782 static int firebird_alloc_prepare_stmt(pdo_dbh_t *dbh, const char *sql, size_t sql_len, /* {{{ */
783 XSQLDA *out_sqlda, isc_stmt_handle *s, HashTable *named_params)
784 {
785 pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
786 char *new_sql;
787
788 /* Firebird allows SQL statements up to 64k, so bail if it doesn't fit */
789 if (sql_len > 65536) {
790 strcpy(dbh->error_code, "01004");
791 return 0;
792 }
793
794 /* start a new transaction implicitly if auto_commit is enabled and no transaction is open */
795 if (dbh->auto_commit && !dbh->in_txn) {
796 /* dbh->transaction_flags = PDO_TRANS_READ_UNCOMMITTED; */
797
798 if (!firebird_handle_begin(dbh)) {
799 return 0;
800 }
801 dbh->in_txn = 1;
802 }
803
804 /* allocate the statement */
805 if (isc_dsql_allocate_statement(H->isc_status, &H->db, s)) {
806 RECORD_ERROR(dbh);
807 return 0;
808 }
809
810 /* in order to support named params, which Firebird itself doesn't,
811 we need to replace :foo by ?, and store the name we just replaced */
812 new_sql = emalloc(sql_len+1);
813 new_sql[0] = '\0';
814 if (!preprocess(sql, sql_len, new_sql, named_params)) {
815 strcpy(dbh->error_code, "07000");
816 efree(new_sql);
817 return 0;
818 }
819
820 /* prepare the statement */
821 if (isc_dsql_prepare(H->isc_status, &H->tr, s, 0, new_sql, H->sql_dialect, out_sqlda)) {
822 RECORD_ERROR(dbh);
823 efree(new_sql);
824 return 0;
825 }
826
827 efree(new_sql);
828 return 1;
829 }
830 /* }}} */
831
832 /* called by PDO to set a driver-specific dbh attribute */
firebird_handle_set_attribute(pdo_dbh_t * dbh,zend_long attr,zval * val)833 static int firebird_handle_set_attribute(pdo_dbh_t *dbh, zend_long attr, zval *val) /* {{{ */
834 {
835 pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
836
837 switch (attr) {
838 case PDO_ATTR_AUTOCOMMIT:
839 {
840 zend_bool bval = zval_get_long(val)? 1 : 0;
841
842 /* ignore if the new value equals the old one */
843 if (dbh->auto_commit ^ bval) {
844 if (dbh->in_txn) {
845 if (bval) {
846 /* turning on auto_commit with an open transaction is illegal, because
847 we won't know what to do with it */
848 H->last_app_error = "Cannot enable auto-commit while a transaction is already open";
849 return 0;
850 } else {
851 /* close the transaction */
852 if (!firebird_handle_commit(dbh)) {
853 break;
854 }
855 dbh->in_txn = 0;
856 }
857 }
858 dbh->auto_commit = bval;
859 }
860 }
861 return 1;
862
863 case PDO_ATTR_FETCH_TABLE_NAMES:
864 H->fetch_table_names = zval_get_long(val)? 1 : 0;
865 return 1;
866
867 case PDO_FB_ATTR_DATE_FORMAT:
868 {
869 zend_string *str = zval_try_get_string(val);
870 if (UNEXPECTED(!str)) {
871 return 0;
872 }
873 if (H->date_format) {
874 efree(H->date_format);
875 }
876 spprintf(&H->date_format, 0, "%s", ZSTR_VAL(str));
877 zend_string_release_ex(str, 0);
878 }
879 return 1;
880
881 case PDO_FB_ATTR_TIME_FORMAT:
882 {
883 zend_string *str = zval_try_get_string(val);
884 if (UNEXPECTED(!str)) {
885 return 0;
886 }
887 if (H->time_format) {
888 efree(H->time_format);
889 }
890 spprintf(&H->time_format, 0, "%s", ZSTR_VAL(str));
891 zend_string_release_ex(str, 0);
892 }
893 return 1;
894
895 case PDO_FB_ATTR_TIMESTAMP_FORMAT:
896 {
897 zend_string *str = zval_try_get_string(val);
898 if (UNEXPECTED(!str)) {
899 return 0;
900 }
901 if (H->timestamp_format) {
902 efree(H->timestamp_format);
903 }
904 spprintf(&H->timestamp_format, 0, "%s", ZSTR_VAL(str));
905 zend_string_release_ex(str, 0);
906 }
907 return 1;
908 }
909 return 0;
910 }
911 /* }}} */
912
913 #define INFO_BUF_LEN 512
914
915 /* callback to used to report database server info */
firebird_info_cb(void * arg,char const * s)916 static void firebird_info_cb(void *arg, char const *s) /* {{{ */
917 {
918 if (arg) {
919 if (*(char*)arg) { /* second call */
920 strlcat(arg, " ", INFO_BUF_LEN);
921 }
922 strlcat(arg, s, INFO_BUF_LEN);
923 }
924 }
925 /* }}} */
926
927 /* called by PDO to get a driver-specific dbh attribute */
firebird_handle_get_attribute(pdo_dbh_t * dbh,zend_long attr,zval * val)928 static int firebird_handle_get_attribute(pdo_dbh_t *dbh, zend_long attr, zval *val) /* {{{ */
929 {
930 pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
931
932 switch (attr) {
933 char tmp[INFO_BUF_LEN];
934
935 case PDO_ATTR_AUTOCOMMIT:
936 ZVAL_LONG(val,dbh->auto_commit);
937 return 1;
938
939 case PDO_ATTR_CONNECTION_STATUS:
940 ZVAL_BOOL(val, !isc_version(&H->db, firebird_info_cb, NULL));
941 return 1;
942
943 case PDO_ATTR_CLIENT_VERSION: {
944 #if defined(__GNUC__) || defined(PHP_WIN32)
945 info_func_t info_func = NULL;
946 #ifdef __GNUC__
947 info_func = (info_func_t)dlsym(RTLD_DEFAULT, "isc_get_client_version");
948 #else
949 HMODULE l = GetModuleHandle("fbclient");
950
951 if (!l) {
952 break;
953 }
954 info_func = (info_func_t)GetProcAddress(l, "isc_get_client_version");
955 #endif
956 if (info_func) {
957 info_func(tmp);
958 ZVAL_STRING(val, tmp);
959 }
960 #else
961 ZVAL_NULL(val);
962 #endif
963 }
964 return 1;
965
966 case PDO_ATTR_SERVER_VERSION:
967 case PDO_ATTR_SERVER_INFO:
968 *tmp = 0;
969
970 if (!isc_version(&H->db, firebird_info_cb, (void*)tmp)) {
971 ZVAL_STRING(val, tmp);
972 return 1;
973 }
974
975 case PDO_ATTR_FETCH_TABLE_NAMES:
976 ZVAL_BOOL(val, H->fetch_table_names);
977 return 1;
978 }
979 return 0;
980 }
981 /* }}} */
982
983 /* called by PDO to retrieve driver-specific information about an error that has occurred */
pdo_firebird_fetch_error_func(pdo_dbh_t * dbh,pdo_stmt_t * stmt,zval * info)984 static int pdo_firebird_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *info) /* {{{ */
985 {
986 pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
987 const ISC_STATUS *s = H->isc_status;
988 char buf[400];
989 zend_long i = 0, l, sqlcode = isc_sqlcode(s);
990
991 if (sqlcode) {
992 add_next_index_long(info, sqlcode);
993
994 while ((sizeof(buf)>(i+2))&&(l = fb_interpret(&buf[i],(sizeof(buf)-i-2),&s))) {
995 i += l;
996 strcpy(&buf[i++], " ");
997 }
998 add_next_index_string(info, buf);
999 } else if (H->last_app_error) {
1000 add_next_index_long(info, -999);
1001 add_next_index_string(info, const_cast(H->last_app_error));
1002 }
1003 return 1;
1004 }
1005 /* }}} */
1006
1007 static const struct pdo_dbh_methods firebird_methods = { /* {{{ */
1008 firebird_handle_closer,
1009 firebird_handle_preparer,
1010 firebird_handle_doer,
1011 firebird_handle_quoter,
1012 firebird_handle_begin,
1013 firebird_handle_commit,
1014 firebird_handle_rollback,
1015 firebird_handle_set_attribute,
1016 NULL, /* last_id not supported */
1017 pdo_firebird_fetch_error_func,
1018 firebird_handle_get_attribute,
1019 NULL /* check_liveness */
1020 };
1021 /* }}} */
1022
1023 /* the driver-specific PDO handle constructor */
pdo_firebird_handle_factory(pdo_dbh_t * dbh,zval * driver_options)1024 static int pdo_firebird_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ */
1025 {
1026 struct pdo_data_src_parser vars[] = {
1027 { "dbname", NULL, 0 },
1028 { "charset", NULL, 0 },
1029 { "role", NULL, 0 },
1030 { "dialect", "3", 0 },
1031 { "user", NULL, 0 },
1032 { "password", NULL, 0 }
1033 };
1034 int i, ret = 0;
1035 short buf_len = 256, dpb_len;
1036
1037 pdo_firebird_db_handle *H = dbh->driver_data = pecalloc(1,sizeof(*H),dbh->is_persistent);
1038
1039 php_pdo_parse_data_source(dbh->data_source, dbh->data_source_len, vars, 6);
1040
1041 if (!dbh->username && vars[4].optval) {
1042 dbh->username = pestrdup(vars[4].optval, dbh->is_persistent);
1043 }
1044
1045 if (!dbh->password && vars[5].optval) {
1046 dbh->password = pestrdup(vars[5].optval, dbh->is_persistent);
1047 }
1048
1049 do {
1050 static char const dpb_flags[] = {
1051 isc_dpb_user_name, isc_dpb_password, isc_dpb_lc_ctype, isc_dpb_sql_role_name };
1052 char const *dpb_values[] = { dbh->username, dbh->password, vars[1].optval, vars[2].optval };
1053 char dpb_buffer[256] = { isc_dpb_version1 }, *dpb;
1054
1055 dpb = dpb_buffer + 1;
1056
1057 /* loop through all the provided arguments and set dpb fields accordingly */
1058 for (i = 0; i < sizeof(dpb_flags); ++i) {
1059 if (dpb_values[i] && buf_len > 0) {
1060 dpb_len = slprintf(dpb, buf_len, "%c%c%s", dpb_flags[i], (unsigned char)strlen(dpb_values[i]),
1061 dpb_values[i]);
1062 dpb += dpb_len;
1063 buf_len -= dpb_len;
1064 }
1065 }
1066
1067 H->sql_dialect = PDO_FB_DIALECT;
1068 if (vars[3].optval) {
1069 H->sql_dialect = atoi(vars[3].optval);
1070 }
1071
1072 /* fire it up baby! */
1073 if (isc_attach_database(H->isc_status, 0, vars[0].optval, &H->db,(short)(dpb-dpb_buffer), dpb_buffer)) {
1074 break;
1075 }
1076
1077 dbh->methods = &firebird_methods;
1078 dbh->native_case = PDO_CASE_UPPER;
1079 dbh->alloc_own_columns = 1;
1080
1081 ret = 1;
1082
1083 } while (0);
1084
1085 for (i = 0; i < sizeof(vars)/sizeof(vars[0]); ++i) {
1086 if (vars[i].freeme) {
1087 efree(vars[i].optval);
1088 }
1089 }
1090
1091 if (!dbh->methods) {
1092 char errmsg[512];
1093 const ISC_STATUS *s = H->isc_status;
1094 fb_interpret(errmsg, sizeof(errmsg),&s);
1095 zend_throw_exception_ex(php_pdo_get_exception(), H->isc_status[1], "SQLSTATE[%s] [%d] %s",
1096 "HY000", H->isc_status[1], errmsg);
1097 }
1098
1099 if (!ret) {
1100 firebird_handle_closer(dbh);
1101 }
1102
1103 return ret;
1104 }
1105 /* }}} */
1106
1107
1108 const pdo_driver_t pdo_firebird_driver = { /* {{{ */
1109 PDO_DRIVER_HEADER(firebird),
1110 pdo_firebird_handle_factory
1111 };
1112 /* }}} */
1113