UnQL

Check-in [1e6ab63d25]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Add the ".json" command to shell.c. ".json" is like ".result", but removes excess white-space and quotes unquoted identifiers from the test result before comparing it with the library output. This allows us to make test files more readable.
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 1e6ab63d25cde1dd812e7143b27f21c6a0576cf5
User & Date: dan 2011-07-27 16:23:43
Context
2011-07-28
15:29
Fix the UPDATE command so that each changes is independent. check-in: 7b79de2564 user: drh tags: trunk
2011-07-27
16:23
Add the ".json" command to shell.c. ".json" is like ".result", but removes excess white-space and quotes unquoted identifiers from the test result before comparing it with the library output. This allows us to make test files more readable. check-in: 1e6ab63d25 user: dan tags: trunk
12:17
Allow FLATTEN/EACH on the properties of nested objects. check-in: 21ef096d9c user: dan tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/json.c.

707
708
709
710
711
712
713






















































714
715
716
717
718
719
720
  x.mxIn = mxIn>0 ? mxIn : xjd1Strlen30(zIn);
  x.iCur = 0;
  x.n = 0;
  x.eType = 0;
  tokenNext(&x);
  return parseJson(&x);
}























































/*
** The JsonNode passed as the first argument must be of type XJD1_STRUCT.
** This function adds a property to the object. The name of the new 
** property is passed as the second argument to this function. The third
** argument is used as the initial value. If the property already exists,
** it is replaced.







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
  x.mxIn = mxIn>0 ? mxIn : xjd1Strlen30(zIn);
  x.iCur = 0;
  x.n = 0;
  x.eType = 0;
  tokenNext(&x);
  return parseJson(&x);
}

/*
** This function is used by the XJD1 shell in test mode. It assumes that
** the string zIn contains a list of white-space separated JSON values.
** It appends the contents of zIn to string pOut, making the following
** edits:
**
**   1. All white-space that appears inside of struct or array values 
**      is removed. A single space characer is left between each top 
**      level value.
**
**   2. Double quotes are added to any unquoted sequences of alphabetic 
**      characters (which are illegal in JSON).
**
** For example, the following input:
**
**      {a:1,  "b":[1, 3, 4]}   [1,2]
**
** is transformed to:
**
**      {"a":1,"b":[1,3,4]} [1,2]
**
** before it is appended to pOut.
*/
int xjd1JsonTidy(String *pOut, const char *zIn){
  int nOpen = 0;                  /* Number of open {} or [] brackets */
  JsonStr x;                      /* Wrapper around zIn */

  /* Set up the string wrapper to tokenize zIn */
  x.zIn = zIn;
  x.mxIn = xjd1Strlen30(zIn);
  x.iCur = 0;
  x.n = 0;
  x.eType = 0;

  while( 1 ){
    int ePrev = x.eType;
    tokenNext(&x);
    if( x.eType==JSON_EOF ) break;
    if( ePrev && nOpen==0 ) xjd1StringAppend(pOut, " ", 1);
    if( x.eType==JSON_BEGIN_ARRAY || x.eType==JSON_BEGIN_STRUCT ) nOpen++;
    if( x.eType==JSON_END_ARRAY   || x.eType==JSON_END_STRUCT ) nOpen--;
    if( x.eType==JSON_ERROR && xjd1Isalpha(zIn[x.iCur]) ){
      while( xjd1Isalpha(zIn[x.iCur+x.n]) ) x.n++;
      xjd1StringAppend(pOut, "\"", 1);
      xjd1StringAppend(pOut, &zIn[x.iCur], x.n);
      xjd1StringAppend(pOut, "\"", 1);
    }else{
      xjd1StringAppend(pOut, &zIn[x.iCur], x.n);
    }
  }while( x.eType!=JSON_EOF );

  return XJD1_OK;
}

/*
** The JsonNode passed as the first argument must be of type XJD1_STRUCT.
** This function adds a property to the object. The name of the new 
** property is passed as the second argument to this function. The third
** argument is used as the initial value. If the property already exists,
** it is replaced.

Changes to src/shell.c.

318
319
320
321
322
323
324




















325
326
327
328
329
330
331
...
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429

430
431
432
433
434
435
436
...
443
444
445
446
447
448
449









450
451
452
453
454
455
456
  return 0;
}
/* Command:  .notglob PATTERN */
static int shellNotGlob(Shell *p, int argc, char **argv){
  shellResultCheck(p,argc,argv,strnotglob);
  return 0;
}





















/*
** Command:  .open FILENAME
*/
static int shellOpenDB(Shell *p, int argc, char **argv){
  if( argc>=2 ){
    int rc;
................................................................................
}

/*
** Process a command intended for this shell - not for the database.
**
** Return 1 to abort or 0 to continue.
*/
static int processMetaCommand(Shell *p){
  char *z;
  int n;
  char *azArg[2];
  int nArg;
  int i;
  int rc;
  static const struct shcmd {
    const char *zCmd;                   /* Name of the command */
    int (*xCmd)(Shell*,int,char**);     /* Procedure to run the command */
    const char *zHelp;                  /* Help string */
  } cmds[] = {
    { "quit",       shellQuit,        ".quit"               },
    { "testcase",   shellTestcase,    ".testcase NAME"      },

    { "result",     shellResult,      ".result TEXT"        },
    { "glob",       shellGlob,        ".glob PATTERN"       },
    { "notglob",    shellNotGlob,     ".notglob PATTERN"    },
    { "puts",       shellPuts,        ".puts TEXT"          },
    { "read",       shellRead,        ".read FILENAME"      },
    { "open",       shellOpenDB,      ".open DATABASE"      },
    { "new",        shellNewDB,       ".new DATABASE"       },
................................................................................
  z = xjd1StringText(&p->inBuf);
  if( p->shellFlags & SHELL_ECHO ){
    fprintf(stdout, "%s", z);
  }
  n = xjd1StringLen(&p->inBuf);
  while( n>0 && shellIsSpace(z[n-1]) ){ n--; }
  z[n] = 0;










  /* Find the command name text and its argument (if any) */
  z++;
  azArg[0] = z;
  for(i=0; z[i] && !shellIsSpace(z[i]); i++){}
  if( z[i] ){
    z[i] = 0;







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







|
<












>







 







>
>
>
>
>
>
>
>
>







318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
...
429
430
431
432
433
434
435
436

437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
...
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
  return 0;
}
/* Command:  .notglob PATTERN */
static int shellNotGlob(Shell *p, int argc, char **argv){
  shellResultCheck(p,argc,argv,strnotglob);
  return 0;
}
/* Command:  .json PATTERN */
static int shellJson(Shell *p, int argc, char **argv){
  char *aArg[2];
  String tidy;

  assert( argc==1 || argc==2 );

  xjd1StringInit(&tidy, 0, 0);
  if( argc==2 ){
    int rc = xjd1JsonTidy(&tidy, argv[1]);
    assert( rc==XJD1_OK );
  }

  aArg[0] = argv[0];
  aArg[1] = tidy.zBuf;
  shellResultCheck(p, argc, aArg, strcmp);
  xjd1StringClear(&tidy);

  return 0;
}

/*
** Command:  .open FILENAME
*/
static int shellOpenDB(Shell *p, int argc, char **argv){
  if( argc>=2 ){
    int rc;
................................................................................
}

/*
** Process a command intended for this shell - not for the database.
**
** Return 1 to abort or 0 to continue.
*/
static int processMetaCommand(Shell *p){ char *z;

  int n;
  char *azArg[2];
  int nArg;
  int i;
  int rc;
  static const struct shcmd {
    const char *zCmd;                   /* Name of the command */
    int (*xCmd)(Shell*,int,char**);     /* Procedure to run the command */
    const char *zHelp;                  /* Help string */
  } cmds[] = {
    { "quit",       shellQuit,        ".quit"               },
    { "testcase",   shellTestcase,    ".testcase NAME"      },
    { "json",       shellJson,        ".json TEXT"          },
    { "result",     shellResult,      ".result TEXT"        },
    { "glob",       shellGlob,        ".glob PATTERN"       },
    { "notglob",    shellNotGlob,     ".notglob PATTERN"    },
    { "puts",       shellPuts,        ".puts TEXT"          },
    { "read",       shellRead,        ".read FILENAME"      },
    { "open",       shellOpenDB,      ".open DATABASE"      },
    { "new",        shellNewDB,       ".new DATABASE"       },
................................................................................
  z = xjd1StringText(&p->inBuf);
  if( p->shellFlags & SHELL_ECHO ){
    fprintf(stdout, "%s", z);
  }
  n = xjd1StringLen(&p->inBuf);
  while( n>0 && shellIsSpace(z[n-1]) ){ n--; }
  z[n] = 0;

  /* If the last character of the command is a backslash, the command 
  ** arguments continue onto the next line. Return early without truncating
  ** the input buffer in this case. */
  if( z[n-1]=='\\' ){
    p->inBuf.nUsed = n;
    z[n-1] = ' ';
    return 0;
  }

  /* Find the command name text and its argument (if any) */
  z++;
  azArg[0] = z;
  for(i=0; z[i] && !shellIsSpace(z[i]); i++){}
  if( z[i] ){
    z[i] = 0;

Changes to src/xjd1Int.h.

441
442
443
444
445
446
447

448
449
450
451
452
453
454
int xjd1JsonCompare(const JsonNode*, const JsonNode*);
JsonNode *xjd1JsonNew(Pool*);
JsonNode *xjd1JsonEdit(JsonNode*);
void xjd1JsonFree(JsonNode*);
void xjd1JsonToNull(JsonNode*);
void xjd1DequoteString(char*,int);
int xjd1JsonInsert(JsonNode *, const char *, JsonNode *);


/******************************** memory.c ***********************************/
Pool *xjd1PoolNew(void);
void xjd1PoolClear(Pool*);
void xjd1PoolDelete(Pool*);
void *xjd1PoolMalloc(Pool*, int);
void *xjd1PoolMallocZero(Pool*, int);







>







441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
int xjd1JsonCompare(const JsonNode*, const JsonNode*);
JsonNode *xjd1JsonNew(Pool*);
JsonNode *xjd1JsonEdit(JsonNode*);
void xjd1JsonFree(JsonNode*);
void xjd1JsonToNull(JsonNode*);
void xjd1DequoteString(char*,int);
int xjd1JsonInsert(JsonNode *, const char *, JsonNode *);
int xjd1JsonTidy(String *, const char *);

/******************************** memory.c ***********************************/
Pool *xjd1PoolNew(void);
void xjd1PoolClear(Pool*);
void xjd1PoolDelete(Pool*);
void *xjd1PoolMalloc(Pool*, int);
void *xjd1PoolMallocZero(Pool*, int);

Changes to test/base08.test.

1
2
3
4
5
6
7
8


9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

56

57
58
59
60
61
62
63
64
65
66
67
68
69
70
71




72


.new t1.db
CREATE COLLECTION c1;
INSERT INTO c1 VALUE { w:"hi", x:["a",{b:123},456] };

.testcase 1
SELECT FROM c1 EACH(x AS y);
.result {"w":"hi","x":["a",{"b":123},456],"y":{"k":0,"v":"a"}} {"w":"hi","x":["a",{"b":123},456],"y":{"k":1,"v":{"b":123}}} {"w":"hi","x":["a",{"b":123},456],"y":{"k":2,"v":456}}



.testcase 2
CREATE COLLECTION c2;
INSERT INTO c2 VALUE { z:"one", l:[1,2,8] };
INSERT INTO c2 VALUE { z:"two", l:[2,4,6] };
SELECT c2.z FROM c2 EACH(l);
.result "one" "one" "one" "two" "two" "two"

.testcase 3
SELECT c2.l.v AS v FROM c2 EACH(l) ORDER BY v DESC;
.result 8 6 4 2 2 1

.testcase 4
SELECT c2.m.v AS v FROM c2 EACH(l AS m) WHERE c2.z=="one" ORDER BY v%4;
.result 8 1 2

.testcase 5
CREATE COLLECTION c3;
INSERT INTO c3 VALUE {y:0, x:{a:"A",b:"B"}};
SELECT FROM c3 EACH(x);
.result {"y":0,"x":{"k":"a","v":"A"}} {"y":0,"x":{"k":"b","v":"B"}}

.testcase 6
SELECT FROM c3 EACH(x AS y);
.result {"y":{"k":"a","v":"A"},"x":{"a":"A","b":"B"}} {"y":{"k":"b","v":"B"},"x":{"a":"A","b":"B"}}


.testcase 7
SELECT FROM c3 EACH(2 AS y);
.error SYNTAX syntax error near "2"

.testcase 8
CREATE COLLECTION c4;
INSERT INTO c4 VALUE {a:1, b: [ [1, 2, 3], [4, 5, 6], [7, 8, 9] ]};
INSERT INTO c4 VALUE {a:2, b: { x:[1, 2, 3], y:{t:4, u:5, v:6}, z:[7, 8, 9] }};
SELECT c4.b.v FROM c4 FLATTEN(b) WHERE c4.a==1;
.result 1 2 3 4 5 6 7 8 9

.testcase 9
SELECT c4.b.k FROM c4 FLATTEN(b) WHERE c4.a==1;
.result [0,0] [0,1] [0,2] [1,0] [1,1] [1,2] [2,0] [2,1] [2,2]

.testcase 10
SELECT c4.c.v FROM c4 FLATTEN(b AS c) WHERE c4.a==2;
.result 1 2 3 4 5 6 7 8 9

.testcase 11
SELECT c4.c.k FROM c4 FLATTEN(b AS c) WHERE c4.a==2;

.result ["x",0] ["x",1] ["x",2] ["y","t"] ["y","u"] ["y","v"] ["z",0] ["z",1] ["z",2]


.testcase 12
CREATE COLLECTION c5;
INSERT INTO c5 VALUE { a:1, b:2, c:{d:3, e:[4,5,6,7,8]} };
SELECT c5.c.e.v FROM c5 EACH(c.e);
.result 4 5 6 7 8

.testcase 13
SELECT c5.c.e.v FROM c5 EACH(c["e"]);
.result 4 5 6 7 8

.testcase 14
SELECT c5.x.k FROM c5 EACH(c["e"] AS x);
.result 0 1 2 3 4
 













|
>
>






|



|



|





|



|
>










|



|



|



>
|
>





|



|



|
|
>
>
>
>
|
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82

.new t1.db
CREATE COLLECTION c1;
INSERT INTO c1 VALUE { w:"hi", x:["a",{b:123},456] };

.testcase 1
SELECT FROM c1 EACH(x AS y);
.json {w:"hi", x:["a",{b:123},456], y:{k:0,v:"a"}}     \
      {w:"hi", x:["a",{b:123},456], y:{k:1,v:{b:123}}} \
      {w:"hi", x:["a",{b:123},456], y:{k:2,v:456}}

.testcase 2
CREATE COLLECTION c2;
INSERT INTO c2 VALUE { z:"one", l:[1,2,8] };
INSERT INTO c2 VALUE { z:"two", l:[2,4,6] };
SELECT c2.z FROM c2 EACH(l);
.json "one" "one" "one" "two" "two" "two"

.testcase 3
SELECT c2.l.v AS v FROM c2 EACH(l) ORDER BY v DESC;
.json 8 6 4 2 2 1

.testcase 4
SELECT c2.m.v AS v FROM c2 EACH(l AS m) WHERE c2.z=="one" ORDER BY v%4;
.json 8 1 2

.testcase 5
CREATE COLLECTION c3;
INSERT INTO c3 VALUE {y:0, x:{a:"A",b:"B"}};
SELECT FROM c3 EACH(x);
.json {y:0, x:{k:"a", v:"A"}} {y:0, x:{k:"b", v:"B"}}

.testcase 6
SELECT FROM c3 EACH(x AS y);
.json {y:{k:"a", v:"A"}, x:{a:"A", b:"B"}} \
      {y:{k:"b", v:"B"}, x:{a:"A", b:"B"}}

.testcase 7
SELECT FROM c3 EACH(2 AS y);
.error SYNTAX syntax error near "2"

.testcase 8
CREATE COLLECTION c4;
INSERT INTO c4 VALUE {a:1, b: [ [1, 2, 3], [4, 5, 6], [7, 8, 9] ]};
INSERT INTO c4 VALUE {a:2, b: { x:[1, 2, 3], y:{t:4, u:5, v:6}, z:[7, 8, 9] }};
SELECT c4.b.v FROM c4 FLATTEN(b) WHERE c4.a==1;
.json 1 2 3 4 5 6 7 8 9

.testcase 9
SELECT c4.b.k FROM c4 FLATTEN(b) WHERE c4.a==1;
.json [0,0] [0,1] [0,2] [1,0] [1,1] [1,2] [2,0] [2,1] [2,2]

.testcase 10
SELECT c4.c.v FROM c4 FLATTEN(b AS c) WHERE c4.a==2;
.json 1 2 3 4 5 6 7 8 9

.testcase 11
SELECT c4.c.k FROM c4 FLATTEN(b AS c) WHERE c4.a==2;
.json ["x",0]   ["x",1]   ["x",2]   \
      ["y","t"] ["y","u"] ["y","v"] \
      ["z",0]   ["z",1]   ["z",2]

.testcase 12
CREATE COLLECTION c5;
INSERT INTO c5 VALUE { a:1, b:2, c:{d:3, e:[4,5,6,7,8]} };
SELECT c5.c.e.v FROM c5 EACH(c.e);
.json 4 5 6 7 8

.testcase 13
SELECT c5.c.e.v FROM c5 EACH(c["e"]);
.json 4 5 6 7 8

.testcase 14
SELECT c5.x.k FROM c5 EACH(c["e"] AS x);
.json 0 1 2 3 4

.testcase 15
SELECT FROM c5;
SELECT c5.x.k FROM c5 EACH(c["e"] AS x);
.json {a:1, b:2, c:{d:3, e:[4,5,6,7,8]}} 0 1 2 3 4