wasCSharpSQLite – Rev 1

Subversion Repositories:
Rev:
using System.Diagnostics;

using u8 = System.Byte;
using u32 = System.UInt32;

namespace Community.CsharpSqlite
{
#if TCLSH
  using tcl.lang;
  using DbPage = Sqlite3.PgHdr;
  using sqlite_int64 = System.Int64;
  using sqlite3_stmt = Sqlite3.Vdbe;
  using sqlite3_value = Sqlite3.Mem;
  using Tcl_CmdInfo = tcl.lang.WrappedCommand;
  using Tcl_Interp = tcl.lang.Interp;
  using Tcl_Obj = tcl.lang.TclObject;
  using ClientData = System.Object;
  using System;
#endif

  public partial class Sqlite3
  {
    /*
    ** 2010 July 12
    **
    ** The author disclaims copyright to this source code.  In place of
    ** 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.
    **
    ******************************************************************************
    **
    ** This file contains an implementation of the "dbstat" virtual table.
    **
    ** The dbstat virtual table is used to extract low-level formatting
    ** information from an SQLite database in order to implement the
    ** "sqlite3_analyzer" utility.  See the ../tool/spaceanal.tcl script
    ** for an example implementation.
    *************************************************************************
    **  Included in SQLite3 port to C#-SQLite;  2008 Noah B Hart
    **  C#-SQLite is an independent reimplementation of the SQLite software library
    **
    **  SQLITE_SOURCE_ID: 2011-06-23 19:49:22 4374b7e83ea0a3fbc3691f9c0c936272862f32f2
    **
    **************************************************************************/

    //#include "sqliteInt.h"

#if !SQLITE_OMIT_VIRTUALTABLE

    /*
** Page paths:
** 
**   The value of the 'path' column describes the path taken from the 
**   root-node of the b-tree structure to each page. The value of the 
**   root-node path is '/'.
**
**   The value of the path for the left-most child page of the root of
**   a b-tree is '/000/'. (Btrees store content ordered from left to right
**   so the pages to the left have smaller keys than the pages to the right.)
**   The next to left-most child of the root page is
**   '/001', and so on, each sibling page identified by a 3-digit hex 
**   value. The children of the 451st left-most sibling have paths such
**   as '/1c2/000/, '/1c2/001/' etc.
**
**   Overflow pages are specified by appending a '+' character and a 
**   six-digit hexadecimal value to the path to the cell they are linked
**   from. For example, the three overflow pages in a chain linked from 
**   the left-most cell of the 450th child of the root page are identified
**   by the paths:
**
**      '/1c2/000+000000'         // First page in overflow chain
**      '/1c2/000+000001'         // Second page in overflow chain
**      '/1c2/000+000002'         // Third page in overflow chain
**
**   If the paths are sorted using the BINARY collation sequence, then
**   the overflow pages associated with a cell will appear earlier in the
**   sort-order than its child page:
**
**      '/1c2/000/'               // Left-most child of 451st child of root
*/
    const string VTAB_SCHEMA =
      "CREATE TABLE xx( " +
      "  name       STRING,           /* Name of table or index */" +
      "  path       INTEGER,          /* Path to page from root */" +
      "  pageno     INTEGER,          /* Page number */" +
      "  pagetype   STRING,           /* 'internal', 'leaf' or 'overflow' */" +
      "  ncell      INTEGER,          /* Cells on page (0 for overflow) */" +
      "  payload    INTEGER,          /* Bytes of payload on this page */" +
      "  unused     INTEGER,          /* Bytes of unused space on this page */" +
      "  mx_payload INTEGER           /* Largest payload size of all cells */" +
      ");";

#if FALSE
//#define VTAB_SCHEMA2                                                        \
  "CREATE TABLE yy( "                                                       \
  "  pageno   INTEGER,            /* B-tree page number */"                 \
  "  cellno   INTEGER,            /* Cell number within page */"            \
  "  local    INTEGER,            /* Bytes of content stored locally */"    \
  "  payload  INTEGER,            /* Total cell payload size */"            \
  "  novfl    INTEGER             /* Number of overflow pages */"           \
  ");"
#endif


    //typedef struct StatTable StatTable;
    //typedef struct StatCursor StatCursor;
    //typedef struct StatPage StatPage;
    //typedef struct StatCell StatCell;

    class StatCell
    {
      public int nLocal;                     /* Bytes of local payload */
      public u32 iChildPg;                   /* Child node (or 0 if this is a leaf) */
      public int nOvfl;                      /* Entries in aOvfl[] */
      public u32[] aOvfl;                    /* Array of overflow page numbers */
      public int nLastOvfl;                  /* Bytes of payload on final overflow page */
      public int iOvfl;                      /* Iterates through aOvfl[] */
    };

    class StatPage
    {
      public u32 iPgno;
      public DbPage pPg;
      public int iCell;

      public string zPath;                    /* Path to this page */

      /* Variables populated by statDecodePage(): */
      public u8 flags;                       /* Copy of flags byte */
      public int nCell;                      /* Number of cells on page */
      public int nUnused;                    /* Number of unused bytes on page */
      public StatCell[] aCell;               /* Array of parsed cells */
      public u32 iRightChildPg;              /* Right-child page number (or 0) */
      public int nMxPayload;                 /* Largest payload of any cell on this page */
    };

    class StatCursor : sqlite3_vtab_cursor
    {
      //sqlite3_vtab_cursor base;
      public sqlite3_stmt pStmt;             /* Iterates through set of root pages */
      public int isEof;                      /* After pStmt has returned SQLITE_DONE */

      public StatPage[] aPage = new StatPage[32];
      public int iPage;                      /* Current entry in aPage[] */

      /* Values to return. */
      public string zName;                    /* Value of 'name' column */
      public string zPath;                    /* Value of 'path' column */
      public u32 iPageno;                    /* Value of 'pageno' column */
      public string zPagetype;                /* Value of 'pagetype' column */
      public int nCell;                      /* Value of 'ncell' column */
      public int nPayload;                   /* Value of 'payload' column */
      public int nUnused;                    /* Value of 'unused' column */
      public int nMxPayload;                 /* Value of 'mx_payload' column */
    };

    class StatTable : sqlite3_vtab
    {
      //sqlite3_vtab base;
      public sqlite3 db;
    };

    //#if !get2byte
    //# define get2byte(x)   ((x)[0]<<8 | (x)[1])
    //#endif

    /*
    ** Connect to or create a statvfs virtual table.
    */
    static int statConnect(
      sqlite3 db,
      object pAux,
      int argc,
      string[] argv,
      out sqlite3_vtab ppVtab,
      out string pzErr
    )
    {
      StatTable pTab;

      pTab = new StatTable();//(StatTable )sqlite3_malloc(sizeof(StatTable));
      //memset(pTab, 0, sizeof(StatTable));
      pTab.db = db;

      sqlite3_declare_vtab( db, VTAB_SCHEMA );
      ppVtab = pTab;
      pzErr = "";
      return SQLITE_OK;
    }

    /*
    ** Disconnect from or destroy a statvfs virtual table.
    */
    static int statDisconnect( ref object pVtab )
    {
      pVtab = null;//sqlite3_free( pVtab );
      return SQLITE_OK;
    }

    /*
    ** There is no "best-index". This virtual table always does a linear
    ** scan of the binary VFS log file.
    */
    static int statBestIndex( sqlite3_vtab tab, ref sqlite3_index_info pIdxInfo )
    {

      /* Records are always returned in ascending order of (name, path). 
      ** If this will satisfy the client, set the orderByConsumed flag so that 
      ** SQLite does not do an external sort.
      */
      if ( ( pIdxInfo.nOrderBy == 1
         && pIdxInfo.aOrderBy[0].iColumn == 0
         && pIdxInfo.aOrderBy[0].desc == false
         ) ||
          ( pIdxInfo.nOrderBy == 2
         && pIdxInfo.aOrderBy[0].iColumn == 0
         && pIdxInfo.aOrderBy[0].desc == false
         && pIdxInfo.aOrderBy[1].iColumn == 1
         && pIdxInfo.aOrderBy[1].desc == false
         )
      )
      {
        pIdxInfo.orderByConsumed = true;
      }

      pIdxInfo.estimatedCost = 10.0;
      return SQLITE_OK;
    }

    /*
    ** Open a new statvfs cursor.
    */
    static int statOpen( sqlite3_vtab pVTab, out sqlite3_vtab_cursor ppCursor )
    {
      StatTable pTab = (StatTable)pVTab;
      StatCursor pCsr;
      int rc;

      pCsr = new StatCursor();//(StatCursor )sqlite3_malloc(sizeof(StatCursor));
      //memset(pCsr, 0, sizeof(StatCursor));
      pCsr.pVtab = pVTab;

      rc = sqlite3_prepare_v2( pTab.db,
          "SELECT 'sqlite_master' AS name, 1 AS rootpage, 'table' AS type" +
          "  UNION ALL  " +
          "SELECT name, rootpage, type FROM sqlite_master WHERE rootpage!=0" +
          "  ORDER BY name", -1,
          ref pCsr.pStmt, 0
      );
      if ( rc != SQLITE_OK )
      {
        pCsr = null;//sqlite3_free( pCsr );
        ppCursor = null;
        return rc;
      }

      ppCursor = (sqlite3_vtab_cursor)pCsr;
      return SQLITE_OK;
    }

    static void statClearPage( ref StatPage p )
    {
      int i;
      if ( p != null && p.aCell != null )
      {
        for ( i = 0; i < p.nCell; i++ )
        {
          p.aCell[i].aOvfl = null;//sqlite3_free( p.aCell[i].aOvfl );
        }
        sqlite3PagerUnref( p.pPg );
        //  sqlite3_free( p.aCell );
        // sqlite3_free( p.zPath );
      }
      p = new StatPage();//memset( p, 0, sizeof( StatPage ) );
    }

    static void statResetCsr( StatCursor pCsr )
    {
      int i;
      sqlite3_reset( pCsr.pStmt );
      for ( i = 0; i < ArraySize( pCsr.aPage ); i++ )
      {
        statClearPage( ref pCsr.aPage[i] );
      }
      pCsr.iPage = 0;
      //sqlite3_free(pCsr.zPath);
      pCsr.zPath = null;
    }

    /*
    ** Close a statvfs cursor.
    */
    static int statClose( ref sqlite3_vtab_cursor pCursor )
    {
      StatCursor pCsr = (StatCursor)pCursor;
      statResetCsr( pCsr );
      sqlite3_finalize( pCsr.pStmt );
      pCsr = null;//sqlite3_free( pCsr );
      return SQLITE_OK;
    }

    static void getLocalPayload(
      int nUsable,                    /* Usable bytes per page */
      u8 flags,                       /* Page flags */
      int nTotal,                     /* Total record (payload) size */
      out int pnLocal                 /* OUT: Bytes stored locally */
    )
    {
      int nLocal;
      int nMinLocal;
      int nMaxLocal;

      if ( flags == 0x0D )
      {              /* Table leaf node */
        nMinLocal = ( nUsable - 12 ) * 32 / 255 - 23;
        nMaxLocal = nUsable - 35;
      }
      else
      {                          /* Index interior and leaf nodes */
        nMinLocal = ( nUsable - 12 ) * 32 / 255 - 23;
        nMaxLocal = ( nUsable - 12 ) * 64 / 255 - 23;
      }

      nLocal = nMinLocal + ( nTotal - nMinLocal ) % ( nUsable - 4 );
      if ( nLocal > nMaxLocal )
        nLocal = nMinLocal;
      pnLocal = nLocal;
    }

    static int statDecodePage( Btree pBt, StatPage p )
    {
      int nUnused;
      int iOff;
      int nHdr;
      int isLeaf;

      u8[] aData = sqlite3PagerGetData( p.pPg );
      u8[] aHdr = new byte[p.iPgno == 1 ? aData.Length - 100 : aData.Length];
      Buffer.BlockCopy( aData, p.iPgno == 1 ? 100 : 0, aHdr, 0, aHdr.Length );

      p.flags = aHdr[0];
      p.nCell = get2byte( aHdr, 3 );
      p.nMxPayload = 0;

      isLeaf = ( p.flags == 0x0A || p.flags == 0x0D ) ? 1 : 0;
      nHdr = 12 - isLeaf * 4 + ( ( p.iPgno == 1 ) ? 1 : 0 ) * 100;

      nUnused = get2byte( aHdr, 5 ) - nHdr - 2 * p.nCell;
      nUnused += (int)aHdr[7];
      iOff = get2byte( aHdr, 1 );
      while ( iOff != 0 )
      {
        nUnused += get2byte( aData, iOff + 2 );
        iOff = get2byte( aData, iOff );
      }
      p.nUnused = nUnused;
      p.iRightChildPg = isLeaf != 0 ? 0 : sqlite3Get4byte( aHdr, 8 );

      if ( p.nCell != 0 )
      {
        int i;                        /* Used to iterate through cells */
        int nUsable = sqlite3BtreeGetPageSize( pBt ) - sqlite3BtreeGetReserve( pBt );

        p.aCell = new StatCell[p.nCell + 1];//    sqlite3_malloc( ( p.nCell + 1 ) * sizeof( StatCell ) );
        //memset(p.aCell, 0, (p.nCell+1) * sizeof(StatCell));

        for ( i = 0; i < p.nCell; i++ )
        {
          p.aCell[i] = new StatCell();
          StatCell pCell = p.aCell[i];

          iOff = get2byte( aData, nHdr + i * 2 );
          if ( 0 == isLeaf )
          {
            pCell.iChildPg = sqlite3Get4byte( aData, iOff );
            iOff += 4;
          }
          if ( p.flags == 0x05 )
          {
            /* A table interior node. nPayload==0. */
          }
          else
          {
            u32 nPayload;             /* Bytes of payload total (local+overflow) */
            int nLocal;               /* Bytes of payload stored locally */
            iOff += getVarint32( aData, iOff, out nPayload );
            if ( p.flags == 0x0D )
            {
              ulong dummy;
              iOff += sqlite3GetVarint( aData, iOff, out dummy );
            }
            if ( nPayload > p.nMxPayload )
              p.nMxPayload = (int)nPayload;
            getLocalPayload( nUsable, p.flags, (int)nPayload, out nLocal );
            pCell.nLocal = nLocal;
            Debug.Assert( nPayload >= nLocal );
            Debug.Assert( nLocal <= ( nUsable - 35 ) );
            if ( nPayload > nLocal )
            {
              int j;
              int nOvfl = (int)( ( nPayload - nLocal ) + nUsable - 4 - 1 ) / ( nUsable - 4 );
              pCell.nLastOvfl = (int)( nPayload - nLocal ) - ( nOvfl - 1 ) * ( nUsable - 4 );
              pCell.nOvfl = nOvfl;
              pCell.aOvfl = new uint[nOvfl];//sqlite3_malloc(sizeof(u32)*nOvfl);
              pCell.aOvfl[0] = sqlite3Get4byte( aData, iOff + nLocal );
              for ( j = 1; j < nOvfl; j++ )
              {
                int rc;
                u32 iPrev = pCell.aOvfl[j - 1];
                DbPage pPg = null;
                rc = sqlite3PagerGet( sqlite3BtreePager( pBt ), iPrev, ref pPg );
                if ( rc != SQLITE_OK )
                {
                  Debug.Assert( pPg == null );
                  return rc;
                }
                pCell.aOvfl[j] = sqlite3Get4byte( sqlite3PagerGetData( pPg ) );
                sqlite3PagerUnref( pPg );
              }
            }
          }
        }
      }

      return SQLITE_OK;
    }

    /*
    ** Move a statvfs cursor to the next entry in the file.
    */
    static int statNext( sqlite3_vtab_cursor pCursor )
    {
      int rc = 0;
      int nPayload;
      StatCursor pCsr = (StatCursor)pCursor;
      StatTable pTab = (StatTable)pCursor.pVtab;
      Btree pBt = pTab.db.aDb[0].pBt;
      Pager pPager = sqlite3BtreePager( pBt );

      //sqlite3_free(pCsr.zPath);
      pCsr.zPath = null;

      if ( pCsr.aPage[0].pPg == null )
      {
        rc = sqlite3_step( pCsr.pStmt );
        if ( rc == SQLITE_ROW )
        {
          u32 nPage;
          u32 iRoot = (u32)sqlite3_column_int64( pCsr.pStmt, 1 );
          sqlite3PagerPagecount( pPager, out nPage );
          if ( nPage == 0 )
          {
            pCsr.isEof = 1;
            return sqlite3_reset( pCsr.pStmt );
          }
          rc = sqlite3PagerGet( pPager, iRoot, ref pCsr.aPage[0].pPg );
          pCsr.aPage[0].iPgno = iRoot;
          pCsr.aPage[0].iCell = 0;
          pCsr.aPage[0].zPath = sqlite3_mprintf( "/" );
          pCsr.iPage = 0;
        }
        else
        {
          pCsr.isEof = 1;
          return sqlite3_reset( pCsr.pStmt );
        }
      }
      else
      {

        /* Page p itself has already been visited. */
        StatPage p = pCsr.aPage[pCsr.iPage];
        StatPage p1 = pCsr.aPage[pCsr.iPage + 1];

        while ( p.iCell < p.nCell )
        {
          StatCell pCell = p.aCell[p.iCell];
          if ( pCell.iOvfl < pCell.nOvfl )
          {
            int nUsable = sqlite3BtreeGetPageSize( pBt ) - sqlite3BtreeGetReserve( pBt );
            pCsr.zName = sqlite3_column_text( pCsr.pStmt, 0 );
            pCsr.iPageno = pCell.aOvfl[pCell.iOvfl];
            pCsr.zPagetype = "overflow";
            pCsr.nCell = 0;
            pCsr.nMxPayload = 0;
            pCsr.zPath = sqlite3_mprintf(
                "%s%.3x+%.6x", p.zPath, p.iCell, pCell.iOvfl
            );
            if ( pCell.iOvfl < pCell.nOvfl - 1 )
            {
              pCsr.nUnused = 0;
              pCsr.nPayload = nUsable - 4;
            }
            else
            {
              pCsr.nPayload = pCell.nLastOvfl;
              pCsr.nUnused = nUsable - 4 - pCsr.nPayload;
            }
            pCell.iOvfl++;
            return SQLITE_OK;
          }
          if ( p.iRightChildPg != 0 )
            break;
          p.iCell++;
        }

        while ( 0 == p.iRightChildPg || p.iCell > p.nCell )
        {
          statClearPage( ref p );
          pCsr.aPage[pCsr.iPage] = p;
          if ( pCsr.iPage == 0 )
            return statNext( pCursor );
          pCsr.iPage--;
          p = pCsr.aPage[pCsr.iPage];
          if ( pCsr.aPage[pCsr.iPage + 1] == null )
            pCsr.aPage[pCsr.iPage + 1] = new StatPage();
          p1 = pCsr.aPage[pCsr.iPage + 1];
        }
        pCsr.iPage++;
        Debug.Assert( p == pCsr.aPage[pCsr.iPage - 1] );

        if ( p.iCell == p.nCell )
        {
          p1.iPgno = p.iRightChildPg;
        }
        else
        {
          p1.iPgno = p.aCell[p.iCell].iChildPg;
        }
        rc = sqlite3PagerGet( pPager, p1.iPgno, ref p1.pPg );
        p1.iCell = 0;
        p1.zPath = sqlite3_mprintf( "%s%.3x/", p.zPath, p.iCell );
        p.iCell++;
      }


      /* Populate the StatCursor fields with the values to be returned
      ** by the xColumn() and xRowid() methods.
      */
      if ( rc == SQLITE_OK )
      {
        int i;
        StatPage p = pCsr.aPage[pCsr.iPage];
        pCsr.zName = sqlite3_column_text( pCsr.pStmt, 0 );
        pCsr.iPageno = p.iPgno;

        statDecodePage( pBt, p );

        switch ( p.flags )
        {
          case 0x05:             /* table internal */
          case 0x02:             /* index internal */
            pCsr.zPagetype = "internal";
            break;
          case 0x0D:             /* table leaf */
          case 0x0A:             /* index leaf */
            pCsr.zPagetype = "leaf";
            break;
          default:
            pCsr.zPagetype = "corrupted";
            break;
        }
        pCsr.nCell = p.nCell;
        pCsr.nUnused = p.nUnused;
        pCsr.nMxPayload = p.nMxPayload;
        pCsr.zPath = sqlite3_mprintf( "%s", p.zPath );
        nPayload = 0;
        for ( i = 0; i < p.nCell; i++ )
        {
          nPayload += p.aCell[i].nLocal;
        }
        pCsr.nPayload = nPayload;
      }

      return rc;
    }

    static int statEof( sqlite3_vtab_cursor pCursor )
    {
      StatCursor pCsr = (StatCursor)pCursor;
      return pCsr.isEof;
    }

    static int statFilter(
      sqlite3_vtab_cursor pCursor,
      int idxNum, string idxStr,
      int argc, sqlite3_value[] argv
    )
    {
      StatCursor pCsr = (StatCursor)pCursor;

      statResetCsr( pCsr );
      return statNext( pCursor );
    }

    static int statColumn(
      sqlite3_vtab_cursor pCursor,
      sqlite3_context ctx,
      int i
    )
    {
      StatCursor pCsr = (StatCursor)pCursor;
      switch ( i )
      {
        case 0:            /* name */
          sqlite3_result_text( ctx, pCsr.zName, -1, SQLITE_STATIC );
          break;
        case 1:            /* path */
          sqlite3_result_text( ctx, pCsr.zPath, -1, SQLITE_TRANSIENT );
          break;
        case 2:            /* pageno */
          sqlite3_result_int64( ctx, pCsr.iPageno );
          break;
        case 3:            /* pagetype */
          sqlite3_result_text( ctx, pCsr.zPagetype, -1, SQLITE_STATIC );
          break;
        case 4:            /* ncell */
          sqlite3_result_int( ctx, pCsr.nCell );
          break;
        case 5:            /* payload */
          sqlite3_result_int( ctx, pCsr.nPayload );
          break;
        case 6:            /* unused */
          sqlite3_result_int( ctx, pCsr.nUnused );
          break;
        case 7:            /* mx_payload */
          sqlite3_result_int( ctx, pCsr.nMxPayload );
          break;
      }
      return SQLITE_OK;
    }

    static int statRowid( sqlite3_vtab_cursor pCursor, out sqlite_int64 pRowid )
    {
      StatCursor pCsr = (StatCursor)pCursor;
      pRowid = pCsr.iPageno;
      return SQLITE_OK;
    }

    static sqlite3_module dbstat_module = new sqlite3_module(
      0,                            /* iVersion */
        statConnect,                  /* xCreate */
        statConnect,                  /* xConnect */
        statBestIndex,                /* xBestIndex */
        statDisconnect,               /* xDisconnect */
        statDisconnect,               /* xDestroy */
        statOpen,                     /* xOpen - open a cursor */
        statClose,                    /* xClose - close a cursor */
        statFilter,                   /* xFilter - configure scan constraints */
        statNext,                     /* xNext - advance a cursor */
        statEof,                      /* xEof - check for end of scan */
        statColumn,                   /* xColumn - read data */
        statRowid,                    /* xRowid - read data */
        null,                            /* xUpdate */
        null,                            /* xBegin */
        null,                            /* xSync */
        null,                            /* xCommit */
        null,                            /* xRollback */
        null,                            /* xFindMethod */
        null                             /* xRename */
      );

    static int sqlite3_dbstat_register( sqlite3 db )
    {
      sqlite3_create_module( db, "dbstat", dbstat_module, 0 );
      return SQLITE_OK;
    }

#endif

#if SQLITE_TEST
    //#include <tcl.h>

    static int test_dbstat(
      object clientData,
      Tcl_Interp interp,
      int objc,
      Tcl_Obj[] objv
    )
    {
#if SQLITE_OMIT_VIRTUALTABLE
  Tcl_AppendResult(interp, "dbstat not available because of "
                           "SQLITE_OMIT_VIRTUALTABLE", (void*)0);
  return TCL.TCL_ERROR;
#else
      //sqlite3 db;
      string zDb;
      Tcl_CmdInfo cmdInfo;

      if ( objc != 2 )
      {
        TCL.Tcl_WrongNumArgs( interp, 1, objv, "DB" );
        return TCL.TCL_ERROR;
      }

      zDb = TCL.Tcl_GetString( objv[1] );
      if ( !TCL.Tcl_GetCommandInfo( interp, zDb, out cmdInfo ) )
      {
        sqlite3 db = ( (SqliteDb)cmdInfo.objClientData ).db;
        sqlite3_dbstat_register( db );
      }
      return TCL.TCL_OK;
#endif
    }

    public static int SqlitetestStat_Init( Tcl_Interp interp )
    {
      TCL.Tcl_CreateObjCommand( interp, "register_dbstat_vtab", test_dbstat, null, null );
      return TCL.TCL_OK;
    }
#endif
  }
}