UnQL

Check-in [a399717a11]
Login

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

Overview
Comment:Add support for ORDER BY on SELECT queries.
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: a399717a113617bb29688029a956f4f8d3d7120d
User & Date: dan 2011-07-19 13:14:53
Context
2011-07-19
17:59
Add support for OFFSET and LIMIT on simple SELECT statements. check-in: 885cc57b2c user: dan tags: trunk
13:14
Add support for ORDER BY on SELECT queries. check-in: a399717a11 user: dan tags: trunk
2011-07-18
11:15
Fix for queries that feature sub-selects in the FROM clause. check-in: e4cab3b6a0 user: dan tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/memory.c.

23
24
25
26
27
28
29










30
31
32
33
34
35
36
** memory pool or NULL on OOM error.
*/
Pool *xjd1PoolNew(void){
  Pool *p = malloc( sizeof(*p) );
  if( p ) memset(p, 0, sizeof(*p));
  return p;
}











/*
** Clear a memory allocation pool.  That is to say, free all the
** memory allocations associated with the pool, though do not free
** the memory pool itself.
*/
void xjd1PoolClear(Pool *p){







>
>
>
>
>
>
>
>
>
>







23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
** memory pool or NULL on OOM error.
*/
Pool *xjd1PoolNew(void){
  Pool *p = malloc( sizeof(*p) );
  if( p ) memset(p, 0, sizeof(*p));
  return p;
}

/*
** Free a memory allocation pool allocated using xjd1PoolNew().
*/
void xjd1PoolDelete(Pool *p){
  if( p ){
    xjd1PoolClear(p);
    free(p);
  }
}

/*
** Clear a memory allocation pool.  That is to say, free all the
** memory allocations associated with the pool, though do not free
** the memory pool itself.
*/
void xjd1PoolClear(Pool *p){

Changes to src/query.c.

14
15
16
17
18
19
20



























































































































21
22
23
24
25
26
27
..
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
..
90
91
92
93
94
95
96



97
98
99
100
101
102
103
104
...
115
116
117
118
119
120
121

122
123
124
125
126
127
128
...
129
130
131
132
133
134
135

**   http://www.hwaci.com/drh/
**
*************************************************************************
** This file contains code used to implement query processing.
*/
#include "xjd1Int.h"




























































































































/*
** Called after statement parsing to initalize every Query object
** within the statement.
*/
int xjd1QueryInit(Query *pQuery, xjd1_stmt *pStmt, Query *pOuter){
  if( pQuery==0 ) return XJD1_OK;
  pQuery->pStmt = pStmt;
................................................................................
/*
** Rewind a query so that it is pointing at the first row.
*/
int xjd1QueryRewind(Query *p){
  if( p==0 ) return XJD1_OK;
  if( p->eQType==TK_SELECT ){
    xjd1DataSrcRewind(p->u.simple.pFrom);


  }else{
    xjd1QueryRewind(p->u.compound.pLeft);
    p->u.compound.doneLeft = 0;
    xjd1QueryRewind(p->u.compound.pRight);
  }
  return XJD1_OK;
}












/*
** Advance a query to the next row.  Return XDJ1_DONE if there is no
** next row, or XJD1_ROW if the step was successful.
*/
int xjd1QueryStep(Query *p){
  int rc;
  if( p==0 ) return XJD1_DONE;
  if( p->eQType==TK_SELECT ){
    do{

      rc = xjd1DataSrcStep(p->u.simple.pFrom);
    }while(
         rc==XJD1_ROW
      && (p->u.simple.pWhere!=0 && !xjd1ExprTrue(p->u.simple.pWhere))
    );

































  }else{
    rc = XJD1_ROW;
    if( !p->u.compound.doneLeft ){
      rc = xjd1QueryStep(p->u.compound.pLeft);
      if( rc==XJD1_DONE ) p->u.compound.doneLeft = 1;
    }
    if( p->u.compound.doneLeft ){
................................................................................
**
** The caller must invoke JsonFree() when it is done with this value.
*/
JsonNode *xjd1QueryDoc(Query *p, const char *zDocName){
  JsonNode *pOut = 0;
  if( p ){
    if( p->eQType==TK_SELECT ){



      if( zDocName==0 && p->u.simple.pRes ){
        pOut = xjd1ExprEval(p->u.simple.pRes);
      }else{
        pOut = xjd1DataSrcDoc(p->u.simple.pFrom, zDocName);
      }
    }else if( !p->u.compound.doneLeft ){
      pOut = xjd1QueryDoc(p->u.compound.pLeft, zDocName);
    }else{
................................................................................
/*
** The destructor for a Query object.
*/
int xjd1QueryClose(Query *pQuery){
  int rc = XJD1_OK;
  if( pQuery==0 ) return rc;
  if( pQuery->eQType==TK_SELECT ){

    xjd1ExprClose(pQuery->u.simple.pRes);
    xjd1DataSrcClose(pQuery->u.simple.pFrom);
    xjd1ExprClose(pQuery->u.simple.pWhere);
    xjd1ExprListClose(pQuery->u.simple.pGroupBy);
    xjd1ExprClose(pQuery->u.simple.pHaving);
    xjd1ExprListClose(pQuery->u.simple.pOrderBy);
    xjd1ExprClose(pQuery->u.simple.pLimit);
................................................................................
    xjd1ExprClose(pQuery->u.simple.pOffset);
  }else{
    xjd1QueryClose(pQuery->u.compound.pLeft);
    xjd1QueryClose(pQuery->u.compound.pRight);
  }
  return rc;
}








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







 







>
>







>
>
>
>
>
>
>
>
>
>
>









<
>
|
|
|
|
<
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>
>
>
|







 







>







 







>
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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
...
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203

204
205
206
207
208

209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
...
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
...
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
...
301
302
303
304
305
306
307
308
**   http://www.hwaci.com/drh/
**
*************************************************************************
** This file contains code used to implement query processing.
*/
#include "xjd1Int.h"


struct ResultItem {
  JsonNode **apKey;               /* List of JSON objects - the sort key */
  ResultItem *pNext;           /* Next element in list */
};

static int addToResultList(
  ResultList *pList,              /* List to append to */
  JsonNode **apKey                /* Array of values to add to list */
){
  ResultItem *pNew;               /* Newly allocated ResultItem */
  int nByte;                      /* Bytes to allocate for new node */
  int i;                          /* Used to iterate through apKey[] */

  nByte = sizeof(ResultItem) + sizeof(JsonNode*) * pList->nKey;
  pNew = (ResultItem *)xjd1PoolMalloc(pList->pPool, nByte);
  if( !pNew ){
    for(i=0; i<pList->nKey; i++){
      xjd1JsonFree(apKey[i]);
    }
    return XJD1_NOMEM;
  }

  pNew->apKey = (JsonNode **)&pNew[1];
  memcpy(pNew->apKey, apKey, pList->nKey * sizeof(JsonNode *));
  pNew->pNext = pList->pItem;
  pList->pItem = pNew;

  return XJD1_OK;
}

static ResultItem *mergeResultItems(
  ExprList *pEList,              /* Used for ASC/DESC of each key */
  ResultItem *p1,                /* First list to merge */
  ResultItem *p2                 /* Second list to merge */
){
  ResultItem *pRet = 0;
  ResultItem **ppNext;

  ppNext = &pRet;
  while( p1 || p2 ){
    if( !p1 ){
      *ppNext = p2;
      p2 = 0;
    }else if( !p2 ){
      *ppNext = p1;
      p1 = 0;
    }else{
      char const *zDir;
      int c;
      int i;

      for(i=0; i<pEList->nEItem; i++){
        if( 0!=(c=xjd1JsonCompare(p1->apKey[i], p2->apKey[i])) ) break;
      }
      zDir = pEList->apEItem[i].zAs;
      if( zDir && zDir[0]=='D' ){
        c = c*-1;
      }

      if( c<=0 ){
        *ppNext = p1;
        ppNext = &p1->pNext;
        p1 = p1->pNext;
      }else{
        *ppNext = p2;
        ppNext = &p2->pNext;
        p2 = p2->pNext;
      }
    }
  }

  return pRet;
}

#define N_BUCKET 40
static void sortResultList(ResultList *pList, ExprList *pEList){
  int i;
  ResultItem *aList[40];

  ResultItem *pNext;
  ResultItem *pHead;

  memset(aList, 0, sizeof(aList));
  pHead = pList->pItem;

  while( pHead ){
    pNext = pHead->pNext;
    pHead->pNext = 0;
    for(i=0; aList[i]; i++){
      assert( i<N_BUCKET );
      pHead = mergeResultItems(pEList, pHead, aList[i]);
      aList[i] = 0;
    }
    aList[i] = pHead;
    pHead = pNext;
  }

  pHead = aList[0];
  for(i=1; i<N_BUCKET; i++){
    pHead = mergeResultItems(pEList, pHead, aList[i]);
  }

  pList->pItem = pHead;
}

static void popResultList(ResultList *pList){
  ResultItem *pItem;
  int i;

  pItem = pList->pItem;
  pList->pItem = pItem->pNext;
  for(i=0; i<pList->nKey; i++){
    xjd1JsonFree(pItem->apKey[i]);
  }
}

static void clearResultList(ResultList *pList){
  while( pList->pItem ) popResultList(pList);
  xjd1PoolDelete(pList->pPool);
  pList->pPool = 0;
}

/*
** Called after statement parsing to initalize every Query object
** within the statement.
*/
int xjd1QueryInit(Query *pQuery, xjd1_stmt *pStmt, Query *pOuter){
  if( pQuery==0 ) return XJD1_OK;
  pQuery->pStmt = pStmt;
................................................................................
/*
** Rewind a query so that it is pointing at the first row.
*/
int xjd1QueryRewind(Query *p){
  if( p==0 ) return XJD1_OK;
  if( p->eQType==TK_SELECT ){
    xjd1DataSrcRewind(p->u.simple.pFrom);
    p->bUseResultList = 0;
    clearResultList(&p->result);
  }else{
    xjd1QueryRewind(p->u.compound.pLeft);
    p->u.compound.doneLeft = 0;
    xjd1QueryRewind(p->u.compound.pRight);
  }
  return XJD1_OK;
}

static int selectStepUnordered(Query *p){
  int rc;                         /* Return code */
  do{
    rc = xjd1DataSrcStep(p->u.simple.pFrom);
  }while(
    rc==XJD1_ROW
    && (p->u.simple.pWhere!=0 && !xjd1ExprTrue(p->u.simple.pWhere))
  );
  return rc;
}

/*
** Advance a query to the next row.  Return XDJ1_DONE if there is no
** next row, or XJD1_ROW if the step was successful.
*/
int xjd1QueryStep(Query *p){
  int rc;
  if( p==0 ) return XJD1_DONE;
  if( p->eQType==TK_SELECT ){


    if( p->u.simple.pOrderBy ){
      /* There is an ORDER BY clause. */
      if( p->bUseResultList==0 ){
        int nKey = p->u.simple.pOrderBy->nEItem + 1;

        ExprList *pOrderBy = p->u.simple.pOrderBy;
        Pool *pPool;
        JsonNode **apKey;

        p->result.nKey = nKey;
        pPool = p->result.pPool = xjd1PoolNew();
        if( !pPool ) return XJD1_NOMEM;
        apKey = xjd1PoolMallocZero(pPool, nKey * sizeof(JsonNode *));
        if( !apKey ) return XJD1_NOMEM;

        while( XJD1_ROW==(rc = selectStepUnordered(p) ) ){
          int i;
          for(i=0; i<pOrderBy->nEItem; i++){
            apKey[i] = xjd1ExprEval(pOrderBy->apEItem[i].pExpr);
          }
          apKey[i] = xjd1QueryDoc(p, 0);

          rc = addToResultList(&p->result, apKey);
          if( rc!=XJD1_OK ) break;
        }
        if( rc!=XJD1_DONE ) return rc;

        sortResultList(&p->result, p->u.simple.pOrderBy);
        p->bUseResultList = 1;
      }else{
        popResultList(&p->result);
      }

      rc = p->result.pItem ? XJD1_ROW : XJD1_DONE;
    }else{
      rc = selectStepUnordered(p);
    }

  }else{
    rc = XJD1_ROW;
    if( !p->u.compound.doneLeft ){
      rc = xjd1QueryStep(p->u.compound.pLeft);
      if( rc==XJD1_DONE ) p->u.compound.doneLeft = 1;
    }
    if( p->u.compound.doneLeft ){
................................................................................
**
** The caller must invoke JsonFree() when it is done with this value.
*/
JsonNode *xjd1QueryDoc(Query *p, const char *zDocName){
  JsonNode *pOut = 0;
  if( p ){
    if( p->eQType==TK_SELECT ){
      if( p->bUseResultList ){
        assert( zDocName==0 && p->result.pItem );
        pOut = xjd1JsonRef(p->result.pItem->apKey[p->result.nKey-1]);
      }else if( zDocName==0 && p->u.simple.pRes ){
        pOut = xjd1ExprEval(p->u.simple.pRes);
      }else{
        pOut = xjd1DataSrcDoc(p->u.simple.pFrom, zDocName);
      }
    }else if( !p->u.compound.doneLeft ){
      pOut = xjd1QueryDoc(p->u.compound.pLeft, zDocName);
    }else{
................................................................................
/*
** The destructor for a Query object.
*/
int xjd1QueryClose(Query *pQuery){
  int rc = XJD1_OK;
  if( pQuery==0 ) return rc;
  if( pQuery->eQType==TK_SELECT ){
    clearResultList(&pQuery->result);
    xjd1ExprClose(pQuery->u.simple.pRes);
    xjd1DataSrcClose(pQuery->u.simple.pFrom);
    xjd1ExprClose(pQuery->u.simple.pWhere);
    xjd1ExprListClose(pQuery->u.simple.pGroupBy);
    xjd1ExprClose(pQuery->u.simple.pHaving);
    xjd1ExprListClose(pQuery->u.simple.pOrderBy);
    xjd1ExprClose(pQuery->u.simple.pLimit);
................................................................................
    xjd1ExprClose(pQuery->u.simple.pOffset);
  }else{
    xjd1QueryClose(pQuery->u.compound.pLeft);
    xjd1QueryClose(pQuery->u.compound.pRight);
  }
  return rc;
}

Changes to src/xjd1Int.h.

54
55
56
57
58
59
60


61
62
63
64
65
66
67
...
221
222
223
224
225
226
227







228
229
230
231
232
233
234
...
244
245
246
247
248
249
250



251
252
253
254
255
256
257
typedef struct JsonStructElem JsonStructElem;
typedef struct Parse Parse;
typedef struct PoolChunk PoolChunk;
typedef struct Pool Pool;
typedef struct Query Query;
typedef struct String String;
typedef struct Token Token;



/* A single allocation from the Pool allocator */
struct PoolChunk {
  PoolChunk *pNext;                 /* Next chunk on list of them all */
};

/* A memory allocation pool */
................................................................................
  xjd1 *pConn;                    /* Connect for recording errors */
  Pool *pPool;                    /* Memory allocation pool */
  Command *pCmd;                  /* Results */
  Token sTok;                     /* Last token seen */
  int errCode;                    /* Error code */
  String errMsg;                  /* Error message string */
};








/* A query statement */
struct Query {
  int eQType;                   /* Query type */
  xjd1_stmt *pStmt;             /* Statement this query is part of */
  Query *pOuter;                /* Next outer query for a subquery */
  union {
................................................................................
      ExprList *pGroupBy;         /* The GROUP BY clause */
      Expr *pHaving;              /* The HAVING clause */
      ExprList *pOrderBy;         /* The ORDER BY clause */
      Expr *pLimit;               /* The LIMIT clause */
      Expr *pOffset;              /* The OFFSET clause */
    } simple;
  } u;



};

/* A Data Source is a representation of a term out of the FROM clause. */
struct DataSrc {
  int eDSType;              /* Source type */
  char *zAs;                /* The identifier after the AS keyword */
  Query *pQuery;            /* Query this data source services */







>
>







 







>
>
>
>
>
>
>







 







>
>
>







54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
...
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
...
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
typedef struct JsonStructElem JsonStructElem;
typedef struct Parse Parse;
typedef struct PoolChunk PoolChunk;
typedef struct Pool Pool;
typedef struct Query Query;
typedef struct String String;
typedef struct Token Token;
typedef struct ResultList ResultList;
typedef struct ResultItem ResultItem;

/* A single allocation from the Pool allocator */
struct PoolChunk {
  PoolChunk *pNext;                 /* Next chunk on list of them all */
};

/* A memory allocation pool */
................................................................................
  xjd1 *pConn;                    /* Connect for recording errors */
  Pool *pPool;                    /* Memory allocation pool */
  Command *pCmd;                  /* Results */
  Token sTok;                     /* Last token seen */
  int errCode;                    /* Error code */
  String errMsg;                  /* Error message string */
};

/* A list of sorted results. */
struct ResultList {
  Pool *pPool;
  int nKey;
  ResultItem *pItem;
};

/* A query statement */
struct Query {
  int eQType;                   /* Query type */
  xjd1_stmt *pStmt;             /* Statement this query is part of */
  Query *pOuter;                /* Next outer query for a subquery */
  union {
................................................................................
      ExprList *pGroupBy;         /* The GROUP BY clause */
      Expr *pHaving;              /* The HAVING clause */
      ExprList *pOrderBy;         /* The ORDER BY clause */
      Expr *pLimit;               /* The LIMIT clause */
      Expr *pOffset;              /* The OFFSET clause */
    } simple;
  } u;

  int bUseResultList;             /* True to read results from Query.result */
  ResultList result;              /* List of query results in sorted order */
};

/* A Data Source is a representation of a term out of the FROM clause. */
struct DataSrc {
  int eDSType;              /* Source type */
  char *zAs;                /* The identifier after the AS keyword */
  Query *pQuery;            /* Query this data source services */

Changes to test/all.test.

1
2
3
4
5


-- Run all test scripts
--
.read base01.test
.read base02.test
.read base03.test







>
>
1
2
3
4
5
6
7
-- Run all test scripts
--
.read base01.test
.read base02.test
.read base03.test
.read base04.test
.read base05.test

Changes to test/base02.test.

100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
...
180
181
182
183
184
185
186

187
188
189
190
191
192
193
194
195
196

.testcase 15
SELECT 1 - 7 FROM c1;
SELECT 45 - 4 FROM c1;
SELECT 67 - 2 FROM c1;
.result -6 41 65


.testcase 16
SELECT 1.12;
SELECT "hello world";
SELECT a;
SELECT { x: "value" };
SELECT [1,4,9,16,25];
SELECT null;
................................................................................

.testcase 25
SELECT x FROM (SELECT {one:c3.two, two:c3.one} FROM c3) AS x;
.result {"one":2,"two":1} 

.testcase 26
SELECT x.one FROM (SELECT {one:c3.two, two:c3.one} FROM c3) AS x;

.result 2

.testcase 27
SELECT c3 FROM (SELECT {one:c3.two, two:c3.one} FROM c3) AS x;
.result null

.testcase 28
SELECT xyz FROM (SELECT {one:c3.two, two:c3.one} FROM c3) AS x;
.result null








<







 







>
|









100
101
102
103
104
105
106

107
108
109
110
111
112
113
...
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196

.testcase 15
SELECT 1 - 7 FROM c1;
SELECT 45 - 4 FROM c1;
SELECT 67 - 2 FROM c1;
.result -6 41 65


.testcase 16
SELECT 1.12;
SELECT "hello world";
SELECT a;
SELECT { x: "value" };
SELECT [1,4,9,16,25];
SELECT null;
................................................................................

.testcase 25
SELECT x FROM (SELECT {one:c3.two, two:c3.one} FROM c3) AS x;
.result {"one":2,"two":1} 

.testcase 26
SELECT x.one FROM (SELECT {one:c3.two, two:c3.one} FROM c3) AS x;
SELECT x["one"] FROM (SELECT {one:c3.two, two:c3.one} FROM c3) AS x;
.result 2 2

.testcase 27
SELECT c3 FROM (SELECT {one:c3.two, two:c3.one} FROM c3) AS x;
.result null

.testcase 28
SELECT xyz FROM (SELECT {one:c3.two, two:c3.one} FROM c3) AS x;
.result null

Added test/base05.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
-- Further warm body tests. The tests in this file focus on ORDER BY, GROUP BY
-- and DISTINCT.

.new t1.db
CREATE COLLECTION c1;
INSERT INTO c1 VALUE {i:13, z:"Grape"};
INSERT INTO c1 VALUE {i:4, z:"Cherry"};
INSERT INTO c1 VALUE {i:12, z:"Gooseberry"};
INSERT INTO c1 VALUE {i:15, z:"Guava"};
INSERT INTO c1 VALUE {i:17, z:"Huckleberry"};
INSERT INTO c1 VALUE {i:3, z:"Cantaloupe"};
INSERT INTO c1 VALUE {i:1, z:"Blackberry"};
INSERT INTO c1 VALUE {i:5, z:"Clementine"};
INSERT INTO c1 VALUE {i:10, z:"Eggplant"};
INSERT INTO c1 VALUE {i:16, z:"Honeydew"};
INSERT INTO c1 VALUE {i:6, z:"Currant"};
INSERT INTO c1 VALUE {i:7, z:"Damson"};
INSERT INTO c1 VALUE {i:9, z:"Duran"};
INSERT INTO c1 VALUE {i:14, z:"Grapefruit"};
INSERT INTO c1 VALUE {i:2, z:"Blueberry"};
INSERT INTO c1 VALUE {i:8, z:"Date"};
INSERT INTO c1 VALUE {i:11, z:"Fig"};

.testcase 1
SELECT c1.i FROM c1 ORDER BY c1.i;
.result 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

.testcase 2
SELECT c1.i FROM c1 ORDER BY c1.z;
.result 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

.testcase 3
SELECT c1.i FROM c1 ORDER BY c1;
.result 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

.testcase 3
SELECT c1.i FROM c1 ORDER BY c1 DESC;
.result 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1