3
* Copyright (c) Karl Dahlke, 2008
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 };
607
setError(MSG_DBUnspecified);
610
sql_exclist(exclist);
611
sql_connect(dbarea, dblogin, dbpw);
613
setError(MSG_DBConnect, rv_vendorStatus);
617
errorPrint("@sql connected, but database not set");
627
static char myTab[64];
628
static const char *myWhere;
629
static char *scl; /* select clause */
631
static char *wcl; /* where clause */
633
static char wherecol[COLNAMELEN + 2];
634
static struct DBTABLE *td;
639
setError(MSG_DBUnexpected, rv_vendorStatus);
643
buildSelectClause(void)
646
scl = initString(&scllen);
647
stringAndString(&scl, &scllen, "select ");
648
for(i = 0; i < td->ncols; ++i) {
650
stringAndChar(&scl, &scllen, ',');
651
stringAndString(&scl, &scllen, td->cols[i]);
653
stringAndString(&scl, &scllen, " from ");
654
stringAndString(&scl, &scllen, td->name);
655
} /* buildSelectClause */
658
buildWhereClause(void)
661
const char *w = myWhere;
664
wcl = initString(&wcllen);
666
if(stringEqual(w, "*"))
672
setError(MSG_DBNoKey);
675
e = td->cols[td->key1 - 1];
678
setError(MSG_DBColumnLong, e, COLNAMELEN);
683
} else if(isdigit(*w)) {
684
n = strtol(w, (char **)&w, 10);
686
setError(MSG_DBSyntax);
689
if(n == 0 || n > td->ncols) {
690
setError(MSG_DBColRange, n);
696
if(e - w <= COLNAMELEN) {
697
strncpy(wherecol, w, e - w);
699
for(i = 0; i < td->ncols; ++i) {
700
if(!strstr(td->cols[i], wherecol))
703
setError(MSG_DBManyColumns, wherecol);
710
setError(MSG_DBNoColumn, wherecol);
717
setError(MSG_DBColumnLong, w, COLNAMELEN);
723
stringAndString(&wcl, &wcllen, "where ");
724
stringAndString(&wcl, &wcllen, wherecol);
728
stringAndString(&wcl, &wcllen, " is null");
729
} else if((i = strtol(e, (char **)&e, 10)) >= 0 &&
730
*e == '-' && (n = strtol(e + 1, (char **)&e, 10)) >= 0 && *e == 0) {
731
stringAndString(&wcl, &wcllen, " between ");
732
stringAndNum(&wcl, &wcllen, i);
733
stringAndString(&wcl, &wcllen, " and ");
734
stringAndNum(&wcl, &wcllen, n);
735
} else if(w[strlen(w) - 1] == '*') {
736
stringAndString(&wcl, &wcllen, lineFormat(" matches %S", w));
738
stringAndString(&wcl, &wcllen, lineFormat(" = %S", w));
742
} /* buildWhereClause */
747
static const short exclist[] = { EXCNOTABLE, EXCNOCOLUMN, EXCSQLMISC, 0 };
748
int cid, nc, i, part1, part2;
749
const char *s = cw->fileName;
750
const char *t = strchr(s, ']');
751
if(t - s >= sizeof (myTab))
752
errorPrint("2table name too long, limit %d characters",
754
strncpy(myTab, s, t - s);
762
/* haven't glommed onto this table yet */
763
td = findTableDescriptor(myTab);
767
sql_exclist(exclist);
768
cid = sql_prepare(scl);
771
if(rv_lastStatus == EXCNOTABLE)
772
setError(MSG_DBNoTable, td->name);
773
else if(rv_lastStatus == EXCNOCOLUMN)
774
setError(MSG_DBBadColumn);
779
td->types = cloneString(rv_type);
785
sql_exclist(exclist);
786
cid = sql_prepare("select * from %s", myTab);
788
if(rv_lastStatus == EXCNOTABLE)
789
setError(MSG_DBNoTable, myTab);
794
td = newTableDescriptor(myTab);
801
printf("warning, only the first %d columns will be selected\n",
805
td->types = cloneString(rv_type);
808
for(i = 0; i < nc; ++i)
809
td->cols[i] = cloneString(rv_name[i]);
812
getPrimaryKey(myTab, &part1, &part2);
821
s = strpbrk(td->types, "BT");
823
s = strpbrk(s + 1, "BT");
825
setError(MSG_DBManyBlobs);
842
printf("table %s", td->name);
843
if(!stringEqual(td->name, td->shortname))
844
printf(" [%s]", td->shortname);
845
i = sql_selectOne("select count(*) from %s", td->name);
846
printf(", %d rows\n", i);
848
for(i = 0; i < td->ncols; ++i) {
849
printf("%d ", i + 1);
850
if(td->key1 == i + 1 || td->key2 == i + 1)
852
printf("%s ", td->cols[i]);
886
printf("%s\n", desc);
891
sqlReadRows(const char *filename, char **bufptr)
894
char *rbuf, *unld, *s;
897
*bufptr = EMPTYSTRING;
903
rbuf = initString(&rlen);
904
myWhere = strchr(filename, ']') + 1;
906
if(!buildWhereClause())
910
cid = sql_prepOpen("%s %0s", scl, wcl);
913
while(sql_fetchNext(cid, 0)) {
914
unld = sql_mkunld('\177');
915
if(strchr(unld, '|')) {
916
setError(MSG_DBPipes);
919
if(strchr(unld, '\n')) {
920
setError(MSG_DBNewline);
923
for(s = unld; *s; ++s)
926
s[-1] = '\n'; /* overwrite the last pipe */
928
/* look for blob column */
929
if(s = strpbrk(td->types, "BT")) {
930
int bfi = s - td->types; /* blob field index */
931
int cx = 0; /* context, where to put the blob */
935
for(j = 0; j < bfi; ++j)
936
u = strchr(u, '|') + 1;
937
v = strpbrk(u, "|\n");
940
cx = sideBuffer(0, rv_blobLoc, rv_blobSize, 0, false);
943
sprintf(myTab, "<%d>", cx);
947
/* unld is pretty long; I'm just going to assume there is enough room for this */
948
memmove(u + j, v, end - v);
949
u[j + (end - v)] = 0;
953
stringAndString(&rbuf, &rlen, unld);
967
static char *lineFields[MAXTCOLS];
969
/* Split a line at pipe boundaries, and make sure the field count is correct */
971
intoFields(char *line)
979
s = strpbrk(s, "|\n");
987
setError(MSG_DBAddField);
993
setError(MSG_DBLostField);
998
rowCountCheck(int action, int cnt1)
1000
int cnt2 = rv_lastNrows;
1005
setError(MSG_DBDeleteCount + action, cnt1, cnt2);
1007
} /* rowCountCheck */
1013
setError(MSG_DBNoKeyCol);
1016
return (td->key2 ? 2 : 1);
1017
} /* keyCountCheck */
1019
/* Typical error conditions for insert update delete */
1020
static const short insupdExceptions[] = { EXCSQLMISC,
1021
EXCVIEWUSE, EXCREFINT, EXCITEMLOCK, EXCPERMISSION,
1022
EXCDEADLOCK, EXCCHECK, EXCTIMEOUT, EXCNOTNULLCOLUMN, 0
1026
insupdError(int action, int rcnt)
1028
int rc = rv_lastStatus;
1046
msg = MSG_DBDeadlock;
1048
case EXCNOTNULLCOLUMN:
1049
msg = MSG_DBNotNull;
1055
msg = MSG_DBTimeout;
1058
setError(MSG_DBMisc, rv_vendorStatus);
1066
return rowCountCheck(action, rcnt);
1070
sqlDelRows(int start, int end)
1072
int nkeys, ndel, key1, key2, ln;
1077
nkeys = keyCountCheck();
1078
key1 = td->key1 - 1;
1079
key2 = td->key2 - 1;
1083
ndel = end - start + 1;
1086
setError(MSG_DBMassDelete);
1090
/* We could delete all the rows with one statement, using an in(list),
1091
* but that won't work when the key is two columns.
1092
* I have to write the one-line-at-a-time code anyways,
1093
* I'll just use that for now. */
1095
char *line = (char *)fetchLine(ln, 0);
1097
sql_exclist(insupdExceptions);
1099
sql_exec("delete from %s where %s = %S",
1100
td->name, td->cols[key1], lineFields[key1]);
1102
sql_exec("delete from %s where %s = %S and %s = %S",
1103
td->name, td->cols[key1], lineFields[key1],
1104
td->cols[key2], lineFields[key2]);
1106
if(!insupdError(0, 1))
1115
sqlUpdateRow(pst source, int slen, pst dest, int dlen)
1117
char *d2; /* clone of dest */
1119
int j, l1, l2, nkeys, key1, key2;
1120
char *u1, *u2; /* pieces of the update statement */
1123
/* compare all the way out to newline, so we know both strings end at the same time */
1124
if(slen == dlen && !memcmp(source, dest, slen + 1))
1130
nkeys = keyCountCheck();
1131
key1 = td->key1 - 1;
1132
key2 = td->key2 - 1;
1136
d2 = (char *)clonePstring(dest);
1137
if(!intoFields(d2)) {
1143
u1 = initString(&u1len);
1144
u2 = initString(&u2len);
1148
t = strpbrk(s, "|\n");
1150
l2 = strlen(lineFields[j]);
1151
if(l1 != l2 || memcmp(s, lineFields[j], l1)) {
1152
if(j == key1 || j == key2) {
1153
setError(MSG_DBChangeKey);
1156
if(td->types[j] == 'B') {
1157
setError(MSG_DBChangeBlob);
1160
if(td->types[j] == 'T') {
1161
setError(MSG_DBChangeText);
1165
stringAndChar(&u1, &u1len, ',');
1166
stringAndString(&u1, &u1len, td->cols[j]);
1168
stringAndChar(&u2, &u2len, ',');
1169
stringAndString(&u2, &u2len, lineFormat("%S", lineFields[j]));
1177
sql_exclist(insupdExceptions);
1179
sql_exec("update %s set(%s) = (%s) where %s = %S",
1180
td->name, u1, u2, td->cols[key1], lineFields[key1]);
1182
sql_exec("update %s set(%s) = (%s) where %s = %S and %s = %S",
1184
td->cols[key1], lineFields[key1], td->cols[key2], lineFields[key2]);
1185
if(!insupdError(2, 1))
1198
} /* sqlUpdateRow */
1203
char *u1, *u2; /* pieces of the insert statement */
1214
u1 = initString(&u1len);
1215
u2 = initString(&u2len);
1216
for(j = 0; j < td->ncols; ++j) {
1218
if(strchr("BT", td->types[j]))
1220
printf("%s: ", td->cols[j]);
1222
if(!fgets(inp, sizeof (inp), stdin)) {
1227
if(l && inp[l - 1] == '\n')
1229
if(stringEqual(inp, ".")) {
1235
/* For now, a null field is always excepted. */
1236
/* Someday we may want to check this against the not-null constraint. */
1240
/* verify the integrity of the entered field */
1241
if(strchr(inp, '|')) {
1242
puts("please, no pipes in the data");
1246
switch (td->types[j]) {
1251
if(stringIsNum(s) < 0) {
1252
puts("number expected");
1257
if(!stringIsFloat(inp, &dv)) {
1258
puts("decimal number expected");
1263
if(strlen(inp) > 1) {
1264
puts("one character expected");
1269
if(stringDate(inp, false) < 0) {
1270
puts("date expected");
1275
if(stringTime(inp) < 0) {
1276
puts("time expected");
1284
stringAndChar(&u1, &u1len, ',');
1285
stringAndString(&u1, &u1len, td->cols[j]);
1287
stringAndChar(&u2, &u2len, ',');
1288
stringAndString(&u2, &u2len, lineFormat("%S", inp));
1290
sql_exclist(insupdExceptions);
1291
sql_exec("insert into %s (%s) values (%s)", td->name, u1, u2);
1294
if(!insupdError(1, 1)) {
1299
/* Fetch the row just entered;
1300
its serial number may have changed from 0 to something real */
1301
rowid = rv_lastRowid;
1302
buildSelectClause();
1303
sql_select("%s where rowid = %d", scl, rowid, 0);
1305
unld = sql_mkunld('|');
1307
unld[l - 1] = '\n'; /* overwrite the last pipe */
1308
if(!addTextToBuffer((pst) unld, l, ln))
1313
/* This pointis not reached; make the compilerhappy */
1318
/*********************************************************************
1319
Sync up two tables, or corresponding sections of two tables.
1320
These are usually equischema tables in parallel databases or machines.
1321
This isn't used by edbrowse; it's just something I wrote,
1322
and I thought you might find it useful.
1323
It follows the C convention of copying the second argument
1324
to the first, like the string and memory functions,
1325
rather than the shell convention of copying (cp) the first argument to the second.
1326
Hey - why have one standard, when you can have two?
1327
*********************************************************************/
1329
static const char *synctable; /* table being sync-ed */
1330
static const char *synckeycol; /* key column */
1331
static const char *sync_clause; /* additional clause, to sync only part of the table */
1334
syncup_comm_fn(char action, char *line1, char *line2, int key)
1337
case '<': /* delete */
1338
sql_exec("delete from %s where %s = %d %0s",
1339
synctable, synckeycol, key, sync_clause);
1341
case '>': /* insert */
1342
sql_exec("insert into %s values(%s)", synctable, line2);
1344
case '*': /* update */
1345
sql_exec("update %s set * = (%s) where %s = %d %0s",
1346
synctable, line2, synckeycol, key, sync_clause);
1350
} /* syncup_comm_fn */
1352
/* make table1 look like table2 */
1354
syncup_table(const char *table1, const char *table2, /* the two tables */
1355
const char *keycol, /* the key column */
1356
const char *otherclause)
1358
char stmt1[200], stmt2[200];
1362
synckeycol = keycol;
1363
sync_clause = otherclause;
1364
len = strlen(table1);
1365
if((int)strlen(table2) > len)
1366
len = strlen(table2);
1368
len += strlen(otherclause);
1369
len += strlen(keycol);
1370
if(len + 30 > sizeof (stmt1))
1372
("2constructed select statement in syncup_table() is too long");
1375
while(*otherclause == ' ')
1377
if(strncmp(otherclause, "and ", 4) && strncmp(otherclause, "AND ", 4))
1379
("2restricting clause in syncup_table() does not start with \"and\".");
1380
sprintf(stmt1, "select * from %s where %s order by %s", table1,
1381
otherclause + 4, keycol);
1382
sprintf(stmt2, "select * from %s where %s order by %s", table2,
1383
otherclause + 4, keycol);
1385
sprintf(stmt1, "select * from %s order by %s", table1, keycol);
1386
sprintf(stmt2, "select * from %s order by %s", table2, keycol);
1389
cursor_comm(stmt1, stmt2, keycol, (fnptr) syncup_comm_fn, 0);
1390
} /* syncup_table */