3
* Copyright (c) Karl Dahlke, 2007
4
* This file is part of the edbrowse project, released under GPL.
10
const char *sql_debuglog = "ebsql.log"; /* log of debug prints */
11
const char *sql_database; /* name of current database */
14
lineFormat(const char *line, ...)
19
s = lineFormatStack(line, 0, &p);
24
#define LFBUFSIZE 4000
25
static char lfbuf[LFBUFSIZE]; /* line formatting buffer */
26
static const char selfref[] =
27
"@lineFormat attempts to expand within its own static buffer";
28
static const char lfoverflow[] = "@lineFormat(), line is too long, limit %d";
31
lineFormatStack(const char *line, /* the sprintf-like formatting string */
32
LF * argv, /* pointer to array of values */
34
{ /* pointers to parameters on the stack */
36
short i, len, maxlen, len_given, flags;
38
double dn; /* double number */
39
char *q, pdir, inquote;
45
if(parmv && argv || !parmv && !argv)
47
("@exactly one of the last two arguments to lineFormatStack should be null");
59
while(*t) { /* more text to format */
60
/* copy up to the next % */
61
if(*t != '%' || t[1] == '%' && ++t) {
62
if(q - lfbuf >= LFBUFSIZE - 1)
63
errorPrint(lfoverflow, LFBUFSIZE);
76
for(; isdigit(*t); ++t) {
78
len = 10 * len + *t - '0';
80
while(*t == '.' || isdigit(*t))
87
if(t - perc >= sizeof (fmt))
88
errorPrint("2percent directive in lineFormat too long");
89
strncpy(fmt, perc, t - perc);
95
/* get the next vararg */
98
dn = va_arg(parmv, double);
103
n = va_arg(parmv, int);
110
if(pdir == 's' && n) {
111
i = strlen((char *)n);
114
if(inquote && strchr((char *)n, inquote)) {
116
if(strchr((char *)n, inquote))
117
errorPrint("2lineFormat() cannot put quotes around %s", n);
122
if(q + maxlen >= lfbuf + LFBUFSIZE)
123
errorPrint(lfoverflow, LFBUFSIZE);
125
/* check for null parameter */
126
if(pdir == 'c' && !n ||
127
pdir == 's' && isnullstring((char *)n) ||
128
pdir == 'f' && dn == nullfloat ||
129
!strchr("scf", pdir) && isnull(n)) {
132
/* turn = %d to is null */
133
for(q1 = q - 1; q1 >= lfbuf && *q1 == ' '; --q1) ;
134
if(q1 >= lfbuf && *q1 == '=') {
135
if(q1 > lfbuf && q1[-1] == '!') {
136
strcpy(q1 - 1, "IS NOT ");
146
} /* null with no length specified */
150
/* parameter is null */
153
fmt[t - perc - 1] = pdir;
161
flags = DTDELIMIT | DTCRUNCH;
165
strcpy(q, timeString(n, flags));
171
flags = DTCRUNCH | DTDELIMIT;
175
flags = DTCRUNCH | DTDELIMIT;
177
strcpy(q, dateString(n, flags));
178
if(len == 4 || len == 5)
182
strcpy(q, moneyString(n));
190
/* extra code to prevent %09s from printing out all zeros
191
when the argument is null (empty string) */
192
if(!*(char *)n && fmt[1] == '0')
193
strcpy(fmt + 1, fmt + 2);
201
} /* loop printing pieces of the string */
203
*q = 0; /* null terminate */
205
/* we relie on the calling function to invoke va_end(), since the arg list
206
is not always the ... varargs of a function, though it usually is.
207
See lineFormat() above for a typical example.
208
Note that the calling function may wish to process additional arguments
209
before calling va_end. */
214
} /* lineFormatStack */
216
/* given a datatype, return the character that, when appended to %,
217
causes lineFormat() to print the data element properly. */
219
sprintfChar(char datatype)
252
/*********************************************************************
253
Using the values just fetched or selected, build a line in unload format.
254
All fields are expanded into ascii, with pipes between them.
255
Conversely, given a line of pipe separated fields,
256
put them back into binary, ready for retsCopy().
257
*********************************************************************/
260
sql_mkunld(char delim)
262
char fmt[NUMRETS * 4 + 1];
266
for(i = 0; i < rv_numRets; ++i) {
267
pftype = sprintfChar(rv_type[i]);
269
errorPrint("2sql_mkunld cannot convert datatype %c", rv_type[i]);
270
sprintf(fmt + 4 * i, "%%0%c%c", pftype, delim);
271
} /* loop over returns */
273
return lineFormatStack(fmt, rv_data, 0);
276
/* like the above, but we build a comma-separated list with quotes,
277
ready for SQL insert or update.
278
You might be tempted to call this routine first, obtaining a string,
279
and then call lineFormat("insert into foo values(%s)",
281
The returned string is built by lineFormat and is already in the buffer.
282
You instead need to make a copy of the string and then call lineFormat. */
286
char fmt[NUMRETS * 3 + 1];
290
for(i = 0; i < rv_numRets; ++i) {
291
pftype = sprintfChar(rv_type[i]);
293
errorPrint("2sql_mkinsupd cannot convert datatype %c", rv_type[i]);
294
if(pftype != 'd' && pftype != 'f')
295
pftype = toupper(pftype);
296
sprintf(fmt + 3 * i, "%%%c,", pftype);
297
} /* loop over returns */
300
return lineFormatStack(fmt, rv_data, 0);
304
/*********************************************************************
306
*********************************************************************/
308
static char ndays[] = { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
322
/* convert year, month, and day into a date. */
323
/* return -1 = bad year, -2 = bad month, -3 = bad day */
325
dateEncode(int year, int month, int day)
329
if((year | month | day) == 0)
331
if(year < 1640 || year > 3000)
333
if(month <= 0 || month > 12)
335
if(day <= 0 || day > ndays[month])
337
if(day == 29 && month == 2 && !isLeapYear(year))
340
d = year * 365L + year / 4 - year / 100 + year / 400;
341
for(i = 1; i < month; ++i)
344
if(month > 2 && !isLeapYear(year))
351
/* convert a date back into year, month, and day */
352
/* the inverse of the above */
354
dateDecode(date d, int *yp, int *mp, int *dp)
356
int year, month, day;
357
year = month = day = 0;
358
if(d >= 0 && d <= 497094) {
359
/* how many years have rolled by; at worst 366 days in each */
362
while(dateEncode(++year, 1, 1) <= d) ;
364
d -= dateEncode(year, 1, 1);
365
if(!isLeapYear(year))
367
for(month = 1; month <= 12; ++month) {
373
ndays[2] = 29; /* put it back */
380
/* convert a string into a date */
381
/* return -4 for bad format */
383
stringDate(const char *s, bool yearfirst)
385
short year, month, day, i, l;
393
while(l && s[l - 1] == ' ')
397
if(l != 8 && l != 10)
401
delim = yearfirst ? '-' : '/';
402
t = strchr(buf, delim);
405
t = strchr(buf, delim);
411
if(!strcmp(buf, " "))
415
strncpy(swap, buf, 4);
416
strncpy(buf, buf + 4, 4);
417
strncpy(buf + 4, swap, 4);
419
for(i = 0; i < 8; ++i)
422
month = 10 * (buf[0] - '0') + buf[1] - '0';
423
day = 10 * (buf[2] - '0') + buf[3] - '0';
424
year = atoi(buf + 4);
425
return dateEncode(year, month, day);
428
/* convert a date into a string, held in a static buffer */
429
/* cram squashes out the century, delimit puts in slashes */
431
dateString(date d, int flags)
435
int year, month, day;
436
dateDecode(d, &year, &month, &day);
438
strcpy(buf, " / / ");
440
sprintf(buf, "%02d/%02d/%04d", month, day, year);
442
strcpy(buf + 6, buf + 8);
443
if(flags & YEARFIRST) {
444
strncpy(swap, buf, 6);
445
swap[2] = swap[5] = 0;
446
strcpy(buf, buf + 6);
447
if(flags & DTDELIMIT)
450
if(flags & DTDELIMIT)
452
strcat(buf, swap + 3);
453
} else if(!(flags & DTDELIMIT)) {
455
s = strchr(buf, '/');
457
s = strchr(buf, '/');
464
timeString(interval seconds, int flags)
469
if(seconds < 0 || seconds >= 86400)
470
strcpy(buf, " : : AM");
473
seconds -= h * 3600L;
486
sprintf(buf, "%02d:%02d:%02d %cM", h, m, s, c);
488
if(!(flags & DTAMPM))
491
strcpy(buf + 5, buf + 8);
492
if(!(flags & DTDELIMIT)) {
493
strcpy(buf + 2, buf + 3);
495
strcpy(buf + 4, buf + 5);
500
/* convert string into time.
501
* Like stringDate, we can return bad hour, bad minute, bad second, or bad format */
503
stringTime(const char *t)
513
while(l && t[l - 1] == ' ')
521
if(buf[l - 1] == 'M' && buf[l - 3] == ' ') {
524
if(c != 'A' && c != 'P')
532
strcpy(buf + 2, buf + 3);
534
strcpy(buf + 4, buf + 5);
538
if(!strncmp(buf, " ", l))
540
for(i = 0; i < l; ++i)
543
h = 10 * (buf[0] - '0') + buf[1] - '0';
544
m = 10 * (buf[2] - '0') + buf[3] - '0';
547
s = 10 * (buf[4] - '0') + buf[5] - '0';
561
return h * 3600L + m * 60 + s;
567
static char buf[20], *s = buf;
572
sprintf(s, "$%ld.%02d", m / 100, (int)(m % 100));
577
stringMoney(const char *s)
593
if(!stringIsFloat(s, &d))
595
m = (long)(d * 100.0 + 0.5);
599
/* Make sure edbrowse is connected to the database */
603
const short exclist[] = { EXCSQLMISC, EXCNOCONNECT, 0 };
608
("the config file does not specify the database - cannot connect");
611
sql_exclist(exclist);
612
sql_connect(dbarea, dblogin, dbpw);
614
setError("cannot connect to the database - error %d", rv_vendorStatus);
618
errorPrint("@sql connected, but database not set");
628
static char myTab[64];
629
static const char *myWhere;
630
static char *scl; /* select clause */
632
static char *wcl; /* where clause */
634
static char wherecol[COLNAMELEN + 2];
635
static const char synwhere[] = "syntax error in where clause";
636
static struct DBTABLE *td;
641
setError("unexpected sql error %d", rv_vendorStatus);
645
buildSelectClause(void)
648
scl = initString(&scllen);
649
stringAndString(&scl, &scllen, "select ");
650
for(i = 0; i < td->ncols; ++i) {
652
stringAndChar(&scl, &scllen, ',');
653
stringAndString(&scl, &scllen, td->cols[i]);
655
stringAndString(&scl, &scllen, " from ");
656
stringAndString(&scl, &scllen, td->name);
657
} /* buildSelectClause */
660
buildWhereClause(void)
663
const char *w = myWhere;
666
wcl = initString(&wcllen);
668
if(stringEqual(w, "*"))
674
setError("no key column specified");
677
e = td->cols[td->key1 - 1];
680
setError("column name %s is too long, limit %d characters", e,
686
} else if(isdigit(*w)) {
687
n = strtol(w, (char **)&w, 10);
692
if(n == 0 || n > td->ncols) {
693
setError("column %d is out of range", n);
699
if(e - w <= COLNAMELEN) {
700
strncpy(wherecol, w, e - w);
702
for(i = 0; i < td->ncols; ++i) {
703
if(!strstr(td->cols[i], wherecol))
706
setError("multiple columns match %s", wherecol);
713
setError("no column matches %s", wherecol);
720
setError("column name %s is too long, limit %d characters", w,
727
stringAndString(&wcl, &wcllen, "where ");
728
stringAndString(&wcl, &wcllen, wherecol);
732
stringAndString(&wcl, &wcllen, " is null");
733
} else if((i = strtol(e, (char **)&e, 10)) >= 0 &&
734
*e == '-' && (n = strtol(e + 1, (char **)&e, 10)) >= 0 && *e == 0) {
735
stringAndString(&wcl, &wcllen, " between ");
736
stringAndNum(&wcl, &wcllen, i);
737
stringAndString(&wcl, &wcllen, " and ");
738
stringAndNum(&wcl, &wcllen, n);
739
} else if(w[strlen(w) - 1] == '*') {
740
stringAndString(&wcl, &wcllen, lineFormat(" matches %S", w));
742
stringAndString(&wcl, &wcllen, lineFormat(" = %S", w));
746
} /* buildWhereClause */
751
static const short exclist[] = { EXCNOTABLE, EXCNOCOLUMN, EXCSQLMISC, 0 };
752
int cid, nc, i, part1, part2;
753
const char *s = cw->fileName;
754
const char *t = strchr(s, ']');
755
if(t - s >= sizeof (myTab))
756
errorPrint("2table name too long, limit %d characters",
758
strncpy(myTab, s, t - s);
766
/* haven't glommed onto this table yet */
767
td = findTableDescriptor(myTab);
771
sql_exclist(exclist);
772
cid = sql_prepare(scl);
775
if(rv_lastStatus == EXCNOTABLE)
776
setError("no such table %s", td->name);
777
else if(rv_lastStatus == EXCNOCOLUMN)
778
setError("invalid column name");
783
td->types = cloneString(rv_type);
789
sql_exclist(exclist);
790
cid = sql_prepare("select * from %s", myTab);
792
if(rv_lastStatus == EXCNOTABLE)
793
setError("no such table %s", myTab);
798
td = newTableDescriptor(myTab);
805
printf("warning, only the first %d columns will be selected\n",
809
td->types = cloneString(rv_type);
812
for(i = 0; i < nc; ++i)
813
td->cols[i] = cloneString(rv_name[i]);
816
getPrimaryKey(myTab, &part1, &part2);
825
s = strpbrk(td->types, "BT");
827
s = strpbrk(s + 1, "BT");
829
setError("cannot select more than one blob column");
846
printf("table %s", td->name);
847
if(!stringEqual(td->name, td->shortname))
848
printf(" [%s]", td->shortname);
849
i = sql_selectOne("select count(*) from %s", td->name);
850
printf(", %d rows\n", i);
852
for(i = 0; i < td->ncols; ++i) {
853
printf("%d ", i + 1);
854
if(td->key1 == i + 1 || td->key2 == i + 1)
856
printf("%s ", td->cols[i]);
890
printf("%s\n", desc);
895
sqlReadRows(const char *filename, char **bufptr)
898
char *rbuf, *unld, *s;
901
*bufptr = EMPTYSTRING;
907
rbuf = initString(&rlen);
908
myWhere = strchr(filename, ']') + 1;
910
if(!buildWhereClause())
914
cid = sql_prepOpen("%s %0s", scl, wcl);
917
while(sql_fetchNext(cid, 0)) {
918
unld = sql_mkunld('\177');
919
if(strchr(unld, '|')) {
921
("the data contains pipes, which is my reserved delimiter");
924
if(strchr(unld, '\n')) {
925
setError("the data contains newlines");
928
for(s = unld; *s; ++s)
931
s[-1] = '\n'; /* overwrite the last pipe */
933
/* look for blob column */
934
if(s = strpbrk(td->types, "BT")) {
935
int bfi = s - td->types; /* blob field index */
936
int cx = 0; /* context, where to put the blob */
940
for(j = 0; j < bfi; ++j)
941
u = strchr(u, '|') + 1;
942
v = strpbrk(u, "|\n");
945
cx = sideBuffer(0, rv_blobLoc, rv_blobSize, 0, false);
948
sprintf(myTab, "<%d>", cx);
952
/* unld is pretty long; I'm just going to assume there is enough room for this */
953
memmove(u + j, v, end - v);
954
u[j + (end - v)] = 0;
958
stringAndString(&rbuf, &rlen, unld);
972
static char *lineFields[MAXTCOLS];
974
/* Split a line at pipe boundaries, and make sure the field count is correct */
976
intoFields(char *line)
984
s = strpbrk(s, "|\n");
993
("line contains too many fields, please do not introduce any pipes into the text");
1000
("line contains too few fields, please do not remove any pipes from the text");
1005
rowCountCheck(const char *action, int cnt1)
1007
int cnt2 = rv_lastNrows;
1008
static char rowword1[] = "rows";
1009
static char rowword2[] = "records";
1014
rowword1[3] = (cnt1 == 1 ? 0 : 's');
1015
rowword2[6] = (cnt2 == 1 ? 0 : 's');
1016
setError("oops! I %s %d %s, and %d database %s were affected.",
1017
action, cnt1, rowword1, cnt2, rowword2);
1019
} /* rowCountCheck */
1025
setError("key column not specified");
1028
return (td->key2 ? 2 : 1);
1029
} /* keyCountCheck */
1031
/* Typical error conditions for insert update delete */
1032
static const short insupdExceptions[] = { EXCSQLMISC,
1033
EXCVIEWUSE, EXCREFINT, EXCITEMLOCK, EXCPERMISSION,
1034
EXCDEADLOCK, EXCCHECK, EXCTIMEOUT, EXCNOTNULLCOLUMN, 0
1038
insupdError(const char *action, int rcnt)
1040
int rc = rv_lastStatus;
1046
desc = "cannot modify a view";
1049
desc = "some other row in the database depends on this row";
1052
desc = "row or table is locked";
1056
"you do not have permission to modify the database in this way";
1059
desc = "deadlock detected";
1061
case EXCNOTNULLCOLUMN:
1062
desc = "placing null into a not-null column";
1065
desc = "check constraint violated";
1068
desc = "daatabase timeout";
1071
setError("miscelaneous sql error %d", rv_vendorStatus);
1078
return rowCountCheck(action, rcnt);
1082
sqlDelRows(int start, int end)
1084
int nkeys, ndel, key1, key2, ln;
1089
nkeys = keyCountCheck();
1090
key1 = td->key1 - 1;
1091
key2 = td->key2 - 1;
1095
ndel = end - start + 1;
1098
setError("cannot delete more than 100 rows at a time");
1102
/* We could delete all the rows with one statement, using an in(list),
1103
* but that won't work when the key is two columns.
1104
* I have to write the one-line-at-a-time code anyways,
1105
* I'll just use that for now. */
1107
char *line = (char *)fetchLine(ln, 0);
1109
sql_exclist(insupdExceptions);
1111
sql_exec("delete from %s where %s = %S",
1112
td->name, td->cols[key1], lineFields[key1]);
1114
sql_exec("delete from %s where %s = %S and %s = %S",
1115
td->name, td->cols[key1], lineFields[key1],
1116
td->cols[key2], lineFields[key2]);
1118
if(!insupdError("deleted", 1))
1127
sqlUpdateRow(pst source, int slen, pst dest, int dlen)
1129
char *d2; /* clone of dest */
1131
int j, l1, l2, nkeys, key1, key2;
1132
char *u1, *u2; /* pieces of the update statement */
1135
/* compare all the way out to newline, so we know both strings end at the same time */
1136
if(slen == dlen && !memcmp(source, dest, slen + 1))
1142
nkeys = keyCountCheck();
1143
key1 = td->key1 - 1;
1144
key2 = td->key2 - 1;
1148
d2 = (char *)clonePstring(dest);
1149
if(!intoFields(d2)) {
1155
u1 = initString(&u1len);
1156
u2 = initString(&u2len);
1160
t = strpbrk(s, "|\n");
1162
l2 = strlen(lineFields[j]);
1163
if(l1 != l2 || memcmp(s, lineFields[j], l1)) {
1164
if(j == key1 || j == key2) {
1165
setError("cannot change a key column");
1168
if(td->types[j] == 'B') {
1169
setError("cannot change a blob field");
1172
if(td->types[j] == 'T') {
1173
setError("cannot change a text field");
1177
stringAndChar(&u1, &u1len, ',');
1178
stringAndString(&u1, &u1len, td->cols[j]);
1180
stringAndChar(&u2, &u2len, ',');
1181
stringAndString(&u2, &u2len, lineFormat("%S", lineFields[j]));
1189
sql_exclist(insupdExceptions);
1191
sql_exec("update %s set(%s) = (%s) where %s = %S",
1192
td->name, u1, u2, td->cols[key1], lineFields[key1]);
1194
sql_exec("update %s set(%s) = (%s) where %s = %S and %s = %S",
1196
td->cols[key1], lineFields[key1], td->cols[key2], lineFields[key2]);
1197
if(!insupdError("updated", 1))
1210
} /* sqlUpdateRow */
1215
char *u1, *u2; /* pieces of the insert statement */
1226
u1 = initString(&u1len);
1227
u2 = initString(&u2len);
1228
for(j = 0; j < td->ncols; ++j) {
1230
if(strchr("BT", td->types[j]))
1232
printf("%s: ", td->cols[j]);
1234
if(!fgets(inp, sizeof (inp), stdin)) {
1239
if(l && inp[l - 1] == '\n')
1241
if(stringEqual(inp, ".")) {
1247
/* For now, a null field is always excepted. */
1248
/* Someday we may want to check this against the not-null constraint. */
1252
/* verify the integrity of the entered field */
1253
if(strchr(inp, '|')) {
1254
puts("please, no pipes in the data");
1258
switch (td->types[j]) {
1263
if(stringIsNum(s) < 0) {
1264
puts("number expected");
1269
if(!stringIsFloat(inp, &dv)) {
1270
puts("decimal number expected");
1275
if(strlen(inp) > 1) {
1276
puts("one character expected");
1281
if(stringDate(inp, false) < 0) {
1282
puts("date expected");
1287
if(stringTime(inp) < 0) {
1288
puts("time expected");
1296
stringAndChar(&u1, &u1len, ',');
1297
stringAndString(&u1, &u1len, td->cols[j]);
1299
stringAndChar(&u2, &u2len, ',');
1300
stringAndString(&u2, &u2len, lineFormat("%S", inp));
1302
sql_exclist(insupdExceptions);
1303
sql_exec("insert into %s (%s) values (%s)", td->name, u1, u2);
1306
if(!insupdError("inserted", 1)) {
1311
/* Fetch the row just entered;
1312
its serial number may have changed from 0 to something real */
1313
rowid = rv_lastRowid;
1314
buildSelectClause();
1315
sql_select("%s where rowid = %d", scl, rowid, 0);
1317
unld = sql_mkunld('|');
1319
unld[l - 1] = '\n'; /* overwrite the last pipe */
1320
if(!addTextToBuffer((pst) unld, l, ln))
1325
/* This pointis not reached; make the compilerhappy */
1330
/*********************************************************************
1331
Sync up two tables, or corresponding sections of two tables.
1332
These are usually equischema tables in parallel databases or machines.
1333
This isn't used by edbrowse; it's just something I wrote,
1334
and I thought you might find it useful.
1335
It follows the C convention of copying the second argument
1336
to the first, like the string and memory functions,
1337
rather than the shell convention of copying (cp) the first argument to the second.
1338
Hey - why have one standard, when you can have two?
1339
*********************************************************************/
1341
static const char *synctable; /* table being sync-ed */
1342
static const char *synckeycol; /* key column */
1343
static const char *sync_clause; /* additional clause, to sync only part of the table */
1346
syncup_comm_fn(char action, char *line1, char *line2, int key)
1349
case '<': /* delete */
1350
sql_exec("delete from %s where %s = %d %0s",
1351
synctable, synckeycol, key, sync_clause);
1353
case '>': /* insert */
1354
sql_exec("insert into %s values(%s)", synctable, line2);
1356
case '*': /* update */
1357
sql_exec("update %s set * = (%s) where %s = %d %0s",
1358
synctable, line2, synckeycol, key, sync_clause);
1362
} /* syncup_comm_fn */
1364
/* make table1 look like table2 */
1366
syncup_table(const char *table1, const char *table2, /* the two tables */
1367
const char *keycol, /* the key column */
1368
const char *otherclause)
1370
char stmt1[200], stmt2[200];
1374
synckeycol = keycol;
1375
sync_clause = otherclause;
1376
len = strlen(table1);
1377
if((int)strlen(table2) > len)
1378
len = strlen(table2);
1380
len += strlen(otherclause);
1381
len += strlen(keycol);
1382
if(len + 30 > sizeof (stmt1))
1384
("2constructed select statement in syncup_table() is too long");
1387
while(*otherclause == ' ')
1389
if(strncmp(otherclause, "and ", 4) && strncmp(otherclause, "AND ", 4))
1391
("2restricting clause in syncup_table() does not start with \"and\".");
1392
sprintf(stmt1, "select * from %s where %s order by %s", table1,
1393
otherclause + 4, keycol);
1394
sprintf(stmt2, "select * from %s where %s order by %s", table2,
1395
otherclause + 4, keycol);
1397
sprintf(stmt1, "select * from %s order by %s", table1, keycol);
1398
sprintf(stmt2, "select * from %s order by %s", table2, keycol);
1401
cursor_comm(stmt1, stmt2, keycol, (fnptr) syncup_comm_fn, 0);
1402
} /* syncup_table */