xref: /PHP-5.3/ext/mysqlnd/mysqlnd_result_meta.c (revision bc11e6fd)
1 /*
2   +----------------------------------------------------------------------+
3   | PHP Version 5                                                        |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 2006-2013 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: Georg Richter <georg@mysql.com>                             |
16   |          Andrey Hristov <andrey@mysql.com>                           |
17   |          Ulf Wendel <uwendel@mysql.com>                              |
18   +----------------------------------------------------------------------+
19 */
20 
21 /* $Id$ */
22 #include "php.h"
23 #include "mysqlnd.h"
24 #include "mysqlnd_priv.h"
25 #include "mysqlnd_result.h"
26 #include "mysqlnd_wireprotocol.h"
27 #include "mysqlnd_debug.h"
28 #include "ext/standard/basic_functions.h"
29 
30 
31 /* {{{ php_mysqlnd_free_field_metadata */
32 static void
php_mysqlnd_free_field_metadata(MYSQLND_FIELD * meta,zend_bool persistent TSRMLS_DC)33 php_mysqlnd_free_field_metadata(MYSQLND_FIELD *meta, zend_bool persistent TSRMLS_DC)
34 {
35 	if (meta) {
36 		if (meta->root) {
37 			mnd_pefree(meta->root, persistent);
38 			meta->root = NULL;
39 		}
40 		if (meta->def) {
41 			mnd_pefree(meta->def, persistent);
42 			meta->def = NULL;
43 		}
44 	}
45 }
46 /* }}} */
47 
48 
49 /* {{{ mysqlnd_handle_numeric */
50 /*
51   The following code is stolen from ZE - HANDLE_NUMERIC() macro from zend_hash.c
52   and modified for the needs of mysqlnd.
53 */
54 static zend_bool
mysqlnd_is_key_numeric(const char * key,size_t length,long * idx)55 mysqlnd_is_key_numeric(const char * key, size_t length, long *idx)
56 {
57 	register const char * tmp = key;
58 
59 	if (*tmp=='-') {
60 		tmp++;
61 	}
62 	if ((*tmp>='0' && *tmp<='9')) {
63 		do { /* possibly a numeric index */
64 			const char *end=key+length-1;
65 
66 			if (*tmp++=='0' && length>2) { /* don't accept numbers with leading zeros */
67 				break;
68 			}
69 			while (tmp<end) {
70 				if (!(*tmp>='0' && *tmp<='9')) {
71 					break;
72 				}
73 				tmp++;
74 			}
75 			if (tmp==end && *tmp=='\0') { /* a numeric index */
76 				if (*key=='-') {
77 					*idx = strtol(key, NULL, 10);
78 					if (*idx!=LONG_MIN) {
79 						return TRUE;
80 					}
81 				} else {
82 					*idx = strtol(key, NULL, 10);
83 					if (*idx!=LONG_MAX) {
84 						return TRUE;
85 					}
86 				}
87 			}
88 		} while (0);
89 	}
90 	return FALSE;
91 }
92 /* }}} */
93 
94 
95 #if MYSQLND_UNICODE
96 /* {{{ mysqlnd_unicode_is_key_numeric */
97 static zend_bool
mysqlnd_unicode_is_key_numeric(UChar * key,size_t length,long * idx)98 mysqlnd_unicode_is_key_numeric(UChar *key, size_t length, long *idx)
99 {
100 	register UChar * tmp=key;
101 
102 	if (*tmp==0x2D /*'-'*/) {
103 		tmp++;
104 	}
105 	if ((*tmp>=0x30 /*'0'*/ && *tmp<=0x39 /*'9'*/)) { /* possibly a numeric index */
106 		do {
107 			UChar *end=key+length-1;
108 
109 			if (*tmp++==0x30 && length>2) { /* don't accept numbers with leading zeros */
110 				break;
111 			}
112 			while (tmp<end) {
113 				if (!(*tmp>=0x30 /*'0'*/ && *tmp<=0x39 /*'9'*/)) {
114 					break;
115 				}
116 				tmp++;
117 			}
118 			if (tmp==end && *tmp==0) { /* a numeric index */
119 				if (*key==0x2D /*'-'*/) {
120 					*idx = zend_u_strtol(key, NULL, 10);
121 					if (*idx!=LONG_MIN) {
122 						return TRUE;
123 					}
124 				} else {
125 					*idx = zend_u_strtol(key, NULL, 10);
126 					if (*idx!=LONG_MAX) {
127 						return TRUE;
128 					}
129 				}
130 			}
131 		} while (0);
132 	}
133 	return FALSE;
134 }
135 /* }}} */
136 #endif
137 
138 
139 /* {{{ mysqlnd_res_meta::read_metadata */
140 static enum_func_status
MYSQLND_METHOD(mysqlnd_res_meta,read_metadata)141 MYSQLND_METHOD(mysqlnd_res_meta, read_metadata)(MYSQLND_RES_METADATA * const meta, MYSQLND *conn TSRMLS_DC)
142 {
143 	unsigned int i = 0;
144 	MYSQLND_PACKET_RES_FIELD * field_packet;
145 #if MYSQLND_UNICODE
146 	UChar *ustr;
147 	int ulen;
148 #endif
149 
150 	DBG_ENTER("mysqlnd_res_meta::read_metadata");
151 
152 	field_packet = conn->protocol->m.get_result_field_packet(conn->protocol, FALSE TSRMLS_CC);
153 	if (!field_packet) {
154 		SET_OOM_ERROR(conn->error_info);
155 		DBG_RETURN(FAIL);
156 	}
157 	field_packet->persistent_alloc = meta->persistent;
158 	for (;i < meta->field_count; i++) {
159 		long idx;
160 
161 		if (meta->fields[i].root) {
162 			/* We re-read metadata for PS */
163 			mnd_pefree(meta->fields[i].root, meta->persistent);
164 			meta->fields[i].root = NULL;
165 		}
166 
167 		field_packet->metadata = &(meta->fields[i]);
168 		if (FAIL == PACKET_READ(field_packet, conn)) {
169 			PACKET_FREE(field_packet);
170 			DBG_RETURN(FAIL);
171 		}
172 		if (field_packet->error_info.error_no) {
173 			conn->error_info = field_packet->error_info;
174 			/* Return back from CONN_QUERY_SENT */
175 			PACKET_FREE(field_packet);
176 			DBG_RETURN(FAIL);
177 		}
178 
179 		if (field_packet->stupid_list_fields_eof == TRUE) {
180 			meta->field_count = i;
181 			break;
182 		}
183 
184 		if (mysqlnd_ps_fetch_functions[meta->fields[i].type].func == NULL) {
185 			DBG_ERR_FMT("Unknown type %u sent by the server.  Please send a report to the developers",
186 						meta->fields[i].type);
187 			php_error_docref(NULL TSRMLS_CC, E_WARNING,
188 							 "Unknown type %u sent by the server. "
189 							 "Please send a report to the developers",
190 							 meta->fields[i].type);
191 			PACKET_FREE(field_packet);
192 			DBG_RETURN(FAIL);
193 		}
194 		if (meta->fields[i].type == MYSQL_TYPE_BIT) {
195 			size_t field_len;
196 			DBG_INF("BIT");
197 			++meta->bit_fields_count;
198 			/* .length is in bits */
199 			field_len = meta->fields[i].length / 8;
200 			/*
201 			  If there is rest, add one byte :
202 			  8 bits = 1 byte but 9 bits = 2 bytes
203 			*/
204 			if (meta->fields[i].length % 8) {
205 				++field_len;
206 			}
207 			switch (field_len) {
208 				case 8:
209 				case 7:
210 				case 6:
211 				case 5:
212 					meta->bit_fields_total_len += 20;/* 21 digis, no sign*/
213 					break;
214 				case 4:
215 					meta->bit_fields_total_len += 10;/* 2 000 000 000*/
216 					break;
217 				case 3:
218 					meta->bit_fields_total_len += 8;/*  12 000 000*/
219 					break;
220 				case 2:
221 					meta->bit_fields_total_len += 5;/* 32 500 */
222 					break;
223 				case 1:
224 					meta->bit_fields_total_len += 3;/* 120 */
225 					break;
226 			}
227 		}
228 
229 #if MYSQLND_UNICODE
230 		zend_string_to_unicode(UG(utf8_conv), &ustr, &ulen,
231 							   meta->fields[i].name,
232 							   meta->fields[i].name_length TSRMLS_CC);
233 		if ((meta->zend_hash_keys[i].is_numeric =
234 						mysqlnd_unicode_is_key_numeric(ustr, ulen + 1, &idx)))
235 		{
236 			meta->zend_hash_keys[i].key = idx;
237 			mnd_efree(ustr);
238 		} else {
239 			meta->zend_hash_keys[i].ustr.u = ustr;
240 			meta->zend_hash_keys[i].ulen = ulen;
241 			meta->zend_hash_keys[i].key = zend_u_get_hash_value(IS_UNICODE, ZSTR(ustr), ulen + 1);
242 		}
243 #else
244 		/* For BC we have to check whether the key is numeric and use it like this */
245 		if ((meta->zend_hash_keys[i].is_numeric =
246 					mysqlnd_is_key_numeric(field_packet->metadata->name,
247 										   field_packet->metadata->name_length + 1,
248 										   &idx)))
249 		{
250 			meta->zend_hash_keys[i].key = idx;
251 		} else {
252 			meta->zend_hash_keys[i].key =
253 					zend_get_hash_value(field_packet->metadata->name,
254 										field_packet->metadata->name_length + 1);
255 		}
256 #endif
257 	}
258 	PACKET_FREE(field_packet);
259 
260 	DBG_RETURN(PASS);
261 }
262 /* }}} */
263 
264 
265 /* {{{ mysqlnd_res_meta::free */
266 static void
MYSQLND_METHOD(mysqlnd_res_meta,free)267 MYSQLND_METHOD(mysqlnd_res_meta, free)(MYSQLND_RES_METADATA * meta TSRMLS_DC)
268 {
269 	int i;
270 	MYSQLND_FIELD *fields;
271 	DBG_ENTER("mysqlnd_res_meta::free");
272 	DBG_INF_FMT("persistent=%u", meta->persistent);
273 
274 	if ((fields = meta->fields)) {
275 		DBG_INF("Freeing fields metadata");
276 		i = meta->field_count;
277 		while (i--) {
278 			php_mysqlnd_free_field_metadata(fields++, meta->persistent TSRMLS_CC);
279 		}
280 		mnd_pefree(meta->fields, meta->persistent);
281 		meta->fields = NULL;
282 	}
283 
284 	if (meta->zend_hash_keys) {
285 		DBG_INF("Freeing zend_hash_keys");
286 #if MYSQLND_UNICODE
287 		if (UG(unicode)) {
288 			for (i = 0; i < meta->field_count; i++) {
289 				if (meta->zend_hash_keys[i].ustr.v) {
290 					mnd_pefree(meta->zend_hash_keys[i].ustr.v, meta->persistent);
291 				}
292 			}
293 		}
294 #endif
295 		mnd_pefree(meta->zend_hash_keys, meta->persistent);
296 		meta->zend_hash_keys = NULL;
297 	}
298 	DBG_INF("Freeing metadata structure");
299 	mnd_pefree(meta, meta->persistent);
300 
301 	DBG_VOID_RETURN;
302 }
303 /* }}} */
304 
305 
306 /* {{{ mysqlnd_res::clone_metadata */
307 static MYSQLND_RES_METADATA *
MYSQLND_METHOD(mysqlnd_res_meta,clone_metadata)308 MYSQLND_METHOD(mysqlnd_res_meta, clone_metadata)(const MYSQLND_RES_METADATA * const meta, zend_bool persistent TSRMLS_DC)
309 {
310 	unsigned int i;
311 	/* +1 is to have empty marker at the end */
312 	MYSQLND_RES_METADATA * new_meta = NULL;
313 	MYSQLND_FIELD * new_fields;
314 	MYSQLND_FIELD * orig_fields = meta->fields;
315 	size_t len = meta->field_count * sizeof(struct mysqlnd_field_hash_key);
316 
317 	DBG_ENTER("mysqlnd_res_meta::clone_metadata");
318 	DBG_INF_FMT("persistent=%u", persistent);
319 
320 	new_meta = mnd_pecalloc(1, sizeof(MYSQLND_RES_METADATA), persistent);
321 	if (!new_meta) {
322 		goto oom;
323 	}
324 	new_meta->persistent = persistent;
325 	new_meta->m = meta->m;
326 
327 	new_fields = mnd_pecalloc(meta->field_count + 1, sizeof(MYSQLND_FIELD), persistent);
328 	if (!new_fields) {
329 		goto oom;
330 	}
331 
332 	new_meta->zend_hash_keys = mnd_pemalloc(len, persistent);
333 	if (!new_meta->zend_hash_keys) {
334 		goto oom;
335 	}
336 	memcpy(new_meta->zend_hash_keys, meta->zend_hash_keys, len);
337 
338 	/*
339 	  This will copy also the strings and the root, which we will have
340 	  to adjust in the loop
341 	*/
342 	memcpy(new_fields, orig_fields, (meta->field_count) * sizeof(MYSQLND_FIELD));
343 	for (i = 0; i < meta->field_count; i++) {
344 		/* First copy the root, then field by field adjust the pointers */
345 		new_fields[i].root = mnd_pemalloc(orig_fields[i].root_len, persistent);
346 		if (!new_fields[i].root) {
347 			goto oom;
348 		}
349 		memcpy(new_fields[i].root, orig_fields[i].root, new_fields[i].root_len);
350 
351 		if (orig_fields[i].name && orig_fields[i].name != mysqlnd_empty_string) {
352 			new_fields[i].name = new_fields[i].root +
353 								 (orig_fields[i].name - orig_fields[i].root);
354 		}
355 		if (orig_fields[i].org_name && orig_fields[i].org_name != mysqlnd_empty_string) {
356 			new_fields[i].org_name = new_fields[i].root +
357 									 (orig_fields[i].org_name - orig_fields[i].root);
358 		}
359 		if (orig_fields[i].table && orig_fields[i].table != mysqlnd_empty_string) {
360 			new_fields[i].table	= new_fields[i].root +
361 								  (orig_fields[i].table - orig_fields[i].root);
362 		}
363 		if (orig_fields[i].org_table && orig_fields[i].org_table != mysqlnd_empty_string) {
364 			new_fields[i].org_table	= new_fields[i].root +
365 									  (orig_fields[i].org_table - orig_fields[i].root);
366 		}
367 		if (orig_fields[i].db && orig_fields[i].db != mysqlnd_empty_string) {
368 			new_fields[i].db = new_fields[i].root + (orig_fields[i].db - orig_fields[i].root);
369 		}
370 		if (orig_fields[i].catalog && orig_fields[i].catalog != mysqlnd_empty_string) {
371 			new_fields[i].catalog = new_fields[i].root + (orig_fields[i].catalog - orig_fields[i].root);
372 		}
373 		/* def is not on the root, if allocated at all */
374 		if (orig_fields[i].def) {
375 			new_fields[i].def = mnd_pemalloc(orig_fields[i].def_length + 1, persistent);
376 			if (!new_fields[i].def) {
377 				goto oom;
378 			}
379 			/* copy the trailing \0 too */
380 			memcpy(new_fields[i].def, orig_fields[i].def, orig_fields[i].def_length + 1);
381 		}
382 #if MYSQLND_UNICODE
383 		if (new_meta->zend_hash_keys[i].ustr.u) {
384 			new_meta->zend_hash_keys[i].ustr.u =
385 					eustrndup(new_meta->zend_hash_keys[i].ustr.u, new_meta->zend_hash_keys[i].ulen);
386 			if (!new_meta->zend_hash_keys[i].ustr.u) {
387 				goto oom;
388 			}
389 		}
390 #endif
391 	}
392 	new_meta->current_field = 0;
393 	new_meta->field_count = meta->field_count;
394 
395 	new_meta->fields = new_fields;
396 
397 	DBG_RETURN(new_meta);
398 oom:
399 	if (new_meta) {
400 		new_meta->m->free_metadata(new_meta TSRMLS_CC);
401 		new_meta = NULL;
402 	}
403 	DBG_RETURN(NULL);
404 }
405 /* }}} */
406 
407 /* {{{ mysqlnd_res_meta::fetch_field */
408 static const MYSQLND_FIELD *
MYSQLND_METHOD(mysqlnd_res_meta,fetch_field)409 MYSQLND_METHOD(mysqlnd_res_meta, fetch_field)(MYSQLND_RES_METADATA * const meta TSRMLS_DC)
410 {
411 	DBG_ENTER("mysqlnd_res_meta::fetch_field");
412 	if (meta->current_field >= meta->field_count) {
413 		DBG_INF("no more fields");
414 		DBG_RETURN(NULL);
415 	}
416 	DBG_INF_FMT("name=%s max_length=%u",
417 		meta->fields[meta->current_field].name? meta->fields[meta->current_field].name:"",
418 		meta->fields[meta->current_field].max_length);
419 	DBG_RETURN(&meta->fields[meta->current_field++]);
420 }
421 /* }}} */
422 
423 
424 /* {{{ mysqlnd_res_meta::fetch_field_direct */
425 static const MYSQLND_FIELD *
MYSQLND_METHOD(mysqlnd_res_meta,fetch_field_direct)426 MYSQLND_METHOD(mysqlnd_res_meta, fetch_field_direct)(const MYSQLND_RES_METADATA * const meta, MYSQLND_FIELD_OFFSET fieldnr TSRMLS_DC)
427 {
428 	DBG_ENTER("mysqlnd_res_meta::fetch_field_direct");
429 	DBG_INF_FMT("fieldnr=%u", fieldnr);
430 	DBG_INF_FMT("name=%s max_length=%u",
431 		meta->fields[meta->current_field].name? meta->fields[meta->current_field].name:"",
432 		meta->fields[meta->current_field].max_length);
433 	DBG_RETURN(&meta->fields[fieldnr]);
434 }
435 /* }}} */
436 
437 
438 /* {{{ mysqlnd_res_meta::fetch_fields */
439 static const MYSQLND_FIELD *
MYSQLND_METHOD(mysqlnd_res_meta,fetch_fields)440 MYSQLND_METHOD(mysqlnd_res_meta, fetch_fields)(MYSQLND_RES_METADATA * const meta TSRMLS_DC)
441 {
442 	DBG_ENTER("mysqlnd_res_meta::fetch_fields");
443 	DBG_RETURN(meta->fields);
444 }
445 /* }}} */
446 
447 
448 /* {{{ mysqlnd_res_meta::field_tell */
449 static MYSQLND_FIELD_OFFSET
MYSQLND_METHOD(mysqlnd_res_meta,field_tell)450 MYSQLND_METHOD(mysqlnd_res_meta, field_tell)(const MYSQLND_RES_METADATA * const meta TSRMLS_DC)
451 {
452 	return meta->current_field;
453 }
454 /* }}} */
455 
456 
457 static
458 MYSQLND_CLASS_METHODS_START(mysqlnd_res_meta)
459 	MYSQLND_METHOD(mysqlnd_res_meta, fetch_field),
460 	MYSQLND_METHOD(mysqlnd_res_meta, fetch_field_direct),
461 	MYSQLND_METHOD(mysqlnd_res_meta, fetch_fields),
462 	MYSQLND_METHOD(mysqlnd_res_meta, field_tell),
463 	MYSQLND_METHOD(mysqlnd_res_meta, read_metadata),
464 	MYSQLND_METHOD(mysqlnd_res_meta, clone_metadata),
465 	MYSQLND_METHOD(mysqlnd_res_meta, free),
466 MYSQLND_CLASS_METHODS_END;
467 
468 
469 /* {{{ mysqlnd_result_meta_init */
470 PHPAPI MYSQLND_RES_METADATA *
mysqlnd_result_meta_init(unsigned int field_count,zend_bool persistent TSRMLS_DC)471 mysqlnd_result_meta_init(unsigned int field_count, zend_bool persistent TSRMLS_DC)
472 {
473 	size_t alloc_size = sizeof(MYSQLND_RES_METADATA) + mysqlnd_plugin_count() * sizeof(void *);
474 	MYSQLND_RES_METADATA *ret = mnd_pecalloc(1, alloc_size, persistent);
475 	DBG_ENTER("mysqlnd_result_meta_init");
476 	DBG_INF_FMT("persistent=%u", persistent);
477 
478 	do {
479 		if (!ret) {
480 			break;
481 		}
482 		ret->m = & mysqlnd_mysqlnd_res_meta_methods;
483 
484 		ret->persistent = persistent;
485 		ret->field_count = field_count;
486 		/* +1 is to have empty marker at the end */
487 		ret->fields = mnd_pecalloc(field_count + 1, sizeof(MYSQLND_FIELD), ret->persistent);
488 		ret->zend_hash_keys = mnd_pecalloc(field_count, sizeof(struct mysqlnd_field_hash_key), ret->persistent);
489 		if (!ret->fields || !ret->zend_hash_keys) {
490 			break;
491 		}
492 		DBG_INF_FMT("meta=%p", ret);
493 		DBG_RETURN(ret);
494 	} while (0);
495 	if (ret) {
496 		ret->m->free_metadata(ret TSRMLS_CC);
497 	}
498 	DBG_RETURN(NULL);
499 }
500 /* }}} */
501 
502 
503 /* {{{ mysqlnd_res_meta_get_methods */
504 PHPAPI struct st_mysqlnd_res_meta_methods *
mysqlnd_result_metadata_get_methods()505 mysqlnd_result_metadata_get_methods()
506 {
507 	return &mysqlnd_mysqlnd_res_meta_methods;
508 }
509 /* }}} */
510 
511 
512 /* {{{ _mysqlnd_plugin_get_plugin_result_metadata_data */
513 PHPAPI void **
_mysqlnd_plugin_get_plugin_result_metadata_data(const MYSQLND_RES_METADATA * meta,unsigned int plugin_id TSRMLS_DC)514 _mysqlnd_plugin_get_plugin_result_metadata_data(const MYSQLND_RES_METADATA * meta, unsigned int plugin_id TSRMLS_DC)
515 {
516 	DBG_ENTER("_mysqlnd_plugin_get_plugin_result_metadata_data");
517 	DBG_INF_FMT("plugin_id=%u", plugin_id);
518 	if (!meta || plugin_id >= mysqlnd_plugin_count()) {
519 		return NULL;
520 	}
521 	DBG_RETURN((void *)((char *)meta + sizeof(MYSQLND_RES_METADATA) + plugin_id * sizeof(void *)));
522 }
523 /* }}} */
524 
525 /*
526  * Local variables:
527  * tab-width: 4
528  * c-basic-offset: 4
529  * End:
530  * vim600: noet sw=4 ts=4 fdm=marker
531  * vim<600: noet sw=4 ts=4
532  */
533