Browse code

- a couple of minor bug fixes backported from internal verion - support for "fake NULL" values (strings or integers that will be written in database if the value is NULL) (compile time option) - proper support for raw SQL statements - the possibility to retrieve fields in the result set and their types from the server added (needed for queries like select *).

Jan Janak authored on 15/11/2007 17:27:06
Showing 5 changed files
... ...
@@ -50,6 +50,14 @@
50 50
 
51 51
 #define STR_BUF_SIZE 256
52 52
 
53
+#ifdef MYSQL_FAKE_NULL
54
+#define FAKE_NULL_STRING "[~NULL~]"
55
+static str  FAKE_NULL_STR=STR_STATIC_INIT(FAKE_NULL_STRING);
56
+/* avoid warning: this decimal constant is unsigned only in ISO C90 :-) */
57
+#define FAKE_NULL_INT (-2147483647-1)
58
+#define STR_EQ(x,y) ((x.len==y.len) && (strncmp(x.s, y.s, x.len)==0))
59
+#endif
60
+
53 61
 enum {
54 62
 	STR_DELETE,
55 63
 	STR_INSERT,
... ...
@@ -74,10 +82,10 @@ enum {
74 82
 static str strings[] = {
75 83
 	STR_STATIC_INIT("delete from "),
76 84
 	STR_STATIC_INIT("insert into "),
77
-	STR_STATIC_INIT("UPDATE "),
85
+	STR_STATIC_INIT("update "),
78 86
 	STR_STATIC_INIT("select "),
79 87
 	STR_STATIC_INIT("replace "),
80
-	STR_STATIC_INIT(" SET "),
88
+	STR_STATIC_INIT(" set "),
81 89
 	STR_STATIC_INIT(" where "),
82 90
 	STR_STATIC_INIT(" is "),
83 91
 	STR_STATIC_INIT(" and "),
... ...
@@ -360,8 +368,10 @@ static inline int sb_add(struct string_buffer *sb, str *nstr)
360 368
 			ERR("not enough memory\n");
361 369
 			return -1;
362 370
 		}
363
-		memcpy(newp, sb->s, sb->len);
364
-		pkg_free(sb->s);
371
+		if (sb->s) {
372
+			memcpy(newp, sb->s, sb->len);
373
+			pkg_free(sb->s);
374
+		}
365 375
 		sb->s = newp;
366 376
 		sb->size = new_size;
367 377
 	}
... ...
@@ -433,7 +443,7 @@ static int build_update_query(str* query, db_cmd_t* cmd)
433 443
 		goto err;
434 444
 	}
435 445
 	query->s = sql_buf.s;
436
-
446
+	query->len = sql_buf.len;
437 447
 	return 0;
438 448
 
439 449
 err:
... ...
@@ -449,9 +459,33 @@ static inline void update_field(MYSQL_BIND *param, db_fld_t* fld)
449 459
 	
450 460
 	fp = DB_GET_PAYLOAD(fld);
451 461
 
462
+#ifndef MYSQL_FAKE_NULL
452 463
 	fp->is_null = fld->flags & DB_NULL;
453 464
 	if (fp->is_null) return;
454
-
465
+#else
466
+	if (fld->flags & DB_NULL) {
467
+		switch(fld->type) {
468
+		case DB_STR:
469
+		case DB_CSTR:
470
+			param->buffer = FAKE_NULL_STR.s;
471
+			fp->length = FAKE_NULL_STR.len;
472
+			break;
473
+		case DB_INT:
474
+			*(int*)param->buffer = FAKE_NULL_INT;
475
+			break;
476
+		case DB_BLOB:
477
+		case DB_DATETIME:
478
+		case DB_NONE:
479
+		case DB_FLOAT:
480
+		case DB_DOUBLE:
481
+		case DB_BITMAP:
482
+			/* we don't have fake null value for these types */
483
+			fp->is_null = DB_NULL;
484
+			break;
485
+		}
486
+		return;
487
+	}
488
+#endif
455 489
 	switch(fld->type) {
456 490
 	case DB_STR:
457 491
 		param->buffer = fld->v.lstr.s;
... ...
@@ -503,7 +537,7 @@ static inline int update_params(MYSQL_STMT* st, db_fld_t* params1, db_fld_t* par
503 537
 {
504 538
 	int  my_idx, fld_idx;
505 539
 	int  count1, count2;
506
-
540
+	
507 541
 	/* Calculate the number of parameters */
508 542
 	for(count1 = 0; !DB_FLD_EMPTY(params1) && !DB_FLD_LAST(params1[count1]); count1++);
509 543
 	for(count2 = 0; !DB_FLD_EMPTY(params2) && !DB_FLD_LAST(params2[count2]); count2++);
... ...
@@ -560,6 +594,11 @@ static inline int update_result(db_fld_t* result, MYSQL_STMT* st)
560 594
 		switch(result[i].type) {
561 595
 		case DB_STR:
562 596
 			result[i].v.lstr.len = rp->length;
597
+#ifdef MYSQL_FAKE_NULL
598
+			if (STR_EQ(FAKE_NULL_STR,result[i].v.lstr)) {
599
+				result[i].flags |= DB_NULL;
600
+			}
601
+#endif
563 602
 			break;
564 603
 
565 604
 		case DB_BLOB:
... ...
@@ -568,8 +607,13 @@ static inline int update_result(db_fld_t* result, MYSQL_STMT* st)
568 607
 
569 608
 		case DB_CSTR:
570 609
 			result[i].v.cstr[rp->length] = '\0';
610
+#ifdef MYSQL_FAKE_NULL
611
+			if (strcmp(FAKE_NULL_STR.s,result[i].v.cstr)==0) {
612
+				result[i].flags |= DB_NULL;
613
+			}
614
+#endif
571 615
 			break;
572
-
616
+			
573 617
 		case DB_DATETIME:
574 618
 			memset(&t, '\0', sizeof(struct tm));
575 619
 			t.tm_sec = rp->time.second;
... ...
@@ -577,7 +621,7 @@ static inline int update_result(db_fld_t* result, MYSQL_STMT* st)
577 621
 			t.tm_hour = rp->time.hour;
578 622
 			t.tm_mday = rp->time.day;
579 623
 			t.tm_mon = rp->time.month - 1;
580
-			t.tm_year = rp->time.year - 1900;;
624
+			t.tm_year = rp->time.year - 1900;
581 625
 
582 626
 			/* Daylight saving information got lost in the database
583 627
 			 * so let timegm to guess it. This eliminates the bug when
... ...
@@ -592,8 +636,14 @@ static inline int update_result(db_fld_t* result, MYSQL_STMT* st)
592 636
 #endif /* HAVE_TIMEGM */
593 637
 			break;
594 638
 
595
-		case DB_NONE:
596 639
 		case DB_INT:
640
+#ifdef MYSQL_FAKE_NULL
641
+			if (FAKE_NULL_INT==result[i].v.int4) {
642
+				result[i].flags |= DB_NULL;
643
+			}
644
+			break;
645
+#endif
646
+		case DB_NONE:
597 647
 		case DB_FLOAT:
598 648
 		case DB_DOUBLE:
599 649
 		case DB_BITMAP:
... ...
@@ -601,7 +651,7 @@ static inline int update_result(db_fld_t* result, MYSQL_STMT* st)
601 651
 			break;
602 652
 		}
603 653
 	}
604
-
654
+	
605 655
 	return 0;
606 656
 }
607 657
 
... ...
@@ -642,6 +692,7 @@ int my_cmd_write(db_res_t* res, db_cmd_t* cmd)
642 692
 		return -1;
643 693
 	}
644 694
 
695
+	mcmd->next_flag = -1;
645 696
 	return 0;
646 697
 }
647 698
 
... ...
@@ -679,6 +730,7 @@ int my_cmd_read(db_res_t* res, db_cmd_t* cmd)
679 730
 		return -1;
680 731
 	}
681 732
 
733
+	mcmd->next_flag = -1;
682 734
 	return 0;
683 735
 }
684 736
 
... ...
@@ -714,6 +766,7 @@ int my_cmd_update(db_res_t* res, db_cmd_t* cmd)
714 766
 		return -1;
715 767
 	}
716 768
 
769
+	mcmd->next_flag = -1;
717 770
 	return 0;
718 771
 }
719 772
 
... ...
@@ -724,6 +777,7 @@ int my_cmd_sql(db_res_t* res, db_cmd_t* cmd)
724 777
 	int ret, myerr;
725 778
    
726 779
 	mcmd = DB_GET_PAYLOAD(cmd);
780
+	if (mcmd->st->param_count && update_params(mcmd->st, NULL, cmd->match) < 0) return -1;
727 781
 	ret = mysql_stmt_execute(mcmd->st);
728 782
 
729 783
 	/* If the connection to the server was lost then try to resubmit the query,
... ...
@@ -747,6 +801,7 @@ int my_cmd_sql(db_res_t* res, db_cmd_t* cmd)
747 801
 		return -1;
748 802
 	}
749 803
 
804
+	mcmd->next_flag = -1;
750 805
 	return 0;
751 806
 }
752 807
 
... ...
@@ -825,6 +880,11 @@ static int bind_params(MYSQL_STMT* st, db_fld_t* params1, db_fld_t* params2)
825 880
 	/* Calculate the number of parameters */
826 881
 	for(count1 = 0; !DB_FLD_EMPTY(params1) && !DB_FLD_LAST(params1[count1]); count1++);
827 882
 	for(count2 = 0; !DB_FLD_EMPTY(params2) && !DB_FLD_LAST(params2[count2]); count2++);
883
+	if (st->param_count != count1+ count2) {
884
+		ERR("MySQL param count do not match the given parameter arrays\n");
885
+		return -1;
886
+	}
887
+	
828 888
 
829 889
 	my_params = (MYSQL_BIND*)pkg_malloc(sizeof(MYSQL_BIND) * (count1+count2));
830 890
 	if (my_params == NULL) {
... ...
@@ -858,6 +918,98 @@ static int bind_params(MYSQL_STMT* st, db_fld_t* params1, db_fld_t* params2)
858 918
 	return -1;
859 919
 }
860 920
 
921
+
922
+/*
923
+ * FIXME: This function will only work if we have one db connection
924
+ * in every context, otherwise it would initialize the result set
925
+ * from the first connection in the context.
926
+ */
927
+static int check_result_columns(db_cmd_t* cmd, struct my_cmd* payload)
928
+{
929
+	int i, n;
930
+	MYSQL_FIELD *fld;
931
+	MYSQL_RES *meta = 0;
932
+
933
+	meta = mysql_stmt_result_metadata(payload->st);
934
+	if (meta == NULL) {
935
+		ERR("Error while getting metadata of SQL query: %s\n",
936
+			mysql_stmt_error(payload->st));
937
+		goto error;
938
+	}
939
+	n = mysql_num_fields(meta);
940
+	if (cmd->result == NULL) {
941
+		/* The result set parameter of db_cmd function was empty, that
942
+		 * means the query is select * and we have to create the array
943
+		 * of result fields in the cmd structure manually.
944
+		 */
945
+                cmd->result = db_fld(n + 1);
946
+		cmd->result_count = n;
947
+		for(i = 0; i < cmd->result_count; i++) {
948
+			struct my_fld *f;
949
+			if (my_fld(cmd->result + i, cmd->table.s) < 0) goto error;
950
+			f = DB_GET_PAYLOAD(cmd->result + i);
951
+			fld = mysql_fetch_field_direct(meta, i);
952
+			f->name = pkg_malloc(strlen(fld->name)+1);
953
+			if (!f->name) goto error;
954
+			strcpy(f->name, fld->name);
955
+			cmd->result[i].name = f->name;
956
+		}
957
+	}
958
+	else {
959
+		if (cmd->result_count != n) {
960
+			BUG("number of fields do not correspond\n");
961
+			goto error;
962
+		}
963
+	}
964
+	/* Now iterate through all the columns in the result set and replace
965
+	 * any occurrence of DB_UNKNOWN type with the type of the column
966
+	 * retrieved from the database and if no column name was provided then
967
+         * update it from the database as well. 
968
+	 */
969
+	for(i = 0; i < cmd->result_count; i++) {
970
+		fld = mysql_fetch_field_direct(meta, i);
971
+		if (cmd->result[i].type != DB_NONE) continue;
972
+		switch(fld->type) {
973
+		case MYSQL_TYPE_TINY:
974
+		case MYSQL_TYPE_SHORT:
975
+		case MYSQL_TYPE_INT24:
976
+		case MYSQL_TYPE_LONG:
977
+			cmd->result[i].type = DB_INT;
978
+			break;
979
+
980
+		case MYSQL_TYPE_FLOAT:
981
+			cmd->result[i].type = DB_FLOAT;
982
+			break;
983
+
984
+		case MYSQL_TYPE_DOUBLE:
985
+			cmd->result[i].type = DB_DOUBLE;
986
+			break;
987
+
988
+		case MYSQL_TYPE_TIMESTAMP:
989
+		case MYSQL_TYPE_DATETIME:
990
+			cmd->result[i].type = DB_DATETIME;
991
+			break;
992
+
993
+		case MYSQL_TYPE_STRING:
994
+		case MYSQL_TYPE_VAR_STRING:
995
+			cmd->result[i].type = DB_STR;
996
+			break;
997
+
998
+		default:
999
+			ERR("Unsupported MySQL column type: %d, table: %s, column: %s\n",
1000
+				fld->type, cmd->table.s, fld->name);
1001
+			goto error;
1002
+		}
1003
+	}
1004
+	mysql_free_result(meta);
1005
+	return 0;
1006
+error:
1007
+	if (meta) mysql_free_result(meta);
1008
+	return -1;
1009
+}
1010
+
1011
+
1012
+
861 1013
 /* FIXME: Add support for DB_NONE, in this case the function should determine
862 1014
  * the type of the column in the database and set the field type appropriately
863 1015
  */
... ...
@@ -910,7 +1062,7 @@ static int bind_result(MYSQL_STMT* st, db_fld_t* fld)
910 1062
 			if (!f->buf.s) f->buf.s = pkg_malloc(STR_BUF_SIZE);
911 1063
 			if (f->buf.s == NULL) {
912 1064
 				ERR("No memory left\n");
913
-				return -1;
1065
+				goto error;
914 1066
 			}
915 1067
 			result[i].buffer = f->buf.s;
916 1068
 			fld[i].v.lstr.s = f->buf.s;
... ...
@@ -922,7 +1074,7 @@ static int bind_result(MYSQL_STMT* st, db_fld_t* fld)
922 1074
 			if (!f->buf.s) f->buf.s = pkg_malloc(STR_BUF_SIZE);
923 1075
 			if (f->buf.s == NULL) {
924 1076
 				ERR("No memory left\n");
925
-				return -1;
1077
+				goto error;
926 1078
 			}
927 1079
 			result[i].buffer = f->buf.s;
928 1080
 			fld[i].v.cstr = f->buf.s;
... ...
@@ -934,7 +1086,7 @@ static int bind_result(MYSQL_STMT* st, db_fld_t* fld)
934 1086
 			if (!f->buf.s) f->buf.s = pkg_malloc(STR_BUF_SIZE);
935 1087
 			if (f->buf.s == NULL) {
936 1088
 				ERR("No memory left\n");
937
-				return -1;
1089
+				goto error;
938 1090
 			}
939 1091
 			result[i].buffer = f->buf.s;
940 1092
 			fld[i].v.blob.s = f->buf.s;
... ...
@@ -970,6 +1122,7 @@ static int upload_query(db_cmd_t* cmd)
970 1122
 	struct my_cmd* res;
971 1123
 	struct my_con* mcon;
972 1124
 	MYSQL_STMT* st;
1125
+	int n;
973 1126
 
974 1127
 	res = DB_GET_PAYLOAD(cmd);
975 1128
 
... ...
@@ -1002,6 +1155,7 @@ static int upload_query(db_cmd_t* cmd)
1002 1155
 		if (!DB_FLD_EMPTY(cmd->match)) {
1003 1156
 			if (bind_params(res->st, NULL, cmd->match) < 0) goto error;
1004 1157
 		}
1158
+		if (check_result_columns(cmd, res) < 0) goto error;
1005 1159
 		if (bind_result(res->st, cmd->result) < 0) goto error;
1006 1160
 		break;
1007 1161
 
... ...
@@ -1019,7 +1173,14 @@ static int upload_query(db_cmd_t* cmd)
1019 1173
 		break;
1020 1174
 
1021 1175
 	case DB_SQL:
1022
-		if (!DB_FLD_EMPTY(cmd->result)) {
1176
+		if (!DB_FLD_EMPTY(cmd->match)) {
1177
+			if (bind_params(res->st, NULL, cmd->match) < 0) goto error;
1178
+		}
1179
+
1180
+		n = mysql_stmt_field_count(res->st);
1181
+		/* create result fields and pass them to client */
1182
+		if (n > 0) {
1183
+			if (check_result_columns(cmd, res) < 0) goto error;
1023 1184
 			if (bind_result(res->st, cmd->result) < 0) goto error;
1024 1185
 		}
1025 1186
 		break;
... ...
@@ -1069,7 +1230,10 @@ int my_cmd(db_cmd_t* cmd)
1069 1230
 		break;
1070 1231
 
1071 1232
 	case DB_SQL:
1072
-		break;
1233
+		if (NULL == (res->query.s = (char*)pkg_malloc(cmd->table.len))) goto error;
1234
+		memcpy(res->query.s,cmd->table.s, cmd->table.len);
1235
+		res->query.len = cmd->table.len;
1236
+        break;
1073 1237
 	}
1074 1238
 
1075 1239
 	DB_SET_PAYLOAD(cmd, res);
... ...
@@ -1086,18 +1250,53 @@ int my_cmd(db_cmd_t* cmd)
1086 1250
 	return -1;
1087 1251
 }
1088 1252
 
1253
+
1254
+int my_cmd_first(db_res_t* res) {
1255
+	struct my_cmd* mcmd;
1256
+
1257
+	mcmd = DB_GET_PAYLOAD(res->cmd);
1258
+	switch (mcmd->next_flag) {
1259
+	case -2: /* table is empty */
1260
+		return 1;
1261
+	case 0:  /* cursor position is 0 */
1262
+		return 0;
1263
+	case 1:  /* next row */
1264
+	case 2:  /* EOF */
1265
+		ERR("my_cmd_first cannot reset cursor position. It's not supported for unbuffered mysql queries\n");
1266
+		return -1;
1267
+	default:
1268
+		return my_cmd_next(res);
1269
+	}
1270
+}
1271
+
1272
+
1089 1273
 int my_cmd_next(db_res_t* res)
1090 1274
 {
1091 1275
 	int ret;
1092 1276
 	struct my_cmd* mcmd;
1093 1277
 
1094 1278
 	mcmd = DB_GET_PAYLOAD(res->cmd);
1279
+	if (mcmd->next_flag == 2 || mcmd->next_flag == -2) return 1;
1095 1280
 	ret = mysql_stmt_fetch(mcmd->st);
1096 1281
 	
1097
-	if (ret == MYSQL_NO_DATA) return 1;
1098
-
1099
-	if (ret != 0) {
1100
-		ERR("Error in mysql_stmt_fetch: %s\n", mysql_stmt_error(mcmd->st));
1282
+	if (ret == MYSQL_NO_DATA) {
1283
+		mcmd->next_flag =  mcmd->next_flag<0?-2:2;
1284
+		return 1;
1285
+	}
1286
+	if (ret == MYSQL_DATA_TRUNCATED) {
1287
+		int i;
1288
+		ERR("my_cmd_next: mysql_stmt_fetch, data truncated, fields: %d\n", res->cmd->result_count);
1289
+		for (i = 0; i < res->cmd->result_count; i++) {
1290
+			if (mcmd->st->bind[i].error /*&& mcmd->st->bind[i].buffer_length*/) {
1291
+				ERR("truncation, bind %d, length: %lu, buffer_length: %lu\n", i, *(mcmd->st->bind[i].length), mcmd->st->bind[i].buffer_length);
1292
+			}
1293
+		}
1294
+	}
1295
+	if (mcmd->next_flag <= 0) {
1296
+		mcmd->next_flag++;
1297
+	}
1298
+	if (ret != 0 && ret != MYSQL_DATA_TRUNCATED) {
1299
+		ERR("Error in mysql_stmt_fetch (ret=%d): %s\n", ret, mysql_stmt_error(mcmd->st));
1101 1300
 		return -1;
1102 1301
 	}
1103 1302
 
... ...
@@ -37,6 +37,7 @@ struct my_cmd {
37 37
 	db_drv_t gen;
38 38
 
39 39
 	str query;
40
+	int next_flag;
40 41
 	MYSQL_STMT* st;
41 42
 };
42 43
 
... ...
@@ -37,6 +37,7 @@ static void my_fld_free(db_fld_t* fld, struct my_fld* payload)
37 37
 {
38 38
 	db_drv_free(&payload->gen);
39 39
 	if (payload->buf.s) pkg_free(payload->buf.s);
40
+	if (payload->name) pkg_free(payload->name);
40 41
 	pkg_free(payload);
41 42
 }
42 43
 
... ...
@@ -40,6 +40,7 @@
40 40
 struct my_fld {
41 41
 	db_drv_t gen;
42 42
 
43
+	char* name;
43 44
 	my_bool is_null;
44 45
 	MYSQL_TIME time;
45 46
 	unsigned long length;
... ...
@@ -77,7 +77,7 @@ static cmd_export_t cmds[] = {
77 77
 	{"db_sql",         (cmd_function)my_cmd_sql, 0, 0, 0},
78 78
 	{"db_res",         (cmd_function)my_res,  0, 0, 0},
79 79
 	{"db_fld",         (cmd_function)my_fld,  0, 0, 0},
80
-	{"db_first",       (cmd_function)my_cmd_next, 0, 0, 0},
80
+	{"db_first",       (cmd_function)my_cmd_first, 0, 0, 0},
81 81
 	{"db_next",        (cmd_function)my_cmd_next,  0, 0, 0},
82 82
 	{0, 0, 0, 0, 0}
83 83
 };