public static RC Prepare_(Context ctx, string sql, int bytes, bool isPrepareV2, Vdbe reprepare, ref Vdbe stmtOut, ref string tailOut) { stmtOut = null; tailOut = null; string errMsg = null; // Error message RC rc = RC.OK; int i; // Allocate the parsing context Parse parse = new Parse(); // Parsing context if (parse == null) { rc = RC.NOMEM; goto end_prepare; } parse.Reprepare = reprepare; parse.LastToken.data = null; //: C#? Debug.Assert(tailOut == null); Debug.Assert(!ctx.MallocFailed); Debug.Assert(MutexEx.Held(ctx.Mutex)); // Check to verify that it is possible to get a read lock on all database schemas. The inability to get a read lock indicates that // some other database connection is holding a write-lock, which in turn means that the other connection has made uncommitted changes // to the schema. // // Were we to proceed and prepare the statement against the uncommitted schema changes and if those schema changes are subsequently rolled // back and different changes are made in their place, then when this prepared statement goes to run the schema cookie would fail to detect // the schema change. Disaster would follow. // // This thread is currently holding mutexes on all Btrees (because of the sqlite3BtreeEnterAll() in sqlite3LockAndPrepare()) so it // is not possible for another thread to start a new schema change while this routine is running. Hence, we do not need to hold // locks on the schema, we just need to make sure nobody else is holding them. // // Note that setting READ_UNCOMMITTED overrides most lock detection, but it does *not* override schema lock detection, so this all still // works even if READ_UNCOMMITTED is set. for (i = 0; i < ctx.DBs.length; i++) { Btree bt = ctx.DBs[i].Bt; if (bt != null) { Debug.Assert(bt.HoldsMutex()); rc = bt.SchemaLocked(); if (rc != 0) { string dbName = ctx.DBs[i].Name; sqlite3Error(ctx, rc, "database schema is locked: %s", dbName); C.ASSERTCOVERAGE((ctx.Flags & Context.FLAG.ReadUncommitted) != 0); goto end_prepare; } } } VTable.UnlockList(ctx); parse.Ctx = ctx; parse.QueryLoops = (double)1; if (bytes >= 0 && (bytes == 0 || sql[bytes - 1] != 0)) { int maxLen = ctx.aLimit[SQLITE_LIMIT_SQL_LENGTH]; C.ASSERTCOVERAGE(bytes == maxLen); C.ASSERTCOVERAGE(bytes == maxLen + 1); if (bytes > maxLen) { sqlite3Error(ctx, RC.TOOBIG, "statement too long"); rc = SysEx.ApiExit(ctx, RC.TOOBIG); goto end_prepare; } string sqlCopy = sql.Substring(0, bytes); if (sqlCopy != null) { parse.RunParser(sqlCopy, ref errMsg); C._tagfree(ctx, ref sqlCopy); parse.Tail = null; //: &sql[parse->Tail - sqlCopy]; } else { parse.Tail = null; //: &sql[bytes]; } } else { parse.RunParser(sql, ref errMsg); } Debug.Assert((int)parse.QueryLoops == 1); if (ctx.MallocFailed) { parse.RC = RC.NOMEM; } if (parse.RC == RC.DONE) { parse.RC = RC.OK; } if (parse.CheckSchema != 0) { SchemaIsValid(parse); } if (ctx.MallocFailed) { parse.RC = RC.NOMEM; } tailOut = (parse.Tail == null ? null : parse.Tail.ToString()); rc = parse.RC; Vdbe v = parse.V; #if !OMIT_EXPLAIN if (rc == RC.OK && parse.V != null && parse.Explain != 0) { int first, max; if (parse.Explain == 2) { v.SetNumCols(4); first = 8; max = 12; } else { v.SetNumCols(8); first = 0; max = 8; } for (i = first; i < max; i++) { v.SetColName(i - first, COLNAME_NAME, _colName[i], C.DESTRUCTOR_STATIC); } } #endif Debug.Assert(!ctx.Init.Busy || !isPrepareV2); if (!ctx.Init.Busy) { Vdbe.SetSql(v, sql, (int)(sql.Length - (parse.Tail == null ? 0 : parse.Tail.Length)), isPrepareV2); } if (v != null && (rc != RC.OK || ctx.MallocFailed)) { v.Finalize(); Debug.Assert(stmtOut == null); } else { stmtOut = v; } if (errMsg != null) { sqlite3Error(ctx, rc, "%s", errMsg); C._tagfree(ctx, ref errMsg); } else { sqlite3Error(ctx, rc, null); } // Delete any TriggerPrg structures allocated while parsing this statement. while (parse.TriggerPrg != null) { TriggerPrg t = parse.TriggerPrg; parse.TriggerPrg = t.Next; C._tagfree(ctx, ref t); } end_prepare: //sqlite3StackFree( db, pParse ); rc = SysEx.ApiExit(ctx, rc); Debug.Assert((RC)((int)rc & ctx.ErrMask) == rc); return(rc); }