xref: /PHP-8.4/ext/pdo_dblib/dblib_stmt.c (revision df481ef9)
1 /*
2   +----------------------------------------------------------------------+
3   | Copyright (c) The PHP Group                                          |
4   +----------------------------------------------------------------------+
5   | This source file is subject to version 3.01 of the PHP license,      |
6   | that is bundled with this package in the file LICENSE, and is        |
7   | available through the world-wide-web at the following url:           |
8   | https://www.php.net/license/3_01.txt                                 |
9   | If you did not receive a copy of the PHP license and are unable to   |
10   | obtain it through the world-wide-web, please send a note to          |
11   | license@php.net so we can mail you a copy immediately.               |
12   +----------------------------------------------------------------------+
13   | Author: Wez Furlong <wez@php.net>                                    |
14   |         Frank M. Kromann <frank@kromann.info>                        |
15   +----------------------------------------------------------------------+
16 */
17 
18 #ifdef HAVE_CONFIG_H
19 # include "config.h"
20 #endif
21 
22 #include "php.h"
23 #include "php_ini.h"
24 #include "ext/standard/info.h"
25 #include "ext/pdo/php_pdo.h"
26 #include "ext/pdo/php_pdo_driver.h"
27 #include "php_pdo_dblib.h"
28 #include "php_pdo_dblib_int.h"
29 #include "zend_exceptions.h"
30 
31 
32 /* {{{ pdo_dblib_get_field_name
33  *
34  * Return the data type name for a given TDS number
35  *
36  */
pdo_dblib_get_field_name(int type)37 static char *pdo_dblib_get_field_name(int type)
38 {
39 	/*
40 	 * I don't return dbprtype(type) because it does not fully describe the type
41 	 * (example: varchar is reported as char by dbprtype)
42 	 *
43 	 * FIX ME: Cache datatypes from server systypes table in pdo_dblib_handle_factory()
44 	 * 		   to make this future-proof.
45 	 */
46 
47 	switch (type) {
48 		case 31: return "nvarchar";
49 		case 34: return "image";
50 		case 35: return "text";
51 		case 36: return "uniqueidentifier";
52 		case 37: return "varbinary"; /* & timestamp - Sybase AS12 */
53 		case 38: return "bigint"; /* & bigintn - Sybase AS12 */
54 		case 39: return "varchar"; /* & sysname & nvarchar - Sybase AS12 */
55 		case 40: return "date";
56 		case 41: return "time";
57 		case 42: return "datetime2";
58 		case 43: return "datetimeoffset";
59 		case 45: return "binary"; /* Sybase AS12 */
60 		case 47: return "char"; /* & nchar & uniqueidentifierstr Sybase AS12 */
61 		case 48: return "tinyint";
62 		case 50: return "bit"; /* Sybase AS12 */
63 		case 52: return "smallint";
64 		case 55: return "decimal"; /* Sybase AS12 */
65 		case 56: return "int";
66 		case 58: return "smalldatetime";
67 		case 59: return "real";
68 		case 60: return "money";
69 		case 61: return "datetime";
70 		case 62: return "float";
71 		case 63: return "numeric"; /* or uint, ubigint, usmallint Sybase AS12 */
72 		case 98: return "sql_variant";
73 		case 99: return "ntext";
74 		case 104: return "bit";
75 		case 106: return "decimal"; /* decimal n on sybase */
76 		case 108: return "numeric"; /* numeric n on sybase */
77 		case 122: return "smallmoney";
78 		case 127: return "bigint";
79 		case 165: return "varbinary";
80 		case 167: return "varchar";
81 		case 173: return "binary";
82 		case 175: return "char";
83 		case 189: return "timestamp";
84 		case 231: return "nvarchar";
85 		case 239: return "nchar";
86 		case 240: return "geometry";
87 		case 241: return "xml";
88 		default: return "unknown";
89 	}
90 }
91 /* }}} */
92 
pdo_dblib_stmt_cursor_closer(pdo_stmt_t * stmt)93 static int pdo_dblib_stmt_cursor_closer(pdo_stmt_t *stmt)
94 {
95 	pdo_dblib_stmt *S = (pdo_dblib_stmt*)stmt->driver_data;
96 	pdo_dblib_db_handle *H = S->H;
97 
98 	/* Cancel any pending results */
99 	dbcancel(H->link);
100 
101 	pdo_dblib_err_dtor(&H->err);
102 
103 	return 1;
104 }
105 
pdo_dblib_stmt_dtor(pdo_stmt_t * stmt)106 static int pdo_dblib_stmt_dtor(pdo_stmt_t *stmt)
107 {
108 	pdo_dblib_stmt *S = (pdo_dblib_stmt*)stmt->driver_data;
109 
110 	pdo_dblib_err_dtor(&S->err);
111 
112 	efree(S);
113 
114 	return 1;
115 }
116 
pdo_dblib_stmt_next_rowset_no_cancel(pdo_stmt_t * stmt)117 static int pdo_dblib_stmt_next_rowset_no_cancel(pdo_stmt_t *stmt)
118 {
119 	pdo_dblib_stmt *S = (pdo_dblib_stmt*)stmt->driver_data;
120 	pdo_dblib_db_handle *H = S->H;
121 	RETCODE ret;
122 	int num_fields;
123 
124 	do {
125 		ret = dbresults(H->link);
126 		num_fields = dbnumcols(H->link);
127 	} while (H->skip_empty_rowsets && num_fields <= 0 && ret == SUCCEED);
128 
129 
130 	if (FAIL == ret) {
131 		pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "PDO_DBLIB: dbresults() returned FAIL");
132 		return 0;
133 	}
134 
135 	if (NO_MORE_RESULTS == ret) {
136 		return 0;
137 	}
138 
139 	if (H->skip_empty_rowsets && num_fields <= 0) {
140 		return 0;
141 	}
142 
143 	stmt->row_count = DBCOUNT(H->link);
144 	stmt->column_count = num_fields;
145 
146 	return 1;
147 }
148 
pdo_dblib_stmt_next_rowset(pdo_stmt_t * stmt)149 static int pdo_dblib_stmt_next_rowset(pdo_stmt_t *stmt)
150 {
151 	pdo_dblib_stmt *S = (pdo_dblib_stmt*)stmt->driver_data;
152 	pdo_dblib_db_handle *H = S->H;
153 	RETCODE ret = SUCCESS;
154 
155 	/* Ideally use dbcanquery here, but there is a bug in FreeTDS's implementation of dbcanquery
156 	 * It has been resolved but is currently only available in nightly builds
157 	 */
158 	while (NO_MORE_ROWS != ret) {
159 		ret = dbnextrow(H->link);
160 
161 		if (FAIL == ret) {
162 			pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "PDO_DBLIB: dbnextrow() returned FAIL");
163 			return 0;
164 		}
165 	}
166 
167 	return pdo_dblib_stmt_next_rowset_no_cancel(stmt);
168 }
169 
pdo_dblib_stmt_execute(pdo_stmt_t * stmt)170 static int pdo_dblib_stmt_execute(pdo_stmt_t *stmt)
171 {
172 	pdo_dblib_stmt *S = (pdo_dblib_stmt*)stmt->driver_data;
173 	pdo_dblib_db_handle *H = S->H;
174 
175 	dbsetuserdata(H->link, (BYTE*) &S->err);
176 
177 	pdo_dblib_stmt_cursor_closer(stmt);
178 
179 	if (FAIL == dbcmd(H->link, ZSTR_VAL(stmt->active_query_string))) {
180 		return 0;
181 	}
182 
183 	if (FAIL == dbsqlexec(H->link)) {
184 		return 0;
185 	}
186 
187 	pdo_dblib_stmt_next_rowset_no_cancel(stmt);
188 
189 	stmt->row_count = DBCOUNT(H->link);
190 	stmt->column_count = dbnumcols(H->link);
191 
192 	return 1;
193 }
194 
pdo_dblib_stmt_fetch(pdo_stmt_t * stmt,enum pdo_fetch_orientation ori,zend_long offset)195 static int pdo_dblib_stmt_fetch(pdo_stmt_t *stmt,
196 	enum pdo_fetch_orientation ori, zend_long offset)
197 {
198 
199 	RETCODE ret;
200 
201 	pdo_dblib_stmt *S = (pdo_dblib_stmt*)stmt->driver_data;
202 	pdo_dblib_db_handle *H = S->H;
203 
204 	ret = dbnextrow(H->link);
205 
206 	if (FAIL == ret) {
207 		pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "PDO_DBLIB: dbnextrow() returned FAIL");
208 		return 0;
209 	}
210 
211 	if(NO_MORE_ROWS == ret) {
212 		return 0;
213 	}
214 
215 	return 1;
216 }
217 
pdo_dblib_stmt_describe(pdo_stmt_t * stmt,int colno)218 static int pdo_dblib_stmt_describe(pdo_stmt_t *stmt, int colno)
219 {
220 	pdo_dblib_stmt *S = (pdo_dblib_stmt*)stmt->driver_data;
221 	pdo_dblib_db_handle *H = S->H;
222 	struct pdo_column_data *col;
223 	char *fname;
224 
225 	if(colno >= stmt->column_count || colno < 0)  {
226 		return FAILURE;
227 	}
228 
229 	if (colno == 0) {
230 		S->computed_column_name_count = 0;
231 	}
232 
233 	col = &stmt->columns[colno];
234 	fname = (char*)dbcolname(H->link, colno+1);
235 
236 	if (fname && *fname) {
237 		col->name =  zend_string_init(fname, strlen(fname), 0);
238 	} else {
239 		if (S->computed_column_name_count > 0) {
240 			char buf[16];
241 			int len;
242 
243 			len = snprintf(buf, sizeof(buf), "computed%d", S->computed_column_name_count);
244 			col->name = zend_string_init(buf, len, 0);
245 		} else {
246 			col->name = ZSTR_INIT_LITERAL("computed", 0);
247 		}
248 
249 		S->computed_column_name_count++;
250 	}
251 
252 	col->maxlen = dbcollen(H->link, colno+1);
253 
254 	return 1;
255 }
256 
pdo_dblib_stmt_should_stringify_col(pdo_stmt_t * stmt,int coltype)257 static int pdo_dblib_stmt_should_stringify_col(pdo_stmt_t *stmt, int coltype)
258 {
259 	pdo_dblib_stmt *S = (pdo_dblib_stmt*)stmt->driver_data;
260 	pdo_dblib_db_handle *H = S->H;
261 
262 	switch (coltype) {
263 		case SQLDECIMAL:
264 		case SQLNUMERIC:
265 		case SQLMONEY:
266 		case SQLMONEY4:
267 		case SQLMONEYN:
268 		case SQLFLT4:
269 		case SQLFLT8:
270 		case SQLINT4:
271 		case SQLINT2:
272 		case SQLINT1:
273 		case SQLBIT:
274 			if (stmt->dbh->stringify) {
275 				return 1;
276 			}
277 			break;
278 
279 		case SQLINT8:
280 			if (stmt->dbh->stringify) {
281 				return 1;
282 			}
283 
284 			/* force stringify if DBBIGINT won't fit in zend_long */
285 			/* this should only be an issue for 32-bit machines */
286 			if (sizeof(zend_long) < sizeof(DBBIGINT)) {
287 				return 1;
288 			}
289 			break;
290 
291 #ifdef SQLMSDATETIME2
292 		case SQLMSDATETIME2:
293 #endif
294 		case SQLDATETIME:
295 		case SQLDATETIM4:
296 			if (H->datetime_convert) {
297 				return 1;
298 			}
299 			break;
300 	}
301 
302 	return 0;
303 }
304 
pdo_dblib_stmt_stringify_col(int coltype,LPBYTE data,DBINT data_len,zval * zv)305 static void pdo_dblib_stmt_stringify_col(int coltype, LPBYTE data, DBINT data_len, zval *zv)
306 {
307 	DBCHAR *tmp_data;
308 
309 	/* FIXME: We allocate more than we need here */
310 	DBINT tmp_data_len = 32 + (2 * (data_len));
311 
312 	switch (coltype) {
313 		case SQLDATETIME:
314 		case SQLDATETIM4: {
315 			if (tmp_data_len < DATETIME_MAX_LEN) {
316 				tmp_data_len = DATETIME_MAX_LEN;
317 			}
318 			break;
319 		}
320 	}
321 
322 	tmp_data = emalloc(tmp_data_len);
323 	data_len = dbconvert(NULL, coltype, data, data_len, SQLCHAR, (LPBYTE) tmp_data, tmp_data_len);
324 
325 	if (data_len > 0) {
326 		/* to prevent overflows, tmp_data_len is provided as a dest len for dbconvert()
327 		 * this code previously passed a dest len of -1
328 		 * the FreeTDS impl of dbconvert() does an rtrim with that value, so replicate that behavior
329 		 */
330 		while (data_len > 0 && tmp_data[data_len - 1] == ' ') {
331 			data_len--;
332 		}
333 
334 		ZVAL_STRINGL(zv, tmp_data, data_len);
335 	} else {
336 		ZVAL_EMPTY_STRING(zv);
337 	}
338 
339 	efree(tmp_data);
340 }
341 
pdo_dblib_stmt_get_col(pdo_stmt_t * stmt,int colno,zval * zv,enum pdo_param_type * type)342 static int pdo_dblib_stmt_get_col(pdo_stmt_t *stmt, int colno, zval *zv, enum pdo_param_type *type)
343 {
344 
345 	pdo_dblib_stmt *S = (pdo_dblib_stmt*)stmt->driver_data;
346 	pdo_dblib_db_handle *H = S->H;
347 
348 	int coltype;
349 	LPBYTE data;
350 	DBCHAR *tmp_data;
351 	DBINT data_len, tmp_data_len;
352 
353 	coltype = dbcoltype(H->link, colno+1);
354 	data = dbdata(H->link, colno+1);
355 	data_len = dbdatlen(H->link, colno+1);
356 
357 	if (data_len != 0 || data != NULL) {
358 		if (pdo_dblib_stmt_should_stringify_col(stmt, coltype) && dbwillconvert(coltype, SQLCHAR)) {
359 			pdo_dblib_stmt_stringify_col(coltype, data, data_len, zv);
360 		} else {
361 			switch (coltype) {
362 				case SQLCHAR:
363 				case SQLVARCHAR:
364 				case SQLTEXT: {
365 #ifdef ilia_0
366 					while (data_len>0 && data[data_len-1] == ' ') { /* nuke trailing whitespace */
367 						data_len--;
368 					}
369 #endif
370 				}
371 				case SQLVARBINARY:
372 				case SQLBINARY:
373 				case SQLIMAGE: {
374 					ZVAL_STRINGL(zv, (DBCHAR *) data, data_len);
375 
376 					break;
377 				}
378 #ifdef SQLMSDATETIME2
379 				case SQLMSDATETIME2:
380 #endif
381 				case SQLDATETIME:
382 				case SQLDATETIM4: {
383 					size_t dl;
384 					DBDATEREC di;
385 					DBDATEREC dt;
386 
387 					dbconvert(H->link, coltype, data, -1, SQLDATETIME, (LPBYTE) &dt, -1);
388 					dbdatecrack(H->link, &di, (DBDATETIME *) &dt);
389 
390 					dl = spprintf(&tmp_data, 20, "%04d-%02d-%02d %02d:%02d:%02d",
391 #if defined(PHP_DBLIB_IS_MSSQL) || defined(MSDBLIB)
392 							di.year,     di.month,       di.day,        di.hour,     di.minute,     di.second
393 #else
394 							di.dateyear, di.datemonth+1, di.datedmonth, di.datehour, di.dateminute, di.datesecond
395 #endif
396 					);
397 
398 					ZVAL_STRINGL(zv, tmp_data, dl);
399 
400 					efree(tmp_data);
401 
402 					break;
403 				}
404 				case SQLFLT4:
405 					ZVAL_DOUBLE(zv, *(DBFLT4 *) data);
406 					break;
407 				case SQLFLT8:
408 					ZVAL_DOUBLE(zv, *(DBFLT8 *) data);
409 					break;
410 				case SQLINT8:
411 					ZVAL_LONG(zv, *(DBBIGINT *) data);
412 					break;
413 				case SQLINT4:
414 					ZVAL_LONG(zv, *(DBINT *) data);
415 					break;
416 				case SQLINT2:
417 					ZVAL_LONG(zv, *(DBSMALLINT *) data);
418 					break;
419 				case SQLINT1:
420 				case SQLBIT:
421 					ZVAL_LONG(zv, *(DBTINYINT *) data);
422 					break;
423 				case SQLDECIMAL:
424 				case SQLNUMERIC:
425 				case SQLMONEY:
426 				case SQLMONEY4:
427 				case SQLMONEYN: {
428 					DBFLT8 float_value;
429 					dbconvert(NULL, coltype, data, 8, SQLFLT8, (LPBYTE) &float_value, -1);
430 					ZVAL_DOUBLE(zv, float_value);
431 					break;
432 				}
433 
434 				case SQLUNIQUE: {
435 					if (H->stringify_uniqueidentifier) {
436 						/* 36-char hex string representation */
437 						tmp_data_len = 36;
438 						tmp_data = safe_emalloc(tmp_data_len, sizeof(char), 1);
439 						data_len = dbconvert(NULL, SQLUNIQUE, data, data_len, SQLCHAR, (LPBYTE) tmp_data, tmp_data_len);
440 						zend_str_toupper(tmp_data, data_len);
441 						ZVAL_STRINGL(zv, tmp_data, data_len);
442 						efree(tmp_data);
443 					} else {
444 						/* 16-byte binary representation */
445 						ZVAL_STRINGL(zv, (DBCHAR *) data, 16);
446 					}
447 					break;
448 				}
449 
450 				default: {
451 					if (dbwillconvert(coltype, SQLCHAR)) {
452 						pdo_dblib_stmt_stringify_col(coltype, data, data_len, zv);
453 					}
454 
455 					break;
456 				}
457 			}
458 		}
459 	}
460 
461 	return 1;
462 }
463 
pdo_dblib_stmt_get_column_meta(pdo_stmt_t * stmt,zend_long colno,zval * return_value)464 static int pdo_dblib_stmt_get_column_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_value)
465 {
466 	pdo_dblib_stmt *S = (pdo_dblib_stmt*)stmt->driver_data;
467 	pdo_dblib_db_handle *H = S->H;
468 	DBTYPEINFO* dbtypeinfo;
469 	int coltype;
470 
471 	if(colno >= stmt->column_count || colno < 0)  {
472 		return FAILURE;
473 	}
474 
475 	array_init(return_value);
476 
477 	dbtypeinfo = dbcoltypeinfo(H->link, colno+1);
478 
479 	if(!dbtypeinfo) return FAILURE;
480 
481 	coltype = dbcoltype(H->link, colno+1);
482 
483 	add_assoc_long(return_value, "max_length", dbcollen(H->link, colno+1) );
484 	add_assoc_long(return_value, "precision", (int) dbtypeinfo->precision );
485 	add_assoc_long(return_value, "scale", (int) dbtypeinfo->scale );
486 	add_assoc_string(return_value, "column_source", dbcolsource(H->link, colno+1));
487 	add_assoc_string(return_value, "native_type", pdo_dblib_get_field_name(coltype));
488 	add_assoc_long(return_value, "native_type_id", coltype);
489 	add_assoc_long(return_value, "native_usertype_id", dbcolutype(H->link, colno+1));
490 
491 	switch (coltype) {
492 		case SQLBIT:
493 		case SQLINT1:
494 		case SQLINT2:
495 		case SQLINT4:
496 			add_assoc_long(return_value, "pdo_type", PDO_PARAM_INT);
497 			break;
498 		default:
499 			add_assoc_long(return_value, "pdo_type", PDO_PARAM_STR);
500 			break;
501 	}
502 
503 	return 1;
504 }
505 
506 
507 const struct pdo_stmt_methods dblib_stmt_methods = {
508 	pdo_dblib_stmt_dtor,
509 	pdo_dblib_stmt_execute,
510 	pdo_dblib_stmt_fetch,
511 	pdo_dblib_stmt_describe,
512 	pdo_dblib_stmt_get_col,
513 	NULL, /* param hook */
514 	NULL, /* set attr */
515 	NULL, /* get attr */
516 	pdo_dblib_stmt_get_column_meta, /* meta */
517 	pdo_dblib_stmt_next_rowset, /* nextrow */
518 	pdo_dblib_stmt_cursor_closer
519 };
520