1/*
2  +----------------------------------------------------------------------+
3  | PHP Version 5                                                        |
4  +----------------------------------------------------------------------+
5  | Copyright (c) 2006-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: Andrey Hristov <andrey@mysql.com>                           |
16  |          Ulf Wendel <uwendel@mysql.com>                              |
17  |          Georg Richter <georg@mysql.com>                             |
18  +----------------------------------------------------------------------+
19*/
20
21/* $Id$ */
22#include "php.h"
23#include "mysqlnd.h"
24#include "mysqlnd_wireprotocol.h"
25#include "mysqlnd_priv.h"
26#include "mysqlnd_debug.h"
27
28#define MYSQLND_SILENT
29
30
31enum mysqlnd_timestamp_type
32{
33  MYSQLND_TIMESTAMP_NONE= -2,
34  MYSQLND_TIMESTAMP_ERROR= -1,
35  MYSQLND_TIMESTAMP_DATE= 0,
36  MYSQLND_TIMESTAMP_DATETIME= 1,
37  MYSQLND_TIMESTAMP_TIME= 2
38};
39
40
41struct st_mysqlnd_time
42{
43  unsigned int  year, month, day, hour, minute, second;
44  unsigned long second_part;
45  zend_bool     neg;
46  enum mysqlnd_timestamp_type time_type;
47};
48
49
50struct st_mysqlnd_perm_bind mysqlnd_ps_fetch_functions[MYSQL_TYPE_LAST + 1];
51
52#define MYSQLND_PS_SKIP_RESULT_W_LEN    -1
53#define MYSQLND_PS_SKIP_RESULT_STR      -2
54
55/* {{{ ps_fetch_from_1_to_8_bytes */
56void
57ps_fetch_from_1_to_8_bytes(zval * zv, const MYSQLND_FIELD * const field, unsigned int pack_len,
58                           zend_uchar ** row, unsigned int byte_count TSRMLS_DC)
59{
60    char tmp[22];
61    size_t tmp_len = 0;
62    zend_bool is_bit = field->type == MYSQL_TYPE_BIT;
63    DBG_ENTER("ps_fetch_from_1_to_8_bytes");
64    DBG_INF_FMT("zv=%p byte_count=%u", zv, byte_count);
65    if (field->flags & UNSIGNED_FLAG) {
66        uint64_t uval = 0;
67
68        switch (byte_count) {
69            case 8:uval = is_bit? (uint64_t) bit_uint8korr(*row):(uint64_t) uint8korr(*row);break;
70            case 7:uval = bit_uint7korr(*row);break;
71            case 6:uval = bit_uint6korr(*row);break;
72            case 5:uval = bit_uint5korr(*row);break;
73            case 4:uval = is_bit? (uint64_t) bit_uint4korr(*row):(uint64_t) uint4korr(*row);break;
74            case 3:uval = is_bit? (uint64_t) bit_uint3korr(*row):(uint64_t) uint3korr(*row);break;
75            case 2:uval = is_bit? (uint64_t) bit_uint2korr(*row):(uint64_t) uint2korr(*row);break;
76            case 1:uval = (uint64_t) uint1korr(*row);break;
77        }
78
79#if SIZEOF_LONG==4
80        if (uval > INT_MAX) {
81            DBG_INF("stringify");
82            tmp_len = sprintf((char *)&tmp, MYSQLND_LLU_SPEC, uval);
83        } else
84#endif /* #if SIZEOF_LONG==4 */
85        {
86            if (byte_count < 8 || uval <= L64(9223372036854775807)) {
87                ZVAL_LONG(zv, (long) uval); /* the cast is safe, we are in the range */
88            } else {
89                DBG_INF("stringify");
90                tmp_len = sprintf((char *)&tmp, MYSQLND_LLU_SPEC, uval);
91            }
92        }
93    } else {
94        /* SIGNED */
95        int64_t lval = 0;
96        switch (byte_count) {
97            case 8:lval = (int64_t) sint8korr(*row);break;
98            /*
99              7, 6 and 5 are not possible.
100              BIT is only unsigned, thus only uint5|6|7 macroses exist
101            */
102            case 4:lval = (int64_t) sint4korr(*row);break;
103            case 3:lval = (int64_t) sint3korr(*row);break;
104            case 2:lval = (int64_t) sint2korr(*row);break;
105            case 1:lval = (int64_t) *(int8_t*)*row;break;
106        }
107
108#if SIZEOF_LONG==4
109        if ((L64(2147483647) < (int64_t) lval) || (L64(-2147483648) > (int64_t) lval)) {
110            DBG_INF("stringify");
111            tmp_len = sprintf((char *)&tmp, MYSQLND_LL_SPEC, lval);
112        } else
113#endif /* SIZEOF */
114        {
115            ZVAL_LONG(zv, (long) lval); /* the cast is safe, we are in the range */
116        }
117    }
118
119    if (tmp_len) {
120        ZVAL_STRINGL(zv, tmp, tmp_len, 1);
121    }
122    (*row)+= byte_count;
123    DBG_VOID_RETURN;
124}
125/* }}} */
126
127
128/* {{{ ps_fetch_null */
129static void
130ps_fetch_null(zval *zv, const MYSQLND_FIELD * const field, unsigned int pack_len, zend_uchar ** row TSRMLS_DC)
131{
132    ZVAL_NULL(zv);
133}
134/* }}} */
135
136
137/* {{{ ps_fetch_int8 */
138static void
139ps_fetch_int8(zval * zv, const MYSQLND_FIELD * const field, unsigned int pack_len, zend_uchar ** row TSRMLS_DC)
140{
141    ps_fetch_from_1_to_8_bytes(zv, field, pack_len, row, 1 TSRMLS_CC);
142}
143/* }}} */
144
145
146/* {{{ ps_fetch_int16 */
147static void
148ps_fetch_int16(zval * zv, const MYSQLND_FIELD * const field, unsigned int pack_len, zend_uchar ** row TSRMLS_DC)
149{
150    ps_fetch_from_1_to_8_bytes(zv, field, pack_len, row, 2 TSRMLS_CC);
151}
152/* }}} */
153
154
155/* {{{ ps_fetch_int32 */
156static void
157ps_fetch_int32(zval * zv, const MYSQLND_FIELD * const field, unsigned int pack_len, zend_uchar ** row TSRMLS_DC)
158{
159    ps_fetch_from_1_to_8_bytes(zv, field, pack_len, row, 4 TSRMLS_CC);
160}
161/* }}} */
162
163
164/* {{{ ps_fetch_int64 */
165static void
166ps_fetch_int64(zval * zv, const MYSQLND_FIELD * const field, unsigned int pack_len, zend_uchar ** row TSRMLS_DC)
167{
168    ps_fetch_from_1_to_8_bytes(zv, field, pack_len, row, 8 TSRMLS_CC);
169}
170/* }}} */
171
172
173/* {{{ ps_fetch_float */
174static void
175ps_fetch_float(zval * zv, const MYSQLND_FIELD * const field, unsigned int pack_len, zend_uchar ** row TSRMLS_DC)
176{
177    float value;
178    DBG_ENTER("ps_fetch_float");
179    float4get(value, *row);
180    ZVAL_DOUBLE(zv, value);
181    (*row)+= 4;
182    DBG_INF_FMT("value=%f", value);
183    DBG_VOID_RETURN;
184}
185/* }}} */
186
187
188/* {{{ ps_fetch_double */
189static void
190ps_fetch_double(zval * zv, const MYSQLND_FIELD * const field, unsigned int pack_len, zend_uchar ** row TSRMLS_DC)
191{
192    double value;
193    DBG_ENTER("ps_fetch_double");
194    float8get(value, *row);
195    ZVAL_DOUBLE(zv, value);
196    (*row)+= 8;
197    DBG_INF_FMT("value=%f", value);
198    DBG_VOID_RETURN;
199}
200/* }}} */
201
202
203/* {{{ ps_fetch_time */
204static void
205ps_fetch_time(zval * zv, const MYSQLND_FIELD * const field, unsigned int pack_len, zend_uchar ** row TSRMLS_DC)
206{
207    struct st_mysqlnd_time t;
208    unsigned long length; /* First byte encodes the length*/
209    char * value;
210    DBG_ENTER("ps_fetch_time");
211
212    if ((length = php_mysqlnd_net_field_length(row))) {
213        zend_uchar * to= *row;
214
215        t.time_type = MYSQLND_TIMESTAMP_TIME;
216        t.neg           = (zend_bool) to[0];
217
218        t.day           = (unsigned long) sint4korr(to+1);
219        t.hour          = (unsigned int) to[5];
220        t.minute        = (unsigned int) to[6];
221        t.second        = (unsigned int) to[7];
222        t.second_part   = (length > 8) ? (unsigned long) sint4korr(to+8) : 0;
223        t.year          = t.month= 0;
224        if (t.day) {
225            /* Convert days to hours at once */
226            t.hour += t.day*24;
227            t.day   = 0;
228        }
229
230        (*row) += length;
231    } else {
232        memset(&t, 0, sizeof(t));
233        t.time_type = MYSQLND_TIMESTAMP_TIME;
234    }
235
236    length = mnd_sprintf(&value, 0, "%s%02u:%02u:%02u", (t.neg ? "-" : ""), t.hour, t.minute, t.second);
237
238    DBG_INF_FMT("%s", value);
239    ZVAL_STRINGL(zv, value, length, 1);
240    mnd_sprintf_free(value);
241    DBG_VOID_RETURN;
242}
243/* }}} */
244
245
246/* {{{ ps_fetch_date */
247static void
248ps_fetch_date(zval * zv, const MYSQLND_FIELD * const field, unsigned int pack_len, zend_uchar ** row TSRMLS_DC)
249{
250    struct st_mysqlnd_time t = {0};
251    unsigned long length; /* First byte encodes the length*/
252    char * value;
253    DBG_ENTER("ps_fetch_date");
254
255    if ((length = php_mysqlnd_net_field_length(row))) {
256        zend_uchar *to= *row;
257
258        t.time_type= MYSQLND_TIMESTAMP_DATE;
259        t.neg= 0;
260
261        t.second_part = t.hour = t.minute = t.second = 0;
262
263        t.year  = (unsigned int) sint2korr(to);
264        t.month = (unsigned int) to[2];
265        t.day   = (unsigned int) to[3];
266
267        (*row)+= length;
268    } else {
269        memset(&t, 0, sizeof(t));
270        t.time_type = MYSQLND_TIMESTAMP_DATE;
271    }
272
273    length = mnd_sprintf(&value, 0, "%04u-%02u-%02u", t.year, t.month, t.day);
274
275    DBG_INF_FMT("%s", value);
276    ZVAL_STRINGL(zv, value, length, 1);
277    mnd_sprintf_free(value);
278    DBG_VOID_RETURN;
279}
280/* }}} */
281
282
283/* {{{ ps_fetch_datetime */
284static void
285ps_fetch_datetime(zval * zv, const MYSQLND_FIELD * const field, unsigned int pack_len, zend_uchar ** row TSRMLS_DC)
286{
287    struct st_mysqlnd_time t;
288    unsigned long length; /* First byte encodes the length*/
289    char * value;
290    DBG_ENTER("ps_fetch_datetime");
291
292    if ((length = php_mysqlnd_net_field_length(row))) {
293        zend_uchar * to = *row;
294
295        t.time_type = MYSQLND_TIMESTAMP_DATETIME;
296        t.neg    = 0;
297
298        t.year   = (unsigned int) sint2korr(to);
299        t.month = (unsigned int) to[2];
300        t.day    = (unsigned int) to[3];
301
302        if (length > 4) {
303            t.hour   = (unsigned int) to[4];
304            t.minute = (unsigned int) to[5];
305            t.second = (unsigned int) to[6];
306        } else {
307            t.hour = t.minute = t.second= 0;
308        }
309        t.second_part = (length > 7) ? (unsigned long) sint4korr(to+7) : 0;
310
311        (*row)+= length;
312    } else {
313        memset(&t, 0, sizeof(t));
314        t.time_type = MYSQLND_TIMESTAMP_DATETIME;
315    }
316
317    length = mnd_sprintf(&value, 0, "%04u-%02u-%02u %02u:%02u:%02u", t.year, t.month, t.day, t.hour, t.minute, t.second);
318
319    DBG_INF_FMT("%s", value);
320    ZVAL_STRINGL(zv, value, length, 1);
321    mnd_sprintf_free(value);
322    DBG_VOID_RETURN;
323}
324/* }}} */
325
326
327/* {{{ ps_fetch_string */
328static void
329ps_fetch_string(zval * zv, const MYSQLND_FIELD * const field, unsigned int pack_len, zend_uchar ** row TSRMLS_DC)
330{
331    /*
332      For now just copy, before we make it possible
333      to write \0 to the row buffer
334    */
335    const unsigned long length = php_mysqlnd_net_field_length(row);
336    DBG_ENTER("ps_fetch_string");
337    DBG_INF_FMT("len = %lu", length);
338    DBG_INF("copying from the row buffer");
339    ZVAL_STRINGL(zv, (char *)*row, length, 1);
340
341    (*row) += length;
342    DBG_VOID_RETURN;
343}
344/* }}} */
345
346
347/* {{{ ps_fetch_bit */
348static void
349ps_fetch_bit(zval * zv, const MYSQLND_FIELD * const field, unsigned int pack_len, zend_uchar ** row TSRMLS_DC)
350{
351    unsigned long length = php_mysqlnd_net_field_length(row);
352    ps_fetch_from_1_to_8_bytes(zv, field, pack_len, row, length TSRMLS_CC);
353}
354/* }}} */
355
356
357/* {{{ _mysqlnd_init_ps_fetch_subsystem */
358void _mysqlnd_init_ps_fetch_subsystem()
359{
360    memset(mysqlnd_ps_fetch_functions, 0, sizeof(mysqlnd_ps_fetch_functions));
361    mysqlnd_ps_fetch_functions[MYSQL_TYPE_NULL].func        = ps_fetch_null;
362    mysqlnd_ps_fetch_functions[MYSQL_TYPE_NULL].pack_len    = 0;
363    mysqlnd_ps_fetch_functions[MYSQL_TYPE_NULL].php_type    = IS_NULL;
364    mysqlnd_ps_fetch_functions[MYSQL_TYPE_NULL].can_ret_as_str_in_uni   = TRUE;
365
366    mysqlnd_ps_fetch_functions[MYSQL_TYPE_TINY].func        = ps_fetch_int8;
367    mysqlnd_ps_fetch_functions[MYSQL_TYPE_TINY].pack_len    = 1;
368    mysqlnd_ps_fetch_functions[MYSQL_TYPE_TINY].php_type    = IS_LONG;
369    mysqlnd_ps_fetch_functions[MYSQL_TYPE_TINY].can_ret_as_str_in_uni   = TRUE;
370
371    mysqlnd_ps_fetch_functions[MYSQL_TYPE_SHORT].func       = ps_fetch_int16;
372    mysqlnd_ps_fetch_functions[MYSQL_TYPE_SHORT].pack_len   = 2;
373    mysqlnd_ps_fetch_functions[MYSQL_TYPE_SHORT].php_type   = IS_LONG;
374    mysqlnd_ps_fetch_functions[MYSQL_TYPE_SHORT].can_ret_as_str_in_uni  = TRUE;
375
376    mysqlnd_ps_fetch_functions[MYSQL_TYPE_YEAR].func        = ps_fetch_int16;
377    mysqlnd_ps_fetch_functions[MYSQL_TYPE_YEAR].pack_len    = 2;
378    mysqlnd_ps_fetch_functions[MYSQL_TYPE_YEAR].php_type    = IS_LONG;
379    mysqlnd_ps_fetch_functions[MYSQL_TYPE_YEAR].can_ret_as_str_in_uni   = TRUE;
380
381    mysqlnd_ps_fetch_functions[MYSQL_TYPE_INT24].func       = ps_fetch_int32;
382    mysqlnd_ps_fetch_functions[MYSQL_TYPE_INT24].pack_len   = 4;
383    mysqlnd_ps_fetch_functions[MYSQL_TYPE_INT24].php_type   = IS_LONG;
384    mysqlnd_ps_fetch_functions[MYSQL_TYPE_INT24].can_ret_as_str_in_uni  = TRUE;
385
386    mysqlnd_ps_fetch_functions[MYSQL_TYPE_LONG].func        = ps_fetch_int32;
387    mysqlnd_ps_fetch_functions[MYSQL_TYPE_LONG].pack_len    = 4;
388    mysqlnd_ps_fetch_functions[MYSQL_TYPE_LONG].php_type    = IS_LONG;
389    mysqlnd_ps_fetch_functions[MYSQL_TYPE_LONG].can_ret_as_str_in_uni   = TRUE;
390
391    mysqlnd_ps_fetch_functions[MYSQL_TYPE_LONGLONG].func    = ps_fetch_int64;
392    mysqlnd_ps_fetch_functions[MYSQL_TYPE_LONGLONG].pack_len= 8;
393    mysqlnd_ps_fetch_functions[MYSQL_TYPE_LONGLONG].php_type= IS_LONG;
394    mysqlnd_ps_fetch_functions[MYSQL_TYPE_LONGLONG].can_ret_as_str_in_uni   = TRUE;
395
396    mysqlnd_ps_fetch_functions[MYSQL_TYPE_FLOAT].func       = ps_fetch_float;
397    mysqlnd_ps_fetch_functions[MYSQL_TYPE_FLOAT].pack_len   = 4;
398    mysqlnd_ps_fetch_functions[MYSQL_TYPE_FLOAT].php_type   = IS_DOUBLE;
399    mysqlnd_ps_fetch_functions[MYSQL_TYPE_FLOAT].can_ret_as_str_in_uni  = TRUE;
400
401    mysqlnd_ps_fetch_functions[MYSQL_TYPE_DOUBLE].func      = ps_fetch_double;
402    mysqlnd_ps_fetch_functions[MYSQL_TYPE_DOUBLE].pack_len  = 8;
403    mysqlnd_ps_fetch_functions[MYSQL_TYPE_DOUBLE].php_type  = IS_DOUBLE;
404    mysqlnd_ps_fetch_functions[MYSQL_TYPE_DOUBLE].can_ret_as_str_in_uni = TRUE;
405
406    mysqlnd_ps_fetch_functions[MYSQL_TYPE_TIME].func        = ps_fetch_time;
407    mysqlnd_ps_fetch_functions[MYSQL_TYPE_TIME].pack_len    = MYSQLND_PS_SKIP_RESULT_W_LEN;
408    mysqlnd_ps_fetch_functions[MYSQL_TYPE_TIME].php_type    = IS_STRING;
409    mysqlnd_ps_fetch_functions[MYSQL_TYPE_TIME].can_ret_as_str_in_uni   = TRUE;
410
411    mysqlnd_ps_fetch_functions[MYSQL_TYPE_DATE].func        = ps_fetch_date;
412    mysqlnd_ps_fetch_functions[MYSQL_TYPE_DATE].pack_len    = MYSQLND_PS_SKIP_RESULT_W_LEN;
413    mysqlnd_ps_fetch_functions[MYSQL_TYPE_DATE].php_type    = IS_STRING;
414    mysqlnd_ps_fetch_functions[MYSQL_TYPE_DATE].can_ret_as_str_in_uni   = TRUE;
415
416    mysqlnd_ps_fetch_functions[MYSQL_TYPE_NEWDATE].func     = ps_fetch_string;
417    mysqlnd_ps_fetch_functions[MYSQL_TYPE_NEWDATE].pack_len = MYSQLND_PS_SKIP_RESULT_W_LEN;
418    mysqlnd_ps_fetch_functions[MYSQL_TYPE_NEWDATE].php_type = IS_STRING;
419    mysqlnd_ps_fetch_functions[MYSQL_TYPE_NEWDATE].can_ret_as_str_in_uni    = TRUE;
420
421    mysqlnd_ps_fetch_functions[MYSQL_TYPE_DATETIME].func    = ps_fetch_datetime;
422    mysqlnd_ps_fetch_functions[MYSQL_TYPE_DATETIME].pack_len= MYSQLND_PS_SKIP_RESULT_W_LEN;
423    mysqlnd_ps_fetch_functions[MYSQL_TYPE_DATETIME].php_type= IS_STRING;
424    mysqlnd_ps_fetch_functions[MYSQL_TYPE_DATETIME].can_ret_as_str_in_uni   = TRUE;
425
426    mysqlnd_ps_fetch_functions[MYSQL_TYPE_TIMESTAMP].func   = ps_fetch_datetime;
427    mysqlnd_ps_fetch_functions[MYSQL_TYPE_TIMESTAMP].pack_len= MYSQLND_PS_SKIP_RESULT_W_LEN;
428    mysqlnd_ps_fetch_functions[MYSQL_TYPE_TIMESTAMP].php_type= IS_STRING;
429    mysqlnd_ps_fetch_functions[MYSQL_TYPE_TIMESTAMP].can_ret_as_str_in_uni  = TRUE;
430
431    mysqlnd_ps_fetch_functions[MYSQL_TYPE_TINY_BLOB].func   = ps_fetch_string;
432    mysqlnd_ps_fetch_functions[MYSQL_TYPE_TINY_BLOB].pack_len= MYSQLND_PS_SKIP_RESULT_STR;
433    mysqlnd_ps_fetch_functions[MYSQL_TYPE_TINY_BLOB].php_type = IS_STRING;
434    mysqlnd_ps_fetch_functions[MYSQL_TYPE_TINY_BLOB].is_possibly_blob = TRUE;
435    mysqlnd_ps_fetch_functions[MYSQL_TYPE_TINY_BLOB].can_ret_as_str_in_uni  = TRUE;
436
437    mysqlnd_ps_fetch_functions[MYSQL_TYPE_BLOB].func        = ps_fetch_string;
438    mysqlnd_ps_fetch_functions[MYSQL_TYPE_BLOB].pack_len    = MYSQLND_PS_SKIP_RESULT_STR;
439    mysqlnd_ps_fetch_functions[MYSQL_TYPE_BLOB].php_type    = IS_STRING;
440    mysqlnd_ps_fetch_functions[MYSQL_TYPE_BLOB].is_possibly_blob = TRUE;
441    mysqlnd_ps_fetch_functions[MYSQL_TYPE_BLOB].can_ret_as_str_in_uni   = TRUE;
442
443    mysqlnd_ps_fetch_functions[MYSQL_TYPE_MEDIUM_BLOB].func     = ps_fetch_string;
444    mysqlnd_ps_fetch_functions[MYSQL_TYPE_MEDIUM_BLOB].pack_len = MYSQLND_PS_SKIP_RESULT_STR;
445    mysqlnd_ps_fetch_functions[MYSQL_TYPE_MEDIUM_BLOB].php_type = IS_STRING;
446    mysqlnd_ps_fetch_functions[MYSQL_TYPE_MEDIUM_BLOB].is_possibly_blob = TRUE;
447    mysqlnd_ps_fetch_functions[MYSQL_TYPE_MEDIUM_BLOB].can_ret_as_str_in_uni    = TRUE;
448
449    mysqlnd_ps_fetch_functions[MYSQL_TYPE_LONG_BLOB].func       = ps_fetch_string;
450    mysqlnd_ps_fetch_functions[MYSQL_TYPE_LONG_BLOB].pack_len   = MYSQLND_PS_SKIP_RESULT_STR;
451    mysqlnd_ps_fetch_functions[MYSQL_TYPE_LONG_BLOB].php_type   = IS_STRING;
452    mysqlnd_ps_fetch_functions[MYSQL_TYPE_LONG_BLOB].is_possibly_blob = TRUE;
453    mysqlnd_ps_fetch_functions[MYSQL_TYPE_LONG_BLOB].can_ret_as_str_in_uni  = TRUE;
454
455    mysqlnd_ps_fetch_functions[MYSQL_TYPE_BIT].func     = ps_fetch_bit;
456    mysqlnd_ps_fetch_functions[MYSQL_TYPE_BIT].pack_len = 8;
457    mysqlnd_ps_fetch_functions[MYSQL_TYPE_BIT].php_type = IS_LONG;
458    mysqlnd_ps_fetch_functions[MYSQL_TYPE_BIT].can_ret_as_str_in_uni = TRUE;
459
460    mysqlnd_ps_fetch_functions[MYSQL_TYPE_VAR_STRING].func      = ps_fetch_string;
461    mysqlnd_ps_fetch_functions[MYSQL_TYPE_VAR_STRING].pack_len  = MYSQLND_PS_SKIP_RESULT_STR;
462    mysqlnd_ps_fetch_functions[MYSQL_TYPE_VAR_STRING].php_type = IS_STRING;
463    mysqlnd_ps_fetch_functions[MYSQL_TYPE_VAR_STRING].is_possibly_blob = TRUE;
464
465    mysqlnd_ps_fetch_functions[MYSQL_TYPE_VARCHAR].func     = ps_fetch_string;
466    mysqlnd_ps_fetch_functions[MYSQL_TYPE_VARCHAR].pack_len = MYSQLND_PS_SKIP_RESULT_STR;
467    mysqlnd_ps_fetch_functions[MYSQL_TYPE_VARCHAR].php_type = IS_STRING;
468    mysqlnd_ps_fetch_functions[MYSQL_TYPE_VARCHAR].is_possibly_blob = TRUE;
469
470    mysqlnd_ps_fetch_functions[MYSQL_TYPE_STRING].func          = ps_fetch_string;
471    mysqlnd_ps_fetch_functions[MYSQL_TYPE_STRING].pack_len      = MYSQLND_PS_SKIP_RESULT_STR;
472    mysqlnd_ps_fetch_functions[MYSQL_TYPE_STRING].php_type  = IS_STRING;
473    mysqlnd_ps_fetch_functions[MYSQL_TYPE_STRING].is_possibly_blob = TRUE;
474
475    mysqlnd_ps_fetch_functions[MYSQL_TYPE_DECIMAL].func     = ps_fetch_string;
476    mysqlnd_ps_fetch_functions[MYSQL_TYPE_DECIMAL].pack_len = MYSQLND_PS_SKIP_RESULT_STR;
477    mysqlnd_ps_fetch_functions[MYSQL_TYPE_DECIMAL].php_type = IS_STRING;
478    mysqlnd_ps_fetch_functions[MYSQL_TYPE_DECIMAL].can_ret_as_str_in_uni    = TRUE;
479
480    mysqlnd_ps_fetch_functions[MYSQL_TYPE_NEWDECIMAL].func      = ps_fetch_string;
481    mysqlnd_ps_fetch_functions[MYSQL_TYPE_NEWDECIMAL].pack_len  = MYSQLND_PS_SKIP_RESULT_STR;
482    mysqlnd_ps_fetch_functions[MYSQL_TYPE_NEWDECIMAL].php_type  = IS_STRING;
483    mysqlnd_ps_fetch_functions[MYSQL_TYPE_NEWDECIMAL].can_ret_as_str_in_uni = TRUE;
484
485    mysqlnd_ps_fetch_functions[MYSQL_TYPE_ENUM].func        = ps_fetch_string;
486    mysqlnd_ps_fetch_functions[MYSQL_TYPE_ENUM].pack_len    = MYSQLND_PS_SKIP_RESULT_STR;
487    mysqlnd_ps_fetch_functions[MYSQL_TYPE_ENUM].php_type    = IS_STRING;
488
489    mysqlnd_ps_fetch_functions[MYSQL_TYPE_SET].func         = ps_fetch_string;
490    mysqlnd_ps_fetch_functions[MYSQL_TYPE_SET].pack_len     = MYSQLND_PS_SKIP_RESULT_STR;
491    mysqlnd_ps_fetch_functions[MYSQL_TYPE_SET].php_type     = IS_STRING;
492
493    mysqlnd_ps_fetch_functions[MYSQL_TYPE_GEOMETRY].func    = ps_fetch_string;
494    mysqlnd_ps_fetch_functions[MYSQL_TYPE_GEOMETRY].pack_len= MYSQLND_PS_SKIP_RESULT_STR;
495    mysqlnd_ps_fetch_functions[MYSQL_TYPE_GEOMETRY].php_type= IS_STRING;
496}
497/* }}} */
498
499
500/* {{{ mysqlnd_stmt_copy_it */
501static enum_func_status
502mysqlnd_stmt_copy_it(zval *** copies, zval * original, unsigned int param_count, unsigned int current TSRMLS_DC)
503{
504    if (!*copies) {
505        *copies = mnd_ecalloc(param_count, sizeof(zval *));
506    }
507    if (*copies) {
508        MAKE_STD_ZVAL((*copies)[current]);
509        *(*copies)[current] = *original;
510        Z_SET_REFCOUNT_P((*copies)[current], 1);
511        zval_copy_ctor((*copies)[current]);
512        return PASS;
513    }
514    return FAIL;
515}
516/* }}} */
517
518
519/* {{{ mysqlnd_stmt_free_copies */
520static void
521mysqlnd_stmt_free_copies(MYSQLND_STMT_DATA * stmt, zval ** copies TSRMLS_DC)
522{
523    if (copies) {
524        unsigned int i;
525        for (i = 0; i < stmt->param_count; i++) {
526            if (copies[i]) {
527                zval_ptr_dtor(&copies[i]);
528            }
529        }
530        mnd_efree(copies);
531    }
532}
533/* }}} */
534
535
536/* {{{ mysqlnd_stmt_execute_check_n_enlarge_buffer */
537static enum_func_status
538mysqlnd_stmt_execute_check_n_enlarge_buffer(zend_uchar **buf, zend_uchar **p, size_t * buf_len, zend_uchar * const provided_buffer, size_t needed_bytes TSRMLS_DC)
539{
540    const size_t overalloc = 5;
541    size_t left = (*buf_len - (*p - *buf));
542
543    if (left < (needed_bytes + overalloc)) {
544        size_t offset = *p - *buf;
545        zend_uchar *tmp_buf;
546        *buf_len = offset + needed_bytes + overalloc;
547        tmp_buf = mnd_emalloc(*buf_len);
548        if (!tmp_buf) {
549            return FAIL;
550        }
551        memcpy(tmp_buf, *buf, offset);
552        if (*buf != provided_buffer) {
553            mnd_efree(*buf);
554        }
555        *buf = tmp_buf;
556        /* Update our pos pointer */
557        *p = *buf + offset;
558    }
559    return PASS;
560}
561/* }}} */
562
563
564/* {{{ mysqlnd_stmt_execute_prepare_param_types */
565static enum_func_status
566mysqlnd_stmt_execute_prepare_param_types(MYSQLND_STMT_DATA * stmt, zval *** copies_param, int * resend_types_next_time TSRMLS_DC)
567{
568    unsigned int i;
569    DBG_ENTER("mysqlnd_stmt_execute_prepare_param_types");
570    for (i = 0; i < stmt->param_count; i++) {
571        short current_type = stmt->param_bind[i].type;
572
573        if (Z_TYPE_P(stmt->param_bind[i].zv) != IS_NULL && (current_type == MYSQL_TYPE_LONG || current_type == MYSQL_TYPE_LONGLONG)) {
574            zval ** copies;
575            /* always copy the var, because we do many conversions */
576            if (Z_TYPE_P(stmt->param_bind[i].zv) != IS_LONG &&
577                PASS != mysqlnd_stmt_copy_it(copies_param, stmt->param_bind[i].zv, stmt->param_count, i TSRMLS_CC))
578            {
579                SET_OOM_ERROR(*stmt->error_info);
580                goto end;
581            }
582            copies = *copies_param;
583            /*
584              if it doesn't fit in a long send it as a string.
585              Check bug #52891 : Wrong data inserted with mysqli/mysqlnd when using bind_param, value > LONG_MAX
586            */
587            if (Z_TYPE_P(stmt->param_bind[i].zv) != IS_LONG) {
588                zval *tmp_data = (copies && copies[i])? copies[i]: stmt->param_bind[i].zv;
589                /*
590                  Because converting to double and back to long can lead
591                  to losing precision we need second variable. Conversion to double is to see if
592                  value is too big for a long. As said, precision could be lost.
593                */
594                zval *tmp_data_copy;
595                MAKE_STD_ZVAL(tmp_data_copy);
596                *tmp_data_copy = *tmp_data;
597                Z_SET_REFCOUNT_P(tmp_data_copy, 1);
598                zval_copy_ctor(tmp_data_copy);
599                convert_to_double_ex(&tmp_data_copy);
600
601                /*
602                  if it doesn't fit in a long send it as a string.
603                  Check bug #52891 : Wrong data inserted with mysqli/mysqlnd when using bind_param, value > LONG_MAX
604                  We do transformation here, which will be used later when sending types. The code later relies on this.
605                */
606                if (Z_DVAL_P(tmp_data_copy) > LONG_MAX || Z_DVAL_P(tmp_data_copy) < LONG_MIN) {
607                    stmt->send_types_to_server = *resend_types_next_time = 1;
608                    convert_to_string_ex(&tmp_data);
609                } else {
610                    convert_to_long_ex(&tmp_data);
611                }
612
613                zval_ptr_dtor(&tmp_data_copy);
614            }
615        }
616    }
617    DBG_RETURN(PASS);
618end:
619    DBG_RETURN(FAIL);
620}
621/* }}} */
622
623
624/* {{{ mysqlnd_stmt_execute_store_types */
625static void
626mysqlnd_stmt_execute_store_types(MYSQLND_STMT_DATA * stmt, zval ** copies, zend_uchar ** p)
627{
628    unsigned int i;
629    for (i = 0; i < stmt->param_count; i++) {
630        short current_type = stmt->param_bind[i].type;
631        /* our types are not unsigned */
632#if SIZEOF_LONG==8
633        if (current_type == MYSQL_TYPE_LONG) {
634            current_type = MYSQL_TYPE_LONGLONG;
635        }
636#endif
637        if (Z_TYPE_P(stmt->param_bind[i].zv) != IS_NULL && (current_type == MYSQL_TYPE_LONG || current_type == MYSQL_TYPE_LONGLONG)) {
638            /*
639              if it doesn't fit in a long send it as a string.
640              Check bug #52891 : Wrong data inserted with mysqli/mysqlnd when using bind_param, value > LONG_MAX
641            */
642            if (Z_TYPE_P(stmt->param_bind[i].zv) != IS_LONG) {
643                const zval *tmp_data = (copies && copies[i])? copies[i]: stmt->param_bind[i].zv;
644                /*
645                  In case of IS_LONG we do nothing, it is ok, in case of string, we just need to set current_type.
646                  The actual transformation has been performed several dozens line above.
647                */
648                if (Z_TYPE_P(tmp_data) == IS_STRING) {
649                    current_type = MYSQL_TYPE_VAR_STRING;
650                    /*
651                      don't change stmt->param_bind[i].type to MYSQL_TYPE_VAR_STRING
652                      we force convert_to_long_ex in all cases, thus the type will be right in the next switch.
653                      if the type is however not long, then we will do a goto in the next switch.
654                      We want to preserve the original bind type given by the user. Thus, we do these hacks.
655                    */
656                }
657            }
658        }
659        int2store(*p, current_type);
660        *p+= 2;
661    }
662}
663/* }}} */
664
665
666/* {{{ mysqlnd_stmt_execute_calculate_param_values_size */
667static enum_func_status
668mysqlnd_stmt_execute_calculate_param_values_size(MYSQLND_STMT_DATA * stmt, zval *** copies_param, size_t * data_size TSRMLS_DC)
669{
670    unsigned int i;
671    DBG_ENTER("mysqlnd_stmt_execute_calculate_param_values_size");
672    for (i = 0; i < stmt->param_count; i++) {
673        unsigned short is_longlong = 0;
674        unsigned int j;
675        zval *the_var = stmt->param_bind[i].zv;
676
677        if (!the_var || (stmt->param_bind[i].type != MYSQL_TYPE_LONG_BLOB && Z_TYPE_P(the_var) == IS_NULL)) {
678            continue;
679        }
680        for (j = i + 1; j < stmt->param_count; j++) {
681            if (stmt->param_bind[j].zv == the_var) {
682                /* Double binding of the same zval, make a copy */
683                if (!*copies_param || !(*copies_param)[i]) {
684                    if (PASS != mysqlnd_stmt_copy_it(copies_param, the_var, stmt->param_count, i TSRMLS_CC)) {
685                        SET_OOM_ERROR(*stmt->error_info);
686                        goto end;
687                    }
688                }
689                break;
690            }
691        }
692
693        switch (stmt->param_bind[i].type) {
694            case MYSQL_TYPE_DOUBLE:
695                *data_size += 8;
696                if (Z_TYPE_P(the_var) != IS_DOUBLE) {
697                    if (!*copies_param || !(*copies_param)[i]) {
698                        if (PASS != mysqlnd_stmt_copy_it(copies_param, the_var, stmt->param_count, i TSRMLS_CC)) {
699                            SET_OOM_ERROR(*stmt->error_info);
700                            goto end;
701                        }
702                    }
703                }
704                break;
705            case MYSQL_TYPE_LONGLONG:
706                is_longlong = 4;
707                /* fall-through */
708            case MYSQL_TYPE_LONG:
709                {
710                    zval *tmp_data = (*copies_param && (*copies_param)[i])? (*copies_param)[i]: stmt->param_bind[i].zv;
711                    if (Z_TYPE_P(tmp_data) == IS_STRING) {
712                        goto use_string;
713                    }
714                    convert_to_long_ex(&tmp_data);
715                }
716                *data_size += 4 + is_longlong;
717                break;
718            case MYSQL_TYPE_LONG_BLOB:
719                if (!(stmt->param_bind[i].flags & MYSQLND_PARAM_BIND_BLOB_USED)) {
720                    /*
721                      User hasn't sent anything, we will send empty string.
722                      Empty string has length of 0, encoded in 1 byte. No real
723                      data will follows after it.
724                    */
725                    (*data_size)++;
726                }
727                break;
728            case MYSQL_TYPE_VAR_STRING:
729use_string:
730                *data_size += 8; /* max 8 bytes for size */
731                if (Z_TYPE_P(the_var) != IS_STRING) {
732                    if (!*copies_param || !(*copies_param)[i]) {
733                        if (PASS != mysqlnd_stmt_copy_it(copies_param, the_var, stmt->param_count, i TSRMLS_CC)) {
734                            SET_OOM_ERROR(*stmt->error_info);
735                            goto end;
736                        }
737                    }
738                    the_var = (*copies_param)[i];
739                }
740                convert_to_string_ex(&the_var);
741                *data_size += Z_STRLEN_P(the_var);
742                break;
743        }
744    }
745    DBG_RETURN(PASS);
746end:
747    DBG_RETURN(FAIL);
748}
749/* }}} */
750
751
752/* {{{ mysqlnd_stmt_execute_store_param_values */
753static void
754mysqlnd_stmt_execute_store_param_values(MYSQLND_STMT_DATA * stmt, zval ** copies, zend_uchar * buf, zend_uchar ** p, size_t null_byte_offset)
755{
756    unsigned int i;
757    for (i = 0; i < stmt->param_count; i++) {
758        zval * data = (copies && copies[i])? copies[i]: stmt->param_bind[i].zv;
759        /* Handle long data */
760        if (stmt->param_bind[i].zv && Z_TYPE_P(data) == IS_NULL) {
761            (buf + null_byte_offset)[i/8] |= (zend_uchar) (1 << (i & 7));
762        } else {
763            switch (stmt->param_bind[i].type) {
764                case MYSQL_TYPE_DOUBLE:
765                    convert_to_double_ex(&data);
766                    float8store(*p, Z_DVAL_P(data));
767                    (*p) += 8;
768                    break;
769                case MYSQL_TYPE_LONGLONG:
770                    if (Z_TYPE_P(data) == IS_STRING) {
771                        goto send_string;
772                    }
773                    /* data has alreade been converted to long */
774                    int8store(*p, Z_LVAL_P(data));
775                    (*p) += 8;
776                    break;
777                case MYSQL_TYPE_LONG:
778                    if (Z_TYPE_P(data) == IS_STRING) {
779                        goto send_string;
780                    }
781                    /* data has alreade been converted to long */
782                    int4store(*p, Z_LVAL_P(data));
783                    (*p) += 4;
784                    break;
785                case MYSQL_TYPE_LONG_BLOB:
786                    if (stmt->param_bind[i].flags & MYSQLND_PARAM_BIND_BLOB_USED) {
787                        stmt->param_bind[i].flags &= ~MYSQLND_PARAM_BIND_BLOB_USED;
788                    } else {
789                        /* send_long_data() not called, send empty string */
790                        *p = php_mysqlnd_net_store_length(*p, 0);
791                    }
792                    break;
793                case MYSQL_TYPE_VAR_STRING:
794send_string:
795                    {
796                        size_t len = Z_STRLEN_P(data);
797                        /* to is after p. The latter hasn't been moved */
798                        *p = php_mysqlnd_net_store_length(*p, len);
799                        memcpy(*p, Z_STRVAL_P(data), len);
800                        (*p) += len;
801                    }
802                    break;
803                default:
804                    /* Won't happen, but set to NULL */
805                    (buf + null_byte_offset)[i/8] |= (zend_uchar) (1 << (i & 7));
806                    break;
807            }
808        }
809    }
810}
811/* }}} */
812
813
814/* {{{ mysqlnd_stmt_execute_store_params */
815static enum_func_status
816mysqlnd_stmt_execute_store_params(MYSQLND_STMT * s, zend_uchar **buf, zend_uchar **p, size_t *buf_len  TSRMLS_DC)
817{
818    MYSQLND_STMT_DATA * stmt = s->data;
819    unsigned int i = 0;
820    zend_uchar * provided_buffer = *buf;
821    size_t data_size = 0;
822    zval **copies = NULL;/* if there are different types */
823    enum_func_status ret = FAIL;
824    int resend_types_next_time = 0;
825    size_t null_byte_offset;
826
827    DBG_ENTER("mysqlnd_stmt_execute_store_params");
828
829    {
830        unsigned int null_count = (stmt->param_count + 7) / 8;
831        if (FAIL == mysqlnd_stmt_execute_check_n_enlarge_buffer(buf, p, buf_len, provided_buffer, null_count TSRMLS_CC)) {
832            SET_OOM_ERROR(*stmt->error_info);
833            goto end;
834        }
835        /* put `null` bytes */
836        null_byte_offset = *p - *buf;
837        memset(*p, 0, null_count);
838        *p += null_count;
839    }
840
841/* 1. Store type information */
842    /*
843      check if need to send the types even if stmt->send_types_to_server is 0. This is because
844      if we send "i" (42) then the type will be int and the server will expect int. However, if next
845      time we try to send > LONG_MAX, the conversion to string will send a string and the server
846      won't expect it and interpret the value as 0. Thus we need to resend the types, if any such values
847      occur, and force resend for the next execution.
848    */
849    if (FAIL == mysqlnd_stmt_execute_prepare_param_types(stmt, &copies, &resend_types_next_time TSRMLS_CC)) {
850        goto end;
851    }
852
853    int1store(*p, stmt->send_types_to_server);
854    (*p)++;
855
856    if (stmt->send_types_to_server) {
857        if (FAIL == mysqlnd_stmt_execute_check_n_enlarge_buffer(buf, p, buf_len, provided_buffer, stmt->param_count * 2 TSRMLS_CC)) {
858            SET_OOM_ERROR(*stmt->error_info);
859            goto end;
860        }
861        mysqlnd_stmt_execute_store_types(stmt, copies, p);
862    }
863
864    stmt->send_types_to_server = resend_types_next_time;
865
866/* 2. Store data */
867    /* 2.1 Calculate how much space we need */
868    if (FAIL == mysqlnd_stmt_execute_calculate_param_values_size(stmt, &copies, &data_size TSRMLS_CC)) {
869        goto end;
870    }
871
872    /* 2.2 Enlarge the buffer, if needed */
873    if (FAIL == mysqlnd_stmt_execute_check_n_enlarge_buffer(buf, p, buf_len, provided_buffer, data_size TSRMLS_CC)) {
874        SET_OOM_ERROR(*stmt->error_info);
875        goto end;
876    }
877
878    /* 2.3 Store the actual data */
879    mysqlnd_stmt_execute_store_param_values(stmt, copies, *buf, p, null_byte_offset);
880
881    ret = PASS;
882end:
883    mysqlnd_stmt_free_copies(stmt, copies TSRMLS_CC);
884
885    DBG_INF_FMT("ret=%s", ret == PASS? "PASS":"FAIL");
886    DBG_RETURN(ret);
887}
888/* }}} */
889
890
891/* {{{ mysqlnd_stmt_execute_generate_request */
892enum_func_status
893mysqlnd_stmt_execute_generate_request(MYSQLND_STMT * const s, zend_uchar ** request, size_t *request_len, zend_bool * free_buffer TSRMLS_DC)
894{
895    MYSQLND_STMT_DATA * stmt = s->data;
896    zend_uchar  *p = stmt->execute_cmd_buffer.buffer,
897                *cmd_buffer = stmt->execute_cmd_buffer.buffer;
898    size_t cmd_buffer_length = stmt->execute_cmd_buffer.length;
899    enum_func_status ret;
900
901    DBG_ENTER("mysqlnd_stmt_execute_generate_request");
902
903    int4store(p, stmt->stmt_id);
904    p += 4;
905
906    /* flags is 4 bytes, we store just 1 */
907    int1store(p, (zend_uchar) stmt->flags);
908    p++;
909
910    /* Make it all zero */
911    int4store(p, 0);
912
913    int1store(p, 1); /* and send 1 for iteration count */
914    p+= 4;
915
916    ret = mysqlnd_stmt_execute_store_params(s, &cmd_buffer, &p, &cmd_buffer_length TSRMLS_CC);
917
918    *free_buffer = (cmd_buffer != stmt->execute_cmd_buffer.buffer);
919    *request_len = (p - cmd_buffer);
920    *request = cmd_buffer;
921    DBG_INF_FMT("ret=%s", ret == PASS? "PASS":"FAIL");
922    DBG_RETURN(ret);
923}
924/* }}} */
925
926/*
927 * Local variables:
928 * tab-width: 4
929 * c-basic-offset: 4
930 * End:
931 * vim600: noet sw=4 ts=4 fdm=marker
932 * vim<600: noet sw=4 ts=4
933 */
934