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