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            zval *parameter;
380            if (Z_ISREF(param->parameter)) {
381                parameter = Z_REFVAL(param->parameter);
382            } else {
383                parameter = &param->parameter;
384            }
385            SEPARATE_ZVAL(&param->parameter);
386            param->param_type = PDO_PARAM_STR;
387            ZVAL_STRINGL(parameter, Z_TYPE_P(parameter) == IS_TRUE ? "t" : "f", 1);
388        }
389    }
390    return 1;
391}
392
393static int pgsql_stmt_fetch(pdo_stmt_t *stmt,
394    enum pdo_fetch_orientation ori, zend_long offset TSRMLS_DC)
395{
396    pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data;
397
398    if (S->cursor_name) {
399        char *ori_str = NULL;
400        char *q = NULL;
401        ExecStatusType status;
402
403        switch (ori) {
404            case PDO_FETCH_ORI_NEXT:    spprintf(&ori_str, 0, "NEXT"); break;
405            case PDO_FETCH_ORI_PRIOR:   spprintf(&ori_str, 0, "BACKWARD"); break;
406            case PDO_FETCH_ORI_FIRST:   spprintf(&ori_str, 0, "FIRST"); break;
407            case PDO_FETCH_ORI_LAST:    spprintf(&ori_str, 0, "LAST"); break;
408            case PDO_FETCH_ORI_ABS:     spprintf(&ori_str, 0, "ABSOLUTE %pd", offset); break;
409            case PDO_FETCH_ORI_REL:     spprintf(&ori_str, 0, "RELATIVE %pd", offset); break;
410            default:
411                return 0;
412        }
413
414        spprintf(&q, 0, "FETCH %s FROM %s", ori_str, S->cursor_name);
415        efree(ori_str);
416        S->result = PQexec(S->H->server, q);
417        efree(q);
418        status = PQresultStatus(S->result);
419
420        if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK) {
421            pdo_pgsql_error_stmt(stmt, status, pdo_pgsql_sqlstate(S->result));
422            return 0;
423        }
424
425        if (PQntuples(S->result)) {
426            S->current_row = 1;
427            return 1;
428        } else {
429            return 0;
430        }
431    } else {
432        if (S->current_row < stmt->row_count) {
433            S->current_row++;
434            return 1;
435        } else {
436            return 0;
437        }
438    }
439}
440
441static int pgsql_stmt_describe(pdo_stmt_t *stmt, int colno TSRMLS_DC)
442{
443    pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data;
444    struct pdo_column_data *cols = stmt->columns;
445    struct pdo_bound_param_data *param;
446
447    if (!S->result) {
448        return 0;
449    }
450
451    cols[colno].name = estrdup(PQfname(S->result, colno));
452    cols[colno].namelen = strlen(cols[colno].name);
453    cols[colno].maxlen = PQfsize(S->result, colno);
454    cols[colno].precision = PQfmod(S->result, colno);
455    S->cols[colno].pgsql_type = PQftype(S->result, colno);
456
457    switch (S->cols[colno].pgsql_type) {
458
459        case BOOLOID:
460            cols[colno].param_type = PDO_PARAM_BOOL;
461            break;
462
463        case OIDOID:
464            /* did the user bind the column as a LOB ? */
465            if (stmt->bound_columns && (
466                    (param = zend_hash_index_find_ptr(stmt->bound_columns, colno)) != NULL ||
467                    (param = zend_hash_str_find_ptr(stmt->bound_columns, cols[colno].name, cols[colno].namelen)) != NULL)) {
468
469                if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB) {
470                    cols[colno].param_type = PDO_PARAM_LOB;
471                    break;
472                }
473            }
474            cols[colno].param_type = PDO_PARAM_INT;
475            break;
476
477        case INT2OID:
478        case INT4OID:
479            cols[colno].param_type = PDO_PARAM_INT;
480            break;
481
482        case INT8OID:
483            if (sizeof(zend_long)>=8) {
484                cols[colno].param_type = PDO_PARAM_INT;
485            } else {
486                cols[colno].param_type = PDO_PARAM_STR;
487            }
488            break;
489
490        case BYTEAOID:
491            cols[colno].param_type = PDO_PARAM_LOB;
492            break;
493
494        default:
495            cols[colno].param_type = PDO_PARAM_STR;
496    }
497
498    return 1;
499}
500
501static int pgsql_stmt_get_col(pdo_stmt_t *stmt, int colno, char **ptr, zend_ulong *len, int *caller_frees  TSRMLS_DC)
502{
503    pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data;
504    struct pdo_column_data *cols = stmt->columns;
505    size_t tmp_len;
506
507    if (!S->result) {
508        return 0;
509    }
510
511    /* We have already increased count by 1 in pgsql_stmt_fetch() */
512    if (PQgetisnull(S->result, S->current_row - 1, colno)) { /* Check if we got NULL */
513        *ptr = NULL;
514        *len = 0;
515    } else {
516        *ptr = PQgetvalue(S->result, S->current_row - 1, colno);
517        *len = PQgetlength(S->result, S->current_row - 1, colno);
518
519        switch (cols[colno].param_type) {
520
521            case PDO_PARAM_INT:
522                ZEND_ATOL(S->cols[colno].intval, *ptr);
523                *ptr = (char *) &(S->cols[colno].intval);
524                *len = sizeof(zend_long);
525                break;
526
527            case PDO_PARAM_BOOL:
528                S->cols[colno].boolval = **ptr == 't' ? 1: 0;
529                *ptr = (char *) &(S->cols[colno].boolval);
530                *len = sizeof(zend_bool);
531                break;
532
533            case PDO_PARAM_LOB:
534                if (S->cols[colno].pgsql_type == OIDOID) {
535                    /* ooo, a real large object */
536                    char *end_ptr;
537                    Oid oid = (Oid)strtoul(*ptr, &end_ptr, 10);
538                    int loid = lo_open(S->H->server, oid, INV_READ);
539                    if (loid >= 0) {
540                        *ptr = (char*)pdo_pgsql_create_lob_stream(&stmt->database_object_handle, loid, oid TSRMLS_CC);
541                        *len = 0;
542                        return *ptr ? 1 : 0;
543                    }
544                    *ptr = NULL;
545                    *len = 0;
546                    return 0;
547                } else {
548                    char *tmp_ptr = (char *)PQunescapeBytea((unsigned char *)*ptr, &tmp_len);
549                    if (!tmp_ptr) {
550                        /* PQunescapeBytea returned an error */
551                        *len = 0;
552                        return 0;
553                    }
554                    if (!tmp_len) {
555                        /* Empty string, return as empty stream */
556                        *ptr = (char *)php_stream_memory_open(TEMP_STREAM_READONLY, "", 0);
557                        PQfreemem(tmp_ptr);
558                        *len = 0;
559                    } else {
560                        *ptr = estrndup(tmp_ptr, tmp_len);
561                        PQfreemem(tmp_ptr);
562                        *len = tmp_len;
563                        *caller_frees = 1;
564                    }
565                }
566                break;
567            case PDO_PARAM_NULL:
568            case PDO_PARAM_STR:
569            case PDO_PARAM_STMT:
570            case PDO_PARAM_INPUT_OUTPUT:
571            case PDO_PARAM_ZVAL:
572            default:
573                break;
574        }
575    }
576
577    return 1;
578}
579
580static int pgsql_stmt_get_column_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_value TSRMLS_DC)
581{
582    pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data;
583    PGresult *res;
584    char *q=NULL;
585    ExecStatusType status;
586
587    if (!S->result) {
588        return FAILURE;
589    }
590
591    if (colno >= stmt->column_count) {
592        return FAILURE;
593    }
594
595    array_init(return_value);
596    add_assoc_long(return_value, "pgsql:oid", S->cols[colno].pgsql_type);
597
598    /* Fetch metadata from Postgres system catalogue */
599    spprintf(&q, 0, "SELECT TYPNAME FROM PG_TYPE WHERE OID=%u", S->cols[colno].pgsql_type);
600    res = PQexec(S->H->server, q);
601    efree(q);
602
603    status = PQresultStatus(res);
604
605    if (status != PGRES_TUPLES_OK) {
606        /* Failed to get system catalogue, but return success
607         * with the data we have collected so far
608         */
609        goto done;
610    }
611
612    /* We want exactly one row returned */
613    if (1 != PQntuples(res)) {
614        goto done;
615    }
616
617    add_assoc_string(return_value, "native_type", PQgetvalue(res, 0, 0));
618done:
619    PQclear(res);
620    return 1;
621}
622
623static int pdo_pgsql_stmt_cursor_closer(pdo_stmt_t *stmt TSRMLS_DC)
624{
625    return 1;
626}
627
628struct pdo_stmt_methods pgsql_stmt_methods = {
629    pgsql_stmt_dtor,
630    pgsql_stmt_execute,
631    pgsql_stmt_fetch,
632    pgsql_stmt_describe,
633    pgsql_stmt_get_col,
634    pgsql_stmt_param_hook,
635    NULL, /* set_attr */
636    NULL, /* get_attr */
637    pgsql_stmt_get_column_meta,
638    NULL,  /* next_rowset */
639    pdo_pgsql_stmt_cursor_closer
640};
641
642/*
643 * Local variables:
644 * tab-width: 4
645 * c-basic-offset: 4
646 * End:
647 * vim600: noet sw=4 ts=4 fdm=marker
648 * vim<600: noet sw=4 ts=4
649 */
650