Ejemplo n.º 1
0
        public static void ResetAutoExtension()
        {
#if !OMIT_AUTOINIT
            if (Initialize() == RC.OK)
#endif
            {
#if THREADSAFE
                MutexEx mutex = MutexEx.Alloc(MutexEx.MUTEX.STATIC_MASTER);
#endif
                MutexEx.Enter(mutex);
                g_autoext.Exts       = null;
                g_autoext.ExtsLength = 0;
                MutexEx.Leave(mutex);
            }
        }
Ejemplo n.º 2
0
Archivo: SysEx.cs Proyecto: BclEx/GpuEx
        public static void PostInitialize(MutexEx masterMutex)
        {
            MutexEx.Leave(_GlobalStatics.InitMutex);

            // Go back under the static mutex and clean up the recursive mutex to prevent a resource leak.
            MutexEx.Enter(masterMutex);
            _GlobalStatics.InitMutexRefs--;
            if (_GlobalStatics.InitMutexRefs <= 0)
            {
                Debug.Assert(_GlobalStatics.InitMutexRefs == 0);
                MutexEx.Free(_GlobalStatics.InitMutex);
                _GlobalStatics.InitMutex.Tag = null;
            }
            MutexEx.Leave(masterMutex);
        }
Ejemplo n.º 3
0
        public static void PutRandom(int length, ref long bufferIdx)
        {
            bufferIdx = 0;
            byte[] b = new byte[length];
#if THREADSAFE
            MutexEx mutex = MutexEx.Alloc(MutexEx.MUTEX.STATIC_PRNG);
            MutexEx.Enter(mutex);
#endif
            while (length-- > 0)
            {
                bufferIdx = (uint)((bufferIdx << 8) + RandomByte());
            }
#if THREADSAFE
            MutexEx.Leave(mutex);
#endif
        }
Ejemplo n.º 4
0
        public static RC Reset(Vdbe p)
        {
            if (p == null)
            {
                return(RC.OK);
            }
#if THREADSAFE
            MutexEx mutex = p.Ctx.Mutex;
#endif
            MutexEx.Enter(mutex);
            RC rc = p.Reset();
            p.Rewind();
            Debug.Assert((rc & (RC)p.Ctx.ErrMask) == rc);
            rc = SysEx.ApiExit(p.Ctx, rc);
            MutexEx.Leave(mutex);
            return(rc);
        }
Ejemplo n.º 5
0
        public static void PutRandom(int length, byte[] buffer, int offset)
        {
            long bufferIdx = System.DateTime.Now.Ticks;

#if THREADSAFE
            MutexEx mutex = MutexEx.Alloc(MutexEx.MUTEX.STATIC_PRNG);
            MutexEx.Enter(mutex);
#endif
            while (length-- > 0)
            {
                bufferIdx        = (uint)((bufferIdx << 8) + RandomByte());
                buffer[offset++] = (byte)bufferIdx;
            }
#if THREADSAFE
            MutexEx.Leave(mutex);
#endif
        }
Ejemplo n.º 6
0
        public RC ClearBindings(Vdbe p)
        {
#if  THREADSAFE
            MutexEx mutex = Ctx.Mutex;
#endif
            MutexEx.Enter(mutex);
            for (int i = 0; i < Vars.length; i++)
            {
                MemRelease(p.Vars[i]);
                p.Vars[i].Flags = MEM.Null;
            }
            if (p.IsPrepareV2 && p.Expmask != 0)
            {
                p.Expired = true;
            }
            MutexEx.Leave(mutex);
            return(RC.OK);
        }
Ejemplo n.º 7
0
        public static RC RegisterVfs(VSystem vfs, bool default_, Func <VFile> createOsFile)
        {
            var mutex = MutexEx.Alloc(MutexEx.MUTEX.STATIC_MASTER);

            MutexEx.Enter(mutex);
            UnlinkVfs(vfs);
            vfs.CreateOsFile = createOsFile;
            if (default_ || _vfsList == null)
            {
                vfs.Next = _vfsList;
                _vfsList = vfs;
            }
            else
            {
                vfs.Next      = _vfsList.Next;
                _vfsList.Next = vfs;
            }
            Debug.Assert(_vfsList != null);
            MutexEx.Leave(mutex);
            return(RC.OK);
        }
Ejemplo n.º 8
0
        public static RC LockAndPrepare(Context ctx, string sql, int bytes, bool isPrepareV2, Vdbe reprepare, ref Vdbe stmtOut, ref string tailOut)
        {
            if (!sqlite3SafetyCheckOk(ctx))
            {
                stmtOut = null;
                tailOut = null;
                return(SysEx.MISUSE_BKPT());
            }
            MutexEx.Enter(ctx.Mutex);
            Btree.EnterAll(ctx);
            RC rc = Prepare_(ctx, sql, bytes, isPrepareV2, reprepare, ref stmtOut, ref tailOut);

            if (rc == RC.SCHEMA)
            {
                stmtOut.Finalize();
                rc = Prepare_(ctx, sql, bytes, isPrepareV2, reprepare, ref stmtOut, ref tailOut);
            }
            Btree.LeaveAll(ctx);
            MutexEx.Leave(ctx.Mutex);
            Debug.Assert(rc == RC.OK || stmtOut == null);
            return(rc);
        }
Ejemplo n.º 9
0
        // Routines used to attach values to wildcards in a compiled SQL statement.

        static RC VdbeUnbind(Vdbe p, int i)
        {
            if (VdbeSafetyNotNull(p))
            {
                return(SysEx.MISUSE_BKPT());
            }
            MutexEx.Enter(p.Ctx.Mutex);
            if (p.Magic != VDBE_MAGIC_RUN || p.PC >= 0)
            {
                SysEx.Error(p.Ctx, RC.MISUSE, 0);
                MutexEx.Leave(p.Ctx.Mutex);
                SysEx.LOG(RC.MISUSE, "bind on a busy prepared statement: [%s]", p.Sql);
                return(SysEx.MISUSE_BKPT());
            }
            if (i < 1 || i > p.Vars)
            {
                SysEx.Error(p.Ctx, RC.RANGE, 0);
                MutexEx.Leave(p.Ctx.Mutex);
                return(RC.RANGE);
            }
            i--;
            Mem var = p.Vars[i];

            MemRelease(var);
            var.Flags = MEM.Null;
            SysEx.Error(p.Ctx, RC.OK, 0);

            // If the bit corresponding to this variable in Vdbe.expmask is set, then binding a new value to this variable invalidates the current query plan.
            //
            // IMPLEMENTATION-OF: R-48440-37595 If the specific value bound to host parameter in the WHERE clause might influence the choice of query plan
            // for a statement, then the statement will be automatically recompiled, as if there had been a schema change, on the first sqlite3_step() call
            // following any change to the bindings of that parameter.
            if (p.IsPrepareV2 && ((i < 32 && p.Expmask != 0 & ((uint)1 << i) != 0) || p.Expmask == 0xffffffff))
            {
                p.Expired = true;
            }
            return(RC.OK);
        }
Ejemplo n.º 10
0
        public static RC CreateModule(Context ctx, string name, ITableModule imodule, object aux, Action <object> destroy)
        {
            RC rc = RC.OK;

            MutexEx.Enter(ctx.Mutex);
            int nameLength = name.Length;

            if (ctx.Modules.Find(name, nameLength, (TableModule)null) != null)
            {
                rc = SysEx.MISUSE_BKPT();
            }
            else
            {
                TableModule module = new TableModule(); //: _tagalloc(ctx, sizeof(TableModule) + nameLength + 1)
                if (module != null)
                {
                    var nameCopy = name;
                    module.Name    = nameCopy;
                    module.IModule = imodule;
                    module.Aux     = aux;
                    module.Destroy = destroy;
                    TableModule del = (TableModule)ctx.Modules.Insert(nameCopy, nameLength, module);
                    Debug.Assert(del == null && del == module);
                    if (del != null)
                    {
                        ctx.MallocFailed = true;
                        C._tagfree(ctx, ref del);
                    }
                }
            }
            rc = SysEx.ApiExit(ctx, rc);
            if (rc != RC.OK && destroy != null)
            {
                destroy(aux);
            }
            MutexEx.Leave(ctx.Mutex);
            return(rc);
        }
Ejemplo n.º 11
0
        public static string ColumnName(Vdbe p, int n, Func <Mem, string> func, bool useType)
        {
            Context ctx = p.Ctx;

            Debug.Assert(ctx != null);
            string r  = null;
            int    n2 = Column_Count(p);

            if (n < n2 && n >= 0)
            {
                n += (useType ? n2 : 0);
                MutexEx.Enter(ctx.Mutex);
                Debug.Assert(ctx.MallocFailed);
                r = func(p.ColNames[n]);
                // A malloc may have failed inside of the xFunc() call. If this is the case, clear the mallocFailed flag and return NULL.
                if (ctx.MallocFailed)
                {
                    ctx.MallocFailed = false;
                    r = null;
                }
                MutexEx.Leave(ctx.Mutex);
            }
            return(r);
        }
Ejemplo n.º 12
0
        public RC Step()
        {
            RC      rc  = RC.OK; // Result from sqlite3Step()
            RC      rc2 = RC.OK; // Result from sqlite3Reprepare()
            int     cnt = 0;     // Counter to prevent infinite loop of reprepares
            Context ctx = Ctx;   // The database connection

            MutexEx.Enter(ctx.Mutex);
            DoingRerun = false;
            while ((rc = Step2()) == RC.SCHEMA && cnt++ < MAX_SCHEMA_RETRY && (rc2 = rc = Reprepare()) == RC.OK)
            {
                Reset(this);
                DoingRerun = true;
                Debug.Assert(!Expired);
            }
            if (rc2 != RC.OK && C._ALWAYS(IsPrepareV2) && C._ALWAYS(ctx.Err != null))
            {
                // This case occurs after failing to recompile an sql statement. The error message from the SQL compiler has already been loaded
                // into the database handle. This block copies the error message from the database handle into the statement and sets the statement
                // program counter to 0 to ensure that when the statement is finalized or reset the parser error message is available via
                // sqlite3_errmsg() and sqlite3_errcode().
                string err = Value_Text(ctx.Err);
                C._tagfree(ctx, ref ErrMsg);
                if (!ctx.MallocFailed)
                {
                    ErrMsg = err; RC_ = rc2;
                }
                else
                {
                    ErrMsg = null; RC_ = rc = RC.NOMEM;
                }
            }
            rc = SysEx.ApiExit(ctx, rc);
            MutexEx.Leave(ctx.Mutex);
            return(rc);
        }
Ejemplo n.º 13
0
        public static void AutoLoadExtensions(Context ctx)
        {
            if (g_autoext.ExtsLength == 0)
            {
                return; // Common case: early out without every having to acquire a mutex
            }
            bool go = true;

            for (int i = 0; go; i++)
            {
                string errmsg = null;
#if THREADSAFE
                MutexEx mutex = MutexEx.Alloc(MutexEx.MUTEX.STATIC_MASTER);
#endif
                MutexEx.Enter(mutex);
                Func <Context, string, core_api_routines, RC> init;
                if (i >= g_autoext.ExtsLength)
                {
                    init = null;
                    go   = false;
                }
                else
                {
                    init = g_autoext.Exts[i];
                }
                MutexEx.Leave(mutex);
                errmsg = null;
                RC rc;
                if (init != null && (rc = init(ctx, errmsg, g_apis)) != 0)
                {
                    Error(ctx, rc, "automatic extension loading failed: %s", errmsg);
                    go = false;
                }
                C._tagfree(ctx, ref errmsg);
            }
        }
Ejemplo n.º 14
0
        static RC AutoExtension(Func <Context, string, core_api_routines, RC> init)
        {
            RC rc = RC.OK;

#if !OMIT_AUTOINIT
            rc = Initialize();
            if (rc != 0)
            {
                return(rc);
            }
            else
#endif
            {
#if THREADSAFE
                MutexEx mutex = MutexEx.Alloc(MutexEx.MUTEX.STATIC_MASTER);
#endif
                MutexEx.Enter(mutex);
                int i;
                for (i = 0; i < g_autoext.ExtsLength; i++)
                {
                    if (g_autoext.Exts[i] == init)
                    {
                        break;
                    }
                }
                if (i == g_autoext.ExtsLength)
                {
                    Array.Resize(ref g_autoext.Exts, g_autoext.ExtsLength + 1);
                    g_autoext.Exts[g_autoext.ExtsLength] = init;
                    g_autoext.ExtsLength++;
                }
                MutexEx.Leave(mutex);
                Debug.Assert((rc & (RC)0xff) == rc);
                return(rc);
            }
        }
Ejemplo n.º 15
0
        public RC Step(int pages)
        {
            MutexEx.Enter(SrcCtx.Mutex);
            Src.Enter();
            if (DestCtx != null)
                MutexEx.Enter(DestCtx.Mutex);

            RC rc = RC_;
            if (!IsFatalError(rc))
            {
                Pager srcPager = Src.get_Pager(); // Source pager
                Pager destPager = Dest.get_Pager(); // Dest pager
                Pid srcPage = 0; // Size of source db in pages
                bool closeTrans = false; // True if src db requires unlocking

                // If the source pager is currently in a write-transaction, return SQLITE_BUSY immediately.
                rc = (DestCtx != null && Src.Bt.InTransaction == TRANS.WRITE ? RC.BUSY : RC.OK);

                // Lock the destination database, if it is not locked already.
                if (rc == RC.OK && !DestLocked && (rc = Dest.BeginTrans(2)) == RC.OK)
                {
                    DestLocked = true;
                    Dest.GetMeta(Btree.META.SCHEMA_VERSION, ref DestSchema);
                }

                // If there is no open read-transaction on the source database, open one now. If a transaction is opened here, then it will be closed
                // before this function exits.
                if (rc == RC.OK && !Src.IsInReadTrans())
                {
                    rc = Src.BeginTrans(0);
                    closeTrans = true;
                }

                // Do not allow backup if the destination database is in WAL mode and the page sizes are different between source and destination
                int pgszSrc = Src.GetPageSize(); // Source page size
                int pgszDest = Dest.GetPageSize(); // Destination page size
                IPager.JOURNALMODE destMode = Dest.get_Pager().GetJournalMode(); // Destination journal mode
                if (rc == RC.OK && destMode == IPager.JOURNALMODE.WAL && pgszSrc != pgszDest)
                    rc = RC.READONLY;

                // Now that there is a read-lock on the source database, query the source pager for the number of pages in the database.
                srcPage = Src.LastPage();
                Debug.Assert(srcPage >= 0);
                for (int ii = 0; (pages < 0 || ii < pages) && NextId <= (Pid)srcPage && rc == 0; ii++)
                {
                    Pid srcPg = NextId; // Source page number
                    if (srcPg != Btree.PENDING_BYTE_PAGE(Src.Bt))
                    {
                        IPage srcPgAsObj = null; // Source page object
                        rc = srcPager.Acquire(srcPg, ref srcPgAsObj, false);
                        if (rc == RC.OK)
                        {
                            rc = BackupOnePage(p, srcPg, Pager.GetData(srcPgAsObj), false);
                            Pager.Unref(srcPgAsObj);
                        }
                    }
                    NextId++;
                }
                if (rc == RC.OK)
                {
                    Pagecount = srcPage;
                    Remaining = (srcPage + 1 - NextId);
                    if (NextId > srcPage)
                        rc = RC.DONE;
                    else if (!IsAttached)
                        AttachBackupObject(p);
                }

                // Update the schema version field in the destination database. This is to make sure that the schema-version really does change in
                // the case where the source and destination databases have the same schema version.
                if (rc == RC.DONE)
                {
                    if (srcPage == null)
                    {
                        rc = Dest.NewDb();
                        srcPage = 1;
                    }
                    if (rc == RC.OK || rc == RC.DONE)
                        rc = Dest.UpdateMeta(Btree.META.SCHEMA_VERSION, DestSchema + 1);
                    if (rc == RC.OK)
                    {
                        if (DestCtx != null)
                            Main.ResetAllSchemasOfConnection(DestCtx);
                        if (destMode == IPager.JOURNALMODE.WAL)
                            rc = Dest.SetVersion(2);
                    }
                    if (rc == RC.OK)
                    {
                        // Set nDestTruncate to the final number of pages in the destination database. The complication here is that the destination page
                        // size may be different to the source page size. 
                        //
                        // If the source page size is smaller than the destination page size, round up. In this case the call to sqlite3OsTruncate() below will
                        // fix the size of the file. However it is important to call sqlite3PagerTruncateImage() here so that any pages in the 
                        // destination file that lie beyond the nDestTruncate page mark are journalled by PagerCommitPhaseOne() before they are destroyed
                        // by the file truncation.

                        Debug.Assert(pgszSrc == Src.GetPageSize());
                        Debug.Assert(pgszDest == Dest.GetPageSize());
                        Pid destTruncate;
                        if (pgszSrc < pgszDest)
                        {
                            int ratio = pgszDest / pgszSrc;
                            destTruncate = (Pid)((srcPage + ratio - 1) / ratio);
                            if (destTruncate == Btree.PENDING_BYTE_PAGE(Dest.Bt))
                                destTruncate--;
                        }
                        else
                            destTruncate = (Pid)(srcPage * (pgszSrc / pgszDest));
                        Debug.Assert(destTruncate > 0);

                        if (pgszSrc < pgszDest)
                        {
                            // If the source page-size is smaller than the destination page-size, two extra things may need to happen:
                            //
                            //   * The destination may need to be truncated, and
                            //
                            //   * Data stored on the pages immediately following the pending-byte page in the source database may need to be
                            //     copied into the destination database.
                            int size = (int)(pgszSrc * srcPage);
                            VFile file = destPager.get_File();
                            Debug.Assert(file != null);
                            Debug.Assert((long)destTruncate * (long)pgszDest >= size || (destTruncate == (int)(Btree.PENDING_BYTE_PAGE(Dest.Bt) - 1) && size >= VFile.PENDING_BYTE && size <= VFile.PENDING_BYTE + pgszDest));

                            // This block ensures that all data required to recreate the original database has been stored in the journal for pDestPager and the
                            // journal synced to disk. So at this point we may safely modify the database file in any way, knowing that if a power failure
                            // occurs, the original database will be reconstructed from the journal file.
                            uint dstPage;
                            destPager.Pages(out dstPage);
                            for (Pid pg = destTruncate; rc == RC.OK && pg <= (Pid)dstPage; pg++)
                            {
                                if (pg != Btree.PENDING_BYTE_PAGE(Dest.Bt))
                                {
                                    IPage pgAsObj;
                                    rc = destPager.Acquire(pg, ref pgAsObj, false);
                                    if (rc == RC.OK)
                                    {
                                        rc = Pager.Write(pgAsObj);
                                        Pager.Unref(pgAsObj);
                                    }
                                }
                            }
                            if (rc == RC.OK)
                                rc = destPager.CommitPhaseOne(null, true);

                            // Write the extra pages and truncate the database file as required.
                            long end = Math.Min(VFile.PENDING_BYTE + pgszDest, size);
                            for (long off = VFile.PENDING_BYTE + pgszSrc; rc == RC.OK && off < end; off += pgszSrc)
                            {
                                Pid srcPg = (Pid)((off / pgszSrc) + 1);
                                PgHdr srcPgAsObj = null;
                                rc = srcPager.Acquire(srcPg, ref srcPgAsObj, false);
                                if (rc == RC.OK)
                                {
                                    byte[] data = Pager.GetData(srcPgAsObj);
                                    rc = file.Write(data, pgszSrc, off);
                                }
                                Pager.Unref(srcPgAsObj);
                            }
                            if (rc == RC.OK)
                                rc = BackupTruncateFile(file, (int)size);

                            // Sync the database file to disk. 
                            if (rc == RC.OK)
                                rc = destPager.Sync();
                        }
                        else
                        {
                            destPager.TruncateImage(destTruncate);
                            rc = destPager.CommitPhaseOne(null, false);
                        }

                        // Finish committing the transaction to the destination database.
                        if (rc == RC.OK && (rc = Dest.CommitPhaseTwo(false)) == RC.OK)
                            rc = RC.DONE;
                    }
                }

                // If bCloseTrans is true, then this function opened a read transaction on the source database. Close the read transaction here. There is
                // no need to check the return values of the btree methods here, as "committing" a read-only transaction cannot fail.
                if (closeTrans)
                {
#if !DEBUG || COVERAGE_TEST
                    RC rc2 = Src.CommitPhaseOne(null);
                    rc2 |= Src.CommitPhaseTwo(false);
                    Debug.Assert(rc2 == RC.OK);
#else
                    Src.CommitPhaseOne(null);
                    Src.CommitPhaseTwo(false);
#endif
                }

                if (rc == RC.IOERR_NOMEM)
                    rc = RC.NOMEM;
                RC_ = rc;
            }
            if (DestCtx != null)
                MutexEx.Leave(DestCtx.Mutex);
            Src.Leave();
            MutexEx.Leave(SrcCtx.Mutex);
            return rc;
        }
Ejemplo n.º 16
0
Archivo: SysEx.cs Proyecto: BclEx/GpuEx
        public static RC PreInitialize(out MutexEx masterMutex)
        {
            masterMutex = default(MutexEx);
            // If SQLite is already completely initialized, then this call to sqlite3_initialize() should be a no-op.  But the initialization
            // must be complete.  So isInit must not be set until the very end of this routine.
            if (_GlobalStatics.IsInit)
            {
                return(RC.OK);
            }

            // The following is just a sanity check to make sure SQLite has been compiled correctly.  It is important to run this code, but
            // we don't want to run it too often and soak up CPU cycles for no reason.  So we run it once during initialization.
#if !NDEBUG && !OMIT_FLOATING_POINT
            // This section of code's only "output" is via assert() statements.
            //ulong x = (((ulong)1)<<63)-1;
            //double y;
            //Debug.Assert(sizeof(ulong) == 8);
            //Debug.Assert(sizeof(ulong) == sizeof(double));
            //_memcpy<void>(&y, &x, 8);
            //Debug.Assert(double.IsNaN(y));
#endif

            RC rc;
#if ENABLE_SQLLOG
            {
                Init_Sqllog();
            }
#endif

            // Make sure the mutex subsystem is initialized.  If unable to initialize the mutex subsystem, return early with the error.
            // If the system is so sick that we are unable to allocate a mutex, there is not much SQLite is going to be able to do.
            // The mutex subsystem must take care of serializing its own initialization.
            rc = RC.OK; //_mutex_init();
            if (rc != 0)
            {
                return(rc);
            }

            // Initialize the malloc() system and the recursive pInitMutex mutex. This operation is protected by the STATIC_MASTER mutex.  Note that
            // MutexAlloc() is called for a static mutex prior to initializing the malloc subsystem - this implies that the allocation of a static
            // mutex must not require support from the malloc subsystem.
            masterMutex = MutexEx.Alloc(MutexEx.MUTEX.STATIC_MASTER); // The main static mutex
            MutexEx.Enter(masterMutex);
            _GlobalStatics.IsMutexInit = true;
            //if (!SysEx_GlobalStatics.IsMallocInit)
            //	rc = sqlite3MallocInit();
            if (rc == RC.OK)
            {
                _GlobalStatics.IsMallocInit = true;
                if (_GlobalStatics.InitMutex.Tag == null)
                {
                    _GlobalStatics.InitMutex = MutexEx.Alloc(MutexEx.MUTEX.RECURSIVE);
                    if (_GlobalStatics.CoreMutex && _GlobalStatics.InitMutex.Tag == null)
                    {
                        rc = RC.NOMEM;
                    }
                }
            }
            if (rc == RC.OK)
            {
                _GlobalStatics.InitMutexRefs++;
            }
            MutexEx.Leave(masterMutex);

            // If rc is not SQLITE_OK at this point, then either the malloc subsystem could not be initialized or the system failed to allocate
            // the pInitMutex mutex. Return an error in either case.
            if (rc != RC.OK)
            {
                return(rc);
            }

            // Do the rest of the initialization under the recursive mutex so that we will be able to handle recursive calls into
            // sqlite3_initialize().  The recursive calls normally come through sqlite3_os_init() when it invokes sqlite3_vfs_register(), but other
            // recursive calls might also be possible.
            //
            // IMPLEMENTATION-OF: R-00140-37445 SQLite automatically serializes calls to the xInit method, so the xInit method need not be threadsafe.
            //
            // The following mutex is what serializes access to the appdef pcache xInit methods.  The sqlite3_pcache_methods.xInit() all is embedded in the
            // call to sqlite3PcacheInitialize().
            MutexEx.Enter(_GlobalStatics.InitMutex);
            if (!_GlobalStatics.IsInit && !_GlobalStatics.InProgress)
            {
                _GlobalStatics.InProgress = true;
                rc = VSystem.Initialize();
            }
            if (rc != RC.OK)
            {
                MutexEx.Leave(_GlobalStatics.InitMutex);
            }
            return(rc);
        }
Ejemplo n.º 17
0
        public static RC Exec(Context ctx, string sql, Func <object, int, string[], string[], bool> callback, object arg, ref string errmsg)
        {
            RC rc = RC.OK; // Return code

            if (!SafetyCheckOk(ctx))
            {
                return(SysEx.MISUSE_BKPT());
            }
            if (sql == null)
            {
                sql = string.Empty;
            }

            MutexEx.Enter(ctx.Mutex);
            Error(ctx, RC.OK, null);
            Vdbe stmt   = null;        // The current SQL statement
            int  retrys = 0;           // Number of retry attempts

            string[] colsNames = null; // Names of result columns

            while ((rc == RC.OK || (rc == RC.SCHEMA && (++retrys) < 2)) && sql.Length > 0)
            {
                stmt = null;
                string leftover = null; // Tail of unprocessed SQL
                rc = Prepare.Prepare_(ctx, sql, -1, ref stmt, ref leftover);
                Debug.Assert(rc == RC.OK || stmt == null);
                if (rc != RC.OK)
                {
                    continue;
                }
                if (stmt == null)
                {
                    sql = leftover; // this happens for a comment or white-space
                    continue;
                }

                bool callbackIsInit = false; // True if callback data is initialized
                int  cols           = Vdbe.Column_Count(stmt);

                while (true)
                {
                    rc = stmt.Step();

                    // Invoke the callback function if required
                    int i;
                    if (callback != null && (rc == RC.ROW || (rc == RC.DONE && !callbackIsInit && (ctx.Flags & Context.FLAG.NullCallback) != 0)))
                    {
                        if (!callbackIsInit)
                        {
                            colsNames = new string[cols];
                            if (colsNames == null)
                            {
                                goto exec_out;
                            }
                            for (i = 0; i < cols; i++)
                            {
                                colsNames[i] = Vdbe.Column_Name(stmt, i);
                                // Vdbe::SetColName() installs column names as UTF8 strings so there is no way for sqlite3_column_name() to fail.
                                Debug.Assert(colsNames[i] != null);
                            }
                            callbackIsInit = true;
                        }
                        string[] colsValues = null;
                        if (rc == RC.ROW)
                        {
                            colsValues = new string[cols];
                            for (i = 0; i < cols; i++)
                            {
                                colsValues[i] = Vdbe.Column_Text(stmt, i);
                                if (colsValues[i] == null && Vdbe.Column_Type(stmt, i) != TYPE.NULL)
                                {
                                    ctx.MallocFailed = true;
                                    goto exec_out;
                                }
                            }
                        }
                        if (callback(arg, cols, colsValues, colsNames))
                        {
                            rc = RC.ABORT;
                            stmt.Finalize();
                            stmt = null;
                            Error(ctx, RC.ABORT, null);
                            goto exec_out;
                        }
                    }

                    if (rc != RC.ROW)
                    {
                        rc   = stmt.Finalize();
                        stmt = null;
                        if (rc != RC.SCHEMA)
                        {
                            retrys = 0;
                            if ((sql = leftover) != string.Empty)
                            {
                                int idx = 0;
                                while (idx < sql.Length && char.IsWhiteSpace(sql[idx]))
                                {
                                    idx++;
                                }
                                if (idx != 0)
                                {
                                    sql = (idx < sql.Length ? sql.Substring(idx) : string.Empty);
                                }
                            }
                        }
                        break;
                    }
                }

                C._tagfree(ctx, ref colsNames);
                colsNames = null;
            }

exec_out:
            if (stmt != null)
            {
                stmt.Finalize();
            }
            C._tagfree(ctx, ref colsNames);

            rc = ApiExit(ctx, rc);
            if (rc != RC.OK && C._ALWAYS(rc == ErrCode(ctx)) && errmsg != null)
            {
                errmsg = ErrMsg(ctx);
            }
            else if (errmsg != null)
            {
                errmsg = null;
            }

            Debug.Assert((rc & (RC)ctx.ErrMask) == rc);
            MutexEx.Leave(ctx.Mutex);
            return(rc);
        }