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