/**
 * SqlJetBtree.java
 * Copyright (C) 2009-2013 TMate Software Ltd
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * For information on how to redistribute this software under
 * the terms of a license other than GNU General Public License
 * contact TMate Software at support@sqljet.com
 */
package org.tmatesoft.sqljet.core.internal.btree;

import java.io.File;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;

import org.tmatesoft.sqljet.core.SqlJetErrorCode;
import org.tmatesoft.sqljet.core.SqlJetException;
import org.tmatesoft.sqljet.core.SqlJetLogDefinitions;
import org.tmatesoft.sqljet.core.SqlJetTransactionMode;
import org.tmatesoft.sqljet.core.internal.ISqlJetBackend;
import org.tmatesoft.sqljet.core.internal.ISqlJetBtree;
import org.tmatesoft.sqljet.core.internal.ISqlJetBtreeCursor;
import org.tmatesoft.sqljet.core.internal.ISqlJetDbHandle;
import org.tmatesoft.sqljet.core.internal.ISqlJetFile;
import org.tmatesoft.sqljet.core.internal.ISqlJetFileSystem;
import org.tmatesoft.sqljet.core.internal.ISqlJetKeyInfo;
import org.tmatesoft.sqljet.core.internal.ISqlJetLimits;
import org.tmatesoft.sqljet.core.internal.ISqlJetMemoryPointer;
import org.tmatesoft.sqljet.core.internal.ISqlJetPage;
import org.tmatesoft.sqljet.core.internal.ISqlJetPageCallback;
import org.tmatesoft.sqljet.core.internal.ISqlJetPager;
import org.tmatesoft.sqljet.core.internal.SqlJetAutoVacuumMode;
import org.tmatesoft.sqljet.core.internal.SqlJetBtreeFlags;
import org.tmatesoft.sqljet.core.internal.SqlJetBtreeTableCreateFlags;
import org.tmatesoft.sqljet.core.internal.SqlJetDbFlags;
import org.tmatesoft.sqljet.core.internal.SqlJetFileOpenPermission;
import org.tmatesoft.sqljet.core.internal.SqlJetFileType;
import org.tmatesoft.sqljet.core.internal.SqlJetPagerJournalMode;
import org.tmatesoft.sqljet.core.internal.SqlJetSafetyLevel;
import org.tmatesoft.sqljet.core.internal.SqlJetSavepointOperation;
import org.tmatesoft.sqljet.core.internal.SqlJetUtility;
import org.tmatesoft.sqljet.core.internal.btree.SqlJetBtreeCursor.CursorState;
import org.tmatesoft.sqljet.core.internal.mutex.SqlJetMutex;
import org.tmatesoft.sqljet.core.internal.pager.SqlJetPager;
import org.tmatesoft.sqljet.core.internal.schema.SqlJetSchema;
import org.tmatesoft.sqljet.core.table.ISqlJetBusyHandler;

/**
 * @author TMate Software Ltd.
 * @author Sergey Scherbina (sergey.scherbina@gmail.com)
 *
 */
public class SqlJetBtree implements ISqlJetBtree {

    private static Logger btreeLogger = Logger.getLogger(SqlJetLogDefinitions.SQLJET_LOG_BTREE);

    private static final boolean SQLJET_LOG_BTREE = SqlJetUtility.getBoolSysProp(SqlJetLogDefinitions.SQLJET_LOG_BTREE,
            false);

    static void TRACE(String format, Object... args) {
        if (SQLJET_LOG_BTREE) {
            SqlJetUtility.log(btreeLogger, format, args);
        }
    }

    private static final ISqlJetMemoryPointer PAGE1_21 = SqlJetUtility.wrapPtr(new byte[] { (byte) 0100, (byte) 040,
            (byte) 040 });

    /** The database connection holding this btree */
    ISqlJetDbHandle db;

    /** Sharable content of this btree */
    SqlJetBtreeShared pBt;

    /**
     * Btree.inTrans may take one of the following values.
     *
     * If the shared-data extension is enabled, there may be multiple users of
     * the Btree structure. At most one of these may open a write transaction,
     * but any number may have active read transactions.
     */
    static enum TransMode {
        NONE, READ, WRITE
    }

    /** TRANS_NONE, TRANS_READ or TRANS_WRITE */
    TransMode inTrans;

    /** True if we can share pBt with another db */
    boolean sharable;

    /** True if db currently has pBt locked */
    boolean locked;

    /** Number of nested calls to sqlite3BtreeEnter() */
    int wantToLock;

    /** List of other sharable Btrees from the same db */
    SqlJetBtree pNext;

    /** Back pointer of the same list */
    SqlJetBtree pPrev;

    private SqlJetTransactionMode transMode = null;

    /**
     * A list of BtShared objects that are eligible for participation in shared
     * cache.
     */
    static List<SqlJetBtreeShared> sharedCacheList = new LinkedList<SqlJetBtreeShared>();

    /**
     * A bunch of assert() statements to check the transaction state variables
     * of handle p (type Btree*) are internally consistent.
     */
    private void integrity() {
        assert (pBt.inTransaction != TransMode.NONE || pBt.nTransaction == 0);
        assert (pBt.inTransaction.compareTo(inTrans) >= 0);
    }

    /**
     * @return the db
     */
    public ISqlJetDbHandle getDb() {
        return db;
    }

    /**
     * @return the transMode
     */
    public SqlJetTransactionMode getTransMode() {
        return transMode;
    }

    /**
     * Invoke the busy handler for a btree.
     */
    public boolean invokeBusyHandler(int number) {
        assert (db != null);
        assert (db.getMutex().held());
        final ISqlJetBusyHandler busyHandler = db.getBusyHandler();
        if (busyHandler == null)
            return false;
        return busyHandler.call(number);
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetBtree#enter()
     */
    public void enter() {
        SqlJetBtree p = this;
        SqlJetBtree pLater;

        /*
         * Some basic sanity checking on the Btree. The list of Btrees connected
         * by pNext and pPrev should be in sorted order by Btree.pBt value. All
         * elements of the list should belong to the same connection. Only
         * shared Btrees are on the list.
         */
        assert (p.pNext == null || p.pNext.pBt.hashCode() > p.pBt.hashCode());
        assert (p.pPrev == null || p.pPrev.pBt.hashCode() < p.pBt.hashCode());
        assert (p.pNext == null || p.pNext.db == p.db);
        assert (p.pPrev == null || p.pPrev.db == p.db);
        assert (p.sharable || (p.pNext == null && p.pPrev == null));

        /* Check for locking consistency */
        assert (!p.locked || p.wantToLock > 0);
        assert (p.sharable || p.wantToLock == 0);

        /* We should already hold a lock on the database connection */
        assert (p.db.getMutex().held());

        if (!p.sharable)
            return;
        p.wantToLock++;
        if (p.locked)
            return;

        /*
         * In most cases, we should be able to acquire the lock we want without
         * having to go throught the ascending lock procedure that follows. Just
         * be sure not to block.
         */
        if (p.pBt.mutex.attempt()) {
            p.locked = true;
            return;
        }

        /*
         * To avoid deadlock, first release all locks with a larger BtShared
         * address. Then acquire our lock. Then reacquire the other BtShared
         * locks that we used to hold in ascending order.
         */
        for (pLater = p.pNext; pLater != null; pLater = pLater.pNext) {
            assert (pLater.sharable);
            assert (pLater.pNext == null || pLater.pNext.pBt.hashCode() > pLater.pBt.hashCode());
            assert (!pLater.locked || pLater.wantToLock > 0);
            if (pLater.locked) {
                pLater.pBt.mutex.leave();
                pLater.locked = false;
            }
        }
        p.pBt.mutex.enter();
        p.locked = true;
        for (pLater = p.pNext; pLater != null; pLater = pLater.pNext) {
            if (pLater.wantToLock > 0) {
                pLater.pBt.mutex.enter();
                pLater.locked = true;
            }
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetBtree#leave()
     */
    public void leave() {
        SqlJetBtree p = this;
        if (p.sharable) {
            assert (p.wantToLock > 0);
            p.wantToLock--;
            if (p.wantToLock == 0) {
                assert (p.locked);
                p.pBt.mutex.leave();
                p.locked = false;
            }
        }
    }

    /**
     * Return true if the BtShared mutex is held on the btree.
     *
     * This routine makes no determination one why or another if the database
     * connection mutex is held.
     *
     * This routine is used only from within assert() statements.
     */
    boolean holdsMutex() {
        return !sharable || (locked && wantToLock != 0 && pBt.mutex.held());
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetBtree#open(java.io.File,
     * org.tmatesoft.sqljet.core.ISqlJetDb, java.util.Set,
     * org.tmatesoft.sqljet.core.SqlJetFileType, java.util.Set)
     */
    public void open(File filename, ISqlJetDbHandle db, Set<SqlJetBtreeFlags> flags, final SqlJetFileType type,
            final Set<SqlJetFileOpenPermission> permissions) throws SqlJetException {

        ISqlJetFileSystem pVfs; /* The VFS to use for this btree */
        SqlJetBtreeShared pBt = null; /* Shared part of btree structure */
        int nReserve;
        ISqlJetMemoryPointer zDbHeader = SqlJetUtility.allocatePtr(100);

        /*
         * Set the variable isMemdb to true for an in-memory database, or false
         * for a file-based database. This symbol is only required if either of
         * the shared-data or autovacuum features are compiled into the library.
         */
        final boolean isMemdb = filename != null && ISqlJetPager.MEMORY_DB.equals(filename.getPath());

        assert (db != null);

        pVfs = db.getFileSystem();
        this.inTrans = TransMode.NONE;
        this.db = db;

        /*
         * If this Btree is a candidate for shared cache, try to find an
         * existing BtShared object that we can share with
         */
        if (!isMemdb && !db.getFlags().contains(SqlJetDbFlags.Vtab) && filename != null
                && !"".equals(filename.getPath())) {
            if (db.getConfig().isSharedCacheEnabled()) {
                this.sharable = true;
                db.getFlags().add(SqlJetDbFlags.SharedCache);
                final String fullPathname = pVfs.getFullPath(filename);
                synchronized (sharedCacheList) {
                    final Iterator<SqlJetBtreeShared> i = sharedCacheList.iterator();
                    while (i.hasNext()) {
                        pBt = i.next();
                        assert (pBt.nRef > 0);
                        final String pagerFilename = pVfs.getFullPath(pBt.pPager.getFileName());
                        if (fullPathname.equals(pagerFilename) && pVfs == pBt.pPager.getFileSystem()) {
                            this.pBt = pBt;
                            pBt.nRef++;
                            break;
                        }
                    }
                }
            }
        }

        try {
            if (this.pBt == null) {
                /*
                 * The following asserts make sure that structures used by the
                 * btree are the right size. This is to guard against size
                 * changes that result when compiling on a different
                 * architecture.
                 */
                // assert( sizeof(i64)==8 || sizeof(i64)==4 );
                // assert( sizeof(u64)==8 || sizeof(u64)==4 );
                // assert( sizeof(u32)==4 );
                // assert( sizeof(u16)==2 );
                // assert( sizeof(Pgno)==4 );
                pBt = new SqlJetBtreeShared();
                pBt.pPager = new SqlJetPager();
                pBt.pPager.open(pVfs, filename, SqlJetBtreeFlags.toPagerFlags(flags), type, permissions);
                pBt.pPager.readFileHeader(zDbHeader.remaining(), zDbHeader);
                pBt.pPager.setBusyhandler(new ISqlJetBusyHandler() {
                    public boolean call(int number) {
                        return invokeBusyHandler(number);
                    }
                });
                this.pBt = pBt;
                pBt.pPager.setReiniter(new ISqlJetPageCallback() {
                    public void pageCallback(ISqlJetPage page) throws SqlJetException {
                        pageReinit(page);
                    }
                });

                pBt.pCursor = null;
                pBt.pPage1 = null;
                pBt.readOnly = pBt.pPager.isReadOnly();
                pBt.pageSize = SqlJetUtility.get2byte(zDbHeader, 16);

                if (pBt.pageSize < ISqlJetLimits.SQLJET_MIN_PAGE_SIZE
                        || pBt.pageSize > ISqlJetLimits.SQLJET_MAX_PAGE_SIZE
                        || ((pBt.pageSize - 1) & pBt.pageSize) != 0) {
                    pBt.pageSize = ISqlJetLimits.SQLJET_DEFAULT_PAGE_SIZE;
                    pBt.pageSize = pBt.pPager.setPageSize(pBt.pageSize);
                    /*
                     * If the magic name ":memory:" will create an in-memory
                     * database, then leave the autoVacuum mode at 0 (do not
                     * auto-vacuum), even if SQLITE_DEFAULT_AUTOVACUUM is true.
                     * On the other hand, if SQLITE_OMIT_MEMORYDB has been
                     * defined, then ":memory:" is just a regular file-name. In
                     * this case the auto-vacuum applies as per normal.
                     */
                    if (null != filename && !isMemdb) {
                        pBt.autoVacuum = SQLJET_DEFAULT_AUTOVACUUM != SqlJetAutoVacuumMode.NONE;
                        pBt.incrVacuum = SQLJET_DEFAULT_AUTOVACUUM == SqlJetAutoVacuumMode.FULL;
                    }
                    nReserve = 0;
                } else {
                    nReserve = SqlJetUtility.getUnsignedByte(zDbHeader, 20);
                    pBt.pageSizeFixed = true;
                    pBt.autoVacuum = (SqlJetUtility.get4byte(zDbHeader, 36 + 4 * 4) != 0);
                    pBt.incrVacuum = (SqlJetUtility.get4byte(zDbHeader, 36 + 7 * 4) != 0);
                }
                pBt.usableSize = pBt.pageSize - nReserve;
                assert ((pBt.pageSize & 7) == 0); /*
                                                   * 8-byte alignment of
                                                   * pageSize
                                                   */
                pBt.pageSize = pBt.pPager.setPageSize(pBt.pageSize);

                /*
                 * Add the new BtShared object to the linked list sharable
                 * BtShareds.
                 */
                if (this.sharable) {
                    pBt.mutex = new SqlJetMutex();
                    pBt.nRef = 1;
                    synchronized (sharedCacheList) {
                        sharedCacheList.add(pBt);
                    }
                }
            }

            /*
             * If the new Btree uses a sharable pBtShared, then link the new
             * Btree into the list of all sharable Btrees for the same
             * connection. The list is kept in ascending order by pBt address.
             */
            if (this.sharable) {
                for (final ISqlJetBackend backend : db.getBackends()) {
                    final ISqlJetBtree btree = backend.getBtree();
                    if (btree == null || !(btree instanceof SqlJetBtree))
                        continue;
                    SqlJetBtree pSib = (SqlJetBtree) btree;
                    if (pSib.sharable) {
                        while (pSib.pPrev != null) {
                            pSib = pSib.pPrev;
                        }
                        if (this.pBt.hashCode() < pSib.pBt.hashCode()) {
                            this.pNext = pSib;
                            this.pPrev = null;
                            pSib.pPrev = this;
                        } else {
                            while (pSib.pNext != null && pSib.pNext.pBt.hashCode() < this.pBt.hashCode()) {
                                pSib = pSib.pNext;
                            }
                            this.pNext = pSib.pNext;
                            this.pPrev = pSib;
                            if (this.pNext != null) {
                                this.pNext.pPrev = this;
                            }
                            pSib.pNext = this;
                        }
                        break;
                    }
                }
            }

        } catch (SqlJetException e) {

            // btree_open_out:
            if (pBt != null && pBt.pPager != null) {
                pBt.pPager.close();
            }
            throw e;
        }

    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetBtree#close()
     */
    public void close() throws SqlJetException {
        SqlJetBtree p = this;
        SqlJetBtreeCursor pCur;

        /* Close all cursors opened via this handle. */

        assert (p.db.getMutex().held());
        p.enter();
        try {
            pBt.db = db;
            pCur = pBt.pCursor;
            while (pCur != null) {
                SqlJetBtreeCursor pTmp = pCur;
                pCur = pCur.pNext;
                if (pTmp.pBtree == p) {
                    pTmp.closeCursor();
                }
            }

            /*
             * Rollback any active transaction and free the handle structure.
             * The call to sqlite3BtreeRollback() drops any table-locks held by
             * this handle.
             */
            p.rollback();
        } finally {
            p.leave();
        }

        /*
         * If there are still other outstanding references to the shared-btree
         * structure, return now. The remainder of this procedure cleans up the
         * shared-btree.
         */
        assert (p.wantToLock == 0 && !p.locked);
        if (!p.sharable || removeFromSharingList(pBt)) {
            /*
             * The pBt is no longer on the sharing list, so we can access it
             * without having to hold the mutex.
             *
             * Clean out and delete the BtShared object.
             */
            assert (pBt.pCursor == null);
            pBt.pPager.close();
            pBt.pSchema = null;
            pBt.pTmpSpace = null;
        }
        pBt = null;

        assert (p.wantToLock == 0);
        assert (!p.locked);
        if (p.pPrev != null)
            p.pPrev.pNext = p.pNext;
        if (p.pNext != null)
            p.pNext.pPrev = p.pPrev;
    }

    /**
     * Decrement the BtShared.nRef counter. When it reaches zero, remove the
     * BtShared structure from the sharing list. Return true if the
     * BtShared.nRef counter reaches zero and return false if it is still
     * positive.
     *
     * @param bt
     * @return
     */
    static private boolean removeFromSharingList(SqlJetBtreeShared pBt) {
        boolean removed = false;
        // assert (!pBt.mutex.held());
        synchronized (sharedCacheList) {
            pBt.mutex.enter();
            try {
                pBt.nRef--;
            } finally {
                pBt.mutex.leave();
            }
            if (pBt.nRef <= 0) {
                sharedCacheList.remove(pBt);
                removed = true;
            }
        }
        return removed;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetBtree#setCacheSize(int)
     */
    public void setCacheSize(int mxPage) {
        assert (db.getMutex().held());
        enter();
        try {
            pBt.pPager.setCacheSize(mxPage);
        } finally {
            leave();
        }
    }

    public int getCacheSize() {
        assert (db.getMutex().held());
        enter();
        try {
            return pBt.pPager.getCacheSize();
        } finally {
            leave();
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.tmatesoft.sqljet.core.ISqlJetBtree#setSafetyLevel(org.tmatesoft.sqljet
     * .core.SqlJetSafetyLevel)
     */
    public void setSafetyLevel(SqlJetSafetyLevel level) {
        assert (db.getMutex().held());
        enter();
        try {
            pBt.pPager.setSafetyLevel(level);
        } finally {
            leave();
        }
    }
    
    public SqlJetSafetyLevel getSafetyLevel() {
        assert (db.getMutex().held());
        enter();
        try {
            return pBt.pPager.getSafetyLevel();
        } finally {
            leave();
        }
    }

    public void setJournalMode(SqlJetPagerJournalMode mode) {
        assert (db.getMutex().held());
        enter();
        try {
            pBt.pPager.setJournalMode(mode);
        } finally {
            leave();
        }
    }
    
    public SqlJetPagerJournalMode getJournalMode() {
        assert (db.getMutex().held());
        enter();
        try {
            return pBt.pPager.getJournalMode();
        } finally {
            leave();
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetBtree#isSyncDisabled()
     */
    public boolean isSyncDisabled() {
        assert (db.getMutex().held());
        enter();
        try {
            assert (pBt != null && pBt.pPager != null);
            return pBt.pPager.isNoSync();
        } finally {
            leave();
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetBtree#setPageSize(int, int)
     */
    public void setPageSize(int pageSize, int reserve) throws SqlJetException {
        assert (reserve >= -1 && reserve <= 255);
        enter();
        try {
            if (pBt.pageSizeFixed) {
                throw new SqlJetException(SqlJetErrorCode.READONLY);
            }
            if (reserve < 0) {
                reserve = pBt.pageSize - pBt.usableSize;
            }
            assert (reserve >= 0 && reserve <= 255);
            if (pageSize >= ISqlJetLimits.SQLJET_MIN_PAGE_SIZE && pageSize <= ISqlJetLimits.SQLJET_MAX_PAGE_SIZE
                    && ((pageSize - 1) & pageSize) == 0) {
                assert ((pageSize & 7) == 0);
                assert (pBt.pPage1 == null && pBt.pCursor == null);
                pBt.pageSize = pageSize;
                pBt.pTmpSpace = null;
                pBt.pageSize = pBt.pPager.setPageSize(pBt.pageSize);
            }
            pBt.usableSize = pBt.pageSize - reserve;
        } finally {
            leave();
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetBtree#getPageSize()
     */
    public int getPageSize() {
        return pBt.pageSize;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetBtree#setMaxPageCount(int)
     */
    public void setMaxPageCount(int mxPage) throws SqlJetException {
        enter();
        try {
            pBt.pPager.setMaxPageCount(mxPage);
        } finally {
            leave();
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetBtree#getReserve()
     */
    public int getReserve() {
        enter();
        try {
            return pBt.pageSize - pBt.usableSize;
        } finally {
            leave();
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.tmatesoft.sqljet.core.ISqlJetBtree#setAutoVacuum(org.tmatesoft.sqljet
     * .core.SqlJetAutoVacuumMode)
     */
    public void setAutoVacuum(SqlJetAutoVacuumMode autoVacuum) throws SqlJetException {
        boolean av = autoVacuum != SqlJetAutoVacuumMode.NONE;
        enter();
        try {
            if (pBt.pageSizeFixed && av != pBt.autoVacuum) {
                throw new SqlJetException(SqlJetErrorCode.READONLY);
            } else {
                pBt.autoVacuum = av;
            }
        } finally {
            leave();
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetBtree#getAutoVacuum()
     */
    public SqlJetAutoVacuumMode getAutoVacuum() {
        enter();
        try {
            return !pBt.autoVacuum ? SqlJetAutoVacuumMode.NONE : !pBt.incrVacuum ? SqlJetAutoVacuumMode.FULL
                    : SqlJetAutoVacuumMode.INCR;
        } finally {
            leave();
        }
    }

    /**
     * Get a reference to pPage1 of the database file. This will also acquire a
     * readlock on that file.
     *
     * SQLITE_OK is returned on success. If the file is not a well-formed
     * database file, then SQLITE_CORRUPT is returned. SQLITE_BUSY is returned
     * if the database is locked. SQLITE_NOMEM is returned if we run out of
     * memory.
     */
    private void lockBtree() throws SqlJetException {

        SqlJetErrorCode rc = null;
        SqlJetMemPage pPage1;
        int nPage;

        assert (pBt.mutex.held());

        if (pBt.pPage1 != null)
            return;

        pPage1 = pBt.getPage(1, false);

        try {
            /*
             * Do some checking to help insure the file we opened really is a
             * valid database file.
             */
            nPage = pBt.pPager.getPageCount();
            if (nPage > 0) {

                int pageSize;
                int usableSize;
                ISqlJetMemoryPointer page1 = pPage1.aData;
                rc = SqlJetErrorCode.NOTADB;
                if (SqlJetUtility.memcmp(page1, zMagicHeader, 16) != 0) {
                    throw new SqlJetException(rc);
                }
                if (SqlJetUtility.getUnsignedByte(page1, 18) > 1) {
                    pBt.readOnly = true;
                }
                if (SqlJetUtility.getUnsignedByte(page1, 19) > 1) {
                    throw new SqlJetException(rc);
                }

                /*
                 * The maximum embedded fraction must be exactly 25%. And the
                 * minimum embedded fraction must be 12.5% for both leaf-data
                 * and non-leaf-data. The original design allowed these amounts
                 * to vary, but as of version 3.6.0, we require them to be
                 * fixed.
                 */
                if (SqlJetUtility.memcmp(page1, 21, PAGE1_21, 0, 3) != 0) {
                    throw new SqlJetException(rc);
                }

                pageSize = SqlJetUtility.get2byte(page1, 16);
                if (((pageSize - 1) & pageSize) != 0 || pageSize < ISqlJetLimits.SQLJET_MIN_PAGE_SIZE
                        || (ISqlJetLimits.SQLJET_MAX_PAGE_SIZE < 32768)) {
                    throw new SqlJetException(rc);
                }
                assert ((pageSize & 7) == 0);
                usableSize = pageSize - SqlJetUtility.getUnsignedByte(page1, 20);
                if (pageSize != pBt.pageSize) {
                    /*
                     * After reading the first page of the database assuming a
                     * page size of BtShared.pageSize, we have discovered that
                     * the page-size is actually pageSize. Unlock the database,
                     * leave pBt->pPage1 at zero and return SQLITE_OK. The
                     * caller will call this function again with the correct
                     * page-size.
                     */
                    SqlJetMemPage.releasePage(pPage1);
                    pBt.usableSize = usableSize;
                    pBt.pageSize = pageSize;
                    freeTempSpace(pBt);
                    pBt.pageSize = pBt.pPager.setPageSize(pBt.pageSize);
                    return;
                }
                if (usableSize < 500) {
                    throw new SqlJetException(rc);
                }
                pBt.pageSize = pageSize;
                pBt.usableSize = usableSize;
                pBt.autoVacuum = (SqlJetUtility.get4byte(page1, 36 + 4 * 4) > 0);
                pBt.incrVacuum = (SqlJetUtility.get4byte(page1, 36 + 7 * 4) > 0);
            }

            /*
             * maxLocal is the maximum amount of payload to store locally for a
             * cell. Make sure it is small enough so that at least minFanout
             * cells can will fit on one page. We assume a 10-byte page header.
             * Besides the payload, the cell must store: 2-byte pointer to the
             * cell 4-byte child pointer 9-byte nKey value 4-byte nData value
             * 4-byte overflow page pointer So a cell consists of a 2-byte
             * poiner, a header which is as much as 17 bytes long, 0 to N bytes
             * of payload, and an optional 4 byte overflow page pointer.
             */
            pBt.maxLocal = (pBt.usableSize - 12) * 64 / 255 - 23;
            pBt.minLocal = (pBt.usableSize - 12) * 32 / 255 - 23;
            pBt.maxLeaf = pBt.usableSize - 35;
            pBt.minLeaf = (pBt.usableSize - 12) * 32 / 255 - 23;
            assert (pBt.maxLeaf + 23 <= pBt.MX_CELL_SIZE());
            pBt.pPage1 = pPage1;
            return;

        } catch (SqlJetException e) {
            // page1_init_failed:
            SqlJetMemPage.releasePage(pPage1);
            pBt.pPage1 = null;
            throw e;
        }
    }

    /**
     * @param bt
     */
    private void freeTempSpace(SqlJetBtreeShared bt) {
        bt.pTmpSpace = null;
    }

    /**
     * Create a new database by initializing the first page of the file.
     */
    private void newDatabase() throws SqlJetException {
        assert (pBt.mutex.held());
        int nPage = pBt.pPager.getPageCount();
        if (nPage > 0) {
            return;
        }

        SqlJetMemPage pP1 = pBt.pPage1;
        assert (pP1 != null);
        ISqlJetMemoryPointer data = pP1.aData;
        pP1.pDbPage.write();
        SqlJetUtility.memcpy(data, zMagicHeader, zMagicHeader.remaining());
        assert (zMagicHeader.remaining() == 16);
        SqlJetUtility.put2byte(data, 16, pBt.pageSize);
        SqlJetUtility.putUnsignedByte(data, 18, (byte) 1);
        SqlJetUtility.putUnsignedByte(data, 19, (byte) 1);
        assert (pBt.usableSize <= pBt.pageSize && pBt.usableSize + 255 >= pBt.pageSize);
        SqlJetUtility.putUnsignedByte(data, 20, (byte) (pBt.pageSize - pBt.usableSize));
        SqlJetUtility.putUnsignedByte(data, 21, (byte) 64);
        SqlJetUtility.putUnsignedByte(data, 22, (byte) 32);
        SqlJetUtility.putUnsignedByte(data, 23, (byte) 32);
        SqlJetUtility.memset(data, 24, (byte) 0, 100 - 24);
        pP1.zeroPage(SqlJetMemPage.PTF_INTKEY | SqlJetMemPage.PTF_LEAF | SqlJetMemPage.PTF_LEAFDATA);
        pBt.pageSizeFixed = true;
        SqlJetUtility.put4byte(data, 36 + 4 * 4, pBt.autoVacuum ? 1 : 0);
        SqlJetUtility.put4byte(data, 36 + 7 * 4, pBt.incrVacuum ? 1 : 0);
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.tmatesoft.sqljet.core.ISqlJetBtree#beginTrans(org.tmatesoft.sqljet
     * .core.SqlJetTransactionMode)
     */
    public void beginTrans(SqlJetTransactionMode mode) throws SqlJetException {

        SqlJetException rc = null;

        enter();

        try {

            pBt.db = db;
            integrity();

            /*
             * If the btree is already in a write-transaction, or it is already
             * in a read-transaction and a read-transaction is requested, this
             * is a no-op.
             */
            if (inTrans == TransMode.WRITE || (inTrans == TransMode.READ && mode == SqlJetTransactionMode.READ_ONLY)) {
                return;
            }

            /* Write transactions are not possible on a read-only database */
            if (pBt.readOnly && mode != SqlJetTransactionMode.READ_ONLY) {
                throw new SqlJetException(SqlJetErrorCode.READONLY);
            }

            /*
             * If another database handle has already opened a write transaction
             * on this shared-btree structure and a second write transaction is
             * requested, return SQLITE_BUSY.
             */
            if (pBt.inTransaction == TransMode.WRITE && mode != SqlJetTransactionMode.READ_ONLY) {
                throw new SqlJetException(SqlJetErrorCode.BUSY);
            }

            if (mode == SqlJetTransactionMode.EXCLUSIVE) {
                Iterator<SqlJetBtreeLock> pIter;
                for (pIter = pBt.pLock.iterator(); pIter.hasNext();) {
                    if (pIter.next().pBtree != this) {
                        throw new SqlJetException(SqlJetErrorCode.BUSY);
                    }
                }
            }

            int nBusy = 0;
            do {
                rc = null;
                
                try {

                    if (pBt.pPage1 == null) {
                        do {
                            lockBtree();
                        } while (pBt.pPage1 == null);
                    }

                    if (mode != SqlJetTransactionMode.READ_ONLY) {
                        if (pBt.readOnly) {
                            throw new SqlJetException(SqlJetErrorCode.READONLY);
                        } else {
                            pBt.pPager.begin(mode == SqlJetTransactionMode.EXCLUSIVE);
                            newDatabase();
                        }
                    }

                    if (mode != SqlJetTransactionMode.READ_ONLY)
                        pBt.inStmt = false;

                } catch (SqlJetException e) {
                    rc = e;
                    pBt.unlockBtreeIfUnused();
                }

            } while (rc != null && rc.getErrorCode() == SqlJetErrorCode.BUSY && pBt.inTransaction == TransMode.NONE
                    && invokeBusyHandler(nBusy) && (nBusy++) > -1);

            if (rc == null) {
                if (inTrans == TransMode.NONE) {
                    pBt.nTransaction++;
                }
                inTrans = (mode != SqlJetTransactionMode.READ_ONLY ? TransMode.WRITE : TransMode.READ);
                if (inTrans.compareTo(pBt.inTransaction) > 0) {
                    pBt.inTransaction = inTrans;
                }
                if (mode == SqlJetTransactionMode.EXCLUSIVE) {
                    assert (pBt.pExclusive == null);
                    pBt.pExclusive = this;
                }
            }

            transMode = mode;

        } catch (SqlJetException e) {
            rc = e;
        } finally {
            // trans_begun:

            if (rc == null && mode != SqlJetTransactionMode.READ_ONLY) {
                /*
                 * This call makes sure that the pager has the correct number of
                 * open savepoints. If the second parameter is greater than 0
                 * and the sub-journal is not already open, then it will be
                 * opened here.
                 */
                try {
                    pBt.pPager.openSavepoint(db.getSavepointNum());
                } catch (SqlJetException e) {
                    rc = e;
                }
            }

            integrity();
            leave();
            if (rc != null)
                throw rc;
        }


    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.tmatesoft.sqljet.core.ISqlJetBtree#commitPhaseOne(java.lang.String)
     */
    public void commitPhaseOne(String master) throws SqlJetException {
        if (this.inTrans == TransMode.WRITE) {
            enter();
            try {
                pBt.db = this.db;
                if (pBt.autoVacuum) {
                    pBt.autoVacuumCommit();
                }
                pBt.pPager.commitPhaseOne(master, false);
            } finally {
                leave();
            }
        }
    }

    /**
     * Release all the table locks (locks obtained via calls to the lockTable()
     * procedure) held by Btree handle p.
     *
     */
    private void unlockAllTables() {

        Iterator<SqlJetBtreeLock> ppIter = pBt.pLock.iterator();

        assert (holdsMutex());
        assert (sharable || !ppIter.hasNext());

        while (ppIter.hasNext()) {
            SqlJetBtreeLock pLock = ppIter.next();
            assert (pBt.pExclusive == null || pBt.pExclusive == pLock.pBtree);
            if (pLock.pBtree == this) {
                ppIter.remove();
            }
        }

        if (pBt.pExclusive == this) {
            pBt.pExclusive = null;
        }

    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetBtree#commitPhaseTwo()
     */
    public void commitPhaseTwo() throws SqlJetException {

        enter();
        try {

            pBt.db = this.db;
            integrity();

            /*
             * If the handle has a write-transaction open, commit the
             * shared-btrees transaction and set the shared state to TRANS_READ.
             */
            if (this.inTrans == TransMode.WRITE) {
                assert (pBt.inTransaction == TransMode.WRITE);
                assert (pBt.nTransaction > 0);
                pBt.pPager.commitPhaseTwo();
                pBt.inTransaction = TransMode.READ;
                pBt.inStmt = false;
            }
            unlockAllTables();

            /*
             * If the handle has any kind of transaction open, decrement the
             * transaction count of the shared btree. If the transaction count
             * reaches 0, set the shared state to TRANS_NONE. The
             * unlockBtreeIfUnused() call below will unlock the pager.
             */
            if (this.inTrans != TransMode.NONE) {
                pBt.nTransaction--;
                if (0 == pBt.nTransaction) {
                    pBt.inTransaction = TransMode.NONE;
                }
            }

            /*
             * Set the handles current transaction state to TRANS_NONE and
             * unlock the pager if this call closed the only read or write
             * transaction.
             */
            this.inTrans = TransMode.NONE;
            pBt.unlockBtreeIfUnused();

            integrity();

        } finally {
            leave();
        }

    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetBtree#commit()
     */
    public void commit() throws SqlJetException {
        enter();
        try {
            commitPhaseOne(null);
            commitPhaseTwo();
            transMode = null;
        } finally {
            leave();
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetBtree#rollback()
     */
    public void rollback() throws SqlJetException {

        SqlJetMemPage pPage1;

        enter();

        try {

            pBt.db = this.db;
            try {
                pBt.saveAllCursors(0, null);
            } catch (SqlJetException e) {
                /*
                 * This is a horrible situation. An IO or malloc() error occured
                 * whilst trying to save cursor positions. If this is an
                 * automatic rollback (as the result of a constraint, malloc()
                 * failure or IO error) then the cache may be internally
                 * inconsistent (not contain valid trees) so we cannot simply
                 * return the error to the caller. Instead, abort all queries
                 * that may be using any of the cursors that failed to save.
                 */
                tripAllCursors(e.getErrorCode());
            }
            integrity();
            unlockAllTables();

            try {
                if (this.inTrans == TransMode.WRITE) {
                    assert (TransMode.WRITE == pBt.inTransaction);
                    try {
                        pBt.pPager.rollback();
                    } finally {
                        /*
                         * The rollback may have destroyed the pPage1->aData
                         * value. So call sqlite3BtreeGetPage() on page 1 again
                         * to make sure pPage1->aData is set correctly.
                         */
                        try {
                            pPage1 = pBt.getPage(1, false);
                            SqlJetMemPage.releasePage(pPage1);
                        } finally {
                            // assert (pBt.countWriteCursors() == 0);
                            pBt.inTransaction = TransMode.READ;
                        }
                    }
                }
            } finally {

                if (this.inTrans != TransMode.NONE) {
                    assert (pBt.nTransaction > 0);
                    pBt.nTransaction--;
                    if (0 == pBt.nTransaction) {
                        pBt.inTransaction = TransMode.NONE;
                    }
                }

                this.inTrans = TransMode.NONE;
                pBt.inStmt = false;
                pBt.unlockBtreeIfUnused();

                integrity();

            }

            transMode = null;

        } finally {
            leave();
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetBtree#beginStmt()
     */
    public void beginStmt() throws SqlJetException {
        enter();
        try {
            pBt.db = this.db;
            assert (this.inTrans == TransMode.WRITE);
            assert (!pBt.inStmt);
            assert (!pBt.readOnly);
            if (this.inTrans != TransMode.WRITE || pBt.inStmt || pBt.readOnly) {
                throw new SqlJetException(SqlJetErrorCode.INTERNAL);
            } else {
                assert (pBt.inTransaction == TransMode.WRITE);
                /*
                 * At the pager level, a statement transaction is a savepoint
                 * with an index greater than all savepoints created explicitly
                 * using SQL statements. It is illegal to open, release or
                 * rollback any such savepoints while the statement transaction
                 * savepoint is active.
                 */
                try {
                    pBt.pPager.openSavepoint(this.db.getSavepointNum() + 1);
                } finally {
                    pBt.inStmt = true;
                }
            }
        } finally {
            leave();
        }

    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetBtree#commitStmt()
     */
    public void commitStmt() throws SqlJetException {
        enter();
        try {
            pBt.db = this.db;
            assert (!pBt.readOnly);
            if (pBt.inStmt)
                try {
                    int iStmtpoint = this.db.getSavepointNum();
                    pBt.pPager.savepoint(SqlJetSavepointOperation.RELEASE, iStmtpoint);
                } finally {
                    pBt.inStmt = false;
                }
        } finally {
            leave();
        }

    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetBtree#rollbackStmt()
     */
    public void rollbackStmt() throws SqlJetException {
        enter();
        try {
            pBt.db = this.db;
            assert (!pBt.readOnly);
            if (pBt.inStmt)
                try {
                    int iStmtpoint = this.db.getSavepointNum();
                    pBt.pPager.savepoint(SqlJetSavepointOperation.ROLLBACK, iStmtpoint);
                    pBt.pPager.savepoint(SqlJetSavepointOperation.RELEASE, iStmtpoint);
                } finally {
                    pBt.inStmt = false;
                }
        } finally {
            leave();
        }
    }

    /**
     * During a rollback, when the pager reloads information into the cache so
     * that the cache is restored to its original state at the start of the
     * transaction, for each page restored this routine is called.
     *
     * This routine needs to reset the extra data section at the end of the page
     * to agree with the restored data.
     *
     * @param page
     * @throws SqlJetException
     */
    protected void pageReinit(ISqlJetPage page) throws SqlJetException {
        final SqlJetMemPage pPage = (SqlJetMemPage) page.getExtra();
        if (pPage != null && pPage.isInit) {
            assert (pPage.pBt.mutex.held());
            pPage.isInit = false;
            if (page.getRefCount() > 0) {
                pPage.initPage();
            }
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetBtree#createTable(java.util.Set)
     */
    public int createTable(Set<SqlJetBtreeTableCreateFlags> flags) throws SqlJetException {
        enter();
        try {
            pBt.db = db;
            return doCreateTable(flags);
        } finally {
            leave();
        }
    }

    /**
     * @param flags
     * @return
     * @throws SqlJetException
     */
    private int doCreateTable(Set<SqlJetBtreeTableCreateFlags> flags) throws SqlJetException {
        SqlJetMemPage pRoot;
        int pgnoRoot;

        assert (holdsMutex());
        assert (pBt.inTransaction == TransMode.WRITE);
        assert (!pBt.readOnly);

        if (pBt.autoVacuum) {
            /* Move a page here to make room for the root-page */
            int[] pgnoMove = new int[1];
            SqlJetMemPage pPageMove; /* The page to move to. */

            /*
             * Creating a new table may probably require moving an existing
             * database* to make room for the new tables root page. In case this
             * page turns* out to be an overflow page, delete all overflow
             * page-map caches* held by open cursors.
             */
            pBt.invalidateAllOverflowCache();

            /*
             * Read the value of meta[3] from the database to determine where
             * the* root page of the new table should go. meta[3] is the largest
             * root-page* created so far, so the new root-page is (meta[3]+1).
             */
            pgnoRoot = getMeta(4);
            pgnoRoot++;

            /*
             * The new root-page may not be allocated on a pointer-map page, or
             * the* PENDING_BYTE page.
             */
            while (pgnoRoot == pBt.PTRMAP_PAGENO(pgnoRoot) || pgnoRoot == pBt.PENDING_BYTE_PAGE()) {
                pgnoRoot++;
            }
            assert (pgnoRoot >= 3);

            /*
             * Allocate a page. The page that currently resides at pgnoRoot will
             * * be moved to the allocated page (unless the allocated page
             * happens* to reside at pgnoRoot).
             */
            pPageMove = pBt.allocatePage(pgnoMove, pgnoRoot, true);

            if (pgnoMove[0] != pgnoRoot) {
                /*
                 * pgnoRoot is the page that will be used for the root-page of*
                 * the new table (assuming an error did not occur). But we were*
                 * allocated pgnoMove. If required (i.e. if it was not allocated
                 * * by extending the file), the current page at position
                 * pgnoMove* is already journaled.
                 */
                short[] eType = { 0 };
                int[] iPtrPage = { 0 };

                SqlJetMemPage.releasePage(pPageMove);

                /* Move the page currently at pgnoRoot to pgnoMove. */
                pRoot = pBt.getPage(pgnoRoot, false);
                try {
                    pBt.ptrmapGet(pgnoRoot, eType, iPtrPage);
                    if (eType[0] == SqlJetBtreeShared.PTRMAP_ROOTPAGE || eType[0] == SqlJetBtreeShared.PTRMAP_FREEPAGE) {
                        throw new SqlJetException(SqlJetErrorCode.CORRUPT);
                    }
                } catch (SqlJetException e) {
                    SqlJetMemPage.releasePage(pRoot);
                    throw e;
                }
                assert (eType[0] != SqlJetBtreeShared.PTRMAP_ROOTPAGE);
                assert (eType[0] != SqlJetBtreeShared.PTRMAP_FREEPAGE);
                try {
                    pRoot.pDbPage.write();
                } catch (SqlJetException e) {
                    SqlJetMemPage.releasePage(pRoot);
                    throw e;
                }
                try {
                    pBt.relocatePage(pRoot, eType[0], iPtrPage[0], pgnoMove[0], false);
                } finally {
                    SqlJetMemPage.releasePage(pRoot);
                }

                /* Obtain the page at pgnoRoot */
                pRoot = pBt.getPage(pgnoRoot, false);
                try {
                    pRoot.pDbPage.write();
                } catch (SqlJetException e) {
                    SqlJetMemPage.releasePage(pRoot);
                    throw e;
                }
            } else {
                pRoot = pPageMove;
            }

            /*
             * Update the pointer-map and meta-data with the new root-page
             * number.
             */
            try {
                pBt.ptrmapPut(pgnoRoot, SqlJetBtreeShared.PTRMAP_ROOTPAGE, 0);
            } catch (SqlJetException e) {
                SqlJetMemPage.releasePage(pRoot);
                throw e;
            }
            try {
                updateMeta(4, pgnoRoot);
            } catch (SqlJetException e) {
                SqlJetMemPage.releasePage(pRoot);
                throw e;
            }

        } else {
            int[] a = new int[1];
            pRoot = pBt.allocatePage(a, 1, false);
            pgnoRoot = a[0];
        }

        // assert( sqlite3PagerIswriteable(pRoot->pDbPage) );
        try {
            pRoot.zeroPage(SqlJetBtreeTableCreateFlags.toByte(flags) | SqlJetMemPage.PTF_LEAF);
        } finally {
            pRoot.pDbPage.unref();
        }

        return pgnoRoot;

    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetBtree#isInTrans()
     */
    public boolean isInTrans() {
        assert (db.getMutex().held());
        return inTrans == TransMode.WRITE;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetBtree#isInStmt()
     */
    public boolean isInStmt() {
        assert (holdsMutex());
        return pBt != null && pBt.inStmt;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetBtree#isInReadTrans()
     */
    public boolean isInReadTrans() {
        assert (db.getMutex().held());
        return inTrans == TransMode.NONE;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetBtree#getSchema()
     */
    public SqlJetSchema getSchema() {
        return (SqlJetSchema) pBt.pSchema;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetBtree#setSchema(java.lang.Object)
     */
    public void setSchema(SqlJetSchema schema) {
        pBt.pSchema = schema;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetBtree#isSchemaLocked()
     */
    public boolean isSchemaLocked() {
        assert (db.getMutex().held());
        enter();
        try {
            return queryTableLock(ISqlJetDbHandle.MASTER_ROOT, SqlJetBtreeLockMode.READ);
        } finally {
            leave();
        }
    }

    /**
     * Query to see if btree handle p may obtain a lock of type eLock (READ_LOCK
     * or WRITE_LOCK) on the table with root-page iTab. Return SQLITE_OK if the
     * lock may be obtained (by calling lockTable()), or SQLITE_LOCKED if not.
     *
     * @param iTab
     * @param eLock
     *
     * @throws SqlJetException
     */
    private boolean queryTableLock(int iTab, SqlJetBtreeLockMode eLock) {

        assert (holdsMutex());
        assert (eLock != null);
        assert (db != null);

        /* This is a no-op if the shared-cache is not enabled */
        if (!sharable) {
            return true;
        }

        /*
         * If some other connection is holding an exclusive lock, the* requested
         * lock may not be obtained.
         */
        if (pBt.pExclusive != null && pBt.pExclusive != this) {
            return false;
        }

        /*
         * This (along with lockTable()) is where the ReadUncommitted flag is*
         * dealt with. If the caller is querying for a read-lock and the flag is
         * * set, it is unconditionally granted - even if there are write-locks*
         * on the table. If a write-lock is requested, the ReadUncommitted flag*
         * is not considered.** In function lockTable(), if a read-lock is
         * demanded and the* ReadUncommitted flag is set, no entry is added to
         * the locks list* (BtShared.pLock).** To summarize: If the
         * ReadUncommitted flag is set, then read cursors do* not create or
         * respect table locks. The locking procedure for a* write-cursor does
         * not change.
         */
        if (!db.getFlags().contains(SqlJetDbFlags.ReadUncommitted) || eLock == SqlJetBtreeLockMode.WRITE
                || iTab == ISqlJetDbHandle.MASTER_ROOT) {
            for (SqlJetBtreeLock pIter : pBt.pLock) {
                if (pIter.pBtree != this && pIter.iTable == iTab
                        && (pIter.eLock != eLock || eLock != SqlJetBtreeLockMode.READ)) {
                    return false;
                }
            }
        }
        return true;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetBtree#lockTable(int, boolean)
     */
    public void lockTable(int table, boolean isWriteLock) {
        if (sharable) {
            enter();
            try {
                final SqlJetBtreeLockMode lockType = isWriteLock ? SqlJetBtreeLockMode.WRITE : SqlJetBtreeLockMode.READ;
                if (queryTableLock(table, lockType)) {
                    lockTable(table, lockType);
                }
            } finally {
                leave();
            }
        }
    }

    /**
     * Add a lock on the table with root-page iTable to the shared-btree used by
     * Btree handle p. Parameter eLock must be either READ_LOCK or WRITE_LOCK.
     *
     * SQLITE_OK is returned if the lock is added successfully. SQLITE_BUSY and
     * SQLITE_NOMEM may also be returned.
     *
     * @param table
     * @param lockType
     */
    private void lockTable(int iTable, SqlJetBtreeLockMode eLock) {

        assert (holdsMutex());
        assert (eLock != null);
        assert (db != null);

        /* This is a no-op if the shared-cache is not enabled */
        if (!sharable) {
            return;
        }

        assert (queryTableLock(iTable, eLock));

        /*
         * If the read-uncommitted flag is set and a read-lock is requested,
         * return early without adding an entry to the BtShared.pLock list. See
         * comment in function queryTableLock() for more info on handling the
         * ReadUncommitted flag.
         */
        if (db.getFlags().contains(SqlJetDbFlags.ReadUncommitted) && (eLock == SqlJetBtreeLockMode.READ)
                && iTable != ISqlJetDbHandle.MASTER_ROOT) {
            return;
        }

        SqlJetBtreeLock pLock = null;

        /* First search the list for an existing lock on this table. */
        for (SqlJetBtreeLock pIter : pBt.pLock) {
            if (pIter.iTable == iTable && pIter.pBtree == this) {
                pLock = pIter;
                break;
            }
        }

        /*
         * If the above search did not find a BtLock struct associating Btree p
         * with table iTable, allocate one and link it into the list.
         */
        if (null == pLock) {
            pLock = new SqlJetBtreeLock();
            pLock.iTable = iTable;
            pLock.pBtree = this;
            pBt.pLock.add(pLock);
            pLock.eLock = eLock;
        }

        /*
         * Set the BtLock.eLock variable to the maximum of the current lock and
         * the requested lock. This means if a write-lock was already held and a
         * read-lock requested, we don't incorrectly downgrade the lock.
         */
        if (eLock == SqlJetBtreeLockMode.WRITE && pLock.eLock == SqlJetBtreeLockMode.READ) {
            pLock.eLock = eLock;
        }

    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.tmatesoft.sqljet.core.ISqlJetBtree#savepoint(org.tmatesoft.sqljet
     * .core.SqlJetSavepointOperation, int)
     */
    public void savepoint(SqlJetSavepointOperation op, int savepoint) throws SqlJetException {
        if (this.inTrans == TransMode.WRITE) {
            assert (pBt.inStmt == false);
            assert (op == SqlJetSavepointOperation.RELEASE || op == SqlJetSavepointOperation.ROLLBACK);
            assert (savepoint >= 0 || (savepoint == -1 && op == SqlJetSavepointOperation.ROLLBACK));
            enter();
            try {
                pBt.db = this.db;
                pBt.pPager.savepoint(op, savepoint);
                newDatabase();
            } finally {
                leave();
            }
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetBtree#getFilename()
     */
    public File getFilename() {
        assert (pBt.pPager != null);
        return pBt.pPager.getFileName();
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetBtree#getDirname()
     */
    public File getDirname() {
        assert (pBt.pPager != null);
        return pBt.pPager.getDirectoryName();
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetBtree#getJournalname()
     */
    public File getJournalname() {
        assert (pBt.pPager != null);
        return pBt.pPager.getJournalName();
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.tmatesoft.sqljet.core.ISqlJetBtree#copyFile(org.tmatesoft.sqljet.
     * core.ISqlJetBtree)
     */
    public void copyFile(ISqlJetBtree from) throws SqlJetException {
        enter();
        try {
            final SqlJetBtree pFrom = (SqlJetBtree) from;
            pFrom.enter();
            try {
                doCopyFile(pFrom);
            } finally {
                leave();
            }
        } finally {
            leave();
        }
    }

    /**
     * Copy the complete content of pBtFrom into pBtTo. A transaction must be
     * active for both files.
     *
     * The size of file pTo may be reduced by this operation. If anything goes
     * wrong, the transaction on pTo is rolled back.
     *
     * If successful, CommitPhaseOne() may be called on pTo before returning.
     * The caller should finish committing the transaction on pTo by calling
     * sqlite3BtreeCommit().
     *
     * @param from
     * @throws SqlJetException
     */
    private void doCopyFile(SqlJetBtree pFrom) throws SqlJetException {

        SqlJetBtree pTo = this;

        int i;

        int nFromPage; /* Number of pages in pFrom */
        int nToPage; /* Number of pages in pTo */
        int nNewPage; /* Number of pages in pTo after the copy */

        int iSkip; /* Pending byte page in pTo */
        int nToPageSize; /* Page size of pTo in bytes */
        int nFromPageSize; /* Page size of pFrom in bytes */

        SqlJetBtreeShared pBtTo = pTo.pBt;
        SqlJetBtreeShared pBtFrom = pFrom.pBt;
        pBtTo.db = pTo.db;
        pBtFrom.db = pFrom.db;

        nToPageSize = pBtTo.pageSize;
        nFromPageSize = pBtFrom.pageSize;

        assert (pTo.inTrans == TransMode.WRITE);
        assert (pFrom.inTrans == TransMode.WRITE);
        if (pBtTo.pCursor != null) {
            throw new SqlJetException(SqlJetErrorCode.BUSY);
        }

        nToPage = pBtTo.pPager.getPageCount();
        nFromPage = pBtFrom.pPager.getPageCount();
        iSkip = pBtTo.PENDING_BYTE_PAGE();

        /*
         * Variable nNewPage is the number of pages required to store the*
         * contents of pFrom using the current page-size of pTo.
         */
        nNewPage = (int) (((long) nFromPage * (long) nFromPageSize + (long) nToPageSize - 1) / (long) nToPageSize);

        for (i = 1; (i <= nToPage || i <= nNewPage); i++) {

            /*
             * Journal the original page.** iSkip is the page number of the
             * locking page (PENDING_BYTE_PAGE)* in database *pTo (before the
             * copy). This page is never written* into the journal file. Unless
             * i==iSkip or the page was not* present in pTo before the copy
             * operation, journal page i from pTo.
             */
            if (i != iSkip && i <= nToPage) {
                ISqlJetPage pDbPage = null;
                pDbPage = pBtTo.pPager.getPage(i);
                try {
                    pDbPage.write();
                    if (i > nFromPage) {
                        /*
                         * Yeah. It seems wierd to call DontWrite() right after
                         * Write(). But* that is because the names of those
                         * procedures do not exactly* represent what they do.
                         * Write() really means "put this page in the* rollback
                         * journal and mark it as dirty so that it will be
                         * written* to the database file later." DontWrite()
                         * undoes the second part of* that and prevents the page
                         * from being written to the database. The* page is
                         * still on the rollback journal, though. And that is
                         * the* whole point of this block: to put pages on the
                         * rollback journal.
                         */
                        pDbPage.dontWrite();
                    }
                } finally {
                    pDbPage.unref();
                }
            }

            /* Overwrite the data in page i of the target database */
            if (i != iSkip && i <= nNewPage) {

                ISqlJetPage pToPage = null;
                long iOff;

                pToPage = pBtTo.pPager.getPage(i);
                pToPage.write();

                for (iOff = (i - 1) * (long)nToPageSize; iOff < i * (long)nToPageSize; iOff += nFromPageSize) {
                    ISqlJetPage pFromPage = null;
                    int iFrom = (int) (iOff / nFromPageSize) + 1;

                    if (iFrom == pBtFrom.PENDING_BYTE_PAGE()) {
                        continue;
                    }

                    pFromPage = pBtFrom.pPager.getPage(iFrom);

                    ISqlJetMemoryPointer zTo = pToPage.getData();
                    ISqlJetMemoryPointer zFrom = pFromPage.getData();
                    int nCopy;

                    int nFrom = 0;
                    int nTo = 0;

                    if (nFromPageSize >= nToPageSize) {
                        nFrom += ((i - 1) * nToPageSize - ((iFrom - 1) * nFromPageSize));
                        nCopy = nToPageSize;
                    } else {
                        nTo += (((iFrom - 1) * nFromPageSize) - (i - 1) * nToPageSize);
                        nCopy = nFromPageSize;
                    }
                    SqlJetUtility.memcpy(zTo, nTo, zFrom, nFrom, nCopy);

                    pFromPage.unref();
                }

                if (pToPage != null) {
                    SqlJetMemPage p = (SqlJetMemPage) pToPage.getExtra();
                    p.isInit = false;
                    pToPage.unref();
                }
            }
        }

        /*
         * If things have worked so far, the database file may need to be*
         * truncated. The complex part is that it may need to be truncated to* a
         * size that is not an integer multiple of nToPageSize - the current*
         * page size used by the pager associated with B-Tree pTo.** For
         * example, say the page-size of pTo is 2048 bytes and the original*
         * number of pages is 5 (10 KB file). If pFrom has a page size of 1024*
         * bytes and 9 pages, then the file needs to be truncated to 9KB.
         */

        ISqlJetFile pFile = pBtTo.pPager.getFile();
        long iSize = (long) nFromPageSize * (long) nFromPage;
        long iNow = (long) ((nToPage > nNewPage) ? nToPage : nNewPage) * (long) nToPageSize;
        long iPending = ((long) pBtTo.PENDING_BYTE_PAGE() - 1) * (long) nToPageSize;

        assert (iSize <= iNow);

        /*
         * Commit phase one syncs the journal file associated with pTo*
         * containing the original data. It does not sync the database file*
         * itself. After doing this it is safe to use OsTruncate() and other*
         * file APIs on the database file directly.
         */
        pBtTo.db = pTo.db;
        pBtTo.pPager.commitPhaseOne(null, true);
        if (iSize < iNow) {
            pFile.truncate(iSize);
        }

        /*
         * The loop that copied data from database pFrom to pTo did not*
         * populate the locking page of database pTo. If the page-size of* pFrom
         * is smaller than that of pTo, this means some data will* not have been
         * copied.** This block copies the missing data from database pFrom to
         * pTo* using file APIs. This is safe because at this point we know that
         * * all of the original data from pTo has been synced into the* journal
         * file. At this point it would be safe to do anything at* all to the
         * database file except truncate it to zero bytes.
         */
        if (nFromPageSize < nToPageSize && iSize > iPending) {
            long iOff;
            for (iOff = iPending; iOff < (iPending + nToPageSize); iOff += nFromPageSize) {
                ISqlJetPage pFromPage = null;
                int iFrom = (int) (iOff / nFromPageSize) + 1;

                if (iFrom == pBtFrom.PENDING_BYTE_PAGE() || iFrom > nFromPage) {
                    continue;
                }

                pFromPage = pBtFrom.pPager.getPage(iFrom);
                ISqlJetMemoryPointer zFrom = pFromPage.getData();
                pFile.write(zFrom, nFromPageSize, iOff);
                pFromPage.unref();
            }
        }

        /* Sync the database file */
        try {
            pBtTo.pPager.sync();
            pBtTo.pageSizeFixed = false;
        } catch (SqlJetException e) {
            pTo.rollback();
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetBtree#incrVacuum()
     */
    public void incrVacuum() throws SqlJetException {
        enter();
        try {
            pBt.db = this.db;
            assert (pBt.inTransaction == TransMode.WRITE && this.inTrans == TransMode.WRITE);
            if (!pBt.autoVacuum) {
                throw new SqlJetException(SqlJetErrorCode.DONE);
            } else {
                pBt.invalidateAllOverflowCache();
                pBt.incrVacuumStep(0, pBt.pPager.imageSize());
            }
        } finally {
            leave();
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetBtree#dropTable(int)
     */
    public int dropTable(int table) throws SqlJetException {
        enter();
        try {
            pBt.db = this.db;
            return doDropTable(table);
        } finally {
            leave();
        }
    }

    /**
     * Erase all information in a table and add the root of the table to the
     * freelist. Except, the root of the principle table (the one on page 1) is
     * never added to the freelist.
     *
     * This routine will fail with SQLITE_LOCKED if there are any open cursors
     * on the table.
     *
     * If AUTOVACUUM is enabled and the page at iTable is not the last root page
     * in the database file, then the last root page in the database file is
     * moved into the slot formerly occupied by iTable and that last slot
     * formerly occupied by the last root page is added to the freelist instead
     * of iTable. In this say, all root pages are kept at the beginning of the
     * database file, which is necessary for AUTOVACUUM to work right. *piMoved
     * is set to the page number that used to be the last root page in the file
     * before the move. If no page gets moved, *piMoved is set to 0. The last
     * root page is recorded in meta[3] and the value of meta[3] is updated by
     * this procedure.
     */
    private int doDropTable(int iTable) throws SqlJetException {

        SqlJetMemPage pPage = null;
        int piMoved;

        assert (holdsMutex());
        assert (this.inTrans == TransMode.WRITE);

        /*
         * It is illegal to drop a table if any cursors are open on the*
         * database. This is because in auto-vacuum mode the backend may* need
         * to move another root-page to fill a gap left by the deleted* root
         * page. If an open cursor was using this page a problem would* occur.
         */
        if (pBt.pCursor != null) {
            throw new SqlJetException(SqlJetErrorCode.LOCKED);
        }

        pPage = pBt.getPage(iTable, false);
        try {
            clearTable(iTable, null);
        } catch (SqlJetException e) {
            SqlJetMemPage.releasePage(pPage);
            throw e;
        }

        piMoved = 0;

        if (iTable > 1) {
            if (pBt.autoVacuum) {
                int maxRootPgno;
                try {
                    maxRootPgno = getMeta(4);
                } catch (SqlJetException e) {
                    SqlJetMemPage.releasePage(pPage);
                    throw e;
                }

                if (iTable == maxRootPgno) {
                    /*
                     * If the table being dropped is the table with the largest
                     * root-page* number in the database, put the root page on
                     * the free list.
                     */
                    try {
                        pPage.freePage();
                    } finally {
                        SqlJetMemPage.releasePage(pPage);
                    }
                } else {
                    /*
                     * The table being dropped does not have the largest
                     * root-page* number in the database. So move the page that
                     * does into the* gap left by the deleted root-page.
                     */
                    SqlJetMemPage pMove;
                    SqlJetMemPage.releasePage(pPage);
                    pMove = pBt.getPage(maxRootPgno, false);
                    try {
                        pBt.relocatePage(pMove, SqlJetBtreeShared.PTRMAP_ROOTPAGE, 0, iTable, false);
                    } finally {
                        SqlJetMemPage.releasePage(pMove);
                    }
                    pMove = pBt.getPage(maxRootPgno, false);
                    try {
                        pMove.freePage();
                    } finally {
                        SqlJetMemPage.releasePage(pMove);
                    }
                    piMoved = maxRootPgno;
                }

                /*
                 * Set the new 'max-root-page' value in the database header.
                 * This* is the old value less one, less one more if that
                 * happens to* be a root-page number, less one again if that is
                 * the* PENDING_BYTE_PAGE.
                 */
                maxRootPgno--;
                if (maxRootPgno == pBt.PENDING_BYTE_PAGE()) {
                    maxRootPgno--;
                }
                if (maxRootPgno == pBt.PTRMAP_PAGENO(maxRootPgno)) {
                    maxRootPgno--;
                }
                assert (maxRootPgno != pBt.PENDING_BYTE_PAGE());

                updateMeta(4, maxRootPgno);
            } else {
                try {
                    pPage.freePage();
                } finally {
                    SqlJetMemPage.releasePage(pPage);
                }
            }
        } else {
            /* If sqlite3BtreeDropTable was called on page 1. */
            try {
                pPage.zeroPage(SqlJetMemPage.PTF_INTKEY | SqlJetMemPage.PTF_LEAF);
            } finally {
                SqlJetMemPage.releasePage(pPage);
            }
        }

        return piMoved;

    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetBtree#clearTable(int, int[])
     */
    public void clearTable(int table, int[] change) throws SqlJetException {
        enter();
        try {
            pBt.db = db;
            assert (inTrans == TransMode.WRITE);
            if (checkReadLocks(table, null, 1)) {
                /* nothing to do */
            } else if (!pBt.saveAllCursors(table, null)) {
                /* nothing to do */
            } else {
                pBt.clearDatabasePage(table, false, change);
            }
        } finally {
            leave();
        }
    }

    /**
     * This routine checks all cursors that point to table pgnoRoot. If any of
     * those cursors were opened with wrFlag==0 in a different database
     * connection (a database connection that shares the pager cache with the
     * current connection) and that other connection is not in the
     * ReadUncommmitted state, then this routine returns SQLITE_LOCKED.
     *
     * As well as cursors with wrFlag==0, cursors with wrFlag==1 and
     * isIncrblobHandle==1 are also considered 'read' cursors. Incremental blob
     * cursors are used for both reading and writing.
     *
     * When pgnoRoot is the root page of an intkey table, this function is also
     * responsible for invalidating incremental blob cursors when the table row
     * on which they are opened is deleted or modified. Cursors are invalidated
     * according to the following rules:
     *
     * <ol>
     *
     * <li>When BtreeClearTable() is called to completely delete the contents of
     * a B-Tree table, pExclude is set to zero and parameter iRow is set to
     * non-zero. In this case all incremental blob cursors open on the table
     * rooted at pgnoRoot are invalidated.</li>
     *
     * <li>When BtreeInsert(), BtreeDelete() or BtreePutData() is called to
     * modify a table row via an SQL statement, pExclude is set to the write
     * cursor used to do the modification and parameter iRow is set to the
     * integer row id of the B-Tree entry being modified. Unless pExclude is
     * itself an incremental blob cursor, then all incremental blob cursors open
     * on row iRow of the B-Tree are invalidated.</li>
     *
     * <li>If both pExclude and iRow are set to zero, no incremental blob
     * cursors are invalidated.</li>
     *
     * </ol>
     */
    boolean checkReadLocks(int pgnoRoot, SqlJetBtreeCursor pExclude, long iRow) {
        SqlJetBtreeCursor p;
        assert (holdsMutex());
        for (p = pBt.pCursor; p != null; p = p.pNext) {
            if (p == pExclude)
                continue;
            if (p.pgnoRoot != pgnoRoot)
                continue;
            if (p.isIncrblobHandle
                    && ((pExclude == null && iRow != 0) || (pExclude != null && !pExclude.isIncrblobHandle && p.info.nKey == iRow))) {
                p.eState = CursorState.INVALID;
            }
            if (p.eState != CursorState.VALID)
                continue;
            if (!p.wrFlag || p.isIncrblobHandle) {
                ISqlJetDbHandle dbOther = p.pBtree.db;
                if (dbOther == null || (dbOther != db && !dbOther.getFlags().contains(SqlJetDbFlags.ReadUncommitted))) {
                    return true;
                }
            }
        }
        return false;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetBtree#getMeta(int)
     */
    public int getMeta(int idx) throws SqlJetException {

        enter();

        try {

            ISqlJetPage pDbPage = null;
            ISqlJetMemoryPointer pP1;

            pBt.db = this.db;

            /*
             * Reading a meta-data value requires a read-lock on page 1 (and
             * hence the sqlite_master table. We grab this lock regardless of
             * whether or not the SQLITE_ReadUncommitted flag is set (the table
             * rooted at page 1 is treated as a special case by queryTableLock()
             * and lockTable()).
             */
            queryTableLock(1, SqlJetBtreeLockMode.READ);

            assert (idx >= 0 && idx <= 15);
            if (pBt.pPage1 != null) {
                /*
                 * The b-tree is already holding a reference to page 1 of the
                 * database file. In this case the required meta-data value can
                 * be read directly from the page data of this reference. This
                 * is slightly faster than* requesting a new reference from the
                 * pager layer.
                 */
                pP1 = pBt.pPage1.aData;
            } else {
                /*
                 * The b-tree does not have a reference to page 1 of the
                 * database file. Obtain one from the pager layer.
                 */
                pDbPage = pBt.pPager.acquirePage(1, true);
                pP1 = pDbPage.getData();
            }

            int pMeta = SqlJetUtility.get4byte(pP1, 36 + idx * 4);

            /*
             * If the b-tree is not holding a reference to page 1, then one was
             * requested from the pager layer in the above block. Release it
             * now.
             */
            if (pBt.pPage1 == null) {
                pDbPage.unref();
            }

            /* Grab the read-lock on page 1. */
            lockTable(1, SqlJetBtreeLockMode.READ);

            return pMeta;

        } finally {
            leave();
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetBtree#updateMeta(int, int[])
     */
    public void updateMeta(int idx, int value) throws SqlJetException {
        assert (idx >= 1 && idx <= 15);
        enter();
        try {
            pBt.db = this.db;
            assert (this.inTrans == TransMode.WRITE);
            assert (pBt.pPage1 != null);
            ISqlJetMemoryPointer pP1 = pBt.pPage1.aData;
            pBt.pPage1.pDbPage.write();
            SqlJetUtility.put4byte(pP1, 36 + idx * 4, value);
            if (idx == 7) {
                assert (pBt.autoVacuum || value == 0);
                assert (value == 0 || value == 1);
                pBt.incrVacuum = value != 0;
            }
        } finally {
            leave();
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.tmatesoft.sqljet.core.ISqlJetBtree#tripAllCursors(org.tmatesoft.sqljet
     * .core.SqlJetErrorCode)
     */
    public void tripAllCursors(SqlJetErrorCode errCode) throws SqlJetException {
        SqlJetBtreeCursor p;
        enter();
        try {
            for (p = pBt.pCursor; p != null; p = p.pNext) {
                int i;
                p.clearCursor();
                p.eState = CursorState.FAULT;
                p.error = errCode;
                p.skip = errCode != null ? 1 : 0;
                for (i = 0; i <= p.iPage; i++) {
                    SqlJetMemPage.releasePage(p.apPage[i]);
                    p.apPage[i] = null;
                }
            }
        } finally {
            leave();
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetBtree#getPager()
     */
    public ISqlJetPager getPager() {
        return pBt.pPager;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetBtree#getCursor(int, boolean,
     * org.tmatesoft.sqljet.core.ISqlJetKeyInfo)
     */
    public ISqlJetBtreeCursor getCursor(int table, boolean wrFlag, ISqlJetKeyInfo keyInfo) throws SqlJetException {
        enter();
        try {
            pBt.db = db;
            return new SqlJetBtreeCursor(this, table, wrFlag, keyInfo);
        } finally {
            leave();
        }
    }

    /**
     * This routine works like lockBtree() except that it also invokes the busy
     * callback if there is lock contention.
     *
     * @throws SqlJetException
     */
    void lockWithRetry() throws SqlJetException {
        assert (holdsMutex());
        if (inTrans == TransMode.NONE) {
            TransMode inTransaction = pBt.inTransaction;
            integrity();
            try {
                beginTrans(SqlJetTransactionMode.READ_ONLY);
            } finally {
                pBt.inTransaction = inTransaction;
                inTrans = TransMode.NONE;
            }
            pBt.nTransaction--;
            integrity();
        }

    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.internal.ISqlJetBtree#closeAllCursors()
     */
    public void closeAllCursors() throws SqlJetException {
        SqlJetBtreeCursor p;
        enter();
        try {
            for (p = pBt.pCursor; p != null; p = p.pNext) {
                p.closeCursor();
            }
        } finally {
            leave();
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see org.tmatesoft.sqljet.core.ISqlJetBtree#integrityCheck(int[], int,
     * int, int[])
     */
    public String integrityCheck(int[] root, int root2, int mxErr, int[] err) {
        return null;
    }
}
