You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

688 lines
18 KiB

21 years ago
21 years ago
21 years ago
21 years ago
21 years ago
18 years ago
18 years ago
18 years ago
  1. /*
  2. +----------------------------------------------------------------------+
  3. | PHP Version 5 |
  4. +----------------------------------------------------------------------+
  5. | Copyright (c) 1997-2009 The PHP Group |
  6. +----------------------------------------------------------------------+
  7. | This source file is subject to version 3.01 of the PHP license, |
  8. | that is bundled with this package in the file LICENSE, and is |
  9. | available through the world-wide-web at the following url: |
  10. | http://www.php.net/license/3_01.txt |
  11. | If you did not receive a copy of the PHP license and are unable to |
  12. | obtain it through the world-wide-web, please send a note to |
  13. | license@php.net so we can mail you a copy immediately. |
  14. +----------------------------------------------------------------------+
  15. | Authors: Edin Kadribasic <edink@emini.dk> |
  16. | Ilia Alshanestsky <ilia@prohost.org> |
  17. | Wez Furlong <wez@php.net> |
  18. +----------------------------------------------------------------------+
  19. */
  20. /* $Id$ */
  21. #ifdef HAVE_CONFIG_H
  22. #include "config.h"
  23. #endif
  24. #include "php.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_pgsql.h"
  30. #include "php_pdo_pgsql_int.h"
  31. #if HAVE_NETINET_IN_H
  32. #include <netinet/in.h>
  33. #endif
  34. /* from postgresql/src/include/catalog/pg_type.h */
  35. #define BOOLOID 16
  36. #define BYTEAOID 17
  37. #define INT8OID 20
  38. #define INT2OID 21
  39. #define INT4OID 23
  40. #define TEXTOID 25
  41. #define OIDOID 26
  42. static int pgsql_stmt_dtor(pdo_stmt_t *stmt TSRMLS_DC)
  43. {
  44. pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data;
  45. if (S->result) {
  46. /* free the resource */
  47. PQclear(S->result);
  48. S->result = NULL;
  49. }
  50. #if HAVE_PQPREPARE
  51. if (S->stmt_name) {
  52. pdo_pgsql_db_handle *H = S->H;
  53. char *q = NULL;
  54. PGresult *res;
  55. if (S->is_prepared) {
  56. spprintf(&q, 0, "DEALLOCATE %s", S->stmt_name);
  57. res = PQexec(H->server, q);
  58. efree(q);
  59. if (res) {
  60. PQclear(res);
  61. }
  62. }
  63. efree(S->stmt_name);
  64. S->stmt_name = NULL;
  65. }
  66. if (S->param_lengths) {
  67. efree(S->param_lengths);
  68. S->param_lengths = NULL;
  69. }
  70. if (S->param_values) {
  71. efree(S->param_values);
  72. S->param_values = NULL;
  73. }
  74. if (S->param_formats) {
  75. efree(S->param_formats);
  76. S->param_formats = NULL;
  77. }
  78. if (S->param_types) {
  79. efree(S->param_types);
  80. S->param_types = NULL;
  81. }
  82. if (S->query) {
  83. efree(S->query);
  84. S->query = NULL;
  85. }
  86. #endif
  87. if (S->cursor_name) {
  88. pdo_pgsql_db_handle *H = S->H;
  89. char *q = NULL;
  90. PGresult *res;
  91. spprintf(&q, 0, "CLOSE %s", S->cursor_name);
  92. res = PQexec(H->server, q);
  93. efree(q);
  94. if (res) PQclear(res);
  95. efree(S->cursor_name);
  96. S->cursor_name = NULL;
  97. }
  98. if(S->cols) {
  99. efree(S->cols);
  100. S->cols = NULL;
  101. }
  102. efree(S);
  103. stmt->driver_data = NULL;
  104. return 1;
  105. }
  106. static int pgsql_stmt_execute(pdo_stmt_t *stmt TSRMLS_DC)
  107. {
  108. pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data;
  109. pdo_pgsql_db_handle *H = S->H;
  110. ExecStatusType status;
  111. /* ensure that we free any previous unfetched results */
  112. if(S->result) {
  113. PQclear(S->result);
  114. S->result = NULL;
  115. }
  116. S->current_row = 0;
  117. #if HAVE_PQPREPARE
  118. if (S->stmt_name) {
  119. /* using a prepared statement */
  120. if (!S->is_prepared) {
  121. stmt_retry:
  122. /* we deferred the prepare until now, because we didn't
  123. * know anything about the parameter types; now we do */
  124. S->result = PQprepare(H->server, S->stmt_name, S->query,
  125. stmt->bound_params ? zend_hash_num_elements(stmt->bound_params) : 0,
  126. S->param_types);
  127. status = PQresultStatus(S->result);
  128. switch (status) {
  129. case PGRES_COMMAND_OK:
  130. case PGRES_TUPLES_OK:
  131. /* it worked */
  132. S->is_prepared = 1;
  133. PQclear(S->result);
  134. break;
  135. default: {
  136. char *sqlstate = pdo_pgsql_sqlstate(S->result);
  137. /* 42P05 means that the prepared statement already existed. this can happen if you use
  138. * a connection pooling software line pgpool which doesn't close the db-connection once
  139. * php disconnects. if php dies (no chanche to run RSHUTDOWN) during execution it has no
  140. * chance to DEALLOCATE the prepared statements it has created. so, if we hit a 42P05 we
  141. * deallocate it and retry ONCE (thies 2005.12.15)
  142. */
  143. if (!strcmp(sqlstate, "42P05")) {
  144. char buf[100]; /* stmt_name == "pdo_pgsql_cursor_%08x" */
  145. PGresult *res;
  146. snprintf(buf, sizeof(buf), "DEALLOCATE %s", S->stmt_name);
  147. res = PQexec(H->server, buf);
  148. if (res) {
  149. PQclear(res);
  150. }
  151. goto stmt_retry;
  152. } else {
  153. pdo_pgsql_error_stmt(stmt, status, sqlstate);
  154. return 0;
  155. }
  156. }
  157. }
  158. }
  159. S->result = PQexecPrepared(H->server, S->stmt_name,
  160. stmt->bound_params ?
  161. zend_hash_num_elements(stmt->bound_params) :
  162. 0,
  163. (const char**)S->param_values,
  164. S->param_lengths,
  165. S->param_formats,
  166. 0);
  167. } else
  168. #endif
  169. if (S->cursor_name) {
  170. char *q = NULL;
  171. spprintf(&q, 0, "DECLARE %s CURSOR FOR %s", S->cursor_name, stmt->active_query_string);
  172. S->result = PQexec(H->server, q);
  173. efree(q);
  174. } else {
  175. S->result = PQexec(H->server, stmt->active_query_string);
  176. }
  177. status = PQresultStatus(S->result);
  178. if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK) {
  179. pdo_pgsql_error_stmt(stmt, status, pdo_pgsql_sqlstate(S->result));
  180. return 0;
  181. }
  182. if (!stmt->executed && !stmt->column_count) {
  183. stmt->column_count = (int) PQnfields(S->result);
  184. S->cols = ecalloc(stmt->column_count, sizeof(pdo_pgsql_column));
  185. }
  186. if (status == PGRES_COMMAND_OK) {
  187. stmt->row_count = (long)atoi(PQcmdTuples(S->result));
  188. H->pgoid = PQoidValue(S->result);
  189. } else {
  190. stmt->row_count = (long)PQntuples(S->result);
  191. }
  192. return 1;
  193. }
  194. static int pgsql_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *param,
  195. enum pdo_param_event event_type TSRMLS_DC)
  196. {
  197. #if HAVE_PQPREPARE
  198. pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data;
  199. if (S->stmt_name && param->is_param) {
  200. switch (event_type) {
  201. case PDO_PARAM_EVT_FREE:
  202. if (param->driver_data) {
  203. efree(param->driver_data);
  204. }
  205. break;
  206. case PDO_PARAM_EVT_NORMALIZE:
  207. /* decode name from $1, $2 into 0, 1 etc. */
  208. if (param->name) {
  209. if (param->name[0] == '$') {
  210. param->paramno = atoi(param->name + 1);
  211. } else {
  212. /* resolve parameter name to rewritten name */
  213. char *nameptr;
  214. if (stmt->bound_param_map && SUCCESS == zend_hash_find(stmt->bound_param_map,
  215. param->name, param->namelen + 1, (void**)&nameptr)) {
  216. param->paramno = atoi(nameptr + 1) - 1;
  217. } else {
  218. pdo_raise_impl_error(stmt->dbh, stmt, "HY093", param->name TSRMLS_CC);
  219. return 0;
  220. }
  221. }
  222. }
  223. break;
  224. case PDO_PARAM_EVT_ALLOC:
  225. case PDO_PARAM_EVT_EXEC_POST:
  226. case PDO_PARAM_EVT_FETCH_PRE:
  227. case PDO_PARAM_EVT_FETCH_POST:
  228. /* work is handled by EVT_NORMALIZE */
  229. return 1;
  230. case PDO_PARAM_EVT_EXEC_PRE:
  231. if (!stmt->bound_param_map) {
  232. return 0;
  233. }
  234. if (!S->param_values) {
  235. S->param_values = ecalloc(
  236. zend_hash_num_elements(stmt->bound_param_map),
  237. sizeof(char*));
  238. S->param_lengths = ecalloc(
  239. zend_hash_num_elements(stmt->bound_param_map),
  240. sizeof(int));
  241. S->param_formats = ecalloc(
  242. zend_hash_num_elements(stmt->bound_param_map),
  243. sizeof(int));
  244. S->param_types = ecalloc(
  245. zend_hash_num_elements(stmt->bound_param_map),
  246. sizeof(Oid));
  247. }
  248. if (param->paramno >= 0) {
  249. if (param->paramno > zend_hash_num_elements(stmt->bound_param_map)) {
  250. pdo_pgsql_error_stmt(stmt, PGRES_FATAL_ERROR, "HY105");
  251. return 0;
  252. }
  253. if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB &&
  254. Z_TYPE_P(param->parameter) == IS_RESOURCE) {
  255. php_stream *stm;
  256. php_stream_from_zval_no_verify(stm, &param->parameter);
  257. if (stm) {
  258. if (php_stream_is(stm, &pdo_pgsql_lob_stream_ops)) {
  259. struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self*)stm->abstract;
  260. pdo_pgsql_bound_param *P = param->driver_data;
  261. if (P == NULL) {
  262. P = ecalloc(1, sizeof(*P));
  263. param->driver_data = P;
  264. }
  265. P->oid = htonl(self->oid);
  266. S->param_values[param->paramno] = (char*)&P->oid;
  267. S->param_lengths[param->paramno] = sizeof(P->oid);
  268. S->param_formats[param->paramno] = 1;
  269. S->param_types[param->paramno] = OIDOID;
  270. return 1;
  271. } else {
  272. int len;
  273. SEPARATE_ZVAL_IF_NOT_REF(&param->parameter);
  274. Z_TYPE_P(param->parameter) = IS_STRING;
  275. if ((len = php_stream_copy_to_mem(stm, &Z_STRVAL_P(param->parameter), PHP_STREAM_COPY_ALL, 0)) > 0) {
  276. Z_STRLEN_P(param->parameter) = len;
  277. } else {
  278. ZVAL_EMPTY_STRING(param->parameter);
  279. }
  280. }
  281. } else {
  282. /* expected a stream resource */
  283. pdo_pgsql_error_stmt(stmt, PGRES_FATAL_ERROR, "HY105");
  284. return 0;
  285. }
  286. }
  287. if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_NULL ||
  288. Z_TYPE_P(param->parameter) == IS_NULL) {
  289. S->param_values[param->paramno] = NULL;
  290. S->param_lengths[param->paramno] = 0;
  291. } else if (Z_TYPE_P(param->parameter) == IS_BOOL) {
  292. S->param_values[param->paramno] = Z_BVAL_P(param->parameter) ? "t" : "f";
  293. S->param_lengths[param->paramno] = 1;
  294. S->param_formats[param->paramno] = 0;
  295. } else {
  296. SEPARATE_ZVAL_IF_NOT_REF(&param->parameter);
  297. convert_to_string(param->parameter);
  298. S->param_values[param->paramno] = Z_STRVAL_P(param->parameter);
  299. S->param_lengths[param->paramno] = Z_STRLEN_P(param->parameter);
  300. S->param_formats[param->paramno] = 0;
  301. }
  302. if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB) {
  303. S->param_types[param->paramno] = 0;
  304. S->param_formats[param->paramno] = 1;
  305. } else {
  306. S->param_types[param->paramno] = 0;
  307. }
  308. }
  309. break;
  310. }
  311. }
  312. #endif
  313. return 1;
  314. }
  315. static int pgsql_stmt_fetch(pdo_stmt_t *stmt,
  316. enum pdo_fetch_orientation ori, long offset TSRMLS_DC)
  317. {
  318. pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data;
  319. if (S->cursor_name) {
  320. char *ori_str;
  321. char *q = NULL;
  322. ExecStatusType status;
  323. switch (ori) {
  324. case PDO_FETCH_ORI_NEXT: ori_str = "FORWARD"; break;
  325. case PDO_FETCH_ORI_PRIOR: ori_str = "BACKWARD"; break;
  326. case PDO_FETCH_ORI_REL: ori_str = "RELATIVE"; break;
  327. default:
  328. return 0;
  329. }
  330. spprintf(&q, 0, "FETCH %s %ld FROM %s", ori_str, offset, S->cursor_name);
  331. S->result = PQexec(S->H->server, q);
  332. efree(q);
  333. status = PQresultStatus(S->result);
  334. if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK) {
  335. pdo_pgsql_error_stmt(stmt, status, pdo_pgsql_sqlstate(S->result));
  336. return 0;
  337. }
  338. S->current_row = 1;
  339. return 1;
  340. } else {
  341. if (S->current_row < stmt->row_count) {
  342. S->current_row++;
  343. return 1;
  344. } else {
  345. return 0;
  346. }
  347. }
  348. }
  349. static int pgsql_stmt_describe(pdo_stmt_t *stmt, int colno TSRMLS_DC)
  350. {
  351. pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data;
  352. struct pdo_column_data *cols = stmt->columns;
  353. struct pdo_bound_param_data *param;
  354. if (!S->result) {
  355. return 0;
  356. }
  357. cols[colno].name = estrdup(PQfname(S->result, colno));
  358. cols[colno].namelen = strlen(cols[colno].name);
  359. cols[colno].maxlen = PQfsize(S->result, colno);
  360. cols[colno].precision = PQfmod(S->result, colno);
  361. S->cols[colno].pgsql_type = PQftype(S->result, colno);
  362. switch(S->cols[colno].pgsql_type) {
  363. case BOOLOID:
  364. cols[colno].param_type = PDO_PARAM_BOOL;
  365. break;
  366. case OIDOID:
  367. /* did the user bind the column as a LOB ? */
  368. if (stmt->bound_columns && (
  369. SUCCESS == zend_hash_index_find(stmt->bound_columns,
  370. colno, (void**)&param) ||
  371. SUCCESS == zend_hash_find(stmt->bound_columns,
  372. cols[colno].name, cols[colno].namelen,
  373. (void**)&param))) {
  374. if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB) {
  375. cols[colno].param_type = PDO_PARAM_LOB;
  376. break;
  377. }
  378. }
  379. cols[colno].param_type = PDO_PARAM_INT;
  380. break;
  381. case INT2OID:
  382. case INT4OID:
  383. cols[colno].param_type = PDO_PARAM_INT;
  384. break;
  385. case INT8OID:
  386. if (sizeof(long)>=8) {
  387. cols[colno].param_type = PDO_PARAM_INT;
  388. } else {
  389. cols[colno].param_type = PDO_PARAM_STR;
  390. }
  391. break;
  392. case BYTEAOID:
  393. cols[colno].param_type = PDO_PARAM_LOB;
  394. break;
  395. default:
  396. cols[colno].param_type = PDO_PARAM_STR;
  397. }
  398. return 1;
  399. }
  400. /* PQunescapeBytea() from PostgreSQL 7.3 to provide bytea unescape feature to 7.2 users.
  401. Renamed to php_pdo_pgsql_unescape_bytea() */
  402. /*
  403. * PQunescapeBytea - converts the null terminated string representation
  404. * of a bytea, strtext, into binary, filling a buffer. It returns a
  405. * pointer to the buffer which is NULL on error, and the size of the
  406. * buffer in retbuflen. The pointer may subsequently be used as an
  407. * argument to the function free(3). It is the reverse of PQescapeBytea.
  408. *
  409. * The following transformations are reversed:
  410. * '\0' == ASCII 0 == \000
  411. * '\'' == ASCII 39 == \'
  412. * '\\' == ASCII 92 == \\
  413. *
  414. * States:
  415. * 0 normal 0->1->2->3->4
  416. * 1 \ 1->5
  417. * 2 \0 1->6
  418. * 3 \00
  419. * 4 \000
  420. * 5 \'
  421. * 6 \\
  422. */
  423. static unsigned char *php_pdo_pgsql_unescape_bytea(unsigned char *strtext, size_t *retbuflen)
  424. {
  425. size_t buflen;
  426. unsigned char *buffer,
  427. *sp,
  428. *bp;
  429. unsigned int state = 0;
  430. if (strtext == NULL)
  431. return NULL;
  432. buflen = strlen(strtext); /* will shrink, also we discover if
  433. * strtext */
  434. buffer = (unsigned char *) emalloc(buflen); /* isn't NULL terminated */
  435. for (bp = buffer, sp = strtext; *sp != '\0'; bp++, sp++)
  436. {
  437. switch (state)
  438. {
  439. case 0:
  440. if (*sp == '\\')
  441. state = 1;
  442. *bp = *sp;
  443. break;
  444. case 1:
  445. if (*sp == '\'') /* state=5 */
  446. { /* replace \' with 39 */
  447. bp--;
  448. *bp = '\'';
  449. buflen--;
  450. state = 0;
  451. }
  452. else if (*sp == '\\') /* state=6 */
  453. { /* replace \\ with 92 */
  454. bp--;
  455. *bp = '\\';
  456. buflen--;
  457. state = 0;
  458. }
  459. else
  460. {
  461. if (isdigit(*sp))
  462. state = 2;
  463. else
  464. state = 0;
  465. *bp = *sp;
  466. }
  467. break;
  468. case 2:
  469. if (isdigit(*sp))
  470. state = 3;
  471. else
  472. state = 0;
  473. *bp = *sp;
  474. break;
  475. case 3:
  476. if (isdigit(*sp)) /* state=4 */
  477. {
  478. unsigned char *start, *end, buf[4]; /* 000 + '\0' */
  479. bp -= 3;
  480. memcpy(buf, sp-2, 3);
  481. buf[3] = '\0';
  482. start = buf;
  483. *bp = (unsigned char)strtoul(start, (char **)&end, 8);
  484. buflen -= 3;
  485. state = 0;
  486. }
  487. else
  488. {
  489. *bp = *sp;
  490. state = 0;
  491. }
  492. break;
  493. }
  494. }
  495. buffer = erealloc(buffer, buflen+1);
  496. buffer[buflen] = '\0';
  497. *retbuflen = buflen;
  498. return buffer;
  499. }
  500. static int pgsql_stmt_get_col(pdo_stmt_t *stmt, int colno, char **ptr, unsigned long *len, int *caller_frees TSRMLS_DC)
  501. {
  502. pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data;
  503. struct pdo_column_data *cols = stmt->columns;
  504. size_t tmp_len;
  505. if (!S->result) {
  506. return 0;
  507. }
  508. /* We have already increased count by 1 in pgsql_stmt_fetch() */
  509. if (PQgetisnull(S->result, S->current_row - 1, colno)) { /* Check if we got NULL */
  510. *ptr = NULL;
  511. *len = 0;
  512. } else {
  513. *ptr = PQgetvalue(S->result, S->current_row - 1, colno);
  514. *len = PQgetlength(S->result, S->current_row - 1, colno);
  515. switch(cols[colno].param_type) {
  516. case PDO_PARAM_INT:
  517. S->cols[colno].intval = atol(*ptr);
  518. *ptr = (char *) &(S->cols[colno].intval);
  519. *len = sizeof(long);
  520. break;
  521. case PDO_PARAM_BOOL:
  522. S->cols[colno].boolval = **ptr == 't' ? 1: 0;
  523. *ptr = (char *) &(S->cols[colno].boolval);
  524. *len = sizeof(zend_bool);
  525. break;
  526. case PDO_PARAM_LOB:
  527. if (S->cols[colno].pgsql_type == OIDOID) {
  528. /* ooo, a real large object */
  529. char *end_ptr;
  530. Oid oid = (Oid)strtoul(*ptr, &end_ptr, 10);
  531. int loid = lo_open(S->H->server, oid, INV_READ);
  532. if (loid >= 0) {
  533. *ptr = (char*)pdo_pgsql_create_lob_stream(stmt->dbh, loid, oid TSRMLS_CC);
  534. *len = 0;
  535. return *ptr ? 1 : 0;
  536. }
  537. *ptr = NULL;
  538. *len = 0;
  539. return 0;
  540. } else {
  541. *ptr = php_pdo_pgsql_unescape_bytea(*ptr, &tmp_len);
  542. *len = tmp_len;
  543. *caller_frees = 1;
  544. }
  545. break;
  546. case PDO_PARAM_NULL:
  547. case PDO_PARAM_STR:
  548. case PDO_PARAM_STMT:
  549. case PDO_PARAM_INPUT_OUTPUT:
  550. case PDO_PARAM_ZVAL:
  551. default:
  552. break;
  553. }
  554. }
  555. return 1;
  556. }
  557. static int pgsql_stmt_get_column_meta(pdo_stmt_t *stmt, long colno, zval *return_value TSRMLS_DC)
  558. {
  559. pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data;
  560. PGresult *res;
  561. char *q=NULL;
  562. ExecStatusType status;
  563. if (!S->result) {
  564. return FAILURE;
  565. }
  566. if (colno >= stmt->column_count) {
  567. return FAILURE;
  568. }
  569. array_init(return_value);
  570. add_assoc_long(return_value, "pgsql:oid", S->cols[colno].pgsql_type);
  571. /* Fetch metadata from Postgres system catalogue */
  572. spprintf(&q, 0, "SELECT TYPNAME FROM PG_TYPE WHERE OID=%d", S->cols[colno].pgsql_type);
  573. res = PQexec(S->H->server, q);
  574. efree(q);
  575. status = PQresultStatus(res);
  576. if (status != PGRES_TUPLES_OK) {
  577. /* Failed to get system catalogue, but return success
  578. * with the data we have collected so far
  579. */
  580. goto done;
  581. }
  582. /* We want exactly one row returned */
  583. if (1 != PQntuples(res)) {
  584. goto done;
  585. }
  586. add_assoc_string(return_value, "native_type", PQgetvalue(res, 0, 0), 1);
  587. done:
  588. PQclear(res);
  589. return 1;
  590. }
  591. static int pdo_pgsql_stmt_cursor_closer(pdo_stmt_t *stmt TSRMLS_DC)
  592. {
  593. return 1;
  594. }
  595. struct pdo_stmt_methods pgsql_stmt_methods = {
  596. pgsql_stmt_dtor,
  597. pgsql_stmt_execute,
  598. pgsql_stmt_fetch,
  599. pgsql_stmt_describe,
  600. pgsql_stmt_get_col,
  601. pgsql_stmt_param_hook,
  602. NULL, /* set_attr */
  603. NULL, /* get_attr */
  604. pgsql_stmt_get_column_meta,
  605. NULL, /* next_rowset */
  606. pdo_pgsql_stmt_cursor_closer
  607. };
  608. /*
  609. * Local variables:
  610. * tab-width: 4
  611. * c-basic-offset: 4
  612. * End:
  613. * vim600: noet sw=4 ts=4 fdm=marker
  614. * vim<600: noet sw=4 ts=4
  615. */