/ Check-in [93a3bf71]
Login

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

Overview
Comment:Use the pointer-map pages to make the incremental blob API more efficient. (CVS 3896)
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1:93a3bf71d576096f4b5a3db256ca6f9b5521d137
User & Date: danielk1977 2007-05-02 13:16:30
Context
2007-05-02
13:30
Add support for zero-blobs to the OP_MakeRecord opcode. First test cases of zeroblob functionality. (CVS 3897) check-in: e6d560dd user: drh tags: trunk
13:16
Use the pointer-map pages to make the incremental blob API more efficient. (CVS 3896) check-in: 93a3bf71 user: danielk1977 tags: trunk
02:08
Fix some compiler warnings. Add the (untested) zeroblob() SQL function. (CVS 3895) check-in: 6f4f8ba7 user: drh tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/btree.c.

5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
....
3019
3020
3021
3022
3023
3024
3025





















































































3026
3027
3028
3029
3030
3031
3032
....
3082
3083
3084
3085
3086
3087
3088














3089

3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
....
4042
4043
4044
4045
4046
4047
4048
4049
4050
4051
4052
4053
4054
4055
4056
4057
4058
4059
4060
4061
4062
4063
4064
4065
4066
4067
4068
4069
4070
4071
4072
4073
4074
4075
4076
4077
4078
4079
4080
4081
4082
4083
4084
4085
4086
4087
4088
4089
4090
4091
4092
4093
4094
4095
4096
4097
4098
4099
4100
4101
4102
4103
4104
4105
4106
4107
4108
4109
....
6821
6822
6823
6824
6825
6826
6827
6828
6829
6830
6831
6832
6833
6834




6835
6836
6837
6838
6839







































6840
6841
6842

6843
6844














6845
6846
6847





6848
6849
6850


6851
6852
6853
6854

6855
6856

6857
6858
6859
6860
6861
6862




6863
6864
6865
6866


6867



6868
6869


6870
6871
6872
6873
6874
6875
6876
6877
** a legal notice, here is a blessing:
**
**    May you do good and not evil.
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
**
*************************************************************************
** $Id: btree.c,v 1.364 2007/05/02 01:34:31 drh Exp $
**
** This file implements a external (disk-based) database using BTrees.
** For a detailed discussion of BTrees, refer to
**
**     Donald E. Knuth, THE ART OF COMPUTER PROGRAMMING, Volume 3:
**     "Sorting And Searching", pages 473-480. Addison-Wesley
**     Publishing Company, Reading, Massachusetts.
................................................................................
    }else{
      getCellInfo(pCur);
      *pSize = pCur->info.nData;
    }
  }
  return rc;
}






















































































/*
** Read payload information from the entry that the pCur cursor is
** pointing to.  Begin reading the payload at "offset" and read
** a total of "amt" bytes.  Put the result in zBuf.
**
** This routine does not make a distinction between key and data.
................................................................................
  }else{
    offset -= pCur->info.nLocal;
  }
  ovflSize = pBt->usableSize - 4;
  if( amt>0 ){
    nextPage = get4byte(&aPayload[pCur->info.nLocal]);
    while( amt>0 && nextPage ){














      DbPage *pDbPage;

      rc = sqlite3PagerGet(pBt->pPager, nextPage, &pDbPage);
      if( rc!=0 ){
        return rc;
      }
      aPayload = sqlite3PagerGetData(pDbPage);
      nextPage = get4byte(aPayload);
      if( offset<ovflSize ){
        int a = amt;
        if( a + offset > ovflSize ){
          a = ovflSize - offset;
        }
        memcpy(pBuf, &aPayload[offset+4], a);
        offset = 0;
        amt -= a;
        pBuf += a;
      }else{
        offset -= ovflSize;
      }
      sqlite3PagerUnref(pDbPage);
    }
  }

  if( amt>0 ){
    return SQLITE_CORRUPT_BKPT;
  }
  return SQLITE_OK;
................................................................................
      TRACE(("FREE-PAGE: %d leaf on trunk page %d\n",pPage->pgno,pTrunk->pgno));
    }
    releasePage(pTrunk);
  }
  return rc;
}

/*
** Get a MemPage structure for overflow page number ovfl. If it
** is not zero, the page number of the overflow page following the
** one retrieved is written to *pPgnoNext.
**
** If it is possible to figure out the page-number of the next
** overflow page without reading the data of page ovfl, then 
** sqlite3PagerAcquire() is passed the "noContent" flag when
** page ovfl is retrieved.
*/
static int getOverflowPage(
  BtShared *pBt, 
  Pgno ovfl, 
  MemPage **ppPage, 
  Pgno *pPgnoNext
){
  Pgno next = 0;
  int rc;

  if( !pPgnoNext ){
    return getPage(pBt, ovfl, ppPage, 1);
  }

#ifndef SQLITE_OMIT_AUTOVACUUM
  if( pBt->autoVacuum ){
    Pgno pgno;
    Pgno iGuess = ovfl+1;
    u8 eType;

    while( PTRMAP_ISPAGE(pBt, iGuess) || iGuess==PENDING_BYTE_PAGE(pBt) ){
      iGuess++;
    }

    if( iGuess<sqlite3PagerPagecount(pBt->pPager) ){
      rc = ptrmapGet(pBt, iGuess, &eType, &pgno);
      if( rc!=SQLITE_OK ){
        return rc;
      }
      if( eType==PTRMAP_OVERFLOW2 && pgno==ovfl ){
        next = iGuess;
      }
    }
  }
#endif

  rc = getPage(pBt, ovfl, ppPage, 0);
  if( rc==SQLITE_OK ){
    assert(next==0 || next==get4byte((*ppPage)->aData));
    next = get4byte((*ppPage)->aData);
  }
  *pPgnoNext = next;
  return rc;
}

/*
** Free any overflow pages associated with the given Cell.
*/
static int clearCell(MemPage *pPage, unsigned char *pCell){
  BtShared *pBt = pPage->pBt;
  CellInfo info;
  Pgno ovflPgno;
................................................................................
** Argument pCsr must be a cursor opened for writing on an 
** INTKEY table currently pointing at a valid table entry. 
** This function modifies the data stored as part of that entry.
** Only the data content may only be modified, it is not possible
** to change the length of the data stored.
*/
int sqlite3BtreePutData(BtCursor *pCsr, u32 offset, u32 amt, const void *z){
  /* TODO: The following is only a stop-gap implementation. It needs
  ** to be made efficient using the optimistic overflow page trick. 
  ** Similar changes need to be made to sqlite3BtreeData().
  */
  i64 iKey;
  int rc;





  int nCopy;
  u32 nData;
  char *zData;

  rc = sqlite3BtreeKeySize(pCsr, &iKey);







































  if( rc!=SQLITE_OK ){
    return rc;
  }


  rc = sqlite3BtreeDataSize(pCsr, &nData);














  if( rc!=SQLITE_OK ){
    return rc;
  }






  zData = sqliteMalloc(nData);
  if( !zData ){


    return SQLITE_NOMEM;
  }

  rc = sqlite3BtreeData(pCsr, 0, nData, (void *)zData);

  if( rc!=SQLITE_OK ){
    sqliteFree(zData);

    return rc;
  }

  nCopy = amt;
  if( nCopy>(nData-offset) ){
    nCopy = nData-offset;




  }
  if( nCopy>0 ){
    memcpy(&zData[offset], z, amt);
    rc = sqlite3BtreeInsert(pCsr, 0, iKey, zData, nData, 0, 0);


  }




  sqliteFree(zData);


  return rc;
}
#endif

/*
** The following debugging interface has to be in this file (rather
** than in, for example, test1.c) so that it can get access to
** the definition of BtShared.







|







 







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







 







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







|
<

<







 







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







 







|
<
<
<
<


>
>
>
>
|
|
<

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

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

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

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







5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
....
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
....
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195


3196
3197
3198
3199
3200
3201
3202
3203

3204

3205
3206
3207
3208
3209
3210
3211
....
4138
4139
4140
4141
4142
4143
4144






















































4145
4146
4147
4148
4149
4150
4151
....
6863
6864
6865
6866
6867
6868
6869
6870




6871
6872
6873
6874
6875
6876
6877
6878

6879

6880
6881
6882
6883
6884
6885
6886
6887
6888
6889
6890
6891
6892
6893
6894
6895
6896
6897
6898
6899
6900
6901
6902
6903
6904
6905
6906
6907
6908
6909
6910
6911
6912
6913
6914
6915
6916
6917
6918
6919
6920
6921
6922
6923

6924
6925
6926
6927
6928
6929
6930
6931
6932
6933
6934
6935
6936
6937
6938
6939
6940
6941
6942
6943
6944
6945
6946


6947
6948
6949
6950


6951
6952

6953
6954
6955
6956



6957
6958
6959
6960
6961



6962
6963
6964
6965
6966
6967
6968

6969
6970
6971
6972
6973
6974
6975
6976
6977
6978
** a legal notice, here is a blessing:
**
**    May you do good and not evil.
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
**
*************************************************************************
** $Id: btree.c,v 1.365 2007/05/02 13:16:30 danielk1977 Exp $
**
** This file implements a external (disk-based) database using BTrees.
** For a detailed discussion of BTrees, refer to
**
**     Donald E. Knuth, THE ART OF COMPUTER PROGRAMMING, Volume 3:
**     "Sorting And Searching", pages 473-480. Addison-Wesley
**     Publishing Company, Reading, Massachusetts.
................................................................................
    }else{
      getCellInfo(pCur);
      *pSize = pCur->info.nData;
    }
  }
  return rc;
}

/*
** Given the page number of an overflow page in the database (parameter
** ovfl), this function finds the page number of the next page in the 
** linked list of overflow pages. If possible, it uses the auto-vacuum
** pointer-map data instead of reading the content of page ovfl to do so. 
**
** If an error occurs an SQLite error code is returned. Otherwise:
**
** Unless pPgnoNext is NULL, the page number of the next overflow 
** page in the linked list is written to *pPgnoNext. If page ovfl
** is the last page in it's linked list, *pPgnoNext is set to zero. 
**
** If ppPage is not NULL, *ppPage is set to the MemPage* handle
** for page ovfl. The underlying pager page may have been requested
** with the noContent flag set, so the page data accessable via
** this handle may not be trusted.
*/
static int getOverflowPage(
  BtShared *pBt, 
  Pgno ovfl,                   /* Overflow page */
  MemPage **ppPage,            /* OUT: MemPage handle */
  Pgno *pPgnoNext              /* OUT: Next overflow page number */
){
  Pgno next = 0;
  int rc;

  /* One of these must not be NULL. Otherwise, why call this function? */
  assert(ppPage || pPgnoNext);

  /* If pPgnoNext is NULL, then this function is being called to obtain
  ** a MemPage* reference only. No page-data is required in this case.
  */
  if( !pPgnoNext ){
    return getPage(pBt, ovfl, ppPage, 1);
  }

#ifndef SQLITE_OMIT_AUTOVACUUM
  /* Try to find the next page in the overflow list using the
  ** autovacuum pointer-map pages. Guess that the next page in 
  ** the overflow list is page number (ovfl+1). If that guess turns 
  ** out to be wrong, fall back to loading the data of page 
  ** number ovfl to determine the next page number.
  */
  if( pBt->autoVacuum ){
    Pgno pgno;
    Pgno iGuess = ovfl+1;
    u8 eType;

    while( PTRMAP_ISPAGE(pBt, iGuess) || iGuess==PENDING_BYTE_PAGE(pBt) ){
      iGuess++;
    }

    if( iGuess<sqlite3PagerPagecount(pBt->pPager) ){
      rc = ptrmapGet(pBt, iGuess, &eType, &pgno);
      if( rc!=SQLITE_OK ){
        return rc;
      }
      if( eType==PTRMAP_OVERFLOW2 && pgno==ovfl ){
        next = iGuess;
      }
    }
  }
#endif

  if( next==0 || ppPage ){
    MemPage *pPage = 0;

    rc = getPage(pBt, ovfl, &pPage, next!=0);
    assert(rc==SQLITE_OK || pPage==0);
    if( next==0 && rc==SQLITE_OK ){
      next = get4byte(pPage->aData);
    }

    if( ppPage ){
      *ppPage = pPage;
    }else{
      releasePage(pPage);
    }
  }
  *pPgnoNext = next;

  return rc;
}


/*
** Read payload information from the entry that the pCur cursor is
** pointing to.  Begin reading the payload at "offset" and read
** a total of "amt" bytes.  Put the result in zBuf.
**
** This routine does not make a distinction between key and data.
................................................................................
  }else{
    offset -= pCur->info.nLocal;
  }
  ovflSize = pBt->usableSize - 4;
  if( amt>0 ){
    nextPage = get4byte(&aPayload[pCur->info.nLocal]);
    while( amt>0 && nextPage ){
      if( offset>=ovflSize ){
        /* The only reason to read this page is to obtain the page
        ** number for the next page in the overflow chain. So try
        ** the getOverflowPage() shortcut.
        */
        rc = getOverflowPage(pBt, nextPage, 0, &nextPage);
        if( rc!=SQLITE_OK ){
          return rc;
        }
        offset -= ovflSize;
      }else{
        /* Need to read this page properly, to obtain data to copy into
        ** the caller's buffer.
        */
        DbPage *pDbPage;
        int a = amt;
        rc = sqlite3PagerGet(pBt->pPager, nextPage, &pDbPage);
        if( rc!=0 ){
          return rc;
        }
        aPayload = sqlite3PagerGetData(pDbPage);
        nextPage = get4byte(aPayload);


        if( a + offset > ovflSize ){
          a = ovflSize - offset;
        }
        memcpy(pBuf, &aPayload[offset+4], a);
        offset = 0;
        amt -= a;
        pBuf += a;
        sqlite3PagerUnref(pDbPage);

      }

    }
  }

  if( amt>0 ){
    return SQLITE_CORRUPT_BKPT;
  }
  return SQLITE_OK;
................................................................................
      TRACE(("FREE-PAGE: %d leaf on trunk page %d\n",pPage->pgno,pTrunk->pgno));
    }
    releasePage(pTrunk);
  }
  return rc;
}























































/*
** Free any overflow pages associated with the given Cell.
*/
static int clearCell(MemPage *pPage, unsigned char *pCell){
  BtShared *pBt = pPage->pBt;
  CellInfo info;
  Pgno ovflPgno;
................................................................................
** Argument pCsr must be a cursor opened for writing on an 
** INTKEY table currently pointing at a valid table entry. 
** This function modifies the data stored as part of that entry.
** Only the data content may only be modified, it is not possible
** to change the length of the data stored.
*/
int sqlite3BtreePutData(BtCursor *pCsr, u32 offset, u32 amt, const void *z){
  BtShared *pBt = pCsr->pBtree->pBt;




  int rc;

  u32 iRem = amt;        /* Remaining bytes to write */
  u8 *zRem = (u8 *)z;    /* Pointer to data not yet written */
  u32 iOffset = offset;  /* Offset from traversal point to start of write */

  Pgno iOvfl;            /* Page number for next overflow page */
  int ovflSize;          /* Bytes of data per overflow page. */



  CellInfo *pInfo;

  /* Check some preconditions: 
  **   (a) a write-transaction is open, 
  **   (b) the cursor is open for writing,
  **   (c) there is no read-lock on the table being modified and
  **   (d) the cursor points at a valid row of an intKey table.
  */
  if( pBt->inTransaction!=TRANS_WRITE ){
    /* Must start a transaction before doing an insert */
    return pBt->readOnly ? SQLITE_READONLY : SQLITE_ERROR;
  }
  assert( !pBt->readOnly );
  if( !pCsr->wrFlag ){
    return SQLITE_PERM;   /* Cursor not open for writing */
  }
  if( checkReadLocks(pCsr->pBtree, pCsr->pgnoRoot, pCsr) ){
    return SQLITE_LOCKED; /* The table pCur points to has a read lock */
  }
  if( pCsr->eState==CURSOR_INVALID || !pCsr->pPage->intKey ){
    return SQLITE_ERROR;
  }

  /* Parse the cell-info. Check that the cell-data area is large
  ** enough for the proposed write operation.
  */
  getCellInfo(pCsr);
  pInfo = &pCsr->info;
  if( pInfo->nData<(offset+amt) ){
    return SQLITE_ERROR;
  }

  if( pInfo->nLocal>iOffset ){
    /* In this case data must be written to the b-tree page. */
    int iWrite = pInfo->nLocal - offset;
    if( iWrite>iRem ){
      iWrite = iRem;
    }
    rc = sqlite3PagerWrite(pCsr->pPage->pDbPage);
    if( rc!=SQLITE_OK ){
      return rc;
    }
    memcpy(&pInfo->pCell[iOffset+pInfo->nHeader], zRem, iWrite);


    zRem += iWrite;
    iRem -= iWrite;
  }
  iOffset = ((iOffset<pInfo->nLocal)?0:(iOffset-pInfo->nLocal));

  ovflSize = pBt->usableSize - 4;
  assert(pInfo->iOverflow>0 || iRem==0);
  iOvfl = get4byte(&pInfo->pCell[pInfo->iOverflow]);
  while( iRem>0 ){
    if( iOffset>ovflSize ){
      /* The only reason to read this page is to obtain the page
      ** number for the next page in the overflow chain. So try
      ** the getOverflowPage() shortcut.  */
      rc = getOverflowPage(pBt, iOvfl, 0, &iOvfl);
      if( rc!=SQLITE_OK ){
        return rc;
      }
      iOffset -= ovflSize;
    }else{
      int iWrite = ovflSize - iOffset;
      DbPage *pOvfl;          /* The overflow page. */
      u8 *aData;              /* Page data */



      rc = sqlite3PagerGet(pBt->pPager, iOvfl, &pOvfl);
      if( rc!=SQLITE_OK ){
        return rc;
      }


      rc = sqlite3PagerWrite(pOvfl);
      if( rc!=SQLITE_OK ){

        sqlite3PagerUnref(pOvfl);
        return rc;
      }




      aData = sqlite3PagerGetData(pOvfl);
      iOvfl = get4byte(aData);
      if( iWrite>iRem ){
        iWrite = iRem;
      }



      memcpy(&aData[iOffset+4], zRem, iWrite);
      sqlite3PagerUnref(pOvfl);

      zRem += iWrite;
      iRem -= iWrite;
      iOffset = ((iOffset<ovflSize)?0:(iOffset-ovflSize));
    }

  }

  return SQLITE_OK;
}
#endif

/*
** The following debugging interface has to be in this file (rather
** than in, for example, test1.c) so that it can get access to
** the definition of BtShared.

Changes to src/tclsqlite.c.

8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
..
85
86
87
88
89
90
91


92
93
94
95
96
97
98
...
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
...
160
161
162
163
164
165
166



167
168
169
170
171
172
173
...
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
...
277
278
279
280
281
282
283
284
285
286









287
288
289
290
291
292
293
294
...
359
360
361
362
363
364
365

366
367
368
369
370
371
372
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
**
*************************************************************************
** A TCL Interface to SQLite.  Append this file to sqlite3.c and
** compile the whole thing to build a TCL-enabled version of SQLite.
**
** $Id: tclsqlite.c,v 1.180 2007/05/01 17:49:49 danielk1977 Exp $
*/
#include "tcl.h"
#include <errno.h>

/*
** Some additional include files are needed if this file is not
** appended to the amalgamation.
................................................................................
  SqlPreparedStmt *pNext;  /* Next in linked list */
  SqlPreparedStmt *pPrev;  /* Previous on the list */
  sqlite3_stmt *pStmt;     /* The prepared statement */
  int nSql;                /* chars in zSql[] */
  char zSql[1];            /* Text of the SQL statement */
};



/*
** There is one instance of this structure for each SQLite database
** that has been opened by the SQLite TCL interface.
*/
typedef struct SqliteDb SqliteDb;
struct SqliteDb {
  sqlite3 *db;               /* The "real" database structure. MUST BE FIRST */
................................................................................
  SqlCollate *pCollate;      /* List of SQL collation functions */
  int rc;                    /* Return code of most recent sqlite3_exec() */
  Tcl_Obj *pCollateNeeded;   /* Collation needed script */
  SqlPreparedStmt *stmtList; /* List of prepared statements*/
  SqlPreparedStmt *stmtLast; /* Last statement in the list */
  int maxStmt;               /* The next maximum number of stmtList */
  int nStmt;                 /* Number of statements in stmtList */

};

typedef struct IncrblobChannel IncrblobChannel;
struct IncrblobChannel {

  sqlite3_blob *pBlob;
  int iSeek;              /* Current seek offset */




};




















/*
** Close an incremental blob channel.
*/
static int incrblobClose(ClientData instanceData, Tcl_Interp *interp){
  IncrblobChannel *p = (IncrblobChannel *)instanceData;
  sqlite3_blob_close(p->pBlob);












  Tcl_Free((char *)p);
  return TCL_OK;
}

/*
** Read data from an incremental blob channel.
*/
................................................................................
    return -1;
  }

  p->iSeek += nRead;
  return nRead;
}




static int incrblobOutput(
  ClientData instanceData, 
  CONST char *buf, 
  int toWrite,
  int *errorCodePtr
){
  IncrblobChannel *p = (IncrblobChannel *)instanceData;
................................................................................
  const char *zTable, 
  const char *zColumn, 
  sqlite_int64 iRow
){
  IncrblobChannel *p;
  sqlite3_blob *pBlob;
  int rc;
  Tcl_Channel channel;
  int flags = TCL_READABLE|TCL_WRITABLE;

  /* This variable is used to name the channels: "incrblob_[incr count]" */
  static int count = 0;
  char zChannel[64];

  rc = sqlite3_blob_open(pDb->db, zDb, zTable, zColumn, iRow, 1, &pBlob);
................................................................................
  }

  p = (IncrblobChannel *)Tcl_Alloc(sizeof(IncrblobChannel));
  p->iSeek = 0;
  p->pBlob = pBlob;

  sprintf(zChannel, "incrblob_%d", ++count);
  channel = Tcl_CreateChannel(&IncrblobChannelType, zChannel, p, flags);
  Tcl_RegisterChannel(interp, channel);










  Tcl_SetResult(interp, (char *)Tcl_GetChannelName(channel), TCL_VOLATILE);
  return TCL_OK;
}

/*
** Look at the script prefix in pCmd.  We will be executing this script
** after first appending one or more arguments.  This routine analyzes
** the script to see if it is safe to use Tcl_EvalObjv() on the script
................................................................................
/*
** TCL calls this procedure when an sqlite3 database command is
** deleted.
*/
static void DbDeleteCmd(void *db){
  SqliteDb *pDb = (SqliteDb*)db;
  flushStmtCache(pDb);

  sqlite3_close(pDb->db);
  while( pDb->pFunc ){
    SqlFunc *pFunc = pDb->pFunc;
    pDb->pFunc = pFunc->pNext;
    Tcl_DecrRefCount(pFunc->pScript);
    Tcl_Free((char*)pFunc);
  }







|







 







>
>







 







>


<

>
|
|
>
>
>
>

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







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







 







>
>
>







 







<







 







|
|

>
>
>
>
>
>
>
>
>
|







 







>







8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
..
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
...
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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
...
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
...
300
301
302
303
304
305
306

307
308
309
310
311
312
313
...
317
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
...
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
**
*************************************************************************
** A TCL Interface to SQLite.  Append this file to sqlite3.c and
** compile the whole thing to build a TCL-enabled version of SQLite.
**
** $Id: tclsqlite.c,v 1.181 2007/05/02 13:16:31 danielk1977 Exp $
*/
#include "tcl.h"
#include <errno.h>

/*
** Some additional include files are needed if this file is not
** appended to the amalgamation.
................................................................................
  SqlPreparedStmt *pNext;  /* Next in linked list */
  SqlPreparedStmt *pPrev;  /* Previous on the list */
  sqlite3_stmt *pStmt;     /* The prepared statement */
  int nSql;                /* chars in zSql[] */
  char zSql[1];            /* Text of the SQL statement */
};

typedef struct IncrblobChannel IncrblobChannel;

/*
** There is one instance of this structure for each SQLite database
** that has been opened by the SQLite TCL interface.
*/
typedef struct SqliteDb SqliteDb;
struct SqliteDb {
  sqlite3 *db;               /* The "real" database structure. MUST BE FIRST */
................................................................................
  SqlCollate *pCollate;      /* List of SQL collation functions */
  int rc;                    /* Return code of most recent sqlite3_exec() */
  Tcl_Obj *pCollateNeeded;   /* Collation needed script */
  SqlPreparedStmt *stmtList; /* List of prepared statements*/
  SqlPreparedStmt *stmtLast; /* Last statement in the list */
  int maxStmt;               /* The next maximum number of stmtList */
  int nStmt;                 /* Number of statements in stmtList */
  IncrblobChannel *pIncrblob;/* Linked list of open incrblob channels */
};


struct IncrblobChannel {
  SqliteDb *pDb;            /* Associated database connection */
  sqlite3_blob *pBlob;      /* sqlite3 blob handle */
  int iSeek;                /* Current seek offset */

  Tcl_Channel channel;      /* Channel identifier */
  IncrblobChannel *pNext;   /* Linked list of all open incrblob channels */
  IncrblobChannel *pPrev;   /* Linked list of all open incrblob channels */
};

/*
** Close all incrblob channels opened using database connection pDb.
** This is called when shutting down the database connection.
*/
static void closeIncrblobChannels(SqliteDb *pDb){
  IncrblobChannel *p;
  IncrblobChannel *pNext;

  for(p=pDb->pIncrblob; p; p=pNext){
    pNext = p->pNext;

    /* Note: Calling unregister here call Tcl_Close on the incrblob channel, 
    ** which deletes the IncrblobChannel structure at *p. So do not
    ** call Tcl_Free() here.
    */
    Tcl_UnregisterChannel(pDb->interp, p->channel);
  }
}

/*
** Close an incremental blob channel.
*/
static int incrblobClose(ClientData instanceData, Tcl_Interp *interp){
  IncrblobChannel *p = (IncrblobChannel *)instanceData;
  sqlite3_blob_close(p->pBlob);

  /* Remove the channel from the SqliteDb.pIncrblob list. */
  if( p->pNext ){
    p->pNext->pPrev = p->pPrev;
  }
  if( p->pPrev ){
    p->pPrev->pNext = p->pNext;
  }
  if( p->pDb->pIncrblob==p ){
    p->pDb->pIncrblob = p->pNext;
  }

  Tcl_Free((char *)p);
  return TCL_OK;
}

/*
** Read data from an incremental blob channel.
*/
................................................................................
    return -1;
  }

  p->iSeek += nRead;
  return nRead;
}

/*
** Write data to an incremental blob channel.
*/
static int incrblobOutput(
  ClientData instanceData, 
  CONST char *buf, 
  int toWrite,
  int *errorCodePtr
){
  IncrblobChannel *p = (IncrblobChannel *)instanceData;
................................................................................
  const char *zTable, 
  const char *zColumn, 
  sqlite_int64 iRow
){
  IncrblobChannel *p;
  sqlite3_blob *pBlob;
  int rc;

  int flags = TCL_READABLE|TCL_WRITABLE;

  /* This variable is used to name the channels: "incrblob_[incr count]" */
  static int count = 0;
  char zChannel[64];

  rc = sqlite3_blob_open(pDb->db, zDb, zTable, zColumn, iRow, 1, &pBlob);
................................................................................
  }

  p = (IncrblobChannel *)Tcl_Alloc(sizeof(IncrblobChannel));
  p->iSeek = 0;
  p->pBlob = pBlob;

  sprintf(zChannel, "incrblob_%d", ++count);
  p->channel = Tcl_CreateChannel(&IncrblobChannelType, zChannel, p, flags);
  Tcl_RegisterChannel(interp, p->channel);

  /* Link the new channel into the SqliteDb.pIncrblob list. */
  p->pNext = pDb->pIncrblob;
  p->pPrev = 0;
  if( p->pNext ){
    p->pNext->pPrev = p;
  }
  pDb->pIncrblob = p;
  p->pDb = pDb;

  Tcl_SetResult(interp, (char *)Tcl_GetChannelName(p->channel), TCL_VOLATILE);
  return TCL_OK;
}

/*
** Look at the script prefix in pCmd.  We will be executing this script
** after first appending one or more arguments.  This routine analyzes
** the script to see if it is safe to use Tcl_EvalObjv() on the script
................................................................................
/*
** TCL calls this procedure when an sqlite3 database command is
** deleted.
*/
static void DbDeleteCmd(void *db){
  SqliteDb *pDb = (SqliteDb*)db;
  flushStmtCache(pDb);
  closeIncrblobChannels(pDb);
  sqlite3_close(pDb->db);
  while( pDb->pFunc ){
    SqlFunc *pFunc = pDb->pFunc;
    pDb->pFunc = pFunc->pNext;
    Tcl_DecrRefCount(pFunc->pScript);
    Tcl_Free((char*)pFunc);
  }

Changes to test/incrblob.test.

5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
..
44
45
46
47
48
49
50
51







52

53






















#
#    May you do good and not evil.
#    May you find forgiveness for yourself and forgive others.
#    May you share freely, never taking more than you give.
#
#***********************************************************************
#
# $Id: incrblob.test,v 1.1 2007/05/01 17:49:49 danielk1977 Exp $

set testdir [file dirname $argv0]
source $testdir/tester.tcl

do_test incrblob-1.1 {
  execsql {
    CREATE TABLE blobs(k PRIMARY KEY, v BLOB);
................................................................................
} {}
do_test incrblob-1.2.6 {
  execsql {
    SELECT v FROM blobs WHERE rowid = 1;
  }
} {1234567890}

finish_test







































|







 







<
>
>
>
>
>
>
>
|
>

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
..
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
#
#    May you do good and not evil.
#    May you find forgiveness for yourself and forgive others.
#    May you share freely, never taking more than you give.
#
#***********************************************************************
#
# $Id: incrblob.test,v 1.2 2007/05/02 13:16:31 danielk1977 Exp $

set testdir [file dirname $argv0]
source $testdir/tester.tcl

do_test incrblob-1.1 {
  execsql {
    CREATE TABLE blobs(k PRIMARY KEY, v BLOB);
................................................................................
} {}
do_test incrblob-1.2.6 {
  execsql {
    SELECT v FROM blobs WHERE rowid = 1;
  }
} {1234567890}


#--------------------------------------------------------------------
# Test cases incrblob-2.X check that it is possible to read and write
# regions of a blob that lie on overflow pages.
do_test incrblob-2.1 {
  set ::str "[string repeat . 10000]"
  execsql {
    INSERT INTO blobs(rowid, k, v) VALUES(3, 'three', $::str);
  }
} {}

do_test incrblob-2.2 {
  set ::blob [db incrblob blobs v 3]
  seek $::blob 8500
  read $::blob 10
} {..........}

do_test incrblob-2.3 {
  seek $::blob 8500
  puts -nonewline $::blob 1234567890
} {}

do_test incrblob-2.4 {
  seek $::blob 8496
  read $::blob 10
} {....123456}

do_test incrblob-2.10 {
  close $::blob
} {}

finish_test