1/*
2  +----------------------------------------------------------------------+
3  | PHP Version 5                                                        |
4  +----------------------------------------------------------------------+
5  | Copyright (c) 1997-2015 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
21/* $Id$ */
22
23#ifdef HAVE_CONFIG_H
24#include "config.h"
25#endif
26
27#include "php.h"
28#include "php_ini.h"
29#include "ext/standard/info.h"
30#include "ext/standard/php_string.h"
31#include "pdo/php_pdo.h"
32#include "pdo/php_pdo_driver.h"
33#include "ext/standard/file.h"
34
35#undef PACKAGE_BUGREPORT
36#undef PACKAGE_NAME
37#undef PACKAGE_STRING
38#undef PACKAGE_TARNAME
39#undef PACKAGE_VERSION
40#include "pg_config.h" /* needed for PG_VERSION */
41#include "php_pdo_pgsql.h"
42#include "php_pdo_pgsql_int.h"
43#include "zend_exceptions.h"
44
45static char * _pdo_pgsql_trim_message(const char *message, int persistent)
46{
47    register int i = strlen(message)-1;
48    char *tmp;
49
50    if (i>1 && (message[i-1] == '\r' || message[i-1] == '\n') && message[i] == '.') {
51        --i;
52    }
53    while (i>0 && (message[i] == '\r' || message[i] == '\n')) {
54        --i;
55    }
56    ++i;
57    tmp = pemalloc(i + 1, persistent);
58    memcpy(tmp, message, i);
59    tmp[i] = '\0';
60
61    return tmp;
62}
63
64static char * _pdo_pgsql_escape_credentials(char *str TSRMLS_DC)
65{
66    int len;
67
68    if (str) {
69        return php_addcslashes(str, strlen(str), &len, 0, "\\'", sizeof("\\'") TSRMLS_CC);
70    }
71
72    return NULL;
73}
74
75int _pdo_pgsql_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, int errcode, const char *sqlstate, const char *file, int line TSRMLS_DC) /* {{{ */
76{
77    pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data;
78    pdo_error_type *pdo_err = stmt ? &stmt->error_code : &dbh->error_code;
79    pdo_pgsql_error_info *einfo = &H->einfo;
80    char *errmsg = PQerrorMessage(H->server);
81
82    einfo->errcode = errcode;
83    einfo->file = file;
84    einfo->line = line;
85
86    if (einfo->errmsg) {
87        pefree(einfo->errmsg, dbh->is_persistent);
88        einfo->errmsg = NULL;
89    }
90
91    if (sqlstate == NULL || strlen(sqlstate) >= sizeof(pdo_error_type)) {
92        strcpy(*pdo_err, "HY000");
93    }
94    else {
95        strcpy(*pdo_err, sqlstate);
96    }
97
98    if (errmsg) {
99        einfo->errmsg = _pdo_pgsql_trim_message(errmsg, dbh->is_persistent);
100    }
101
102    if (!dbh->methods) {
103        zend_throw_exception_ex(php_pdo_get_exception(), einfo->errcode TSRMLS_CC, "SQLSTATE[%s] [%d] %s",
104                *pdo_err, einfo->errcode, einfo->errmsg);
105    }
106
107    return errcode;
108}
109/* }}} */
110
111static void _pdo_pgsql_notice(pdo_dbh_t *dbh, const char *message) /* {{{ */
112{
113/*  pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; */
114}
115/* }}} */
116
117static int pdo_pgsql_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *info TSRMLS_DC) /* {{{ */
118{
119    pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data;
120    pdo_pgsql_error_info *einfo = &H->einfo;
121
122    if (einfo->errcode) {
123        add_next_index_long(info, einfo->errcode);
124        add_next_index_string(info, einfo->errmsg, 1);
125    }
126
127    return 1;
128}
129/* }}} */
130
131/* {{{ pdo_pgsql_create_lob_stream */
132static size_t pgsql_lob_write(php_stream *stream, const char *buf, size_t count TSRMLS_DC)
133{
134    struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self*)stream->abstract;
135    return lo_write(self->conn, self->lfd, (char*)buf, count);
136}
137
138static size_t pgsql_lob_read(php_stream *stream, char *buf, size_t count TSRMLS_DC)
139{
140    struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self*)stream->abstract;
141    return lo_read(self->conn, self->lfd, buf, count);
142}
143
144static int pgsql_lob_close(php_stream *stream, int close_handle TSRMLS_DC)
145{
146    struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self*)stream->abstract;
147    pdo_dbh_t *dbh = self->dbh;
148
149    if (close_handle) {
150        lo_close(self->conn, self->lfd);
151    }
152    efree(self);
153    php_pdo_dbh_delref(dbh TSRMLS_CC);
154    return 0;
155}
156
157static int pgsql_lob_flush(php_stream *stream TSRMLS_DC)
158{
159    return 0;
160}
161
162static int pgsql_lob_seek(php_stream *stream, off_t offset, int whence,
163        off_t *newoffset TSRMLS_DC)
164{
165    struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self*)stream->abstract;
166    int pos = lo_lseek(self->conn, self->lfd, offset, whence);
167    *newoffset = pos;
168    return pos >= 0 ? 0 : -1;
169}
170
171php_stream_ops pdo_pgsql_lob_stream_ops = {
172    pgsql_lob_write,
173    pgsql_lob_read,
174    pgsql_lob_close,
175    pgsql_lob_flush,
176    "pdo_pgsql lob stream",
177    pgsql_lob_seek,
178    NULL,
179    NULL,
180    NULL
181};
182
183php_stream *pdo_pgsql_create_lob_stream(pdo_dbh_t *dbh, int lfd, Oid oid TSRMLS_DC)
184{
185    php_stream *stm;
186    struct pdo_pgsql_lob_self *self = ecalloc(1, sizeof(*self));
187    pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data;
188
189    self->dbh = dbh;
190    self->lfd = lfd;
191    self->oid = oid;
192    self->conn = H->server;
193
194    stm = php_stream_alloc(&pdo_pgsql_lob_stream_ops, self, 0, "r+b");
195
196    if (stm) {
197        php_pdo_dbh_addref(dbh TSRMLS_CC);
198        return stm;
199    }
200
201    efree(self);
202    return NULL;
203}
204/* }}} */
205
206static int pgsql_handle_closer(pdo_dbh_t *dbh TSRMLS_DC) /* {{{ */
207{
208    pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data;
209    if (H) {
210        if (H->server) {
211            PQfinish(H->server);
212            H->server = NULL;
213        }
214        if (H->einfo.errmsg) {
215            pefree(H->einfo.errmsg, dbh->is_persistent);
216            H->einfo.errmsg = NULL;
217        }
218        pefree(H, dbh->is_persistent);
219        dbh->driver_data = NULL;
220    }
221    return 0;
222}
223/* }}} */
224
225static int pgsql_handle_preparer(pdo_dbh_t *dbh, const char *sql, long sql_len, pdo_stmt_t *stmt, zval *driver_options TSRMLS_DC)
226{
227    pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data;
228    pdo_pgsql_stmt *S = ecalloc(1, sizeof(pdo_pgsql_stmt));
229    int scrollable;
230#if HAVE_PQPREPARE
231    int ret;
232    char *nsql = NULL;
233    int nsql_len = 0;
234    int emulate = 0;
235#endif
236
237    S->H = H;
238    stmt->driver_data = S;
239    stmt->methods = &pgsql_stmt_methods;
240
241    scrollable = pdo_attr_lval(driver_options, PDO_ATTR_CURSOR,
242        PDO_CURSOR_FWDONLY TSRMLS_CC) == PDO_CURSOR_SCROLL;
243
244    if (scrollable) {
245        if (S->cursor_name) {
246            efree(S->cursor_name);
247        }
248        spprintf(&S->cursor_name, 0, "pdo_crsr_%08x", ++H->stmt_counter);
249#if HAVE_PQPREPARE
250        emulate = 1;
251#endif
252    }
253
254#if HAVE_PQPREPARE
255    else if (driver_options) {
256        if (pdo_attr_lval(driver_options, PDO_PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT, H->disable_native_prepares TSRMLS_CC) == 1 ||
257            pdo_attr_lval(driver_options, PDO_ATTR_EMULATE_PREPARES, H->emulate_prepares TSRMLS_CC) == 1) {
258            emulate = 1;
259        }
260    } else {
261        emulate = H->disable_native_prepares || H->emulate_prepares;
262    }
263
264    if (!emulate && PQprotocolVersion(H->server) > 2) {
265        stmt->supports_placeholders = PDO_PLACEHOLDER_NAMED;
266        stmt->named_rewrite_template = "$%d";
267        ret = pdo_parse_params(stmt, (char*)sql, sql_len, &nsql, &nsql_len TSRMLS_CC);
268
269        if (ret == 1) {
270            /* query was re-written */
271            sql = nsql;
272        } else if (ret == -1) {
273            /* couldn't grok it */
274            strcpy(dbh->error_code, stmt->error_code);
275            return 0;
276        }
277
278        spprintf(&S->stmt_name, 0, "pdo_stmt_%08x", ++H->stmt_counter);
279        /* that's all for now; we'll defer the actual prepare until the first execute call */
280
281        if (nsql) {
282            S->query = nsql;
283        } else {
284            S->query = estrdup(sql);
285        }
286
287        return 1;
288    }
289#endif
290
291    stmt->supports_placeholders = PDO_PLACEHOLDER_NONE;
292    return 1;
293}
294
295static long pgsql_handle_doer(pdo_dbh_t *dbh, const char *sql, long sql_len TSRMLS_DC)
296{
297    pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data;
298    PGresult *res;
299    long ret = 1;
300    ExecStatusType qs;
301
302    if (!(res = PQexec(H->server, sql))) {
303        /* fatal error */
304        pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL);
305        return -1;
306    }
307    qs = PQresultStatus(res);
308    if (qs != PGRES_COMMAND_OK && qs != PGRES_TUPLES_OK) {
309        pdo_pgsql_error(dbh, qs, pdo_pgsql_sqlstate(res));
310        PQclear(res);
311        return -1;
312    }
313    H->pgoid = PQoidValue(res);
314    ret = (qs == PGRES_COMMAND_OK) ? atol(PQcmdTuples(res)) : 0L;
315    PQclear(res);
316
317    return ret;
318}
319
320static int pgsql_handle_quoter(pdo_dbh_t *dbh, const char *unquoted, int unquotedlen, char **quoted, int *quotedlen, enum pdo_param_type paramtype TSRMLS_DC)
321{
322    unsigned char *escaped;
323    pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data;
324    size_t tmp_len;
325
326    switch (paramtype) {
327        case PDO_PARAM_LOB:
328            /* escapedlen returned by PQescapeBytea() accounts for trailing 0 */
329#ifdef HAVE_PQESCAPE_BYTEA_CONN
330            escaped = PQescapeByteaConn(H->server, (unsigned char *)unquoted, (size_t)unquotedlen, &tmp_len);
331#else
332            escaped = PQescapeBytea((unsigned char *)unquoted, (size_t)unquotedlen, &tmp_len);
333#endif
334            *quotedlen = (int)tmp_len + 1;
335            *quoted = emalloc(*quotedlen + 1);
336            memcpy((*quoted)+1, escaped, *quotedlen-2);
337            (*quoted)[0] = '\'';
338            (*quoted)[*quotedlen-1] = '\'';
339            (*quoted)[*quotedlen] = '\0';
340            PQfreemem(escaped);
341            break;
342        default:
343            *quoted = safe_emalloc(2, unquotedlen, 3);
344            (*quoted)[0] = '\'';
345#ifndef HAVE_PQESCAPE_CONN
346            *quotedlen = PQescapeString(*quoted + 1, unquoted, (size_t)unquotedlen);
347#else
348            *quotedlen = PQescapeStringConn(H->server, *quoted + 1, unquoted, (size_t)unquotedlen, NULL);
349#endif
350            (*quoted)[*quotedlen + 1] = '\'';
351            (*quoted)[*quotedlen + 2] = '\0';
352            *quotedlen += 2;
353    }
354    return 1;
355}
356
357static char *pdo_pgsql_last_insert_id(pdo_dbh_t *dbh, const char *name, unsigned int *len TSRMLS_DC)
358{
359    pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data;
360    char *id = NULL;
361
362    if (name == NULL) {
363        if (H->pgoid == InvalidOid) {
364            return NULL;
365        }
366        *len = spprintf(&id, 0, "%ld", (long) H->pgoid);
367    } else {
368        PGresult *res;
369        ExecStatusType status;
370        const char *q[1];
371        q[0] = name;
372        res = PQexecParams(H->server, "SELECT CURRVAL($1)", 1, NULL, q, NULL, NULL, 0);
373        status = PQresultStatus(res);
374
375        if (res && (status == PGRES_TUPLES_OK)) {
376            id = estrdup((char *)PQgetvalue(res, 0, 0));
377            *len = PQgetlength(res, 0, 0);
378        } else {
379            pdo_pgsql_error(dbh, status, pdo_pgsql_sqlstate(res));
380        }
381
382        if (res) {
383            PQclear(res);
384        }
385    }
386    return id;
387}
388
389static int pdo_pgsql_get_attribute(pdo_dbh_t *dbh, long attr, zval *return_value TSRMLS_DC)
390{
391    pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data;
392
393    switch (attr) {
394        case PDO_ATTR_EMULATE_PREPARES:
395            ZVAL_BOOL(return_value, H->emulate_prepares);
396            break;
397
398        case PDO_PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT:
399            ZVAL_BOOL(return_value, H->disable_native_prepares);
400            break;
401
402        case PDO_ATTR_CLIENT_VERSION:
403            ZVAL_STRING(return_value, PG_VERSION, 1);
404            break;
405
406        case PDO_ATTR_SERVER_VERSION:
407            if (PQprotocolVersion(H->server) >= 3) { /* PostgreSQL 7.4 or later */
408                ZVAL_STRING(return_value, (char*)PQparameterStatus(H->server, "server_version"), 1);
409            } else /* emulate above via a query */
410            {
411                PGresult *res = PQexec(H->server, "SELECT VERSION()");
412                if (res && PQresultStatus(res) == PGRES_TUPLES_OK) {
413                    ZVAL_STRING(return_value, (char *)PQgetvalue(res, 0, 0), 1);
414                }
415
416                if (res) {
417                    PQclear(res);
418                }
419            }
420            break;
421
422        case PDO_ATTR_CONNECTION_STATUS:
423            switch (PQstatus(H->server)) {
424                case CONNECTION_STARTED:
425                    ZVAL_STRINGL(return_value, "Waiting for connection to be made.", sizeof("Waiting for connection to be made.")-1, 1);
426                    break;
427
428                case CONNECTION_MADE:
429                case CONNECTION_OK:
430                    ZVAL_STRINGL(return_value, "Connection OK; waiting to send.", sizeof("Connection OK; waiting to send.")-1, 1);
431                    break;
432
433                case CONNECTION_AWAITING_RESPONSE:
434                    ZVAL_STRINGL(return_value, "Waiting for a response from the server.", sizeof("Waiting for a response from the server.")-1, 1);
435                    break;
436
437                case CONNECTION_AUTH_OK:
438                    ZVAL_STRINGL(return_value, "Received authentication; waiting for backend start-up to finish.", sizeof("Received authentication; waiting for backend start-up to finish.")-1, 1);
439                    break;
440#ifdef CONNECTION_SSL_STARTUP
441                case CONNECTION_SSL_STARTUP:
442                    ZVAL_STRINGL(return_value, "Negotiating SSL encryption.", sizeof("Negotiating SSL encryption.")-1, 1);
443                    break;
444#endif
445                case CONNECTION_SETENV:
446                    ZVAL_STRINGL(return_value, "Negotiating environment-driven parameter settings.", sizeof("Negotiating environment-driven parameter settings.")-1, 1);
447                    break;
448
449                case CONNECTION_BAD:
450                default:
451                    ZVAL_STRINGL(return_value, "Bad connection.", sizeof("Bad connection.")-1, 1);
452                    break;
453            }
454            break;
455
456        case PDO_ATTR_SERVER_INFO: {
457            int spid = PQbackendPID(H->server);
458            char *tmp;
459            spprintf(&tmp, 0,
460                "PID: %d; Client Encoding: %s; Is Superuser: %s; Session Authorization: %s; Date Style: %s",
461                spid,
462                (char*)PQparameterStatus(H->server, "client_encoding"),
463                (char*)PQparameterStatus(H->server, "is_superuser"),
464                (char*)PQparameterStatus(H->server, "session_authorization"),
465                (char*)PQparameterStatus(H->server, "DateStyle"));
466            ZVAL_STRING(return_value, tmp, 0);
467        }
468            break;
469
470        default:
471            return 0;
472    }
473
474    return 1;
475}
476
477/* {{{ */
478static int pdo_pgsql_check_liveness(pdo_dbh_t *dbh TSRMLS_DC)
479{
480    pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data;
481    if (PQstatus(H->server) == CONNECTION_BAD) {
482        PQreset(H->server);
483    }
484    return (PQstatus(H->server) == CONNECTION_OK) ? SUCCESS : FAILURE;
485}
486/* }}} */
487
488static int pgsql_handle_in_transaction(pdo_dbh_t *dbh TSRMLS_DC)
489{
490    pdo_pgsql_db_handle *H;
491
492    H = (pdo_pgsql_db_handle *)dbh->driver_data;
493
494    return PQtransactionStatus(H->server) > PQTRANS_IDLE;
495}
496
497static int pdo_pgsql_transaction_cmd(const char *cmd, pdo_dbh_t *dbh TSRMLS_DC)
498{
499    pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data;
500    PGresult *res;
501    int ret = 1;
502
503    res = PQexec(H->server, cmd);
504
505    if (PQresultStatus(res) != PGRES_COMMAND_OK) {
506        pdo_pgsql_error(dbh, PQresultStatus(res), pdo_pgsql_sqlstate(res));
507        ret = 0;
508    }
509
510    PQclear(res);
511    return ret;
512}
513
514static int pgsql_handle_begin(pdo_dbh_t *dbh TSRMLS_DC)
515{
516    return pdo_pgsql_transaction_cmd("BEGIN", dbh TSRMLS_CC);
517}
518
519static int pgsql_handle_commit(pdo_dbh_t *dbh TSRMLS_DC)
520{
521    int ret = pdo_pgsql_transaction_cmd("COMMIT", dbh TSRMLS_CC);
522
523    /* When deferred constraints are used the commit could
524       fail, and a ROLLBACK implicitly ran. See bug #67462 */
525    if (!ret) {
526        dbh->in_txn = pgsql_handle_in_transaction(dbh TSRMLS_CC);
527    }
528
529    return ret;
530}
531
532static int pgsql_handle_rollback(pdo_dbh_t *dbh TSRMLS_DC)
533{
534    return pdo_pgsql_transaction_cmd("ROLLBACK", dbh TSRMLS_CC);
535}
536
537/* {{{ proto string PDO::pgsqlCopyFromArray(string $table_name , array $rows [, string $delimiter [, string $null_as ] [, string $fields])
538   Returns true if the copy worked fine or false if error */
539static PHP_METHOD(PDO, pgsqlCopyFromArray)
540{
541    pdo_dbh_t *dbh;
542    pdo_pgsql_db_handle *H;
543
544    zval *pg_rows;
545
546    char *table_name, *pg_delim = NULL, *pg_null_as = NULL, *pg_fields = NULL;
547    int table_name_len, pg_delim_len = 0, pg_null_as_len = 0, pg_fields_len;
548    char *query;
549
550    PGresult *pgsql_result;
551    ExecStatusType status;
552
553    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s/a|sss",
554                    &table_name, &table_name_len, &pg_rows,
555                    &pg_delim, &pg_delim_len, &pg_null_as, &pg_null_as_len, &pg_fields, &pg_fields_len) == FAILURE) {
556        return;
557    }
558
559    if (!zend_hash_num_elements(Z_ARRVAL_P(pg_rows))) {
560        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot copy from an empty array");
561        RETURN_FALSE;
562    }
563
564    dbh = zend_object_store_get_object(getThis() TSRMLS_CC);
565    PDO_CONSTRUCT_CHECK;
566
567    if (pg_fields) {
568        spprintf(&query, 0, "COPY %s (%s) FROM STDIN DELIMITERS E'%c' WITH NULL AS E'%s'", table_name, pg_fields, (pg_delim_len ? *pg_delim : '\t'), (pg_null_as_len ? pg_null_as : "\\\\N"));
569    } else {
570        spprintf(&query, 0, "COPY %s FROM STDIN DELIMITERS E'%c' WITH NULL AS E'%s'", table_name, (pg_delim_len ? *pg_delim : '\t'), (pg_null_as_len ? pg_null_as : "\\\\N"));
571    }
572
573    /* Obtain db Handle */
574    H = (pdo_pgsql_db_handle *)dbh->driver_data;
575
576    while ((pgsql_result = PQgetResult(H->server))) {
577        PQclear(pgsql_result);
578    }
579    pgsql_result = PQexec(H->server, query);
580
581    efree(query);
582    query = NULL;
583
584    if (pgsql_result) {
585        status = PQresultStatus(pgsql_result);
586    } else {
587        status = (ExecStatusType) PQstatus(H->server);
588    }
589
590    if (status == PGRES_COPY_IN && pgsql_result) {
591        int command_failed = 0;
592        int buffer_len = 0;
593        zval **tmp;
594        HashPosition pos;
595
596        PQclear(pgsql_result);
597        zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(pg_rows), &pos);
598        while (zend_hash_get_current_data_ex(Z_ARRVAL_P(pg_rows), (void **) &tmp, &pos) == SUCCESS) {
599            int query_len;
600            convert_to_string_ex(tmp);
601
602            if (buffer_len < Z_STRLEN_PP(tmp)) {
603                buffer_len = Z_STRLEN_PP(tmp);
604                query = erealloc(query, buffer_len + 2); /* room for \n\0 */
605            }
606            memcpy(query, Z_STRVAL_PP(tmp), Z_STRLEN_PP(tmp));
607            query_len = Z_STRLEN_PP(tmp);
608            if (query[query_len - 1] != '\n') {
609                query[query_len++] = '\n';
610            }
611            query[query_len] = '\0';
612            if (PQputCopyData(H->server, query, query_len) != 1) {
613                            efree(query);
614                            pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, "copy failed");
615                            RETURN_FALSE;
616                    }
617            zend_hash_move_forward_ex(Z_ARRVAL_P(pg_rows), &pos);
618                }
619        if (query) {
620            efree(query);
621        }
622
623        if (PQputCopyEnd(H->server, NULL) != 1) {
624            pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, "putcopyend failed");
625            RETURN_FALSE;
626        }
627
628        while ((pgsql_result = PQgetResult(H->server))) {
629            if (PGRES_COMMAND_OK != PQresultStatus(pgsql_result)) {
630                pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, "Copy command failed");
631                command_failed = 1;
632            }
633            PQclear(pgsql_result);
634        }
635
636        RETURN_BOOL(!command_failed);
637    } else {
638        PQclear(pgsql_result);
639        pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, "Copy command failed");
640        RETURN_FALSE;
641    }
642}
643/* }}} */
644
645/* {{{ proto string PDO::pgsqlCopyFromFile(string $table_name , string $filename [, string $delimiter [, string $null_as ] [, string $fields])
646   Returns true if the copy worked fine or false if error */
647static PHP_METHOD(PDO, pgsqlCopyFromFile)
648{
649    pdo_dbh_t *dbh;
650    pdo_pgsql_db_handle *H;
651
652    char *table_name, *filename, *pg_delim = NULL, *pg_null_as = NULL, *pg_fields = NULL;
653    int  table_name_len, filename_len, pg_delim_len = 0, pg_null_as_len = 0, pg_fields_len;
654    char *query;
655    PGresult *pgsql_result;
656    ExecStatusType status;
657    php_stream *stream;
658
659    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sp|sss",
660                &table_name, &table_name_len, &filename, &filename_len,
661                &pg_delim, &pg_delim_len, &pg_null_as, &pg_null_as_len, &pg_fields, &pg_fields_len) == FAILURE) {
662        return;
663    }
664
665    /* Obtain db Handler */
666    dbh = zend_object_store_get_object(getThis() TSRMLS_CC);
667    PDO_CONSTRUCT_CHECK;
668
669    stream = php_stream_open_wrapper_ex(filename, "rb", ENFORCE_SAFE_MODE | REPORT_ERRORS, NULL, FG(default_context));
670    if (!stream) {
671        pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, "Unable to open the file");
672        RETURN_FALSE;
673    }
674
675    if (pg_fields) {
676        spprintf(&query, 0, "COPY %s (%s) FROM STDIN DELIMITERS E'%c' WITH NULL AS E'%s'", table_name, pg_fields, (pg_delim_len ? *pg_delim : '\t'), (pg_null_as_len ? pg_null_as : "\\\\N"));
677    } else {
678        spprintf(&query, 0, "COPY %s FROM STDIN DELIMITERS E'%c' WITH NULL AS E'%s'", table_name, (pg_delim_len ? *pg_delim : '\t'), (pg_null_as_len ? pg_null_as : "\\\\N"));
679    }
680
681    H = (pdo_pgsql_db_handle *)dbh->driver_data;
682
683    while ((pgsql_result = PQgetResult(H->server))) {
684        PQclear(pgsql_result);
685    }
686    pgsql_result = PQexec(H->server, query);
687
688    efree(query);
689
690    if (pgsql_result) {
691        status = PQresultStatus(pgsql_result);
692    } else {
693        status = (ExecStatusType) PQstatus(H->server);
694    }
695
696    if (status == PGRES_COPY_IN && pgsql_result) {
697        char *buf;
698        int command_failed = 0;
699        size_t line_len = 0;
700
701        PQclear(pgsql_result);
702        while ((buf = php_stream_get_line(stream, NULL, 0, &line_len)) != NULL) {
703            if (PQputCopyData(H->server, buf, line_len) != 1) {
704                            efree(buf);
705                            pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, "copy failed");
706                php_stream_close(stream);
707                RETURN_FALSE;
708            }
709            efree(buf);
710        }
711        php_stream_close(stream);
712
713        if (PQputCopyEnd(H->server, NULL) != 1) {
714            pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, "putcopyend failed");
715            RETURN_FALSE;
716        }
717
718        while ((pgsql_result = PQgetResult(H->server))) {
719            if (PGRES_COMMAND_OK != PQresultStatus(pgsql_result)) {
720                pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, "Copy command failed");
721                command_failed = 1;
722            }
723            PQclear(pgsql_result);
724        }
725
726        RETURN_BOOL(!command_failed);
727    } else {
728        PQclear(pgsql_result);
729        php_stream_close(stream);
730        pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, "Copy command failed");
731        RETURN_FALSE;
732    }
733}
734/* }}} */
735
736
737/* {{{ proto string PDO::pgsqlCopyToFile(string $table_name , $filename, [string $delimiter [, string $null_as [, string $fields]]])
738   Returns true if the copy worked fine or false if error */
739static PHP_METHOD(PDO, pgsqlCopyToFile)
740{
741    pdo_dbh_t *dbh;
742    pdo_pgsql_db_handle *H;
743
744    char *table_name, *pg_delim = NULL, *pg_null_as = NULL, *pg_fields = NULL, *filename = NULL;
745    int table_name_len, pg_delim_len = 0, pg_null_as_len = 0, pg_fields_len, filename_len;
746    char *query;
747
748    PGresult *pgsql_result;
749    ExecStatusType status;
750
751    php_stream *stream;
752
753    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sp|sss",
754                    &table_name, &table_name_len, &filename, &filename_len,
755                    &pg_delim, &pg_delim_len, &pg_null_as, &pg_null_as_len, &pg_fields, &pg_fields_len) == FAILURE) {
756        return;
757    }
758
759    dbh = zend_object_store_get_object(getThis() TSRMLS_CC);
760    PDO_CONSTRUCT_CHECK;
761
762    H = (pdo_pgsql_db_handle *)dbh->driver_data;
763
764    stream = php_stream_open_wrapper_ex(filename, "wb", ENFORCE_SAFE_MODE | REPORT_ERRORS, NULL, FG(default_context));
765    if (!stream) {
766        pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, "Unable to open the file for writing");
767        RETURN_FALSE;
768    }
769
770    while ((pgsql_result = PQgetResult(H->server))) {
771        PQclear(pgsql_result);
772    }
773
774    if (pg_fields) {
775        spprintf(&query, 0, "COPY %s (%s) TO STDIN DELIMITERS E'%c' WITH NULL AS E'%s'", table_name, pg_fields, (pg_delim_len ? *pg_delim : '\t'), (pg_null_as_len ? pg_null_as : "\\\\N"));
776    } else {
777        spprintf(&query, 0, "COPY %s TO STDIN DELIMITERS E'%c' WITH NULL AS E'%s'", table_name, (pg_delim_len ? *pg_delim : '\t'), (pg_null_as_len ? pg_null_as : "\\\\N"));
778    }
779    pgsql_result = PQexec(H->server, query);
780    efree(query);
781
782    if (pgsql_result) {
783        status = PQresultStatus(pgsql_result);
784    } else {
785        status = (ExecStatusType) PQstatus(H->server);
786    }
787
788    if (status == PGRES_COPY_OUT && pgsql_result) {
789        PQclear(pgsql_result);
790        while (1) {
791            char *csv = NULL;
792            int ret = PQgetCopyData(H->server, &csv, 0);
793
794            if (ret == -1) {
795                break; /* done */
796            } else if (ret > 0) {
797                if (php_stream_write(stream, csv, ret) != ret) {
798                    pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, "Unable to write to file");
799                    PQfreemem(csv);
800                    php_stream_close(stream);
801                    RETURN_FALSE;
802                } else {
803                    PQfreemem(csv);
804                }
805            } else {
806                pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, "Copy command failed: getline failed");
807                php_stream_close(stream);
808                RETURN_FALSE;
809            }
810        }
811        php_stream_close(stream);
812
813        while ((pgsql_result = PQgetResult(H->server))) {
814            PQclear(pgsql_result);
815        }
816        RETURN_TRUE;
817    } else {
818        php_stream_close(stream);
819        PQclear(pgsql_result);
820        pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, "Copy command failed");
821        RETURN_FALSE;
822    }
823}
824/* }}} */
825
826/* {{{ proto string PDO::pgsqlCopyToArray(string $table_name , [string $delimiter [, string $null_as [, string $fields]]])
827   Returns true if the copy worked fine or false if error */
828static PHP_METHOD(PDO, pgsqlCopyToArray)
829{
830    pdo_dbh_t *dbh;
831    pdo_pgsql_db_handle *H;
832
833    char *table_name, *pg_delim = NULL, *pg_null_as = NULL, *pg_fields = NULL;
834    int table_name_len, pg_delim_len = 0, pg_null_as_len = 0, pg_fields_len;
835    char *query;
836
837    PGresult *pgsql_result;
838    ExecStatusType status;
839
840    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|sss",
841        &table_name, &table_name_len,
842        &pg_delim, &pg_delim_len, &pg_null_as, &pg_null_as_len, &pg_fields, &pg_fields_len) == FAILURE) {
843        return;
844    }
845
846    dbh = zend_object_store_get_object(getThis() TSRMLS_CC);
847    PDO_CONSTRUCT_CHECK;
848
849    H = (pdo_pgsql_db_handle *)dbh->driver_data;
850
851    while ((pgsql_result = PQgetResult(H->server))) {
852        PQclear(pgsql_result);
853    }
854
855    if (pg_fields) {
856        spprintf(&query, 0, "COPY %s (%s) TO STDIN DELIMITERS E'%c' WITH NULL AS E'%s'", table_name, pg_fields, (pg_delim_len ? *pg_delim : '\t'), (pg_null_as_len ? pg_null_as : "\\\\N"));
857    } else {
858        spprintf(&query, 0, "COPY %s TO STDIN DELIMITERS E'%c' WITH NULL AS E'%s'", table_name, (pg_delim_len ? *pg_delim : '\t'), (pg_null_as_len ? pg_null_as : "\\\\N"));
859    }
860    pgsql_result = PQexec(H->server, query);
861    efree(query);
862
863    if (pgsql_result) {
864        status = PQresultStatus(pgsql_result);
865    } else {
866        status = (ExecStatusType) PQstatus(H->server);
867    }
868
869    if (status == PGRES_COPY_OUT && pgsql_result) {
870        PQclear(pgsql_result);
871                array_init(return_value);
872
873        while (1) {
874            char *csv = NULL;
875            int ret = PQgetCopyData(H->server, &csv, 0);
876            if (ret == -1) {
877                break; /* copy done */
878            } else if (ret > 0) {
879                add_next_index_stringl(return_value, csv, ret, 1);
880                PQfreemem(csv);
881            } else {
882                pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, "Copy command failed: getline failed");
883                RETURN_FALSE;
884            }
885        }
886
887        while ((pgsql_result = PQgetResult(H->server))) {
888            PQclear(pgsql_result);
889        }
890    } else {
891        PQclear(pgsql_result);
892        pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, "Copy command failed");
893        RETURN_FALSE;
894    }
895}
896/* }}} */
897
898
899/* {{{ proto string PDO::pgsqlLOBCreate()
900   Creates a new large object, returning its identifier.  Must be called inside a transaction. */
901static PHP_METHOD(PDO, pgsqlLOBCreate)
902{
903    pdo_dbh_t *dbh;
904    pdo_pgsql_db_handle *H;
905    Oid lfd;
906
907    dbh = zend_object_store_get_object(getThis() TSRMLS_CC);
908    PDO_CONSTRUCT_CHECK;
909
910    H = (pdo_pgsql_db_handle *)dbh->driver_data;
911    lfd = lo_creat(H->server, INV_READ|INV_WRITE);
912
913    if (lfd != InvalidOid) {
914        char *buf;
915        spprintf(&buf, 0, "%lu", (long) lfd);
916        RETURN_STRING(buf, 0);
917    }
918
919    pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, "HY000");
920    RETURN_FALSE;
921}
922/* }}} */
923
924/* {{{ proto resource PDO::pgsqlLOBOpen(string oid [, string mode = 'rb'])
925   Opens an existing large object stream.  Must be called inside a transaction. */
926static PHP_METHOD(PDO, pgsqlLOBOpen)
927{
928    pdo_dbh_t *dbh;
929    pdo_pgsql_db_handle *H;
930    Oid oid;
931    int lfd;
932    char *oidstr;
933    int oidstrlen;
934    char *modestr = "rb";
935    int modestrlen;
936    int mode = INV_READ;
937    char *end_ptr;
938
939    if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|s",
940                &oidstr, &oidstrlen, &modestr, &modestrlen)) {
941        RETURN_FALSE;
942    }
943
944    oid = (Oid)strtoul(oidstr, &end_ptr, 10);
945    if (oid == 0 && (errno == ERANGE || errno == EINVAL)) {
946        RETURN_FALSE;
947    }
948
949    if (strpbrk(modestr, "+w")) {
950        mode = INV_READ|INV_WRITE;
951    }
952
953    dbh = zend_object_store_get_object(getThis() TSRMLS_CC);
954    PDO_CONSTRUCT_CHECK;
955
956    H = (pdo_pgsql_db_handle *)dbh->driver_data;
957
958    lfd = lo_open(H->server, oid, mode);
959
960    if (lfd >= 0) {
961        php_stream *stream = pdo_pgsql_create_lob_stream(dbh, lfd, oid TSRMLS_CC);
962        if (stream) {
963            php_stream_to_zval(stream, return_value);
964            return;
965        }
966    } else {
967        pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, "HY000");
968    }
969    RETURN_FALSE;
970}
971/* }}} */
972
973/* {{{ proto bool PDO::pgsqlLOBUnlink(string oid)
974   Deletes the large object identified by oid.  Must be called inside a transaction. */
975static PHP_METHOD(PDO, pgsqlLOBUnlink)
976{
977    pdo_dbh_t *dbh;
978    pdo_pgsql_db_handle *H;
979    Oid oid;
980    char *oidstr, *end_ptr;
981    int oidlen;
982
983    if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s",
984                &oidstr, &oidlen)) {
985        RETURN_FALSE;
986    }
987
988    oid = (Oid)strtoul(oidstr, &end_ptr, 10);
989    if (oid == 0 && (errno == ERANGE || errno == EINVAL)) {
990        RETURN_FALSE;
991    }
992
993    dbh = zend_object_store_get_object(getThis() TSRMLS_CC);
994    PDO_CONSTRUCT_CHECK;
995
996    H = (pdo_pgsql_db_handle *)dbh->driver_data;
997
998    if (1 == lo_unlink(H->server, oid)) {
999        RETURN_TRUE;
1000    }
1001    pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, "HY000");
1002    RETURN_FALSE;
1003}
1004/* }}} */
1005
1006
1007static const zend_function_entry dbh_methods[] = {
1008    PHP_ME(PDO, pgsqlLOBCreate, NULL, ZEND_ACC_PUBLIC)
1009    PHP_ME(PDO, pgsqlLOBOpen, NULL, ZEND_ACC_PUBLIC)
1010    PHP_ME(PDO, pgsqlLOBUnlink, NULL, ZEND_ACC_PUBLIC)
1011    PHP_ME(PDO, pgsqlCopyFromArray, NULL, ZEND_ACC_PUBLIC)
1012    PHP_ME(PDO, pgsqlCopyFromFile, NULL, ZEND_ACC_PUBLIC)
1013    PHP_ME(PDO, pgsqlCopyToArray, NULL, ZEND_ACC_PUBLIC)
1014    PHP_ME(PDO, pgsqlCopyToFile, NULL, ZEND_ACC_PUBLIC)
1015    PHP_FE_END
1016};
1017
1018static const zend_function_entry *pdo_pgsql_get_driver_methods(pdo_dbh_t *dbh, int kind TSRMLS_DC)
1019{
1020    switch (kind) {
1021        case PDO_DBH_DRIVER_METHOD_KIND_DBH:
1022            return dbh_methods;
1023        default:
1024            return NULL;
1025    }
1026}
1027
1028static int pdo_pgsql_set_attr(pdo_dbh_t *dbh, long attr, zval *val TSRMLS_DC)
1029{
1030    pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data;
1031
1032    switch (attr) {
1033#if HAVE_PQPREPARE
1034        case PDO_ATTR_EMULATE_PREPARES:
1035            H->emulate_prepares = Z_LVAL_P(val);
1036            return 1;
1037        case PDO_PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT:
1038            H->disable_native_prepares = Z_LVAL_P(val);
1039            return 1;
1040#endif
1041
1042        default:
1043            return 0;
1044    }
1045}
1046
1047static struct pdo_dbh_methods pgsql_methods = {
1048    pgsql_handle_closer,
1049    pgsql_handle_preparer,
1050    pgsql_handle_doer,
1051    pgsql_handle_quoter,
1052    pgsql_handle_begin,
1053    pgsql_handle_commit,
1054    pgsql_handle_rollback,
1055    pdo_pgsql_set_attr,
1056    pdo_pgsql_last_insert_id,
1057    pdo_pgsql_fetch_error_func,
1058    pdo_pgsql_get_attribute,
1059    pdo_pgsql_check_liveness,   /* check_liveness */
1060    pdo_pgsql_get_driver_methods,  /* get_driver_methods */
1061    NULL,
1062    pgsql_handle_in_transaction,
1063};
1064
1065static int pdo_pgsql_handle_factory(pdo_dbh_t *dbh, zval *driver_options TSRMLS_DC) /* {{{ */
1066{
1067    pdo_pgsql_db_handle *H;
1068    int ret = 0;
1069    char *conn_str, *p, *e;
1070    char *tmp_user, *tmp_pass;
1071    long connect_timeout = 30;
1072
1073    H = pecalloc(1, sizeof(pdo_pgsql_db_handle), dbh->is_persistent);
1074    dbh->driver_data = H;
1075
1076    H->einfo.errcode = 0;
1077    H->einfo.errmsg = NULL;
1078
1079    /* PostgreSQL wants params in the connect string to be separated by spaces,
1080     * if the PDO standard semicolons are used, we convert them to spaces
1081     */
1082    e = (char *) dbh->data_source + strlen(dbh->data_source);
1083    p = (char *) dbh->data_source;
1084    while ((p = memchr(p, ';', (e - p)))) {
1085        *p = ' ';
1086    }
1087
1088    if (driver_options) {
1089        connect_timeout = pdo_attr_lval(driver_options, PDO_ATTR_TIMEOUT, 30 TSRMLS_CC);
1090    }
1091
1092    /* escape username and password, if provided */
1093    tmp_user = _pdo_pgsql_escape_credentials(dbh->username TSRMLS_CC);
1094    tmp_pass = _pdo_pgsql_escape_credentials(dbh->password TSRMLS_CC);
1095
1096    /* support both full connection string & connection string + login and/or password */
1097    if (tmp_user && tmp_pass) {
1098        spprintf(&conn_str, 0, "%s user='%s' password='%s' connect_timeout=%ld", dbh->data_source, tmp_user, tmp_pass, connect_timeout);
1099    } else if (tmp_user) {
1100        spprintf(&conn_str, 0, "%s user='%s' connect_timeout=%ld", dbh->data_source, tmp_user, connect_timeout);
1101    } else if (tmp_pass) {
1102        spprintf(&conn_str, 0, "%s password='%s' connect_timeout=%ld", dbh->data_source, tmp_pass, connect_timeout);
1103    } else {
1104        spprintf(&conn_str, 0, "%s connect_timeout=%ld", (char *) dbh->data_source, connect_timeout);
1105    }
1106
1107    H->server = PQconnectdb(conn_str);
1108
1109    if (tmp_user) {
1110        efree(tmp_user);
1111    }
1112    if (tmp_pass) {
1113        efree(tmp_pass);
1114    }
1115
1116    efree(conn_str);
1117
1118    if (PQstatus(H->server) != CONNECTION_OK) {
1119        pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, PHP_PDO_PGSQL_CONNECTION_FAILURE_SQLSTATE);
1120        goto cleanup;
1121    }
1122
1123    PQsetNoticeProcessor(H->server, (void(*)(void*,const char*))_pdo_pgsql_notice, (void *)&dbh);
1124
1125    H->attached = 1;
1126    H->pgoid = -1;
1127
1128    dbh->methods = &pgsql_methods;
1129    dbh->alloc_own_columns = 1;
1130    dbh->max_escaped_char_length = 2;
1131
1132    ret = 1;
1133
1134cleanup:
1135    dbh->methods = &pgsql_methods;
1136    if (!ret) {
1137        pgsql_handle_closer(dbh TSRMLS_CC);
1138    }
1139
1140    return ret;
1141}
1142/* }}} */
1143
1144pdo_driver_t pdo_pgsql_driver = {
1145    PDO_DRIVER_HEADER(pgsql),
1146    pdo_pgsql_handle_factory
1147};
1148
1149/*
1150 * Local variables:
1151 * tab-width: 4
1152 * c-basic-offset: 4
1153 * End:
1154 * vim600: noet sw=4 ts=4 fdm=marker
1155 * vim<600: noet sw=4 ts=4
1156 */
1157