... | ... |
@@ -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 |
|
... | ... |
@@ -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 |
}; |