SQLite

Check-in [bcbe5308]
Login

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

Overview
Comment:Fix the OP_OpenDup opcode so that it is able to duplicate a cursor that was itself opened by OP_OpenDup. Add additional verification of ephemeral tables. Fix for ticket [bb8a9fd4a9b7fce5].
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: bcbe5308f3a3b94f965b0f5627cb29cce2e09343b86d757e2de889f7773576e7
User & Date: drh 2021-03-18 16:47:24
Context
2021-03-18
16:52
Increase the patch level to 3.35.3. (check-in: 259b7c3e user: drh tags: trunk)
16:47
Fix the OP_OpenDup opcode so that it is able to duplicate a cursor that was itself opened by OP_OpenDup. Add additional verification of ephemeral tables. Fix for ticket [bb8a9fd4a9b7fce5]. (check-in: bcbe5308 user: drh tags: trunk)
16:36
Remove unnecessary code from sqlite3BtreeClose() and add test cases. (Closed-Leaf check-in: edbfdcae user: drh tags: opendup-fix)
14:36
Fix the dbsqlfuzz-derived authorizer in fuzzcheck to avoid creating junk files when running rogue tests. (check-in: eca21a62 user: drh tags: trunk)
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/btree.c.

2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744




2745
2746
2747
2748
2749
2750
2751
2752

2753
2754
2755
2756
2757
2758
2759
}

/*
** Close an open database and invalidate all cursors.
*/
int sqlite3BtreeClose(Btree *p){
  BtShared *pBt = p->pBt;
  BtCursor *pCur;

  /* Close all cursors opened via this handle.  */
  assert( sqlite3_mutex_held(p->db->mutex) );
  sqlite3BtreeEnter(p);




  pCur = pBt->pCursor;
  while( pCur ){
    BtCursor *pTmp = pCur;
    pCur = pCur->pNext;
    if( pTmp->pBtree==p ){
      sqlite3BtreeCloseCursor(pTmp);
    }
  }


  /* Rollback any active transaction and free the handle structure.
  ** The call to sqlite3BtreeRollback() drops any table-locks held by
  ** this handle.
  */
  sqlite3BtreeRollback(p, SQLITE_OK, 0);
  sqlite3BtreeLeave(p);







<




>
>
>
>
|
|
|
|
|
|


>







2733
2734
2735
2736
2737
2738
2739

2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
}

/*
** Close an open database and invalidate all cursors.
*/
int sqlite3BtreeClose(Btree *p){
  BtShared *pBt = p->pBt;


  /* Close all cursors opened via this handle.  */
  assert( sqlite3_mutex_held(p->db->mutex) );
  sqlite3BtreeEnter(p);

  /* Verify that no other cursors have this Btree open */
#ifdef SQLITE_DEBUG
  {
    BtCursor *pCur = pBt->pCursor;
    while( pCur ){
      BtCursor *pTmp = pCur;
      pCur = pCur->pNext;
      assert( pTmp->pBtree!=p );

    }
  }
#endif

  /* Rollback any active transaction and free the handle structure.
  ** The call to sqlite3BtreeRollback() drops any table-locks held by
  ** this handle.
  */
  sqlite3BtreeRollback(p, SQLITE_OK, 0);
  sqlite3BtreeLeave(p);
4537
4538
4539
4540
4541
4542
4543






4544

4545
4546
4547
4548
4549
4550
4551
        pPrev = pPrev->pNext;
      }while( ALWAYS(pPrev) );
    }
    btreeReleaseAllCursorPages(pCur);
    unlockBtreeIfUnused(pBt);
    sqlite3_free(pCur->aOverflow);
    sqlite3_free(pCur->pKey);






    sqlite3BtreeLeave(pBtree);

    pCur->pBtree = 0;
  }
  return SQLITE_OK;
}

/*
** Make sure the BtCursor* given in the argument has a valid







>
>
>
>
>
>
|
>







4541
4542
4543
4544
4545
4546
4547
4548
4549
4550
4551
4552
4553
4554
4555
4556
4557
4558
4559
4560
4561
4562
        pPrev = pPrev->pNext;
      }while( ALWAYS(pPrev) );
    }
    btreeReleaseAllCursorPages(pCur);
    unlockBtreeIfUnused(pBt);
    sqlite3_free(pCur->aOverflow);
    sqlite3_free(pCur->pKey);
    if( (pBt->openFlags & BTREE_SINGLE) && pBt->pCursor==0 ){
      /* Since the BtShared is not sharable, there is no need to
      ** worry about the missing sqlite3BtreeLeave() call here.  */
      assert( pBtree->sharable==0 );
      sqlite3BtreeClose(pBtree);
    }else{
      sqlite3BtreeLeave(pBtree);
    }
    pCur->pBtree = 0;
  }
  return SQLITE_OK;
}

/*
** Make sure the BtCursor* given in the argument has a valid

Changes to src/vdbe.c.

268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
  VdbeCursor *pCx = 0;
  nByte = 
      ROUND8(sizeof(VdbeCursor)) + 2*sizeof(u32)*nField + 
      (eCurType==CURTYPE_BTREE?sqlite3BtreeCursorSize():0);

  assert( iCur>=0 && iCur<p->nCursor );
  if( p->apCsr[iCur] ){ /*OPTIMIZATION-IF-FALSE*/
    /* Before calling sqlite3VdbeFreeCursor(), ensure the isEphemeral flag
    ** is clear. Otherwise, if this is an ephemeral cursor created by 
    ** OP_OpenDup, the cursor will not be closed and will still be part
    ** of a BtShared.pCursor list.  */
    if( p->apCsr[iCur]->pBtx==0 ) p->apCsr[iCur]->isEphemeral = 0;
    sqlite3VdbeFreeCursor(p, p->apCsr[iCur]);
    p->apCsr[iCur] = 0;
  }
  if( SQLITE_OK==sqlite3VdbeMemClearAndResize(pMem, nByte) ){
    p->apCsr[iCur] = pCx = (VdbeCursor*)pMem->z;
    memset(pCx, 0, offsetof(VdbeCursor,pAltCursor));
    pCx->eCurType = eCurType;







<
<
<
<
<







268
269
270
271
272
273
274





275
276
277
278
279
280
281
  VdbeCursor *pCx = 0;
  nByte = 
      ROUND8(sizeof(VdbeCursor)) + 2*sizeof(u32)*nField + 
      (eCurType==CURTYPE_BTREE?sqlite3BtreeCursorSize():0);

  assert( iCur>=0 && iCur<p->nCursor );
  if( p->apCsr[iCur] ){ /*OPTIMIZATION-IF-FALSE*/





    sqlite3VdbeFreeCursor(p, p->apCsr[iCur]);
    p->apCsr[iCur] = 0;
  }
  if( SQLITE_OK==sqlite3VdbeMemClearAndResize(pMem, nByte) ){
    p->apCsr[iCur] = pCx = (VdbeCursor*)pMem->z;
    memset(pCx, 0, offsetof(VdbeCursor,pAltCursor));
    pCx->eCurType = eCurType;
3869
3870
3871
3872
3873
3874
3875
3876
3877
3878
3879
3880
3881
3882
3883
3884
3885



3886
3887
3888
3889
3890
3891
3892
3893
*/
case OP_OpenDup: {
  VdbeCursor *pOrig;    /* The original cursor to be duplicated */
  VdbeCursor *pCx;      /* The new cursor */

  pOrig = p->apCsr[pOp->p2];
  assert( pOrig );
  assert( pOrig->pBtx!=0 );  /* Only ephemeral cursors can be duplicated */

  pCx = allocateCursor(p, pOp->p1, pOrig->nField, -1, CURTYPE_BTREE);
  if( pCx==0 ) goto no_mem;
  pCx->nullRow = 1;
  pCx->isEphemeral = 1;
  pCx->pKeyInfo = pOrig->pKeyInfo;
  pCx->isTable = pOrig->isTable;
  pCx->pgnoRoot = pOrig->pgnoRoot;
  pCx->isOrdered = pOrig->isOrdered;



  rc = sqlite3BtreeCursor(pOrig->pBtx, pCx->pgnoRoot, BTREE_WRCSR,
                          pCx->pKeyInfo, pCx->uc.pCursor);
  /* The sqlite3BtreeCursor() routine can only fail for the first cursor
  ** opened for a database.  Since there is already an open cursor when this
  ** opcode is run, the sqlite3BtreeCursor() cannot fail */
  assert( rc==SQLITE_OK );
  break;
}







|









>
>
>
|







3864
3865
3866
3867
3868
3869
3870
3871
3872
3873
3874
3875
3876
3877
3878
3879
3880
3881
3882
3883
3884
3885
3886
3887
3888
3889
3890
3891
*/
case OP_OpenDup: {
  VdbeCursor *pOrig;    /* The original cursor to be duplicated */
  VdbeCursor *pCx;      /* The new cursor */

  pOrig = p->apCsr[pOp->p2];
  assert( pOrig );
  assert( pOrig->isEphemeral );  /* Only ephemeral cursors can be duplicated */

  pCx = allocateCursor(p, pOp->p1, pOrig->nField, -1, CURTYPE_BTREE);
  if( pCx==0 ) goto no_mem;
  pCx->nullRow = 1;
  pCx->isEphemeral = 1;
  pCx->pKeyInfo = pOrig->pKeyInfo;
  pCx->isTable = pOrig->isTable;
  pCx->pgnoRoot = pOrig->pgnoRoot;
  pCx->isOrdered = pOrig->isOrdered;
  pCx->pBtx = pOrig->pBtx;
  pCx->hasBeenDuped = 1;
  pOrig->hasBeenDuped = 1;
  rc = sqlite3BtreeCursor(pCx->pBtx, pCx->pgnoRoot, BTREE_WRCSR, 
                          pCx->pKeyInfo, pCx->uc.pCursor);
  /* The sqlite3BtreeCursor() routine can only fail for the first cursor
  ** opened for a database.  Since there is already an open cursor when this
  ** opcode is run, the sqlite3BtreeCursor() cannot fail */
  assert( rc==SQLITE_OK );
  break;
}
3945
3946
3947
3948
3949
3950
3951
3952
3953

3954
3955
3956
3957
3958
3959
3960
3961
3962
3963
3964
3965
3966
3967
3968
3969
3970
3971
3972
3973
3974
3975
3976
3977
3978
3979
3980
3981
3982
3983
3984
3985
3986
3987
3988
3989
3990
3991
3992
3993
3994




3995
3996
3997
3998
3999
4000
4001
    assert( pOp->p2==0 ); /* Only used when number of columns is zero */
    assert( pOp->opcode==OP_OpenEphemeral );
    assert( aMem[pOp->p3].flags & MEM_Null );
    aMem[pOp->p3].n = 0;
    aMem[pOp->p3].z = "";
  }
  pCx = p->apCsr[pOp->p1];
  if( pCx && ALWAYS(pCx->pBtx) ){
    /* If the ephermeral table is already open, erase all existing content

    ** so that the table is empty again, rather than creating a new table. */
    assert( pCx->isEphemeral );
    pCx->seqCount = 0;
    pCx->cacheStatus = CACHE_STALE;
    rc = sqlite3BtreeClearTable(pCx->pBtx, pCx->pgnoRoot, 0);
  }else{
    pCx = allocateCursor(p, pOp->p1, pOp->p2, -1, CURTYPE_BTREE);
    if( pCx==0 ) goto no_mem;
    pCx->isEphemeral = 1;
    rc = sqlite3BtreeOpen(db->pVfs, 0, db, &pCx->pBtx, 
                          BTREE_OMIT_JOURNAL | BTREE_SINGLE | pOp->p5,
                          vfsFlags);
    if( rc==SQLITE_OK ){
      rc = sqlite3BtreeBeginTrans(pCx->pBtx, 1, 0);
    }
    if( rc==SQLITE_OK ){
      /* If a transient index is required, create it by calling
      ** sqlite3BtreeCreateTable() with the BTREE_BLOBKEY flag before
      ** opening it. If a transient table is required, just use the
      ** automatically created table with root-page 1 (an BLOB_INTKEY table).
      */
      if( (pCx->pKeyInfo = pKeyInfo = pOp->p4.pKeyInfo)!=0 ){
        assert( pOp->p4type==P4_KEYINFO );
        rc = sqlite3BtreeCreateTable(pCx->pBtx, &pCx->pgnoRoot,
                                     BTREE_BLOBKEY | pOp->p5); 
        if( rc==SQLITE_OK ){
          assert( pCx->pgnoRoot==SCHEMA_ROOT+1 );
          assert( pKeyInfo->db==db );
          assert( pKeyInfo->enc==ENC(db) );
          rc = sqlite3BtreeCursor(pCx->pBtx, pCx->pgnoRoot, BTREE_WRCSR,
                                  pKeyInfo, pCx->uc.pCursor);
        }
        pCx->isTable = 0;
      }else{
        pCx->pgnoRoot = SCHEMA_ROOT;
        rc = sqlite3BtreeCursor(pCx->pBtx, SCHEMA_ROOT, BTREE_WRCSR,
                                0, pCx->uc.pCursor);
        pCx->isTable = 1;
      }
    }
    pCx->isOrdered = (pOp->p5!=BTREE_UNORDERED);




  }
  if( rc ) goto abort_due_to_error;
  pCx->nullRow = 1;
  break;
}

/* Opcode: SorterOpen P1 P2 P3 P4 *







|
|
>
|













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







3943
3944
3945
3946
3947
3948
3949
3950
3951
3952
3953
3954
3955
3956
3957
3958
3959
3960
3961
3962
3963
3964
3965
3966

3967
3968
3969
3970
3971
3972
3973
3974
3975
3976
3977
3978
3979
3980
3981
3982
3983
3984
3985
3986
3987
3988
3989
3990
3991
3992
3993
3994
3995
3996
3997
3998
3999
4000
4001
4002
4003
    assert( pOp->p2==0 ); /* Only used when number of columns is zero */
    assert( pOp->opcode==OP_OpenEphemeral );
    assert( aMem[pOp->p3].flags & MEM_Null );
    aMem[pOp->p3].n = 0;
    aMem[pOp->p3].z = "";
  }
  pCx = p->apCsr[pOp->p1];
  if( pCx && !pCx->hasBeenDuped ){
    /* If the ephermeral table is already open and has no duplicates from
    ** OP_OpenDup, then erase all existing content so that the table is
    ** empty again, rather than creating a new table. */
    assert( pCx->isEphemeral );
    pCx->seqCount = 0;
    pCx->cacheStatus = CACHE_STALE;
    rc = sqlite3BtreeClearTable(pCx->pBtx, pCx->pgnoRoot, 0);
  }else{
    pCx = allocateCursor(p, pOp->p1, pOp->p2, -1, CURTYPE_BTREE);
    if( pCx==0 ) goto no_mem;
    pCx->isEphemeral = 1;
    rc = sqlite3BtreeOpen(db->pVfs, 0, db, &pCx->pBtx, 
                          BTREE_OMIT_JOURNAL | BTREE_SINGLE | pOp->p5,
                          vfsFlags);
    if( rc==SQLITE_OK ){
      rc = sqlite3BtreeBeginTrans(pCx->pBtx, 1, 0);

      if( rc==SQLITE_OK ){
        /* If a transient index is required, create it by calling
        ** sqlite3BtreeCreateTable() with the BTREE_BLOBKEY flag before
        ** opening it. If a transient table is required, just use the
        ** automatically created table with root-page 1 (an BLOB_INTKEY table).
        */
        if( (pCx->pKeyInfo = pKeyInfo = pOp->p4.pKeyInfo)!=0 ){
          assert( pOp->p4type==P4_KEYINFO );
          rc = sqlite3BtreeCreateTable(pCx->pBtx, &pCx->pgnoRoot,
              BTREE_BLOBKEY | pOp->p5); 
          if( rc==SQLITE_OK ){
            assert( pCx->pgnoRoot==SCHEMA_ROOT+1 );
            assert( pKeyInfo->db==db );
            assert( pKeyInfo->enc==ENC(db) );
            rc = sqlite3BtreeCursor(pCx->pBtx, pCx->pgnoRoot, BTREE_WRCSR,
                pKeyInfo, pCx->uc.pCursor);
          }
          pCx->isTable = 0;
        }else{
          pCx->pgnoRoot = SCHEMA_ROOT;
          rc = sqlite3BtreeCursor(pCx->pBtx, SCHEMA_ROOT, BTREE_WRCSR,
              0, pCx->uc.pCursor);
          pCx->isTable = 1;
        }
      }
      pCx->isOrdered = (pOp->p5!=BTREE_UNORDERED);
      if( rc ){
        sqlite3BtreeClose(pCx->pBtx);
      }
    }
  }
  if( rc ) goto abort_due_to_error;
  pCx->nullRow = 1;
  break;
}

/* Opcode: SorterOpen P1 P2 P3 P4 *

Changes to src/vdbeInt.h.

82
83
84
85
86
87
88

89
90
91
92
93
94
95
#ifdef SQLITE_DEBUG
  u8 seekOp;              /* Most recent seek operation on this cursor */
  u8 wrFlag;              /* The wrFlag argument to sqlite3BtreeCursor() */
#endif
  Bool isEphemeral:1;     /* True for an ephemeral table */
  Bool useRandomRowid:1;  /* Generate new record numbers semi-randomly */
  Bool isOrdered:1;       /* True if the table is not BTREE_UNORDERED */

  u16 seekHit;            /* See the OP_SeekHit and OP_IfNoHope opcodes */
  Btree *pBtx;            /* Separate file holding temporary table */
  i64 seqCount;           /* Sequence counter */
  u32 *aAltMap;           /* Mapping from table to index column numbers */

  /* Cached OP_Column parse information is only valid if cacheStatus matches
  ** Vdbe.cacheCtr.  Vdbe.cacheCtr will never take on the value of







>







82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#ifdef SQLITE_DEBUG
  u8 seekOp;              /* Most recent seek operation on this cursor */
  u8 wrFlag;              /* The wrFlag argument to sqlite3BtreeCursor() */
#endif
  Bool isEphemeral:1;     /* True for an ephemeral table */
  Bool useRandomRowid:1;  /* Generate new record numbers semi-randomly */
  Bool isOrdered:1;       /* True if the table is not BTREE_UNORDERED */
  Bool hasBeenDuped:1;    /* This cursor was source or target of OP_OpenDup */
  u16 seekHit;            /* See the OP_SeekHit and OP_IfNoHope opcodes */
  Btree *pBtx;            /* Separate file holding temporary table */
  i64 seqCount;           /* Sequence counter */
  u32 *aAltMap;           /* Mapping from table to index column numbers */

  /* Cached OP_Column parse information is only valid if cacheStatus matches
  ** Vdbe.cacheCtr.  Vdbe.cacheCtr will never take on the value of

Changes to src/vdbeaux.c.

2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
  assert( pCx->pBtx==0 || pCx->isEphemeral );
  switch( pCx->eCurType ){
    case CURTYPE_SORTER: {
      sqlite3VdbeSorterClose(p->db, pCx);
      break;
    }
    case CURTYPE_BTREE: {
      if( pCx->isEphemeral ){
        if( pCx->pBtx ) sqlite3BtreeClose(pCx->pBtx);
        /* The pCx->pCursor will be close automatically, if it exists, by
        ** the call above. */
      }else{
        assert( pCx->uc.pCursor!=0 );
        sqlite3BtreeCloseCursor(pCx->uc.pCursor);
      }
      break;
    }
#ifndef SQLITE_OMIT_VIRTUALTABLE
    case CURTYPE_VTAB: {
      sqlite3_vtab_cursor *pVCur = pCx->uc.pVCur;
      const sqlite3_module *pModule = pVCur->pVtab->pModule;
      assert( pVCur->pVtab->nRef>0 );







<
<
<
<
<
|
|
<







2475
2476
2477
2478
2479
2480
2481





2482
2483

2484
2485
2486
2487
2488
2489
2490
  assert( pCx->pBtx==0 || pCx->isEphemeral );
  switch( pCx->eCurType ){
    case CURTYPE_SORTER: {
      sqlite3VdbeSorterClose(p->db, pCx);
      break;
    }
    case CURTYPE_BTREE: {





      assert( pCx->uc.pCursor!=0 );
      sqlite3BtreeCloseCursor(pCx->uc.pCursor);

      break;
    }
#ifndef SQLITE_OMIT_VIRTUALTABLE
    case CURTYPE_VTAB: {
      sqlite3_vtab_cursor *pVCur = pCx->uc.pVCur;
      const sqlite3_module *pModule = pVCur->pVtab->pModule;
      assert( pVCur->pVtab->nRef>0 );

Changes to test/with2.test.

410
411
412
413
414
415
416




















































































































417
418
    SELECT 1
    UNION ALL
    SELECT a+1 FROM q, v WHERE a<5
  )
  SELECT * FROM q;
} {1 2 3 4 5}






















































































































finish_test







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


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
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
    SELECT 1
    UNION ALL
    SELECT a+1 FROM q, v WHERE a<5
  )
  SELECT * FROM q;
} {1 2 3 4 5}

# 2021-03-18
# Ticket bb8a9fd4a9b7fce5
reset_db
do_execsql_test 9.1 {
  WITH xyz(a) AS (
    WITH abc AS ( SELECT 1234 ) SELECT * FROM abc
  )
  SELECT * FROM xyz AS one, xyz AS two, (
    SELECT * FROM xyz UNION ALL SELECT * FROM xyz
  );
} {1234 1234 1234 1234 1234 1234}
load_static_extension db series
do_execsql_test 9.2 {
  WITH
    cst(rsx, rsy) AS  (
      SELECT 100, 100
    ),
    cst2(minx, maxx, stepx, miny, maxy, stepy, qualitativex, qualitativey) AS (
      SELECT NULL, NULL, NULL, NULL, NULL, NULL, 0, 0
    ),
    ds0(m, n, x, y, x2, y2, title, size, mark, label, markmode) AS (
      SELECT 1, 2, 3, 4, 5, 6, 7 , 8, 9, 10, 11
    ),
    ds(m, n, x, y, x2, y2, title, size, mark, label, markmode) AS (
      SELECT m, n, x,
        y, x2,
        y2,
        title, size, mark, label, markmode
      FROM ds0
      WINDOW w AS (PARTITION BY m, x ORDER BY n)
    ),
    d(m, n, x, y, x2, y2, labelx,labely,title,size,mark,label,markmode) AS (
      SELECT m, n, x, y,  x2, y2, x, y, title, size, mark, label, markmode
      FROM ds, cst2
    ),
    ylabels(y, label) AS (
      SELECT y, MIN(labely) FROM d GROUP BY y
    ),
    yaxis(maxy, miny, stepy , minstepy) AS (
      WITH
        xt0(minx, maxx) AS (
          SELECT  coalesce(miny, min(min(y2),
                  min(y))), coalesce(maxy, max(max(y2),
                  max(y))) + qualitativey
           FROM d, cst2
        ),
        xt1(mx, mn) AS (SELECT maxx, minx FROM xt0),
        xt2(mx, mn, step) AS (SELECT mx, mn, (mx-mn)  FROM xt1),
        
        xt3(mx, mn, ms) AS (
          SELECT mx, mn, first_value(rs) OVER (order by x desc) AS ms
            FROM (SELECT mx, mn, step, f,(mx-mn) as rng,
                         1.0*step/f as rs, 1.0*(mx-mn)/(step/f) AS x
                    FROM xt2, (SELECT 1 AS f UNION ALL SELECT 2
                                UNION ALL SELECT 4
                                UNION ALL SELECT 5)) AS src
                   WHERE x < 10 limit 1),
        xt4(minstepy) AS (
          SELECT MIN(abs(y2-y)) FROM d WHERE y2 != y
        )
      SELECT (mx/ms)*ms, (mn/ms)*ms, coalesce(stepy, ms),
                     coalesce(minstepy, ms, stepy)  FROM xt3, cst2,xt4
    ),
    distinct_mark_n_m(mark, ze, zem, title) AS (
      SELECT DISTINCT mark, n AS ze, m AS zem, title FROM ds0
    ),
    facet0(m, mi, title, radial) AS (
      SELECT md, row_number() OVER () - 1, title, 'radial'
                      IN (SELECT mark FROM distinct_mark_n_m WHERE zem = md)
      FROM (SELECT DISTINCT zem AS md, title AS title
                       FROM distinct_mark_n_m ORDER BY 2, 1)
    ),
    facet(m, mi, xorigin, yorigin, title, radial) AS (
      SELECT m, mi,
        rsx * 1.2 * IFNULL(CASE WHEN (
          0
        ) > 0 THEN mi / (
          0
        ) ELSE mi % (
          2
        )  END, mi),
        rsy  * 1.2 * IFNULL(CASE WHEN (
          2
        ) > 0 THEN mi / (
          2
        ) ELSE mi / (
          0
        )  END, 0),
        title, radial FROM facet0, cst
    ),
    radygrid(m, mi, tty, wty, ttx, ttx2, xorigin, yorigin) AS (
      SELECT m, mi,  rsy / 2 / ((maxy-miny)/stepy) * (value-1) AS tty,
             coalesce(NULL, miny + stepy * (value-1)) AS wty,
             xorigin, xorigin+rsx, xorigin + rsx / 2,
             yorigin + rsy / 2
        FROM generate_series(1), yaxis, cst,
             facet LEFT JOIN ylabels ON ylabels.y = (miny + (value-1) * stepy)
       WHERE radial AND stop = 1+1.0*(maxy-miny)/stepy
    ),
    ypos(m, mi, pcx, pcy, radial) AS (
      SELECT m, mi, xorigin, yorigin + CASE
        WHEN 0 BETWEEN miny AND maxy THEN
          rsy - (0 - miny) * rsy / (maxy-miny)
        WHEN 0 >= maxy THEN 0
        ELSE  rsy
      END, radial FROM yaxis, cst, facet WHERE NOT radial
      UNION ALL
      SELECT m, mi, xorigin + rsx / 2, yorigin + (CASE
        WHEN 0 BETWEEN miny AND maxy THEN
          rsy - (0 - miny) * rsy / 2 / (maxy-miny)
        WHEN 0 >= maxy THEN 0
        ELSE  rsy
      END ) / 2, radial FROM yaxis, cst, facet WHERE radial
    )
  SELECT * FROM radygrid , ypos;
} {}

finish_test