firebird_driver.c 29 KB

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