1/*
2** Copyright (c) 2008 D. Richard Hipp
3**
4** This program is free software; you can redistribute it and/or
5** modify it under the terms of the GNU General Public
6** License version 2 as published by the Free Software Foundation.
7**
8** This program is distributed in the hope that it will be useful,
9** but WITHOUT ANY WARRANTY; without even the implied warranty of
10** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11** General Public License for more details.
12**
13** You should have received a copy of the GNU General Public
14** License along with this library; if not, write to the
15** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
16** Boston, MA 02111-1307, USA.
17**
18** Author contact information:
19** drh@hwaci.com
20** http://www.hwaci.com/drh/
21**
22*******************************************************************************
23**
24** This main driver for the sqllogictest program.
25*/
26#include "catch.hpp"
27#include "sqllogictest.hpp"
28
29#include <ctype.h>
30#include <stdio.h>
31#include <stdlib.h>
32#ifndef _WIN32
33#include <unistd.h>
34#define stricmp strcasecmp
35#endif
36#include "slt_duckdb.hpp"
37
38#include <algorithm>
39#include <dirent.h>
40#include <functional>
41#include <string.h>
42#include <string>
43#include <vector>
44
45using namespace std;
46
47#define DEFAULT_HASH_THRESHOLD 8
48
49/*
50** A structure to keep track of the state of scanning the input script.
51*/
52typedef struct Script Script;
53struct Script {
54 char *zScript; /* Complete text of the input script */
55 int iCur; /* Index in zScript of start of current line */
56 char *zLine; /* Pointer to start of current line */
57 int len; /* Length of current line */
58 int iNext; /* index of start of next line */
59 int nLine; /* line number for the current line */
60 int iEnd; /* Index in zScript of '\000' at end of script */
61 int startLine; /* Line number of start of current record */
62 int copyFlag; /* If true, copy lines to output as they are read */
63 char azToken[4][200]; /* tokenization of a line */
64};
65
66// stub because not used
67void sqllogictestRegisterEngine(const DbEngine *p) {
68}
69
70/*
71** Advance the cursor to the start of the next non-comment line of the
72** script. Make p->zLine point to the start of the line. Make p->len
73** be the length of the line. Zero-terminate the line. Any \r at the
74** end of the line is removed.
75**
76** Return 1 on success. Return 0 and no-op at end-of-file.
77*/
78static int nextLine(Script *p) {
79 int i;
80
81 /* Loop until a non-comment line is found, or until end-of-file */
82 while (1) {
83 /* When we reach end-of-file, return 0 */
84 if (p->iNext >= p->iEnd) {
85 p->iCur = p->iEnd;
86 p->zLine = &p->zScript[p->iEnd];
87 p->len = 0;
88 return 0;
89 }
90
91 /* Advance the cursor to the next line */
92 p->iCur = p->iNext;
93 p->nLine++;
94 p->zLine = &p->zScript[p->iCur];
95 for (i = p->iCur; i < p->iEnd && p->zScript[i] != '\n'; i++) {
96 }
97 p->zScript[i] = 0;
98 p->len = i - p->iCur;
99 p->iNext = i + 1;
100
101 /* If the current line ends in a \r then remove the \r. */
102 if (p->len > 0 && p->zScript[i - 1] == '\r') {
103 p->len--;
104 i--;
105 p->zScript[i] = 0;
106 }
107
108 /* If the line consists of all spaces, make it an empty line */
109 for (i = i - 1; i >= p->iCur && isspace(p->zScript[i]); i--) {
110 }
111 if (i < p->iCur) {
112 p->zLine[0] = 0;
113 }
114
115 /* If the copy flag is set, write the line to standard output */
116 if (p->copyFlag) {
117 printf("%s\n", p->zLine);
118 }
119
120 /* If the line is not a comment line, then we are finished, so break
121 ** out of the loop. If the line is a comment, the loop will repeat in
122 ** order to skip this line. */
123 if (p->zLine[0] != '#')
124 break;
125 }
126 return 1;
127}
128
129/*
130** Look ahead to the next line and return TRUE if it is a blank line.
131** But do not advance to the next line yet.
132*/
133static int nextIsBlank(Script *p) {
134 int i = p->iNext;
135 if (i >= p->iEnd)
136 return 1;
137 while (i < p->iEnd && isspace(p->zScript[i])) {
138 if (p->zScript[i] == '\n')
139 return 1;
140 i++;
141 }
142 return 0;
143}
144
145/*
146** Advance the cursor to the start of the next record. To do this,
147** first skip over the tail section of the record in which we are
148** currently located, then skip over blank lines.
149**
150** Return 1 on success. Return 0 at end-of-file.
151*/
152static int findStartOfNextRecord(Script *p) {
153 /* Skip over any existing content to find a blank line */
154 if (p->iCur > 0) {
155 while (p->zLine[0] && p->iCur < p->iEnd) {
156 nextLine(p);
157 }
158 } else {
159 nextLine(p);
160 }
161
162 /* Skip over one or more blank lines to find the first line of the
163 ** new record */
164 while (p->zLine[0] == 0 && p->iCur < p->iEnd) {
165 nextLine(p);
166 }
167
168 /* Return 1 if we have not reached end of file. */
169 return p->iCur < p->iEnd;
170}
171
172/*
173** Find a single token in a string. Return the index of the start
174** of the token and the length of the token.
175*/
176static void findToken(const char *z, int *piStart, int *pLen) {
177 int i;
178 int iStart;
179 for (i = 0; isspace(z[i]); i++) {
180 }
181 *piStart = iStart = i;
182 while (z[i] && !isspace(z[i])) {
183 i++;
184 }
185 *pLen = i - iStart;
186}
187
188#define count(X) (sizeof(X) / sizeof(X[0]))
189
190/*
191** tokenize the current line in up to 3 tokens and store those values
192** into p->azToken[0], p->azToken[1], and p->azToken[2]. Record the
193** current line in p->startLine.
194*/
195static void tokenizeLine(Script *p) {
196 int i, j, k;
197 int len, n;
198 for (i = 0; i < (int)count(p->azToken); i++)
199 p->azToken[i][0] = 0;
200 p->startLine = p->nLine;
201 for (i = j = 0; j < p->len && i < (int)count(p->azToken); i++) {
202 findToken(&p->zLine[j], &k, &len);
203 j += k;
204 n = len;
205 if (n >= (int)sizeof(p->azToken[0])) {
206 n = sizeof(p->azToken[0]) - 1;
207 }
208 memcpy(p->azToken[i], &p->zLine[j], n);
209 p->azToken[i][n] = 0;
210 j += n + 1;
211 }
212}
213
214/*
215** The number columns in a row of the current result set
216*/
217static int nColumn = 0;
218
219/*
220** Comparison function for sorting the result set.
221*/
222static int rowCompare(const void *pA, const void *pB) {
223 const char **azA = (const char **)pA;
224 const char **azB = (const char **)pB;
225 int c = 0, i;
226 for (i = 0; c == 0 && i < nColumn; i++) {
227 c = strcmp(azA[i], azB[i]);
228 }
229 return c;
230}
231
232/*
233** Entry in a hash table of prior results
234*/
235typedef struct HashEntry HashEntry;
236struct HashEntry {
237 char zKey[24]; /* The search key */
238 char zHash[33]; /* The hash value stored */
239 HashEntry *pNext; /* Next with same hash */
240 HashEntry *pAll; /* Next overall */
241};
242
243/*
244** The hash table
245*/
246#define NHASH 1009
247static HashEntry *aHash[NHASH];
248static HashEntry *pAll;
249
250/*
251** Try to look up the value zKey in the hash table. If the value
252** does not exist, create it and return 0. If the value does already
253** exist return 0 if hash matches and 1 if the hash is different.
254*/
255static int checkValue(const char *zKey, const char *zHash) {
256 unsigned int h;
257 HashEntry *p;
258 unsigned int i;
259
260 h = 0;
261 for (i = 0; zKey[i] && i < sizeof(p->zKey); i++) {
262 h = h << 3 ^ h ^ zKey[i];
263 }
264 h = h % NHASH;
265 for (p = aHash[h]; p; p = p->pNext) {
266 if (strcmp(p->zKey, zKey) == 0) {
267 return strcmp(p->zHash, zHash) != 0;
268 }
269 }
270 p = (HashEntry *)malloc(sizeof(*p));
271 if (p == 0) {
272 fprintf(stderr, "out of memory at %s:%d\n", __FILE__, __LINE__);
273 exit(1);
274 }
275 for (i = 0; zKey[i] && i < sizeof(p->zKey) - 1; i++) {
276 p->zKey[i] = zKey[i];
277 }
278 p->zKey[i] = 0;
279 for (i = 0; zHash[i] && i < sizeof(p->zHash) - 1; i++) {
280 p->zHash[i] = zHash[i];
281 }
282 p->zHash[i] = 0;
283 p->pAll = pAll;
284 pAll = p;
285 p->pNext = aHash[h];
286 aHash[h] = p;
287 return 0;
288}
289
290#define IFAIL() \
291 { \
292 if (zScript) \
293 free(zScript); \
294 if (pConn) \
295 pEngine->xDisconnect(pConn); \
296 REQUIRE(false); \
297 return; \
298 }
299
300static void execute_file(string script) {
301 int haltOnError = 0; /* Stop on first error if true */
302 int enableTrace = 0; /* Trace SQL statements if true */
303 const char *zScriptFile = 0; /* Input script filename */
304 const char *zDbEngine = "DuckDB"; /* Name of database engine */
305 const char *zConnection = 0; /* Connection string on DB engine */
306 const DbEngine *pEngine = 0; /* Pointer to DbEngine object */
307 int i; /* Loop counter */
308 char *zScript; /* Content of the script */
309 long nScript; /* Size of the script in bytes */
310 long nGot; /* Number of bytes read */
311 void *pConn = nullptr; /* Connection to the database engine */
312 int rc; /* Result code from subroutine call */
313 int nErr = 0; /* Number of errors */
314 int nCmd = 0; /* Number of SQL statements processed */
315 int nSkipped = 0; /* Number of SQL statements skipped */
316 int nResult; /* Number of query results */
317 char **azResult; /* Query result vector */
318 Script sScript; /* Script parsing status */
319 FILE *in; /* For reading script */
320 char zHash[100]; /* Storage space for hash results */
321 int hashThreshold = DEFAULT_HASH_THRESHOLD; /* Threshold for hashing res */
322 int bHt = 0; /* True if -ht command-line option */
323 const char *zParam = 0; /* Argument to -parameters */
324
325 const DbEngine duckdbEngine = {
326 "DuckDB", /* zName */
327 0, /* pAuxData */
328 duckdbConnect, /* xConnect */
329 duckdbGetEngineName, /* xGetEngineName */
330 duckdbStatement, /* xStatement */
331 duckdbQuery, /* xQuery */
332 duckdbFreeResults, /* xFreeResults */
333 duckdbDisconnect /* xDisconnect */
334 };
335 pEngine = &duckdbEngine;
336
337 REQUIRE(pEngine);
338 /*
339 ** Read the entire script file contents into memory
340 */
341
342 zScriptFile = script.c_str();
343 in = fopen(zScriptFile, "rb");
344 if (!in) {
345 FAIL("Could not find test script '" + script + "'. Perhaps run `make sqlite`. ");
346 }
347 REQUIRE(in);
348 fseek(in, 0L, SEEK_END);
349 nScript = ftell(in);
350 REQUIRE(nScript > 0);
351 zScript = (char *)malloc(nScript + 1);
352 if (!zScript) {
353 IFAIL();
354 }
355 fseek(in, 0L, SEEK_SET);
356 nGot = fread(zScript, 1, nScript, in);
357 fclose(in);
358 REQUIRE(nGot <= nScript);
359 zScript[nGot] = 0;
360
361 // zap hash table as result labels are only valid within one test file
362 memset(aHash, 0, sizeof(aHash));
363
364 /* Initialize the sScript structure so that the cursor will be pointing
365 ** to the start of the first line in the file after nextLine() is called
366 ** once. */
367 memset(&sScript, 0, sizeof(sScript));
368 sScript.zScript = zScript;
369 sScript.zLine = zScript;
370 sScript.iEnd = nScript;
371 sScript.copyFlag = 0;
372
373 /* Open the database engine under test
374 */
375 rc = pEngine->xConnect(pEngine->pAuxData, zConnection, &pConn, zParam);
376 REQUIRE(rc == 0);
377
378 /* Loop over all records in the file */
379 while ((nErr == 0 || !haltOnError) && findStartOfNextRecord(&sScript)) {
380 int bSkip = false; /* True if we should skip the current record. */
381
382 /* Tokenizer the first line of the record. This also records the
383 ** line number of the first record in sScript.startLine */
384 tokenizeLine(&sScript);
385
386 bSkip = false;
387 while (strcmp(sScript.azToken[0], "skipif") == 0 || strcmp(sScript.azToken[0], "onlyif") == 0) {
388 int bMatch;
389 /* The "skipif" and "onlyif" modifiers allow skipping or using
390 ** statement or query record for a particular database engine.
391 ** In this way, SQL features implmented by a majority of the
392 ** engines can be tested without causing spurious errors for
393 ** engines that don't support it.
394 **
395 ** Once this record is encountered, an the current selected
396 ** db interface matches the db engine specified in the record,
397 ** the we skip this rest of this record for "skipif" or for
398 ** "onlyif" we skip the record if the record does not match.
399 */
400 bMatch = stricmp(sScript.azToken[1], zDbEngine) == 0;
401 if (sScript.azToken[0][0] == 's') {
402 if (bMatch)
403 bSkip = true;
404 } else {
405 if (!bMatch)
406 bSkip = true;
407 }
408 nextLine(&sScript);
409 tokenizeLine(&sScript);
410 }
411 if (bSkip) {
412 int n;
413 nSkipped++;
414 if (strcmp(sScript.azToken[0], "query") != 0)
415 continue;
416 if (sScript.azToken[3][0] == 0)
417 continue;
418
419 /* We are skipping this record. But we observe that it is a
420 *query
421 ** with a named hash value and we are in verify mode. Even
422 *though
423 ** we are going to skip the SQL evaluation, we might as well
424 *check
425 ** the hash of the result.
426 */
427 while (!nextIsBlank(&sScript) && nextLine(&sScript) && strcmp(sScript.zLine, "----") != 0) {
428 /* Skip over the SQL text */
429 }
430 if (strcmp(sScript.zLine, "----") == 0)
431 nextLine(&sScript);
432 if (sScript.zLine[0] == 0)
433 continue;
434 n = sscanf(sScript.zLine, "%*d values hashing to %32s", zHash);
435 if (n != 1) {
436 md5_add(sScript.zLine);
437 md5_add("\n");
438 while (!nextIsBlank(&sScript) && nextLine(&sScript)) {
439 md5_add(sScript.zLine);
440 md5_add("\n");
441 }
442 strcpy(zHash, md5_finish());
443 }
444 if (checkValue(sScript.azToken[3], zHash)) {
445 fprintf(stderr,
446 "%s:%d: labeled result [%s] does not agree with "
447 "previous values\n",
448 zScriptFile, sScript.startLine, sScript.azToken[3]);
449 IFAIL();
450 }
451 continue;
452 }
453
454 /* Figure out the record type and do appropriate processing */
455 if (strcmp(sScript.azToken[0], "statement") == 0) {
456 int k = 0;
457 int bExpectOk = 0;
458 int bExpectError = 0;
459
460 /* Extract the SQL from second and subsequent lines of the
461 ** record. Copy the SQL into contiguous memory at the beginning
462 ** of zScript - we are guaranteed to have enough space there. */
463 while (nextLine(&sScript) && sScript.zLine[0]) {
464 if (k > 0)
465 zScript[k++] = '\n';
466 memmove(&zScript[k], sScript.zLine, sScript.len);
467 k += sScript.len;
468 }
469 zScript[k] = 0;
470
471 bExpectOk = strcmp(sScript.azToken[1], "ok") == 0;
472 bExpectError = strcmp(sScript.azToken[1], "error") == 0;
473
474 /* Run the statement. Remember the results
475 ** If we're expecting an error, pass true to suppress
476 ** printing of any errors.
477 */
478 if (enableTrace)
479 printf("%s;\n", zScript);
480 rc = pEngine->xStatement(pConn, zScript, bExpectError);
481 nCmd++;
482
483 /* Check to see if we are expecting success or failure */
484 if (bExpectOk) {
485 /* do nothing if we expect success */
486 } else if (bExpectError) {
487 /* Invert the result if we expect failure */
488 rc = !rc;
489 } else {
490 fprintf(stderr, "%s:%d: statement argument should be 'ok' or 'error'\n", zScriptFile,
491 sScript.startLine);
492 IFAIL();
493 }
494
495 /* Report an error if the results do not match expectation */
496 if (rc) {
497 fprintf(stderr, "%s:%d: statement error\n", zScriptFile, sScript.startLine);
498 IFAIL();
499 }
500 } else if (strcmp(sScript.azToken[0], "query") == 0) {
501 int k = 0;
502 int c;
503
504 /* Verify that the type string consists of one or more
505 *characters
506 ** from the set "TIR". */
507 for (k = 0; (c = sScript.azToken[1][k]) != 0; k++) {
508 if (c != 'T' && c != 'I' && c != 'R') {
509 fprintf(stderr,
510 "%s:%d: unknown type character '%c' in type "
511 "string\n",
512 zScriptFile, sScript.startLine, c);
513 nErr++;
514 break;
515 }
516 }
517 if (c != 0)
518 continue;
519 if (k <= 0) {
520 fprintf(stderr, "%s:%d: missing type string\n", zScriptFile, sScript.startLine);
521 IFAIL();
522 }
523
524 /* Extract the SQL from second and subsequent lines of the
525 *record
526 ** until the first "----" line or until end of record.
527 */
528 k = 0;
529 while (!nextIsBlank(&sScript) && nextLine(&sScript) && sScript.zLine[0] &&
530 strcmp(sScript.zLine, "----") != 0) {
531 if (k > 0)
532 zScript[k++] = '\n';
533 memmove(&zScript[k], sScript.zLine, sScript.len);
534 k += sScript.len;
535 }
536 zScript[k] = 0;
537
538 /* Run the query */
539 nResult = 0;
540 azResult = 0;
541 if (enableTrace)
542 printf("%s;\n", zScript);
543 rc = pEngine->xQuery(pConn, zScript, sScript.azToken[1], &azResult, &nResult);
544 nCmd++;
545 if (rc) {
546 fprintf(stderr, "%s:%d: query failed\n", zScriptFile, sScript.startLine);
547 pEngine->xFreeResults(pConn, azResult, nResult);
548 IFAIL();
549 }
550
551 /* Do any required sorting of query results */
552 if (sScript.azToken[2][0] == 0 || strcmp(sScript.azToken[2], "nosort") == 0) {
553 /* Do no sorting */
554 } else if (strcmp(sScript.azToken[2], "rowsort") == 0) {
555 /* Row-oriented sorting */
556 nColumn = (int)strlen(sScript.azToken[1]);
557 qsort(azResult, nResult / nColumn, sizeof(azResult[0]) * nColumn, rowCompare);
558 } else if (strcmp(sScript.azToken[2], "valuesort") == 0) {
559 /* Sort all values independently */
560 nColumn = 1;
561 qsort(azResult, nResult, sizeof(azResult[0]), rowCompare);
562 } else {
563 fprintf(stderr, "%s:%d: unknown sort method: '%s'\n", zScriptFile, sScript.startLine,
564 sScript.azToken[2]);
565 IFAIL();
566 }
567
568 /* Hash the results if we are over the hash threshold or if we
569 ** there is a hash label */
570 if (sScript.azToken[3][0] || (hashThreshold > 0 && nResult > hashThreshold)) {
571 md5_add(""); /* make sure md5 is reset, even if no results */
572 for (i = 0; i < nResult; i++) {
573 md5_add(azResult[i]);
574 md5_add("\n");
575 }
576 snprintf(zHash, sizeof(zHash), "%d values hashing to %s", nResult, md5_finish());
577 sScript.azToken[3][20] = 0;
578 if (sScript.azToken[3][0] && checkValue(sScript.azToken[3], md5_finish())) {
579 fprintf(stderr,
580 "%s:%d: labeled result [%s] does not agree with "
581 "previous values\n",
582 zScriptFile, sScript.startLine, sScript.azToken[3]);
583 IFAIL();
584 }
585 }
586
587 /* In verify mode, first skip over the ---- line if we are
588 *still
589 ** pointing at it. */
590 if (strcmp(sScript.zLine, "----") == 0)
591 nextLine(&sScript);
592
593 /* Compare subsequent lines of the script against the
594 *results
595 ** from the query. Report an error if any differences are
596 *found.
597 */
598 if (hashThreshold == 0 || nResult <= hashThreshold) {
599 for (i = 0; i < nResult && sScript.zLine[0]; nextLine(&sScript), i++) {
600 if (strcmp(sScript.zLine, azResult[i]) != 0) {
601 fprintf(stderr, "%s:%d: wrong result\n", zScriptFile, sScript.nLine);
602
603 fprintf(stderr, "%s <> %s\n", sScript.zLine, azResult[i]);
604 IFAIL();
605 }
606 // we check this already but this inflates the test
607 // case count as desired
608 REQUIRE(strcmp(sScript.zLine, azResult[i]) == 0);
609 }
610 } else {
611 if (strcmp(sScript.zLine, zHash) != 0) {
612 fprintf(stderr, "%s:%d: wrong result hash\n", zScriptFile, sScript.nLine);
613 IFAIL();
614 }
615 }
616
617 /* Free the query results */
618 pEngine->xFreeResults(pConn, azResult, nResult);
619 } else if (strcmp(sScript.azToken[0], "hash-threshold") == 0) {
620 /* Set the maximum number of result values that will be accepted
621 ** for a query. If the number of result values exceeds this
622 *number,
623 ** then an MD5 hash is computed of all values, and the resulting
624 *hash
625 ** is the only result.
626 **
627 ** If the threshold is 0, then hashing is never used.
628 **
629 ** If a threshold was specified on the command line, ignore
630 ** any specifed in the script.
631 */
632 if (!bHt) {
633 hashThreshold = atoi(sScript.azToken[1]);
634 }
635 } else if (strcmp(sScript.azToken[0], "halt") == 0) {
636 /* Used for debugging. Stop reading the test script and shut
637 *down.
638 ** A "halt" record can be inserted in the middle of a test
639 *script in
640 ** to run the script up to a particular point that is giving a
641 ** faulty result, then terminate at that point for analysis.
642 */
643 fprintf(stderr, "%s:%d: halt\n", zScriptFile, sScript.startLine);
644 break;
645 } else {
646 /* An unrecognized record type is an error */
647 fprintf(stderr, "%s:%d: unknown record type: '%s'\n", zScriptFile, sScript.startLine, sScript.azToken[0]);
648 IFAIL();
649 }
650 }
651
652 /* Shutdown the database connection.
653 */
654 pEngine->xDisconnect(pConn);
655 free(zScript);
656}
657
658// code below traverses the test directory and makes individual test cases out
659// of each script
660static void listFiles(const string &path, std::function<void(const string &)> cb) {
661#ifndef SUN
662 if (auto dir = opendir(path.c_str())) {
663 while (auto f = readdir(dir)) {
664 if (f->d_name[0] == '.')
665 continue;
666 if (f->d_type == DT_DIR)
667 listFiles(path + f->d_name + "/", cb);
668
669 if (f->d_type == DT_REG)
670 cb(path + f->d_name);
671 }
672 closedir(dir);
673 }
674#endif
675}
676
677static bool endsWith(const string &mainStr, const string &toMatch) {
678 return (mainStr.size() >= toMatch.size() &&
679 mainStr.compare(mainStr.size() - toMatch.size(), toMatch.size(), toMatch) == 0);
680}
681
682static void testRunner() {
683 // this is an ugly hack that uses the test case name to pass the script file
684 // name if someone has a better idea...
685 auto name = Catch::getResultCapture().getCurrentTestName();
686 fprintf(stderr, "%s\n", name.c_str());
687 execute_file(name);
688}
689
690TEST_CASE("SQLite select1", "[sqlitelogic]") {
691 execute_file("test/sqlite/select1.test");
692}
693
694TEST_CASE("SQLite select2", "[sqlitelogic]") {
695 execute_file("test/sqlite/select2.test");
696}
697
698TEST_CASE("SQLite select3", "[sqlitelogic]") {
699 execute_file("test/sqlite/select3.test");
700}
701
702TEST_CASE("SQLite select4", "[sqlitelogic][.]") {
703 execute_file("test/sqlite/select4.test");
704}
705
706struct AutoRegTests {
707 AutoRegTests() {
708 vector<string> excludes = {
709 "test/select1.test", // tested separately
710 "test/select2.test", "test/select3.test", "test/select4.test",
711 "test/index", // no index yet
712 "random/groupby/", // having column binding issue with first
713 "random/select/slt_good_70.test", // join on not between
714 "random/expr/slt_good_10.test", // these all fail because the AVG
715 // decimal rewrite
716 "random/expr/slt_good_102.test", "random/expr/slt_good_107.test", "random/expr/slt_good_108.test",
717 "random/expr/slt_good_109.test", "random/expr/slt_good_111.test", "random/expr/slt_good_112.test",
718 "random/expr/slt_good_113.test", "random/expr/slt_good_115.test", "random/expr/slt_good_116.test",
719 "random/expr/slt_good_117.test", "random/expr/slt_good_13.test", "random/expr/slt_good_15.test",
720 "random/expr/slt_good_16.test", "random/expr/slt_good_17.test", "random/expr/slt_good_19.test",
721 "random/expr/slt_good_21.test", "random/expr/slt_good_22.test", "random/expr/slt_good_24.test",
722 "random/expr/slt_good_28.test", "random/expr/slt_good_29.test", "random/expr/slt_good_3.test",
723 "random/expr/slt_good_30.test", "random/expr/slt_good_34.test", "random/expr/slt_good_38.test",
724 "random/expr/slt_good_4.test", "random/expr/slt_good_41.test", "random/expr/slt_good_44.test",
725 "random/expr/slt_good_45.test", "random/expr/slt_good_49.test", "random/expr/slt_good_52.test",
726 "random/expr/slt_good_53.test", "random/expr/slt_good_55.test", "random/expr/slt_good_59.test",
727 "random/expr/slt_good_6.test", "random/expr/slt_good_60.test", "random/expr/slt_good_63.test",
728 "random/expr/slt_good_64.test", "random/expr/slt_good_67.test", "random/expr/slt_good_69.test",
729 "random/expr/slt_good_7.test", "random/expr/slt_good_71.test", "random/expr/slt_good_72.test",
730 "random/expr/slt_good_8.test", "random/expr/slt_good_80.test", "random/expr/slt_good_82.test",
731 "random/expr/slt_good_85.test", "random/expr/slt_good_9.test", "random/expr/slt_good_90.test",
732 "random/expr/slt_good_91.test", "random/expr/slt_good_94.test", "random/expr/slt_good_95.test",
733 "random/expr/slt_good_96.test", "random/expr/slt_good_99.test", "random/aggregates/slt_good_2.test",
734 "random/aggregates/slt_good_5.test", "random/aggregates/slt_good_7.test",
735 "random/aggregates/slt_good_9.test", "random/aggregates/slt_good_17.test",
736 "random/aggregates/slt_good_28.test", "random/aggregates/slt_good_45.test",
737 "random/aggregates/slt_good_50.test", "random/aggregates/slt_good_52.test",
738 "random/aggregates/slt_good_58.test", "random/aggregates/slt_good_65.test",
739 "random/aggregates/slt_good_66.test", "random/aggregates/slt_good_76.test",
740 "random/aggregates/slt_good_81.test", "random/aggregates/slt_good_90.test",
741 "random/aggregates/slt_good_96.test", "random/aggregates/slt_good_102.test",
742 "random/aggregates/slt_good_106.test", "random/aggregates/slt_good_112.test",
743 "random/aggregates/slt_good_118.test",
744 "third_party/sqllogictest/test/evidence/in1.test", // UNIQUE index on text
745 "evidence/slt_lang_replace.test", // feature not supported
746 "evidence/slt_lang_reindex.test", // "
747 "evidence/slt_lang_dropindex.test", // "
748 "evidence/slt_lang_createtrigger.test", // "
749 "evidence/slt_lang_droptrigger.test" // "
750 };
751 listFiles("third_party/sqllogictest/test/", [excludes](const string &path) {
752 if (endsWith(path, ".test")) {
753 for (auto excl : excludes) {
754 if (path.find(excl) != string::npos) {
755 return;
756 }
757 }
758 REGISTER_TEST_CASE(testRunner, path, "[sqlitelogic][.]");
759 }
760 });
761 }
762};
763AutoRegTests autoreg;
764