/ Check-in [3a63ea65]
Login
SQLite training in Houston TX on 2019-11-05 (details)
Part of the 2019 Tcl Conference

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

Overview
Comment:Have zonefile store encryption keys in a hash-table instead of a linked list. Add extra tests for key management.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | zonefile
Files: files | file ages | folders
SHA3-256: 3a63ea652546a4c63eccd72665becff38a97a0e39d2f11703cb6899451570fd4
User & Date: dan 2018-02-21 16:36:08
Context
2018-02-21
21:15
Modifications to the zonefile module to make it easier to add a cache of uncompressed frame content. check-in: d9d5cc62 user: dan tags: zonefile
16:36
Have zonefile store encryption keys in a hash-table instead of a linked list. Add extra tests for key management. check-in: 3a63ea65 user: dan tags: zonefile
10:43
In zonefile, change the "file TEXT" column back to "fileid INTEGER". The fileid can be used as a key with the associated zonefile_files table, which contains more information than just the filename. check-in: 38d23888 user: dan tags: zonefile
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Changes to ext/zonefile/zonefile.c.

    11     11   ******************************************************************************
    12     12   */
    13     13   
    14     14   #include "sqlite3ext.h"
    15     15   SQLITE_EXTENSION_INIT1
    16     16   #ifndef SQLITE_OMIT_VIRTUALTABLE
    17     17   
           18  +#include <stdio.h>
           19  +#include <string.h>
           20  +#include <assert.h>
           21  +
    18     22   #ifndef SQLITE_AMALGAMATION
    19     23   typedef sqlite3_int64 i64;
    20     24   typedef sqlite3_uint64 u64;
    21     25   typedef unsigned char u8;
    22     26   typedef unsigned short u16;
    23     27   typedef unsigned long u32;
    24     28   #define MIN(a,b) ((a)<(b) ? (a) : (b))
................................................................................
   126    130   ** It is harmless to pass in a NULL pointer.
   127    131   */
   128    132   static void zonefileCodecDestroy(ZonefileCodec *pCodec){
   129    133     sqlite3_free(pCodec);
   130    134   }
   131    135   #endif                           /* SQLITE_HAVE_ZONEFILE_CODEC */
   132    136   
          137  +/*
          138  +** All zonefile and zonefile_files virtual table instances that belong
          139  +** to the same database handle (sqlite3*) share a single instance of the
          140  +** ZonefileGlobal object. This global object contains a table of 
          141  +** configured encryption keys for the various zonefiles in the system.
          142  +*/
   133    143   typedef struct ZonefileGlobal ZonefileGlobal;
   134    144   typedef struct ZonefileKey ZonefileKey;
   135    145   struct ZonefileGlobal {
   136         -  ZonefileKey *pList;
          146  +  int nEntry;                     /* Number of entries in the hash table */
          147  +  int nHash;                      /* Size of aHash[] array */
          148  +  ZonefileKey **aHash;            /* Hash buckets */
   137    149   };
   138    150   struct ZonefileKey {
   139         -  const char *zName;              /* Table name */
   140         -  const char *zDb;                /* Database name */
          151  +  const char *zName;              /* Zonefile table name */
          152  +  const char *zDb;                /* Database name ("main", "temp" etc.) */
   141    153     i64 iFileid;                    /* File id */
   142    154     const char *zKey;               /* Key buffer */
   143    155     int nKey;                       /* Size of zKey in bytes */
   144         -  ZonefileKey *pNext;             /* Next key on same db connection */
          156  +  u32 iHash;                      /* zonefileKeyHash() value */
          157  +  ZonefileKey *pHashNext;         /* Next colliding key in hash table */
   145    158   };
          159  +
          160  +static u32 zonefileKeyHash(
          161  +  const char *zDb,
          162  +  const char *zTab,
          163  +  i64 iFileid
          164  +){
          165  +  u32 iHash = 0;
          166  +  int i;
          167  +  for(i=0; zDb[i]; i++) iHash += (iHash<<3) + (u8)zDb[i];
          168  +  for(i=0; zTab[i]; i++) iHash += (iHash<<3) + (u8)zTab[i];
          169  +  return (iHash ^ (iFileid & 0xFFFFFFFF));
          170  +}
          171  +
          172  +/* 
          173  +** Store encryption key zKey in the key-store passed as the first argument.
          174  +** Return SQLITE_OK if successful, or an SQLite error code (SQLITE_NOMEM)
          175  +** otherwise.
          176  +*/
          177  +static int zonefileKeyStore(
          178  +  ZonefileGlobal *pGlobal,
          179  +  const char *zDb,                /* Database containing zonefile table */
          180  +  const char *zTab,               /* Name of zonefile table */
          181  +  i64 iFileid,                    /* File-id to configure key for */
          182  +  const char *zKey                /* Key to store */
          183  +){
          184  +  ZonefileKey **pp;
          185  +  u32 iHash = zonefileKeyHash(zDb, zTab, iFileid);
          186  +
          187  +  /* Remove any old entry */
          188  +  if( pGlobal->nHash ){
          189  +    for(pp=&pGlobal->aHash[iHash%pGlobal->nHash]; *pp; pp=&((*pp)->pHashNext)){
          190  +      ZonefileKey *pThis = *pp;
          191  +      if( pThis->iFileid==iFileid 
          192  +          && 0==sqlite3_stricmp(zTab, pThis->zName)
          193  +          && 0==sqlite3_stricmp(zDb, pThis->zDb)
          194  +        ){
          195  +        pGlobal->nEntry--;
          196  +        *pp = pThis->pHashNext;
          197  +        sqlite3_free(pThis);
          198  +        break;
          199  +      }
          200  +    }
          201  +  }
          202  +
          203  +  if( zKey ){
          204  +    int nKey = strlen(zKey);
          205  +    int nDb = strlen(zDb);
          206  +    int nTab = strlen(zTab);
          207  +    ZonefileKey *pNew;
          208  +
          209  +    /* Resize the hash-table, if necessary */
          210  +    if( pGlobal->nEntry>=pGlobal->nHash ){
          211  +      int i;
          212  +      int n = pGlobal->nHash ? pGlobal->nHash*2 : 16;
          213  +      ZonefileKey **a = (ZonefileKey**)sqlite3_malloc(n*sizeof(ZonefileKey*));
          214  +      if( a==0 ) return SQLITE_NOMEM;
          215  +      memset(a, 0, n*sizeof(ZonefileKey*));
          216  +      for(i=0; i<pGlobal->nHash; i++){
          217  +        ZonefileKey *p;
          218  +        ZonefileKey *pNext;
          219  +        for(p=pGlobal->aHash[i]; p; p=pNext){
          220  +          pNext = p->pHashNext;
          221  +          p->pHashNext = a[p->iHash % n];
          222  +          a[p->iHash % n] = p;
          223  +        }
          224  +      }
          225  +      sqlite3_free(pGlobal->aHash);
          226  +      pGlobal->aHash = a;
          227  +      pGlobal->nHash = n;
          228  +    }
          229  +
          230  +    pNew = (ZonefileKey*)sqlite3_malloc(
          231  +        sizeof(ZonefileKey) + nKey+1 + nDb+1 + nTab+1
          232  +    );
          233  +    if( pNew==0 ) return SQLITE_NOMEM;
          234  +    memset(pNew, 0, sizeof(ZonefileKey));
          235  +    pNew->iFileid = iFileid;
          236  +    pNew->iHash = iHash;
          237  +    pNew->zKey = (const char*)&pNew[1];
          238  +    pNew->nKey = nKey;
          239  +    pNew->zDb = &pNew->zKey[nKey+1];
          240  +    pNew->zName = &pNew->zDb[nDb+1];
          241  +    memcpy((char*)pNew->zKey, zKey, nKey+1);
          242  +    memcpy((char*)pNew->zDb, zDb, nDb+1);
          243  +    memcpy((char*)pNew->zName, zTab, nTab+1);
          244  +
          245  +    pNew->pHashNext = pGlobal->aHash[iHash % pGlobal->nHash];
          246  +    pGlobal->aHash[iHash % pGlobal->nHash] = pNew;
          247  +  }
          248  +
          249  +  return SQLITE_OK;
          250  +}
          251  +
          252  +/*
          253  +** Search the key-store passed as the first argument for an encryption
          254  +** key to use with the file with file-id iFileid in zonefile table zTab
          255  +** in database zDb. If successful, set (*pzKey) to point to the key
          256  +** buffer and return the size of the key in bytes.
          257  +**
          258  +** If no key is found, return 0. The final value of (*pzKey) is undefined
          259  +** in this case.
          260  +*/
          261  +static int zonefileKeyFind(
          262  +  ZonefileGlobal *pGlobal,
          263  +  const char *zDb,                /* Database containing zonefile table */
          264  +  const char *zTab,               /* Name of zonefile table */
          265  +  i64 iFileid,                    /* File-id to configure key for */
          266  +  const char **pzKey              /* OUT: Pointer to key buffer */
          267  +){
          268  +  if( pGlobal->nHash ){
          269  +    ZonefileKey *pKey;
          270  +    u32 iHash = zonefileKeyHash(zDb, zTab, iFileid);
          271  +    for(pKey=pGlobal->aHash[iHash%pGlobal->nHash]; pKey; pKey=pKey->pHashNext){
          272  +      if( pKey->iFileid==iFileid 
          273  +       && 0==sqlite3_stricmp(zDb, pKey->zDb)
          274  +       && 0==sqlite3_stricmp(zTab, pKey->zName)
          275  +      ){
          276  +        *pzKey = pKey->zKey;
          277  +        return pKey->nKey;
          278  +      }
          279  +    }
          280  +  }
          281  +
          282  +  return 0;
          283  +}
          284  +
          285  +/*
          286  +** The pointer passed as the only argument must actually point to a
          287  +** ZonefileGlobal structure. This function frees the structure and all
          288  +** of its components.
          289  +*/
          290  +static void zonefileKeyDestroy(void *p){
          291  +  ZonefileGlobal *pGlobal = (ZonefileGlobal*)p;
          292  +  int i;
          293  +  for(i=0; i<pGlobal->nHash; i++){
          294  +    ZonefileKey *pKey;
          295  +    ZonefileKey *pNext;
          296  +    for(pKey=pGlobal->aHash[i]; pKey; pKey=pNext){
          297  +      pNext = pKey->pHashNext;
          298  +      sqlite3_free(pKey);
          299  +    }
          300  +  }
          301  +  sqlite3_free(pGlobal->aHash);
          302  +  sqlite3_free(pGlobal);
          303  +}
   146    304   
   147    305   
   148    306   #define ZONEFILE_DEFAULT_MAXAUTOFRAMESIZE (64*1024)
   149    307   #define ZONEFILE_DEFAULT_ENCRYPTION       1
   150    308   #define ZONEFILE_DEFAULT_COMPRESSION      0
   151    309   #define ZONEFILE_DEFAULT_DICTSIZE         (64*1024)
   152    310   
................................................................................
   174    332   static u32 zonefileGet32(const u8 *aBuf){
   175    333     return (((u32)aBuf[0]) << 24)
   176    334          + (((u32)aBuf[1]) << 16)
   177    335          + (((u32)aBuf[2]) <<  8)
   178    336          + (((u32)aBuf[3]) <<  0);
   179    337   }
   180    338   
   181         -#include <stdio.h>
   182         -#include <string.h>
   183         -#include <assert.h>
   184         -
   185    339   static int zfGenericOpen(void **pp, u8 *aDict, int nDict){
   186    340     *pp = 0;
   187    341     return SQLITE_OK;
   188    342   }
   189    343   static void zfGenericClose(void *p){
   190    344   }
   191    345   static int zfGenericUncompressSize(
................................................................................
  1640   1794       sqlite3_free(aKey);
  1641   1795     }
  1642   1796   
  1643   1797     zonefileFileClose(pFd);
  1644   1798     return rc;
  1645   1799   }
  1646   1800   
  1647         -static int zonefileStoreKey(
  1648         -  ZonefileFilesTab *pTab, 
  1649         -  i64 iFileid, 
  1650         -  const char *zKey
  1651         -){
  1652         -  ZonefileKey **pp;
  1653         -
  1654         -  for(pp=&pTab->pGlobal->pList; *pp; pp=&((*pp)->pNext)){
  1655         -    ZonefileKey *pThis = *pp;
  1656         -    if( pThis->iFileid==iFileid 
  1657         -     && 0==sqlite3_stricmp(pTab->zBase, pThis->zName)
  1658         -     && 0==sqlite3_stricmp(pTab->zDb, pThis->zDb)
  1659         -    ){
  1660         -      *pp = pThis->pNext;
  1661         -      sqlite3_free(pThis);
  1662         -      break;
  1663         -    }
  1664         -  }
  1665         -
  1666         -  if( zKey ){
  1667         -    int nKey = strlen(zKey);
  1668         -    int nDb = strlen(pTab->zDb);
  1669         -    int nName = strlen(pTab->zBase);
  1670         -    ZonefileKey *pNew;
  1671         -    pNew = (ZonefileKey*)sqlite3_malloc(
  1672         -        sizeof(ZonefileKey) + nKey+1 + nDb+1 + nName+1
  1673         -        );
  1674         -    if( pNew==0 ) return SQLITE_NOMEM;
  1675         -    memset(pNew, 0, sizeof(ZonefileKey));
  1676         -    pNew->iFileid = iFileid;
  1677         -    pNew->zKey = (const char*)&pNew[1];
  1678         -    pNew->nKey = nKey;
  1679         -    pNew->zDb = &pNew->zKey[nKey+1];
  1680         -    pNew->zName = &pNew->zDb[nDb+1];
  1681         -    memcpy((char*)pNew->zKey, zKey, nKey+1);
  1682         -    memcpy((char*)pNew->zDb, pTab->zDb, nDb+1);
  1683         -    memcpy((char*)pNew->zName, pTab->zBase, nName+1);
  1684         -
  1685         -    pNew->pNext = pTab->pGlobal->pList;
  1686         -    pTab->pGlobal->pList = pNew;
  1687         -  }
  1688         -  return SQLITE_OK;
  1689         -}
  1690         -
  1691   1801   /*
  1692   1802   ** zonefile_files virtual table module xUpdate method.
  1693   1803   **
  1694   1804   ** A delete specifies a single argument - the rowid of the row to remove.
  1695   1805   ** 
  1696   1806   ** Update and insert operations pass:
  1697   1807   **
................................................................................
  1708   1818     int rc = SQLITE_OK;
  1709   1819     ZonefileFilesTab *pTab = (ZonefileFilesTab*)pVtab;
  1710   1820   
  1711   1821     if( sqlite3_value_type(apVal[0])==SQLITE_INTEGER ){
  1712   1822       if( nVal>1 && sqlite3_value_nochange(apVal[2]) ){
  1713   1823         const char *zKey = (const char*)sqlite3_value_text(apVal[3]);
  1714   1824         i64 iFileid = sqlite3_value_int64(apVal[0]);
  1715         -      return zonefileStoreKey(pTab, iFileid, zKey);
         1825  +      return zonefileKeyStore(
         1826  +          pTab->pGlobal, pTab->zDb, pTab->zBase, iFileid, zKey
         1827  +      );
  1716   1828       }else{
  1717   1829         if( pTab->pDelete==0 ){
  1718   1830           rc = zonefilePrepare(pTab->db, &pTab->pDelete, &pVtab->zErrMsg,
  1719   1831               "DELETE FROM %Q.'%q_shadow_file' WHERE fileid=?",
  1720   1832               pTab->zDb, pTab->zBase
  1721   1833               );
  1722   1834         }
................................................................................
  1762   1874       if( rc==SQLITE_OK ){
  1763   1875         iFileid = sqlite3_last_insert_rowid(pTab->db);
  1764   1876         rc = zonefilePopulateIndex(pTab, zFile, iFileid);
  1765   1877       }
  1766   1878   
  1767   1879       if( rc==SQLITE_OK ){
  1768   1880         const char *zKey = (const char*)sqlite3_value_text(apVal[3]);
  1769         -      rc = zonefileStoreKey(pTab, iFileid, zKey);
         1881  +      rc = zonefileKeyStore(pTab->pGlobal, pTab->zDb, pTab->zBase,iFileid,zKey);
  1770   1882       }
  1771   1883     }
  1772   1884   
  1773   1885     return rc;
  1774   1886   }
  1775   1887   
  1776   1888   typedef struct ZonefileTab ZonefileTab;
................................................................................
  2119   2231       zonefileCtxError(pCtx, "failed to uncompress frame");
  2120   2232     }
  2121   2233   
  2122   2234     sqlite3_free(aUn);
  2123   2235     return rc;
  2124   2236   }
  2125   2237   
  2126         -static int zonefileFindKey(ZonefileTab *pTab, i64 iFileid, const char **pzKey){
  2127         -  ZonefileKey *pKey;
  2128         -
  2129         -  for(pKey=pTab->pGlobal->pList; pKey; pKey=pKey->pNext){
  2130         -    if( pKey->iFileid==iFileid 
  2131         -     && 0==sqlite3_stricmp(pTab->zName, pKey->zName)
  2132         -     && 0==sqlite3_stricmp(pTab->zDb, pKey->zDb)
  2133         -    ){
  2134         -      *pzKey = pKey->zKey;
  2135         -      return pKey->nKey;
  2136         -    }
  2137         -  }
  2138         -
  2139         -  return 0;
  2140         -}
  2141         -
  2142   2238   static int zonefileGetFile(
  2143   2239     sqlite3_context *pCtx,          /* Leave error message here */
  2144   2240     ZonefileCsr *pCsr,              /* Cursor object */
  2145   2241     const char **pzFile             /* OUT: Pointer to current file */
  2146   2242   ){
  2147   2243     ZonefileTab *pTab = (ZonefileTab*)pCsr->base.pVtab;
  2148   2244     int rc = SQLITE_OK;
................................................................................
  2224   2320         rc = pCmpMethod->xOpen(&pCmp, aDict, nDict);
  2225   2321       }
  2226   2322       sqlite3_free(aDict);
  2227   2323     }
  2228   2324   
  2229   2325     /* Find the encryption method and key. */
  2230   2326     if( hdr.encryptionType ){
  2231         -    const char *z= 0;
  2232         -    int nKey = zonefileFindKey(pTab, sqlite3_column_int64(pCsr->pSelect,1), &z);
  2233         -    if( nKey==0 ){
         2327  +    i64 iFileid = sqlite3_column_int64(pCsr->pSelect, 1);
         2328  +    const char *z = 0;
         2329  +    int n = zonefileKeyFind(pTab->pGlobal, pTab->zDb, pTab->zName, iFileid, &z);
         2330  +    if( n==0 ){
  2234   2331         zErr = sqlite3_mprintf("missing encryption key for file \"%s\"", zFile);
  2235   2332         rc = SQLITE_ERROR;
  2236   2333       }else{
  2237         -      rc = zonefileCodecCreate(hdr.encryptionType,(u8*)z, nKey, &pCodec, &zErr);
         2334  +      rc = zonefileCodecCreate(hdr.encryptionType, (u8*)z, n, &pCodec, &zErr);
  2238   2335       }
  2239   2336     }
  2240   2337   
  2241   2338     /* Read some data into memory. If the data is uncompressed, then just
  2242   2339     ** the required record is read. Otherwise, the entire frame is read
  2243   2340     ** into memory.  */
  2244   2341     if( rc==SQLITE_OK ){
................................................................................
  2334   2431   */
  2335   2432   static int zonefileRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
  2336   2433     ZonefileCsr *pCsr = (ZonefileCsr*)cur;
  2337   2434     *pRowid = sqlite3_column_int64(pCsr->pSelect, 0);
  2338   2435     return SQLITE_OK;
  2339   2436   }
  2340   2437   
  2341         -static void zonefileGlobalFree(void *p){
  2342         -  ZonefileGlobal *pGlobal = (ZonefileGlobal*)p;
  2343         -  ZonefileKey *pKey;
  2344         -  ZonefileKey *pNext;
  2345         -  for(pKey=pGlobal->pList; pKey; pKey=pNext){
  2346         -    pNext = pKey->pNext;
  2347         -    sqlite3_free(pKey);
  2348         -  }
  2349         -  sqlite3_free(pGlobal);
  2350         -}
  2351         -
  2352   2438   /*
  2353   2439   ** Register the "zonefile" extensions.
  2354   2440   */
  2355   2441   static int zonefileRegister(sqlite3 *db){
  2356   2442     static sqlite3_module filesModule = {
  2357   2443       0,                            /* iVersion */
  2358   2444       zffCreate,                    /* xCreate - create a table */
................................................................................
  2434   2520     if( rc==SQLITE_OK ){
  2435   2521       rc = sqlite3_create_module(
  2436   2522           db, "zonefile_files", &filesModule, (void*)pGlobal
  2437   2523       );
  2438   2524     }
  2439   2525     if( rc==SQLITE_OK ){
  2440   2526       rc = sqlite3_create_module_v2(db, "zonefile", &zonefileModule, 
  2441         -        (void*)pGlobal, zonefileGlobalFree
         2527  +        (void*)pGlobal, zonefileKeyDestroy
  2442   2528       );
  2443   2529       pGlobal = 0;
  2444   2530     }
  2445   2531   
  2446   2532     sqlite3_free(pGlobal);
  2447   2533     return rc;
  2448   2534   }

Added ext/zonefile/zonefileenc.test.

            1  +# 2018 Feb 11
            2  +#
            3  +# The author disclaims copyright to this source code.  In place of
            4  +# a legal notice, here is a blessing:
            5  +#
            6  +#    May you do good and not evil.
            7  +#    May you find forgiveness for yourself and forgive others.
            8  +#    May you share freely, never taking more than you give.
            9  +#
           10  +#***********************************************************************
           11  +#
           12  +# The focus of this file is testing the zonefile extension.
           13  +#
           14  +
           15  +if {![info exists testdir]} {
           16  +  set testdir [file join [file dirname [info script]] .. .. test]
           17  +}
           18  +source [file join $testdir tester.tcl]
           19  +set testprefix zonefileenc
           20  +load_static_extension db zonefile
           21  +
           22  +set K {
           23  +  braking bramble brambles brambly
           24  +  bran branch branched branches
           25  +  branching branchings brand branded
           26  +}
           27  +
           28  +set nFile 100
           29  +
           30  +do_execsql_test 1.0 {
           31  +  CREATE TABLE zz(k INTEGER PRIMARY KEY, frame INTEGER, idx INTEGER, v BLOB);
           32  +  CREATE TABLE rr(k INTEGER PRIMARY KEY, v);
           33  +}
           34  +do_test 1.1 {
           35  +  for {set i 0} {$i < $nFile} {incr i} {
           36  +    set k [lindex $K [expr $i % [llength $K]]]
           37  +    execsql {
           38  +      DELETE FROM zz;
           39  +      INSERT INTO zz VALUES($i*10+1, 1, -1, randomblob(100));
           40  +      INSERT INTO zz VALUES($i*10+2, 2, -1, randomblob(100));
           41  +      INSERT INTO zz VALUES($i*10+3, 1, -1, randomblob(100));
           42  +      INSERT INTO rr SELECT k,v FROM zz;
           43  +
           44  +      WITH p(n,v) AS (
           45  +          VALUES('encryptionType', 'xor') UNION ALL
           46  +          VALUES('encryptionKey', $k)
           47  +      )
           48  +      SELECT zonefile_write('test' || $i || '.zonefile', 'zz', 
           49  +        json_group_object(n, v)
           50  +      ) FROM p;
           51  +    }
           52  +  }
           53  +} {}
           54  +
           55  +do_test 1.2 {
           56  +  execsql {
           57  +    CREATE VIRTUAL TABLE gg USING zonefile;
           58  +  }
           59  +  for {set i 0} {$i < $nFile} {incr i} {
           60  +    execsql { 
           61  +      INSERT INTO gg_files(filename) VALUES('test' || $i || '.zonefile')
           62  +    }
           63  +  }
           64  +} {}
           65  +
           66  +do_catchsql_test 1.3 {
           67  +  SELECT count(*) FROM rr JOIN gg USING(k) WHERE rr.v!=gg.v;
           68  +} {1 {missing encryption key for file "test0.zonefile"}}
           69  +do_execsql_test 1.4 {
           70  +  UPDATE gg_files SET ekey = 'braking' WHERE filename='test0.zonefile';
           71  +}
           72  +do_catchsql_test 1.5 {
           73  +  SELECT count(*) FROM rr JOIN gg USING(k) WHERE rr.v!=gg.v;
           74  +} {1 {missing encryption key for file "test1.zonefile"}}
           75  +
           76  +proc k {i} {
           77  +  lindex $::K [expr $i % [llength $::K]]
           78  +}
           79  +db func k k
           80  +do_execsql_test 1.6 {
           81  +  UPDATE gg_files SET ekey = k(rowid-1);
           82  +}
           83  +do_execsql_test 1.7 {
           84  +  SELECT count(*) FROM rr JOIN gg USING(k) WHERE rr.v!=gg.v;
           85  +} {0}
           86  +do_execsql_test 1.8 {
           87  +  SELECT count(*) FROM rr JOIN gg USING(k) WHERE rr.v==gg.v;
           88  +} {300}
           89  +
           90  +
           91  +finish_test
           92  +