| 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 |  | 
|---|
| 45 | using 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 | */ | 
|---|
| 52 | typedef struct Script Script; | 
|---|
| 53 | struct 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 | 
|---|
| 67 | void 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 | */ | 
|---|
| 78 | static 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 | */ | 
|---|
| 133 | static 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 | */ | 
|---|
| 152 | static 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 | */ | 
|---|
| 176 | static 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 | */ | 
|---|
| 195 | static 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 | */ | 
|---|
| 217 | static int nColumn = 0; | 
|---|
| 218 |  | 
|---|
| 219 | /* | 
|---|
| 220 | ** Comparison function for sorting the result set. | 
|---|
| 221 | */ | 
|---|
| 222 | static 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 | */ | 
|---|
| 235 | typedef struct HashEntry HashEntry; | 
|---|
| 236 | struct 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 | 
|---|
| 247 | static HashEntry *aHash[NHASH]; | 
|---|
| 248 | static 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 | */ | 
|---|
| 255 | static 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 |  | 
|---|
| 300 | static 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 | 
|---|
| 660 | static 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 |  | 
|---|
| 677 | static 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 |  | 
|---|
| 682 | static 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 |  | 
|---|
| 690 | TEST_CASE( "SQLite select1", "[sqlitelogic]") { | 
|---|
| 691 | execute_file( "test/sqlite/select1.test"); | 
|---|
| 692 | } | 
|---|
| 693 |  | 
|---|
| 694 | TEST_CASE( "SQLite select2", "[sqlitelogic]") { | 
|---|
| 695 | execute_file( "test/sqlite/select2.test"); | 
|---|
| 696 | } | 
|---|
| 697 |  | 
|---|
| 698 | TEST_CASE( "SQLite select3", "[sqlitelogic]") { | 
|---|
| 699 | execute_file( "test/sqlite/select3.test"); | 
|---|
| 700 | } | 
|---|
| 701 |  | 
|---|
| 702 | TEST_CASE( "SQLite select4", "[sqlitelogic][.]") { | 
|---|
| 703 | execute_file( "test/sqlite/select4.test"); | 
|---|
| 704 | } | 
|---|
| 705 |  | 
|---|
| 706 | struct 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 | }; | 
|---|
| 763 | AutoRegTests autoreg; | 
|---|
| 764 |  | 
|---|