current commit
261029.4 · sqlite: cache the SQLTagStore constructor template
#include "node_sqlite.h"
#include <path.h>
#include "base_object-inl.h"
#include "debug_utils-inl.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "node.h"
#include "node_diagnostics_channel.h"
#include "node_errors.h"
#include "node_external_reference.h"
#include "node_mem-inl.h"
#include "node_url.h"
#include "simdutf.h"
#include "sqlite3.h"
#include "threadpoolwork-inl.h"
#include "util-inl.h"

#include <array>
#include <charconv>
#include <cinttypes>
#include <cmath>
#include <cstring>
#include <limits>
#include <string>
#include <string_view>

namespace node {
namespace sqlite {

using v8::Array;
using v8::ArrayBuffer;
using v8::BackingStore;
using v8::BackingStoreInitializationMode;
using v8::BackingStoreOnFailureMode;
using v8::BigInt;
using v8::Boolean;
using v8::ConstructorBehavior;
using v8::Context;
using v8::DictionaryTemplate;
using v8::DontDelete;
using v8::EscapableHandleScope;
using v8::Exception;
using v8::Function;
using v8::FunctionCallback;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::Global;
using v8::HandleScope;
using v8::Int32;
using v8::Integer;
using v8::Intercepted;
using v8::Isolate;
using v8::JustVoid;
using v8::Local;
using v8::LocalVector;
using v8::Maybe;
using v8::MaybeLocal;
using v8::Name;
using v8::NamedPropertyHandlerConfiguration;
using v8::NewStringType;
using v8::Nothing;
using v8::Null;
using v8::Number;
using v8::Object;
using v8::ObjectTemplate;
using v8::Promise;
using v8::PropertyCallbackInfo;
using v8::PropertyHandlerFlags;
using v8::SideEffectType;
using v8::String;
using v8::TryCatch;
using v8::Uint8Array;
using v8::Value;

inline MaybeLocal<String> Utf8StringMaybeOneByte(Isolate* isolate,
                                                 std::string_view input) {
  // SQLITE_MAX_LENGTH exceeds String::kMaxLength, and V8 returns an empty
  // handle without throwing. Raise the error here or the value is dropped.
  if (input.size() > static_cast<size_t>(String::kMaxLength)) [[unlikely]] {
    isolate->ThrowException(node::ERR_STRING_TOO_LONG(isolate));
    return MaybeLocal<String>();
  }

  const int len = static_cast<int>(input.size());
  if (simdutf::validate_ascii(input.data(), input.size())) {
    return String::NewFromOneByte(
        isolate,
        reinterpret_cast<const uint8_t*>(input.data()),
        NewStringType::kNormal,
        len);
  }
  return String::NewFromUtf8(
      isolate, input.data(), NewStringType::kNormal, len);
}

BindingData::BindingData(Realm* realm, Local<Object> wrap)
    : BaseObject(realm, wrap) {
  MakeWeak();
}

void BindingData::MemoryInfo(MemoryTracker* tracker) const {
  tracker->TrackFieldWithSize("open_databases",
                              open_databases.size() * sizeof(Database*),
                              "open_databases");
}

void BindingData::CreatePerContextProperties(Local<Object> target,
                                             Local<Value> unused,
                                             Local<Context> context,
                                             void* priv) {
  Realm* realm = Realm::GetCurrent(context);
  Environment* env = realm->env();
  Realm* principal = env->principal_realm();

  if (principal->GetBindingData<BindingData>() != nullptr) return;

  principal->AddBindingData<BindingData>(target);
}

inline MaybeLocal<Value> IntegerToValue(Isolate* isolate,
                                        sqlite3_int64 value,
                                        bool use_big_ints) {
  if (use_big_ints) {
    return BigInt::New(isolate, value);
  }

  if (value < -kMaxSafeJsInteger || value > kMaxSafeJsInteger) {
    THROW_ERR_OUT_OF_RANGE(
        isolate,
        "Value is too large to be represented as a JavaScript number: %" PRId64,
        value);
    return MaybeLocal<Value>();
  }

  return Number::New(isolate, value);
}

#define CHECK_ERROR_OR_THROW(isolate, db, expr, expected, ret)                 \
  do {                                                                         \
    int r_ = (expr);                                                           \
    if (r_ != (expected)) {                                                    \
      THROW_ERR_SQLITE_ERROR((isolate), (db));                                 \
      return (ret);                                                            \
    }                                                                          \
  } while (0)

#define RESET_OR_THROW(isolate, db, stmt, ret)                                 \
  CHECK_ERROR_OR_THROW((isolate), (db), sqlite3_reset((stmt)), SQLITE_OK, (ret))

// Surface deferred SQLite errors that sqlite3_reset() returns from the prior
// sqlite3_step(). Disables the safety-net reset guard via |needs_reset|.
#define RESET_AND_CHECK(isolate, db, stmt, needs_reset, ret)                   \
  do {                                                                         \
    (needs_reset) = false;                                                     \
    RESET_OR_THROW((isolate), (db), (stmt), (ret));                            \
  } while (0)

#define THROW_AND_RETURN_ON_BAD_STATE(env, condition, msg)                     \
  do {                                                                         \
    if ((condition)) {                                                         \
      THROW_ERR_INVALID_STATE((env), (msg));                                   \
      return;                                                                  \
    }                                                                          \
  } while (0)

// SQLite requires that an authorizer callback not modify the connection that
// invoked it. Preparing and stepping statements both count as modifying it.
// See https://www.sqlite.org/c3ref/set_authorizer.html.
#define THROW_AND_RETURN_IF_IN_AUTHORIZER(env, db)                             \
  THROW_AND_RETURN_ON_BAD_STATE(                                               \
      (env),                                                                   \
      (db)->IsInAuthorizerCallback(),                                          \
      "database cannot be accessed from an authorizer callback")

// Finalizing a busy statement from an authorizer can release its locks and
// change the outer statement's outcome.
#define THROW_AND_RETURN_IF_BUSY_IN_AUTHORIZER(env, stmt)                      \
  THROW_AND_RETURN_ON_BAD_STATE(                                               \
      (env),                                                                   \
      (stmt)->db_->IsInAuthorizerCallback() &&                                 \
          sqlite3_stmt_busy((stmt)->statement_.get()),                         \
      "database cannot be accessed from an authorizer callback")

// SQLite's session module reaches back into JavaScript from inside the
// pre-update hook, while it is still walking the connection's session list and
// reading the table it found there. Deleting a session frees memory that walk
// is still using, so no callback may close one.
#define THROW_AND_RETURN_IF_SESSION_IN_CALLBACK(env, session)                  \
  THROW_AND_RETURN_ON_BAD_STATE(                                               \
      (env),                                                                   \
      (session)->database_->IsInCallback(),                                    \
      "session cannot be closed while in a callback")

// A statement's virtual machine cannot be reentered while sqlite3_step() is
// running it. Finalizing it frees the VM outright, and re-running it resets the
// VM mid-execution; both are use-after-free rather than merely a contract
// violation. Callbacks that SQLite invokes during execution are therefore
// barred from reaching the statement being stepped, though other statements on
// the connection stay usable.
#define THROW_AND_RETURN_IF_STEPPING(env, stmt)                                \
  THROW_AND_RETURN_ON_BAD_STATE(                                               \
      (env),                                                                   \
      (stmt)->db_->IsSteppingStatement((stmt)->statement_.get()),              \
      "statement is already being executed")

#define SQLITE_VALUE_TO_JS(from, isolate, use_big_int_args, result, ...)       \
  do {                                                                         \
    switch (sqlite3_##from##_type(__VA_ARGS__)) {                              \
      case SQLITE_INTEGER: {                                                   \
        sqlite3_int64 val = sqlite3_##from##_int64(__VA_ARGS__);               \
        (result) = IntegerToValue((isolate), val, (use_big_int_args));         \
        break;                                                                 \
      }                                                                        \
      case SQLITE_FLOAT: {                                                     \
        (result) =                                                             \
            Number::New((isolate), sqlite3_##from##_double(__VA_ARGS__));      \
        break;                                                                 \
      }                                                                        \
      case SQLITE_TEXT: {                                                      \
        const char* v =                                                        \
            reinterpret_cast<const char*>(sqlite3_##from##_text(__VA_ARGS__)); \
        if (v == nullptr) [[unlikely]] {                                       \
          THROW_ERR_MEMORY_ALLOCATION_FAILED((isolate));                       \
          break;                                                               \
        }                                                                      \
        const int v_len = sqlite3_##from##_bytes(__VA_ARGS__);                 \
        (result) =                                                             \
            Utf8StringMaybeOneByte((isolate), std::string_view(v, v_len))      \
                .As<Value>();                                                  \
        break;                                                                 \
      }                                                                        \
      case SQLITE_NULL: {                                                      \
        (result) = Null((isolate));                                            \
        break;                                                                 \
      }                                                                        \
      case SQLITE_BLOB: {                                                      \
        size_t size =                                                          \
            static_cast<size_t>(sqlite3_##from##_bytes(__VA_ARGS__));          \
        auto data = reinterpret_cast<const uint8_t*>(                          \
            sqlite3_##from##_blob(__VA_ARGS__));                               \
        auto store = ArrayBuffer::NewBackingStore(                             \
            (isolate), size, BackingStoreInitializationMode::kUninitialized);  \
        if (data != nullptr) [[likely]] {                                      \
          memcpy(store->Data(), data, size);                                   \
        }                                                                      \
        auto ab = ArrayBuffer::New((isolate), std::move(store));               \
        (result) = Uint8Array::New(ab, 0, size);                               \
        break;                                                                 \
      }                                                                        \
      default:                                                                 \
        UNREACHABLE("Bad SQLite value");                                       \
    }                                                                          \
  } while (0)

namespace {
Local<DictionaryTemplate> getLazyIterTemplate(Environment* env) {
  auto iter_template = env->iter_template();
  if (iter_template.IsEmpty()) {
    static constexpr std::string_view iter_keys[] = {"done", "value"};
    iter_template = DictionaryTemplate::New(env->isolate(), iter_keys);
    env->set_iter_template(iter_template);
  }
  return iter_template;
}
}  // namespace

// Helper function to look up a mapping entry by its JS-facing name
template <typename T, size_t N>
static constexpr const T* FindByJsName(const std::array<T, N>& mapping,
                                       std::string_view name) {
  for (const auto& info : mapping) {
    if (name == info.js_name) {
      return &info;
    }
  }
  return nullptr;
}

namespace {
MaybeLocal<Object> CreateSQLiteErrorImpl(Isolate* isolate,
                                         const char* message,
                                         const char* errstr,
                                         int errcode) {
  Environment* env = Environment::GetCurrent(isolate);
  Local<Context> context = isolate->GetCurrentContext();
  Local<String> js_msg;
  Local<Object> e;
  // SQLite error messages embed the offending identifier or token, so they
  // can exceed String::kMaxLength.
  if (!Utf8StringMaybeOneByte(isolate, message).ToLocal(&js_msg) ||
      !Exception::Error(js_msg)->ToObject(context).ToLocal(&e) ||
      e->Set(context, env->code_string(), env->err_sqlite_error_string())
          .IsNothing()) {
    return MaybeLocal<Object>();
  }

  if (errstr != nullptr) {
    Local<String> js_errstr;
    if (!String::NewFromUtf8(isolate, errstr).ToLocal(&js_errstr) ||
        e->Set(context, env->errcode_string(), Integer::New(isolate, errcode))
            .IsNothing() ||
        e->Set(context, env->errstr_string(), js_errstr).IsNothing()) {
      return MaybeLocal<Object>();
    }
  }
  return e;
}
}  // namespace

inline MaybeLocal<Object> CreateSQLiteError(Isolate* isolate,
                                            const char* message) {
  return CreateSQLiteErrorImpl(isolate, message, nullptr, 0);
}

inline MaybeLocal<Object> CreateSQLiteError(Isolate* isolate, int errcode) {
  const char* errstr = sqlite3_errstr(errcode);
  return CreateSQLiteErrorImpl(isolate, errstr, errstr, errcode);
}

inline MaybeLocal<Object> CreateSQLiteError(Isolate* isolate, sqlite3* db) {
  int errcode = sqlite3_extended_errcode(db);
  return CreateSQLiteErrorImpl(
      isolate, sqlite3_errmsg(db), sqlite3_errstr(errcode), errcode);
}

void JSValueToSQLiteResult(Isolate* isolate,
                           sqlite3_context* ctx,
                           Local<Value> value) {
  if (value->IsNullOrUndefined()) {
    sqlite3_result_null(ctx);
  } else if (value->IsNumber()) {
    sqlite3_result_double(ctx, value.As<Number>()->Value());
  } else if (value->IsString()) {
    Utf8Value val(isolate, value.As<String>());
    sqlite3_result_text(ctx, *val, val.length(), SQLITE_TRANSIENT);
  } else if (value->IsArrayBufferView() || value->IsArrayBuffer() ||
             value->IsSharedArrayBuffer()) {
    ArrayBufferViewContents<uint8_t> buf(value);
    sqlite3_result_blob(ctx, buf.data(), buf.length(), SQLITE_TRANSIENT);
  } else if (value->IsBigInt()) {
    bool lossless;
    int64_t as_int = value.As<BigInt>()->Int64Value(&lossless);
    if (!lossless) {
      sqlite3_result_error(ctx, "BigInt value is too large for SQLite", -1);
      return;
    }
    sqlite3_result_int64(ctx, as_int);
  } else if (value->IsPromise()) {
    sqlite3_result_error(
        ctx, "Asynchronous user-defined functions are not supported", -1);
  } else {
    sqlite3_result_error(
        ctx,
        "Returned JavaScript value cannot be converted to a SQLite value",
        -1);
  }
}

class Database;

inline void THROW_ERR_SQLITE_ERROR(Isolate* isolate, Database* db) {
  if (db->ShouldIgnoreSQLiteError()) {
    db->SetIgnoreNextSQLiteError(false);
    // Suppression that swallows no pending exception would also swallow the
    // SQLite error, reporting a failed statement as a success.
    if (isolate->HasPendingException()) {
      return;
    }
  }

  Local<Object> e;
  if (CreateSQLiteError(isolate, db->Connection()).ToLocal(&e)) {
    isolate->ThrowException(e);
  }
}

inline void THROW_ERR_SQLITE_ERROR(Isolate* isolate, const char* message) {
  Local<Object> e;
  if (CreateSQLiteError(isolate, message).ToLocal(&e)) {
    isolate->ThrowException(e);
  }
}

inline void THROW_ERR_SQLITE_ERROR(Isolate* isolate, int errcode) {
  const char* errstr = sqlite3_errstr(errcode);

  Environment* env = Environment::GetCurrent(isolate);
  Local<Object> error;
  if (CreateSQLiteError(isolate, errstr).ToLocal(&error) &&
      error
          ->Set(isolate->GetCurrentContext(),
                env->errcode_string(),
                Integer::New(isolate, errcode))
          .IsJust()) {
    isolate->ThrowException(error);
  }
}

inline MaybeLocal<Value> NullableSQLiteStringToValue(Isolate* isolate,
                                                     const char* str) {
  if (str == nullptr) {
    return Null(isolate);
  }

  // With the default length of -1, V8 aborts on strings over kMaxLength.
  const size_t len = strlen(str);
  if (len > static_cast<size_t>(String::kMaxLength)) [[unlikely]] {
    isolate->ThrowException(node::ERR_STRING_TOO_LONG(isolate));
    return MaybeLocal<Value>();
  }

  return String::NewFromUtf8(
             isolate, str, NewStringType::kInternalized, static_cast<int>(len))
      .As<Value>();
}

class CustomAggregate {
 public:
  explicit CustomAggregate(Environment* env,
                           BaseObjectWeakPtr<Database> db,
                           bool use_bigint_args,
                           Local<Value> start,
                           Local<Function> step_fn,
                           Local<Function> inverse_fn,
                           Local<Function> result_fn)
      : env_(env),
        db_(std::move(db)),
        use_bigint_args_(use_bigint_args),
        start_(env->isolate(), start),
        step_fn_(env->isolate(), step_fn),
        inverse_fn_(env->isolate(), inverse_fn),
        result_fn_(env->isolate(), result_fn) {
    db_->user_defined_functions_.insert(this);
  }

  ~CustomAggregate() {
    if (db_) db_->user_defined_functions_.erase(this);
  }

  static void xStep(sqlite3_context* ctx, int argc, sqlite3_value** argv) {
    xStepBase(ctx, argc, argv, &CustomAggregate::step_fn_);
  }

  static void xInverse(sqlite3_context* ctx, int argc, sqlite3_value** argv) {
    xStepBase(ctx, argc, argv, &CustomAggregate::inverse_fn_);
  }

  static void xFinal(sqlite3_context* ctx) { xValueBase(ctx, true); }

  static void xValue(sqlite3_context* ctx) { xValueBase(ctx, false); }

  static void xDestroy(void* self) {
    delete static_cast<CustomAggregate*>(self);
  }

 private:
  struct aggregate_data {
    Global<Value> value;
    bool initialized;
    bool is_window;
  };

  static inline void xStepBase(sqlite3_context* ctx,
                               int argc,
                               sqlite3_value** argv,
                               Global<Function> CustomAggregate::*mptr) {
    CustomAggregate* self =
        static_cast<CustomAggregate*>(sqlite3_user_data(ctx));
    CallbackDepthGuard guard(self->db_.get());
    Environment* env = self->env_;
    Isolate* isolate = env->isolate();
    auto agg = self->GetAggregate(ctx);

    if (!agg) {
      return;
    }

    auto recv = Undefined(isolate);
    LocalVector<Value> js_argv(isolate);
    js_argv.reserve(argc + 1);

    js_argv.emplace_back(Local<Value>::New(isolate, agg->value));

    for (int i = 0; i < argc; ++i) {
      sqlite3_value* value = argv[i];
      MaybeLocal<Value> js_val;
      SQLITE_VALUE_TO_JS(value, isolate, self->use_bigint_args_, js_val, value);
      if (js_val.IsEmpty()) {
        // Ignore the SQLite error because a JavaScript exception is pending.
        self->db_->SetIgnoreNextSQLiteError(true);
        sqlite3_result_error(ctx, "", 0);
        return;
      }

      Local<Value> local;
      if (!js_val.ToLocal(&local)) {
        // Ignore the SQLite error because a JavaScript exception is pending.
        self->db_->SetIgnoreNextSQLiteError(true);
        sqlite3_result_error(ctx, "", 0);
        return;
      }

      js_argv.emplace_back(local);
    }

    Local<Value> ret;
    if (!(self->*mptr)
             .Get(isolate)
             ->Call(env->context(), recv, argc + 1, js_argv.data())
             .ToLocal(&ret)) {
      self->db_->SetIgnoreNextSQLiteError(true);
      sqlite3_result_error(ctx, "", 0);
      return;
    }

    if (!self->db_->IsOpen()) {
      THROW_ERR_INVALID_STATE(env, "database is not open");
      return;
    }

    agg->value.Reset(isolate, ret);
  }

  static inline void xValueBase(sqlite3_context* ctx, bool is_final) {
    CustomAggregate* self =
        static_cast<CustomAggregate*>(sqlite3_user_data(ctx));
    CallbackDepthGuard guard(self->db_.get());
    Environment* env = self->env_;
    Isolate* isolate = env->isolate();
    auto agg = self->GetAggregate(ctx);

    if (!agg) {
      return;
    }

    if (!is_final) {
      agg->is_window = true;
    } else if (agg->is_window) {
      DestroyAggregateData(ctx);
      return;
    }

    Local<Value> result;
    if (!self->result_fn_.IsEmpty()) {
      Local<Function> fn =
          Local<Function>::New(env->isolate(), self->result_fn_);
      Local<Value> js_arg[] = {Local<Value>::New(isolate, agg->value)};

      if (!fn->Call(env->context(), Null(isolate), 1, js_arg)
               .ToLocal(&result)) {
        self->db_->SetIgnoreNextSQLiteError(true);
        sqlite3_result_error(ctx, "", 0);
      } else if (!self->db_->IsOpen()) {
        THROW_ERR_INVALID_STATE(env, "database is not open");
        return;
      }
    } else {
      result = Local<Value>::New(isolate, agg->value);
    }

    if (!result.IsEmpty()) {
      JSValueToSQLiteResult(isolate, ctx, result);
    }

    if (is_final) {
      DestroyAggregateData(ctx);
    }
  }

  static void DestroyAggregateData(sqlite3_context* ctx) {
    aggregate_data* agg = static_cast<aggregate_data*>(
        sqlite3_aggregate_context(ctx, sizeof(aggregate_data)));
    CHECK(agg->initialized);
    agg->value.Reset();
  }

  aggregate_data* GetAggregate(sqlite3_context* ctx) {
    aggregate_data* agg = static_cast<aggregate_data*>(
        sqlite3_aggregate_context(ctx, sizeof(aggregate_data)));
    if (!agg->initialized) {
      Isolate* isolate = env_->isolate();
      Local<Value> start_v = Local<Value>::New(isolate, start_);
      if (start_v->IsFunction()) {
        auto fn = start_v.As<Function>();
        MaybeLocal<Value> retval =
            fn->Call(env_->context(), Null(isolate), 0, nullptr);
        if (!db_->IsOpen()) {
          THROW_ERR_INVALID_STATE(env_, "database is not open");
          return nullptr;
        }
        if (!retval.ToLocal(&start_v)) {
          db_->SetIgnoreNextSQLiteError(true);
          sqlite3_result_error(ctx, "", 0);
          return nullptr;
        }
      }

      agg->value.Reset(env_->isolate(), start_v);
      agg->initialized = true;
    }

    return agg;
  }

  Environment* env_;
  BaseObjectWeakPtr<Database> db_;
  bool use_bigint_args_;
  Global<Value> start_;
  Global<Function> step_fn_;
  Global<Function> inverse_fn_;
  Global<Function> result_fn_;
};

class BackupJob : public ThreadPoolWork {
 public:
  explicit BackupJob(Environment* env,
                     Database* source,
                     Local<Promise::Resolver> resolver,
                     std::string source_db,
                     std::string destination_name,
                     std::string dest_db,
                     int pages,
                     Local<Function> progressFunc)
      : ThreadPoolWork(env, "node_sqlite3.BackupJob"),
        env_(env),
        source_(source),
        pages_(pages),
        source_db_(std::move(source_db)),
        destination_name_(std::move(destination_name)),
        dest_db_(std::move(dest_db)) {
    resolver_.Reset(env->isolate(), resolver);
    progressFunc_.Reset(env->isolate(), progressFunc);
  }

  void ScheduleBackup() {
    Isolate* isolate = env()->isolate();
    HandleScope handle_scope(isolate);
    sqlite3* dest_raw = nullptr;
    backup_status_ = sqlite3_open_v2(
        destination_name_.c_str(),
        &dest_raw,
        SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI,
        nullptr);
    dest_.reset(dest_raw);
    Local<Promise::Resolver> resolver =
        Local<Promise::Resolver>::New(env()->isolate(), resolver_);
    if (backup_status_ != SQLITE_OK) {
      Settle(resolver, CreateSQLiteError(isolate, dest_.get()), {});
      return;
    }

    backup_.reset(sqlite3_backup_init(dest_.get(),
                                      dest_db_.c_str(),
                                      source_->Connection(),
                                      source_db_.c_str()));
    if (!backup_) {
      Settle(resolver, CreateSQLiteError(isolate, dest_.get()), {});
      return;
    }

    this->ScheduleWork();
  }

  void DoThreadPoolWork() override {
    backup_status_ = sqlite3_backup_step(backup_.get(), pages_);
  }

  void AfterThreadPoolWork(int status) override {
    Isolate* isolate = env()->isolate();
    HandleScope handle_scope(isolate);
    Context::Scope context_scope(env()->context());
    InternalCallbackScope callback_scope(
        env(), Object::New(isolate), {0, 0}, InternalCallbackScope::kNoFlags);
    Local<Promise::Resolver> resolver =
        Local<Promise::Resolver>::New(isolate, resolver_);

    if (!(backup_status_ == SQLITE_OK || backup_status_ == SQLITE_DONE ||
          backup_status_ == SQLITE_BUSY || backup_status_ == SQLITE_LOCKED)) {
      Settle(resolver, CreateSQLiteError(isolate, backup_status_), {});
      return;
    }

    if (backup_status_ == SQLITE_BUSY || backup_status_ == SQLITE_LOCKED) {
      this->ScheduleWork();
      return;
    }

    int total_pages = sqlite3_backup_pagecount(backup_.get());
    int remaining_pages = sqlite3_backup_remaining(backup_.get());
    if (remaining_pages != 0) {
      Local<Function> fn =
          Local<Function>::New(env()->isolate(), progressFunc_);
      if (!fn.IsEmpty()) {
        Local<Object> progress_info = Object::New(env()->isolate());
        if (progress_info
                ->Set(env()->context(),
                      env()->total_pages_string(),
                      Integer::New(env()->isolate(), total_pages))
                .IsNothing() ||
            progress_info
                ->Set(env()->context(),
                      env()->remaining_pages_string(),
                      Integer::New(env()->isolate(), remaining_pages))
                .IsNothing()) {
          Settle(resolver, {}, {});
          return;
        }

        Local<Value> argv[] = {progress_info};
        TryCatch try_catch(env()->isolate());
        USE(fn->Call(env()->context(), Null(env()->isolate()), 1, argv));
        if (try_catch.HasCaught()) {
          Settle(resolver, try_catch.Exception(), {});
          return;
        }
      }

      // There's still work to do
      this->ScheduleWork();
      return;
    }

    if (backup_status_ != SQLITE_DONE) {
      Settle(resolver, CreateSQLiteError(isolate, dest_.get()), {});
      return;
    }

    Settle(resolver, {}, Integer::New(isolate, total_pages));
  }

  void Finalize() {
    Cleanup();
    if (source_) {
      source_->RemoveBackup(this);
      source_.reset();
    }
  }

  void Cleanup() {
    backup_.reset();

    if (dest_) {
      backup_status_ = sqlite3_errcode(dest_.get());
      dest_.reset();
    }
  }

 private:
  // The single exit point of a backup. Rejects with `error` if it is set,
  // otherwise resolves with `value` if it is set. Neither being set means an
  // exception is already pending. Deletes the job.
  void Settle(Local<Promise::Resolver> resolver,
              MaybeLocal<Value> error,
              Local<Value> value) {
    Finalize();
    Local<Value> e;
    if (error.ToLocal(&e)) {
      resolver->Reject(env()->context(), e).ToChecked();
    } else if (!value.IsEmpty()) {
      resolver->Resolve(env()->context(), value).ToChecked();
    }
    delete this;
  }

  Environment* env() const { return env_; }

  Environment* env_;
  BaseObjectPtr<Database> source_;
  Global<Promise::Resolver> resolver_;
  Global<Function> progressFunc_;
  ConnectionPtr dest_;
  BackupPtr backup_;
  int pages_;
  int backup_status_ = SQLITE_OK;
  std::string source_db_;
  std::string destination_name_;
  std::string dest_db_;
};

UserDefinedFunction::UserDefinedFunction(Environment* env,
                                         Local<Function> fn,
                                         BaseObjectWeakPtr<Database> db,
                                         bool use_bigint_args)
    : env_(env),
      fn_(env->isolate(), fn),
      db_(std::move(db)),
      use_bigint_args_(use_bigint_args) {
  db_->user_defined_functions_.insert(this);
}

UserDefinedFunction::~UserDefinedFunction() {
  if (db_) db_->user_defined_functions_.erase(this);
}

void UserDefinedFunction::xFunc(sqlite3_context* ctx,
                                int argc,
                                sqlite3_value** argv) {
  UserDefinedFunction* self =
      static_cast<UserDefinedFunction*>(sqlite3_user_data(ctx));
  CallbackDepthGuard guard(self->db_.get());
  Environment* env = self->env_;
  Isolate* isolate = env->isolate();
  auto recv = Undefined(isolate);
  auto fn = self->fn_.Get(isolate);
  LocalVector<Value> js_argv(isolate);
  js_argv.reserve(argc);

  for (int i = 0; i < argc; ++i) {
    sqlite3_value* value = argv[i];
    MaybeLocal<Value> js_val = MaybeLocal<Value>();
    SQLITE_VALUE_TO_JS(value, isolate, self->use_bigint_args_, js_val, value);
    if (js_val.IsEmpty()) {
      // Ignore the SQLite error because a JavaScript exception is pending.
      self->db_->SetIgnoreNextSQLiteError(true);
      sqlite3_result_error(ctx, "", 0);
      return;
    }

    Local<Value> local;
    if (!js_val.ToLocal(&local)) {
      // Ignore the SQLite error because a JavaScript exception is pending.
      self->db_->SetIgnoreNextSQLiteError(true);
      sqlite3_result_error(ctx, "", 0);
      return;
    }

    js_argv.emplace_back(local);
  }

  MaybeLocal<Value> retval =
      fn->Call(env->context(), recv, argc, js_argv.data());

  if (!self->db_->IsOpen()) {
    THROW_ERR_INVALID_STATE(env, "database is not open");
    return;
  }

  Local<Value> result;
  if (!retval.ToLocal(&result)) {
    // Ignore the SQLite error because a JavaScript exception is pending.
    self->db_->SetIgnoreNextSQLiteError(true);
    sqlite3_result_error(ctx, "", 0);
    return;
  }

  JSValueToSQLiteResult(isolate, ctx, result);
}

void UserDefinedFunction::xDestroy(void* self) {
  delete static_cast<UserDefinedFunction*>(self);
}

DatabaseLimits::DatabaseLimits(Environment* env,
                               Local<Object> object,
                               BaseObjectWeakPtr<Database> database)
    : BaseObject(env, object), database_(std::move(database)) {
  MakeWeak();
}

DatabaseLimits::~DatabaseLimits() = default;

void DatabaseLimits::MemoryInfo(MemoryTracker* tracker) const {
  tracker->TrackField("database", database_);
}

Local<ObjectTemplate> DatabaseLimits::GetTemplate(Environment* env) {
  Local<ObjectTemplate> tmpl = env->sqlite_limits_template();
  if (!tmpl.IsEmpty()) return tmpl;

  Isolate* isolate = env->isolate();
  tmpl = ObjectTemplate::New(isolate);
  tmpl->SetInternalFieldCount(DatabaseLimits::kInternalFieldCount);
  tmpl->SetHandler(NamedPropertyHandlerConfiguration(
      LimitsGetter,
      LimitsSetter,
      LimitsQuery,
      nullptr,  // deleter - not allowed
      LimitsEnumerator,
      nullptr,  // definer
      nullptr,
      Local<Value>(),
      PropertyHandlerFlags::kHasNoSideEffect));

  env->set_sqlite_limits_template(tmpl);
  return tmpl;
}

Intercepted DatabaseLimits::LimitsGetter(
    Local<Name> property, const PropertyCallbackInfo<Value>& info) {
  // Skip symbols
  if (!property->IsString()) {
    return Intercepted::kNo;
  }

  DatabaseLimits* limits;
  ASSIGN_OR_RETURN_UNWRAP(&limits, info.Holder(), Intercepted::kNo);

  Environment* env = limits->env();
  Isolate* isolate = env->isolate();

  Utf8Value prop_name(isolate, property);
  const LimitInfo* limit_info =
      FindByJsName(kLimitMapping, prop_name.ToStringView());

  if (limit_info == nullptr) {
    return Intercepted::kNo;  // Unknown property, let default handling occur
  }

  if (!limits->database_ || !limits->database_->IsOpen()) {
    THROW_ERR_INVALID_STATE(env, "database is not open");
    return Intercepted::kYes;
  }

  int current_value = sqlite3_limit(
      limits->database_->Connection(), limit_info->sqlite_limit_id, -1);
  info.GetReturnValue().Set(Integer::New(isolate, current_value));
  return Intercepted::kYes;
}

Intercepted DatabaseLimits::LimitsSetter(
    Local<Name> property,
    Local<Value> value,
    const PropertyCallbackInfo<Boolean>& info) {
  if (!property->IsString()) {
    return Intercepted::kNo;
  }

  DatabaseLimits* limits;
  ASSIGN_OR_RETURN_UNWRAP(&limits, info.Holder(), Intercepted::kNo);

  Environment* env = limits->env();
  Isolate* isolate = env->isolate();

  Utf8Value prop_name(isolate, property);
  const LimitInfo* limit_info =
      FindByJsName(kLimitMapping, prop_name.ToStringView());

  if (limit_info == nullptr) {
    return Intercepted::kNo;
  }

  if (!limits->database_ || !limits->database_->IsOpen()) {
    THROW_ERR_INVALID_STATE(env, "database is not open");
    return Intercepted::kYes;
  }

  if (limits->database_->IsInAuthorizerCallback()) {
    THROW_ERR_INVALID_STATE(
        env, "database cannot be accessed from an authorizer callback");
    return Intercepted::kYes;
  }

  if (!value->IsNumber()) {
    THROW_ERR_INVALID_ARG_TYPE(
        isolate, "Limit value must be a non-negative integer or Infinity.");
    return Intercepted::kYes;
  }

  const double num_value = value.As<Number>()->Value();
  int new_value;

  if (std::isinf(num_value) && num_value > 0) {
    // Positive Infinity resets the limit to the compile-time maximum
    new_value = std::numeric_limits<int>::max();
  } else if (!value->IsInt32()) {
    THROW_ERR_INVALID_ARG_TYPE(
        isolate, "Limit value must be a non-negative integer or Infinity.");
    return Intercepted::kYes;
  } else {
    new_value = value.As<Int32>()->Value();
    if (new_value < 0) {
      THROW_ERR_OUT_OF_RANGE(isolate, "Limit value must be non-negative.");
      return Intercepted::kYes;
    }
  }

  limits->database_->SetLimit(limit_info->sqlite_limit_id, new_value);
  return Intercepted::kYes;
}

Intercepted DatabaseLimits::LimitsQuery(
    Local<Name> property, const PropertyCallbackInfo<Integer>& info) {
  if (!property->IsString()) {
    return Intercepted::kNo;
  }

  Isolate* isolate = info.GetIsolate();
  Utf8Value prop_name(isolate, property);
  const LimitInfo* limit_info =
      FindByJsName(kLimitMapping, prop_name.ToStringView());

  if (!limit_info) {
    return Intercepted::kNo;
  }

  // Property exists and is writable
  info.GetReturnValue().Set(
      Integer::New(isolate, v8::PropertyAttribute::DontDelete));
  return Intercepted::kYes;
}

void DatabaseLimits::LimitsEnumerator(const PropertyCallbackInfo<Array>& info) {
  Isolate* isolate = info.GetIsolate();
  LocalVector<Value> names(isolate);

  for (const auto& [js_name, sqlite_limit_id] : kLimitMapping) {
    Local<String> name;
    if (!String::NewFromUtf8(
             isolate, js_name.data(), NewStringType::kNormal, js_name.size())
             .ToLocal(&name)) {
      return;
    }
    names.push_back(name);
  }

  info.GetReturnValue().Set(Array::New(isolate, names.data(), names.size()));
}

BaseObjectPtr<DatabaseLimits> DatabaseLimits::Create(
    Environment* env, BaseObjectWeakPtr<Database> database) {
  Local<Object> obj;
  if (!GetTemplate(env)->NewInstance(env->context()).ToLocal(&obj)) {
    return nullptr;
  }

  return MakeBaseObject<DatabaseLimits>(env, obj, std::move(database));
}

// ---------------------------------------------------------------------------
// VirtualTableModule
// ---------------------------------------------------------------------------

VirtualTableModule::VirtualTableModule(Environment* env,
                                       BaseObjectWeakPtr<Database> db,
                                       Local<Function> rows_fn,
                                       std::string&& schema_sql,
                                       int num_columns,
                                       std::vector<int>&& hidden_col_indices,
                                       bool use_bigint_args,
                                       bool direct_only)
    : env_(env),
      db_(std::move(db)),
      rows_fn_(env->isolate(), rows_fn),
      schema_sql_(std::move(schema_sql)),
      num_columns_(num_columns),
      hidden_col_indices_(std::move(hidden_col_indices)),
      use_bigint_args_(use_bigint_args),
      direct_only_(direct_only),
      module_def_({}) {
  // Initialize the sqlite3_module definition. Each VirtualTableModule instance
  // gets its own copy to avoid thread-safety issues with worker threads.
  module_def_.iVersion = 1;
  module_def_.xCreate = VirtualTableModule::xCreate;
  module_def_.xConnect = VirtualTableModule::xCreate;
  module_def_.xBestIndex = VirtualTableModule::xBestIndex;
  module_def_.xDisconnect = VirtualTableModule::xDisconnect;
  module_def_.xDestroy = VirtualTableModule::xDestroy;
  module_def_.xOpen = VirtualTableModule::xOpen;
  module_def_.xClose = VirtualTableModule::xClose;
  module_def_.xFilter = VirtualTableModule::xFilter;
  module_def_.xNext = VirtualTableModule::xNext;
  module_def_.xEof = VirtualTableModule::xEof;
  module_def_.xColumn = VirtualTableModule::xColumn;
  module_def_.xRowid = VirtualTableModule::xRowid;

  // Build mapping from schema column index to row array index.
  // Visible columns are numbered sequentially; hidden columns map to -1.
  col_index_map_.assign(num_columns, 0);
  for (int idx : hidden_col_indices_) {
    col_index_map_[idx] = -1;
  }
  int visible_idx = 0;
  for (int i = 0; i < num_columns; i++) {
    if (col_index_map_[i] < 0) {
      continue;
    }
    col_index_map_[i] = visible_idx++;
  }
}

VirtualTableModule::~VirtualTableModule() {}

int VirtualTableModule::PropagateJSError() {
  if (db_) {
    db_->SetIgnoreNextSQLiteError(true);
  }
  return SQLITE_ERROR;
}

int VirtualTableModule::ReportProtocolError(sqlite3_vtab* vtab,
                                            const char* message) {
  sqlite3_free(vtab->zErrMsg);
  vtab->zErrMsg = sqlite3_mprintf("%s", message);
  return SQLITE_ERROR;
}

bool VirtualTableModule::CanCallIntoJS() const {
  return db_ && !db_->IsInDestructor();
}

bool VirtualTableModule::CloseIterator(NodeVTabCursor* cursor) {
  VirtualTableModule* mod = cursor->module;

  // Skipped in three cases:
  //
  // - While the database is being torn down from a destructor, because those
  //   run from a garbage collection callback where JavaScript cannot be
  //   executed. An abandoned generator does not run `finally` in JavaScript
  //   either, so skipping matches the language.
  // - When an error is already pending, because calling into JavaScript would
  //   discard it and the caller would see an empty result instead of the error.
  //   A generator whose own body threw has already run its `finally` as part of
  //   that throw, so this only affects an iterator abandoned while suspended
  //   because something else failed.
  // - When next() has already reported `done: true`, because the iteration ran
  //   to completion. `return()` is only called on abrupt termination in
  //   `for...of`; a finished iterator must not be asked to clean up again, and
  //   a custom iterable may throw from `return()` even after completion.
  if (cursor->iterator.IsEmpty() || cursor->done || !mod->CanCallIntoJS() ||
      mod->env_->isolate()->HasPendingException()) {
    return false;
  }

  Environment* env = mod->env_;
  Isolate* isolate = env->isolate();
  HandleScope handle_scope(isolate);
  CallbackDepthGuard callback_guard(mod->db_.get());

  // Scoped above the property lookup so a throwing `return` getter is
  // handled the same way as a throwing `return()` method.
  TryCatch try_catch(isolate);
  Local<Object> iterator = cursor->iterator.Get(isolate);
  Local<Value> return_method;
  if (iterator->Get(env->context(), FIXED_ONE_BYTE_STRING(isolate, "return"))
          .ToLocal(&return_method) &&
      return_method->IsFunction()) {
    USE(return_method.As<Function>()->Call(
        env->context(), iterator, 0, nullptr));
  }

  // Re-throw so that a throwing `finally` is not silently discarded.
  if (try_catch.HasCaught() && !try_catch.HasTerminated()) {
    try_catch.ReThrow();
    return true;
  }
  return false;
}

void VirtualTableModule::ReleaseHiddenValues(NodeVTabCursor* cursor) {
  for (sqlite3_value*& value : cursor->hidden_values) {
    if (value != nullptr) {
      sqlite3_value_free(value);
      value = nullptr;
    }
  }
}

int VirtualTableModule::xCreate(sqlite3* db,
                                void* pAux,
                                int argc,
                                const char* const* argv,
                                sqlite3_vtab** ppVTab,
                                char** pzErr) {
  VirtualTableModule* mod = static_cast<VirtualTableModule*>(pAux);

  int rc = sqlite3_declare_vtab(db, mod->schema_sql_.c_str());
  if (rc != SQLITE_OK) {
    *pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(db));
    return rc;
  }

  if (mod->direct_only_) {
    sqlite3_vtab_config(db, SQLITE_VTAB_DIRECTONLY);
  }

  NodeVTab* vtab = new NodeVTab();
  memset(&vtab->base, 0, sizeof(vtab->base));
  vtab->module = mod;
  *ppVTab = &vtab->base;
  return SQLITE_OK;
}

int VirtualTableModule::xBestIndex(sqlite3_vtab* pVTab,
                                   sqlite3_index_info* pInfo) {
  NodeVTab* vtab = reinterpret_cast<NodeVTab*>(pVTab);
  VirtualTableModule* mod = vtab->module;
  int num_hidden = static_cast<int>(mod->hidden_col_indices_.size());
  int argv_index = 0;
  // Comma-separated list of the hidden column indices that received a
  // constraint, in argv order. Passed to xFilter via idxStr so it can map each
  // argv value back to the right parameter. A bitmask in idxNum would cap the
  // number of parameters at the width of an int.
  std::string idx_str;

  // For each hidden column (parameter), look for a usable EQ constraint.
  for (int hidden_idx = 0; hidden_idx < num_hidden; hidden_idx++) {
    int col = mod->hidden_col_indices_[hidden_idx];

    int args_before = argv_index;
    for (int i = 0; i < pInfo->nConstraint; i++) {
      if (pInfo->aConstraint[i].iColumn == col &&
          pInfo->aConstraint[i].usable &&
          pInfo->aConstraint[i].op == SQLITE_INDEX_CONSTRAINT_EQ) {
        argv_index++;
        pInfo->aConstraintUsage[i].argvIndex = argv_index;
        pInfo->aConstraintUsage[i].omit = 1;
        if (!idx_str.empty()) {
          idx_str += ',';
        }
        idx_str += std::to_string(hidden_idx);
        break;
      }
    }

    // No usable constraint means the query supplied a parameter whose value is
    // not available at this point in the plan. Accepting it would pass null to
    // rows() and silently return empty results, so reject the plan and let
    // SQLite pick a different ordering instead.
    // https://www.sqlite.org/vtab.html#enforcing_required_parameters_on_table_valued_functions
    if (argv_index == args_before) {
      for (int i = 0; i < pInfo->nConstraint; i++) {
        if (pInfo->aConstraint[i].iColumn == col &&
            pInfo->aConstraint[i].op == SQLITE_INDEX_CONSTRAINT_EQ) {
          return SQLITE_CONSTRAINT;
        }
      }
    }
  }

  if (!idx_str.empty()) {
    pInfo->idxStr = sqlite3_mprintf("%s", idx_str.c_str());
    if (pInfo->idxStr == nullptr) {
      return SQLITE_NOMEM;
    }
    pInfo->needToFreeIdxStr = 1;
  }

  pInfo->idxNum = argv_index;

  // Each consumed constraint has to make the plan look cheaper, or the planner
  // is free to choose the unconstrained plan and recheck the constraints
  // afterwards. That recheck is what turns an unpicked plan into an empty
  // result for table-valued syntax.
  double estimated_rows = 1000.0;
  for (int i = 0; i < argv_index; i++) {
    estimated_rows /= 10.0;
  }
  estimated_rows = std::max(estimated_rows, 1.0);
  pInfo->estimatedRows = static_cast<sqlite3_int64>(estimated_rows);
  pInfo->estimatedCost = estimated_rows;
  return SQLITE_OK;
}

int VirtualTableModule::xDisconnect(sqlite3_vtab* pVTab) {
  NodeVTab* vtab = reinterpret_cast<NodeVTab*>(pVTab);
  delete vtab;
  return SQLITE_OK;
}

int VirtualTableModule::xDestroy(sqlite3_vtab* pVTab) {
  return xDisconnect(pVTab);
}

int VirtualTableModule::xOpen(sqlite3_vtab* pVTab,
                              sqlite3_vtab_cursor** ppCursor) {
  NodeVTab* vtab = reinterpret_cast<NodeVTab*>(pVTab);
  NodeVTabCursor* cursor = new NodeVTabCursor();
  memset(&cursor->base, 0, sizeof(cursor->base));
  cursor->module = vtab->module;
  cursor->hidden_values.assign(vtab->module->num_columns_, nullptr);
  cursor->rowid = 0;
  cursor->done = true;
  *ppCursor = &cursor->base;
  return SQLITE_OK;
}

int VirtualTableModule::xClose(sqlite3_vtab_cursor* pCursor) {
  NodeVTabCursor* cursor = reinterpret_cast<NodeVTabCursor*>(pCursor);
  VirtualTableModule* mod = cursor->module;

  // Close the iterator so generator `finally` blocks still run when SQLite
  // stops stepping early, as it does for LIMIT or a `break` out of a for...of
  // loop. SQLite discards xClose's return value, so a throwing cleanup is
  // re-thrown rather than paired with a SQLite error here.
  mod->CloseIterator(cursor);

  ReleaseHiddenValues(cursor);
  cursor->iterator.Reset();
  cursor->current_row.Reset();
  delete cursor;
  return SQLITE_OK;
}

int VirtualTableModule::xFilter(sqlite3_vtab_cursor* pCursor,
                                int idxNum,
                                const char* idxStr,
                                int argc,
                                sqlite3_value** argv) {
  NodeVTabCursor* cursor = reinterpret_cast<NodeVTabCursor*>(pCursor);
  VirtualTableModule* mod = cursor->module;
  Environment* env = mod->env_;
  Isolate* isolate = env->isolate();
  HandleScope handle_scope(isolate);
  if (!mod->CanCallIntoJS()) {
    return SQLITE_ERROR;
  }
  CallbackDepthGuard callback_guard(mod->db_.get());

  // Re-filtering a cursor occurs when SQLite re-invokes xFilter on a cursor it
  // already used, as it does for the inner table of a correlated subquery or
  // join. The previous iterator is abandoned mid-loop, so close it the same way
  // xClose does; otherwise its generator `finally` blocks never run. A throwing
  // cleanup is surfaced as the error for this query.
  if (mod->CloseIterator(cursor)) {
    return mod->PropagateJSError();
  }

  cursor->rowid = 0;
  cursor->done = false;
  cursor->iterator.Reset();
  cursor->current_row.Reset();
  ReleaseHiddenValues(cursor);

  // Build arguments for rows() from hidden column constraint values.
  // idxStr (set in xBestIndex) lists the hidden column indices that received
  // an EQ constraint, in argv order. Unconstrained parameters stay null.
  int num_hidden = static_cast<int>(mod->hidden_col_indices_.size());
  LocalVector<Value> js_args(isolate, num_hidden);
  for (int i = 0; i < num_hidden; i++) {
    js_args[i] = Null(isolate);
  }

  const char* p = idxStr;
  const char* idx_end = p == nullptr ? nullptr : p + std::strlen(p);
  for (int argv_pos = 0; argv_pos < argc && p != nullptr && p != idx_end;
       argv_pos++) {
    int hidden_idx = 0;
    auto [next, ec] = std::from_chars(p, idx_end, hidden_idx);
    if (ec != std::errc() || hidden_idx >= num_hidden) {
      return SQLITE_ERROR;
    }
    p = (next != idx_end && *next == ',') ? next + 1 : next;

    MaybeLocal<Value> js_val;
    SQLITE_VALUE_TO_JS(
        value, isolate, mod->use_bigint_args_, js_val, argv[argv_pos]);
    Local<Value> local;
    if (!js_val.ToLocal(&local)) {
      return mod->PropagateJSError();
    }
    js_args[hidden_idx] = local;

    // Keep a copy so xColumn can report what the column was constrained to.
    // SQLite treats `omit` as a hint, so it may still recheck the constraint
    // against the value xColumn returns.
    int schema_idx = mod->hidden_col_indices_[hidden_idx];
    cursor->hidden_values[schema_idx] = sqlite3_value_dup(argv[argv_pos]);
    if (cursor->hidden_values[schema_idx] == nullptr) {
      return SQLITE_NOMEM;
    }
  }

  // Call the rows() function.
  auto recv = Undefined(isolate);
  auto fn = mod->rows_fn_.Get(isolate);
  MaybeLocal<Value> retval =
      fn->Call(env->context(), recv, js_args.size(), js_args.data());
  Local<Value> result;
  if (!retval.ToLocal(&result)) {
    return mod->PropagateJSError();
  }

  // Get an iterator from the result. If the result has Symbol.iterator,
  // call it. Otherwise, assume the result is already an iterator.
  Local<Object> iterator_obj;
  if (result->IsObject()) {
    Local<Object> result_obj = result.As<Object>();
    Local<Value> iter_method_val;
    if (!result_obj->Get(env->context(), v8::Symbol::GetIterator(isolate))
             .ToLocal(&iter_method_val)) {
      return mod->PropagateJSError();
    }

    if (iter_method_val->IsFunction()) {
      MaybeLocal<Value> iter_result = iter_method_val.As<Function>()->Call(
          env->context(), result_obj, 0, nullptr);
      Local<Value> iter_val;
      if (!iter_result.ToLocal(&iter_val)) {
        return mod->PropagateJSError();
      }
      if (!iter_val->IsObject()) {
        return ReportProtocolError(
            pCursor->pVtab,
            "The \"options.rows\" iterable's Symbol.iterator method must "
            "return an object");
      }
      iterator_obj = iter_val.As<Object>();
    } else {
      // Assume result is already an iterator (has .next()).
      iterator_obj = result_obj;
    }
  } else {
    return ReportProtocolError(
        pCursor->pVtab,
        "The \"options.rows\" function must return an iterable object");
  }

  cursor->iterator.Reset(isolate, iterator_obj);

  // Advance to the first row.
  return xNext(pCursor);
}

int VirtualTableModule::xNext(sqlite3_vtab_cursor* pCursor) {
  NodeVTabCursor* cursor = reinterpret_cast<NodeVTabCursor*>(pCursor);
  VirtualTableModule* mod = cursor->module;
  Environment* env = mod->env_;
  Isolate* isolate = env->isolate();
  HandleScope handle_scope(isolate);
  if (!mod->CanCallIntoJS()) {
    return SQLITE_ERROR;
  }
  CallbackDepthGuard callback_guard(mod->db_.get());

  Local<Object> iterator = cursor->iterator.Get(isolate);

  // Call iterator.next().
  Local<Value> next_method_val;
  if (!iterator->Get(env->context(), FIXED_ONE_BYTE_STRING(isolate, "next"))
           .ToLocal(&next_method_val)) {
    return mod->PropagateJSError();
  }
  // On a protocol error, drop the iterator so it is not closed later.
  // `for...of` does not call return() when next() is invalid either.
  if (!next_method_val->IsFunction()) {
    cursor->iterator.Reset();
    return ReportProtocolError(
        pCursor->pVtab,
        "The \"options.rows\" iterator must have a next() method");
  }

  MaybeLocal<Value> next_result = next_method_val.As<Function>()->Call(
      env->context(), iterator, 0, nullptr);
  Local<Value> next_val;
  if (!next_result.ToLocal(&next_val)) {
    return mod->PropagateJSError();
  }
  if (!next_val->IsObject()) {
    cursor->iterator.Reset();
    return ReportProtocolError(
        pCursor->pVtab,
        "The \"options.rows\" iterator's next() method must return an object");
  }

  Local<Object> next_obj = next_val.As<Object>();

  // Read "done" property.
  Local<Value> done_val;
  if (!next_obj->Get(env->context(), env->done_string()).ToLocal(&done_val)) {
    return mod->PropagateJSError();
  }

  if (done_val->BooleanValue(isolate)) {
    cursor->done = true;
    cursor->current_row.Reset();
  } else {
    cursor->done = false;
    cursor->rowid++;

    // Read "value" property.
    Local<Value> value_val;
    if (!next_obj->Get(env->context(), env->value_string())
             .ToLocal(&value_val)) {
      return mod->PropagateJSError();
    }

    cursor->current_row.Reset(isolate, value_val);
  }

  return SQLITE_OK;
}

int VirtualTableModule::xEof(sqlite3_vtab_cursor* pCursor) {
  NodeVTabCursor* cursor = reinterpret_cast<NodeVTabCursor*>(pCursor);
  return cursor->done ? 1 : 0;
}

int VirtualTableModule::xColumn(sqlite3_vtab_cursor* pCursor,
                                sqlite3_context* ctx,
                                int i) {
  NodeVTabCursor* cursor = reinterpret_cast<NodeVTabCursor*>(pCursor);
  VirtualTableModule* mod = cursor->module;

  if (i < 0 || i >= mod->num_columns_) {
    sqlite3_result_null(ctx);
    return SQLITE_OK;
  }

  // Hidden columns are parameters rather than data, so they are not present in
  // the row array. Report the value the query constrained the column to, so
  // that a recheck of that constraint still matches.
  if (mod->col_index_map_[i] < 0) {
    if (cursor->hidden_values[i] != nullptr) {
      sqlite3_result_value(ctx, cursor->hidden_values[i]);
    } else {
      sqlite3_result_null(ctx);
    }
    return SQLITE_OK;
  }

  Environment* env = mod->env_;
  Isolate* isolate = env->isolate();
  HandleScope handle_scope(isolate);
  if (!mod->CanCallIntoJS()) {
    return SQLITE_ERROR;
  }
  CallbackDepthGuard callback_guard(mod->db_.get());

  Local<Value> row = cursor->current_row.Get(isolate);
  if (!row->IsObject()) {
    sqlite3_result_null(ctx);
    return SQLITE_OK;
  }

  Local<Object> row_obj = row.As<Object>();
  Local<Value> col_val;
  if (!row_obj->Get(env->context(), mod->col_index_map_[i]).ToLocal(&col_val)) {
    sqlite3_result_error(ctx, "", 0);
    return mod->PropagateJSError();
  }

  JSValueToSQLiteResult(isolate, ctx, col_val);
  return SQLITE_OK;
}

int VirtualTableModule::xRowid(sqlite3_vtab_cursor* pCursor,
                               sqlite3_int64* pRowid) {
  NodeVTabCursor* cursor = reinterpret_cast<NodeVTabCursor*>(pCursor);
  *pRowid = cursor->rowid;
  return SQLITE_OK;
}

void VirtualTableModule::xDestroyModule(void* pAux) {
  delete static_cast<VirtualTableModule*>(pAux);
}

Database::Database(Environment* env,
                   Local<Object> object,
                   DatabaseOpenConfiguration&& open_config,
                   bool open,
                   bool allow_load_extension)
    : BaseObject(env, object), open_config_(std::move(open_config)) {
  MakeWeak();
  allow_load_extension_ = allow_load_extension;
  enable_load_extension_ = allow_load_extension;
  ignore_next_sqlite_error_ = false;

  BindingData* binding = env->principal_realm()->GetBindingData<BindingData>();
  if (binding != nullptr) binding->open_databases.insert(this);

  if (open) {
    Open();
  }
}

void Database::AddBackup(BackupJob* job) {
  backups_.insert(job);
}

void Database::RemoveBackup(BackupJob* job) {
  backups_.erase(job);
}

std::vector<BaseObjectPtr<Session>> Database::PinSessions() const {
  std::vector<BaseObjectPtr<Session>> pinned;
  pinned.reserve(sessions_.size());
  for (Session* session : sessions_) {
    pinned.emplace_back(session);
  }
  return pinned;
}

void Database::DeleteSessions() {
  // all attached sessions need to be deleted before the database is closed
  // https://www.sqlite.org/session/sqlite3session_create.html
  while (!sessions_.empty()) {
    (*sessions_.begin())->Delete();
  }
}

Database::~Database() {
  // See the note in ~Statement: closing the connection here must not reach
  // back into JavaScript.
  DestructorScope destructor_scope(this);

  BindingData* binding =
      env()->principal_realm()->GetBindingData<BindingData>();
  if (binding != nullptr) binding->open_databases.erase(this);

  FinalizeBackups();

  if (IsOpen()) {
    FinalizeStatements();
    DeleteSessions();
    connection_.reset();
  }
}

void Database::MemoryInfo(MemoryTracker* tracker) const {
  tracker->TrackFieldWithSize("user_defined_functions",
                              user_defined_functions_.size() * sizeof(void*));
  // TODO(tniessen): more accurately track the size of all fields
  tracker->TrackFieldWithSize(
      "open_config", sizeof(open_config_), "DatabaseOpenConfiguration");
}

bool Database::Open() {
  if (IsOpen()) {
    THROW_ERR_INVALID_STATE(env(), "database is already open");
    return false;
  }

  // sqlite3_open_v2() assigns a database handle even when it fails. Such a
  // handle is in a "sick" state and may only be used to retrieve the error
  // and must then be released with sqlite3_close(). Close and reset the
  // handle on any failure below so that a failed open() does not leave the
  // database in an open state.
  bool opened = false;
  auto reset_connection_on_failure = OnScopeLeave([&]() {
    if (!opened) {
      connection_.reset();
    }
  });

  // TODO(cjihrig): Support additional flags.
  int default_flags = SQLITE_OPEN_URI;
  int flags = open_config_.get_read_only()
                  ? SQLITE_OPEN_READONLY
                  : SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
  sqlite3* raw_conn = nullptr;
  int r = sqlite3_open_v2(open_config_.location().c_str(),
                          &raw_conn,
                          flags | default_flags,
                          nullptr);
  connection_.reset(raw_conn);
  CHECK_ERROR_OR_THROW(env()->isolate(), this, r, SQLITE_OK, false);

  r = sqlite3_db_config(connection_.get(),
                        SQLITE_DBCONFIG_DQS_DML,
                        static_cast<int>(open_config_.get_enable_dqs()),
                        nullptr);
  CHECK_ERROR_OR_THROW(env()->isolate(), this, r, SQLITE_OK, false);
  r = sqlite3_db_config(connection_.get(),
                        SQLITE_DBCONFIG_DQS_DDL,
                        static_cast<int>(open_config_.get_enable_dqs()),
                        nullptr);
  CHECK_ERROR_OR_THROW(env()->isolate(), this, r, SQLITE_OK, false);

  int foreign_keys_enabled;
  r = sqlite3_db_config(
      connection_.get(),
      SQLITE_DBCONFIG_ENABLE_FKEY,
      static_cast<int>(open_config_.get_enable_foreign_keys()),
      &foreign_keys_enabled);
  CHECK_ERROR_OR_THROW(env()->isolate(), this, r, SQLITE_OK, false);
  CHECK_EQ(foreign_keys_enabled, open_config_.get_enable_foreign_keys());

  int defensive_enabled;
  r = sqlite3_db_config(connection_.get(),
                        SQLITE_DBCONFIG_DEFENSIVE,
                        static_cast<int>(open_config_.get_enable_defensive()),
                        &defensive_enabled);
  CHECK_ERROR_OR_THROW(env()->isolate(), this, r, SQLITE_OK, false);
  CHECK_EQ(defensive_enabled, open_config_.get_enable_defensive());

  sqlite3_busy_timeout(connection_.get(), open_config_.get_timeout());

  for (const auto& [js_name, sqlite_limit_id] : kLimitMapping) {
    const auto& limit_value = open_config_.limits()[sqlite_limit_id];
    if (limit_value.has_value()) {
      sqlite3_limit(connection_.get(), sqlite_limit_id, *limit_value);
    }
  }

  if (enable_load_extension_) {
    if (env()->permission()->enabled()) [[unlikely]] {
      THROW_ERR_LOAD_SQLITE_EXTENSION(env(),
                                      "Cannot load SQLite extensions when the "
                                      "permission model is enabled.");
      return false;
    }
    const int load_extension_ret = sqlite3_db_config(
        connection_.get(), SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, 1, nullptr);
    CHECK_ERROR_OR_THROW(
        env()->isolate(), this, load_extension_ret, SQLITE_OK, false);
  }

  trace_channel_ = diagnostics_channel::Channel::Get(env(), "sqlite.db.query");
  if (trace_channel_ && trace_channel_->HasSubscribers()) {
    sqlite3_trace_v2(
        connection_.get(), SQLITE_TRACE_PROFILE, TraceCallback, this);
  }

  // The authorizer outlives the connection, so reopening must reinstall it.
  Local<Value> authorizer =
      object()->GetInternalField(kAuthorizerCallback).template As<Value>();
  if (authorizer->IsFunction()) {
    r = sqlite3_set_authorizer(
        connection_.get(), Database::AuthorizerCallback, this);
    CHECK_ERROR_OR_THROW(env()->isolate(), this, r, SQLITE_OK, false);
  }

  opened = true;
  return true;
}

void Database::EnableTracing() {
  if (!IsOpen()) return;
  if (!trace_channel_) {
    trace_channel_ =
        diagnostics_channel::Channel::Get(env(), "sqlite.db.query");
  }
  sqlite3_trace_v2(
      connection_.get(), SQLITE_TRACE_PROFILE, TraceCallback, this);
}

void Database::DisableTracing() {
  if (!IsOpen()) return;
  sqlite3_trace_v2(connection_.get(), 0, nullptr, nullptr);
}

void Database::FinalizeBackups() {
  for (auto backup : backups_) {
    backup->Cleanup();
  }

  backups_.clear();
}

void Database::FinalizeStatements() {
  for (auto stmt : statements_) {
    stmt->Finalize();
  }

  statements_.clear();
}

void Database::TrackStatement(Statement* statement) {
  statements_.insert(statement);
}

void Database::UntrackStatement(Statement* statement) {
  statements_.erase(statement);
}

inline bool Database::IsOpen() {
  return connection_ != nullptr;
}

inline sqlite3* Database::Connection() {
  return connection_.get();
}

void Database::SetLimit(int sqlite_limit_id, int value) {
  sqlite3_limit(connection_.get(), sqlite_limit_id, value);
  open_config_.set_limit(sqlite_limit_id, value);
}

void Database::SetIgnoreNextSQLiteError(bool ignore) {
  ignore_next_sqlite_error_ = ignore;
}

bool Database::ShouldIgnoreSQLiteError() {
  return ignore_next_sqlite_error_;
}

void Database::CreateTagStore(const FunctionCallbackInfo<Value>& args) {
  Database* db = BaseObject::Unwrap<Database>(args.This());
  Environment* env = Environment::GetCurrent(args);

  if (!db->IsOpen()) {
    THROW_ERR_INVALID_STATE(env, "database is not open");
    return;
  }
  THROW_AND_RETURN_IF_IN_AUTHORIZER(env, db);
  int capacity = 1000;
  if (args.Length() > 0 && !args[0]->IsUndefined()) {
    if (!args[0]->IsNumber()) {
      THROW_ERR_INVALID_ARG_TYPE(
          env->isolate(),
          "The \"maxSize\" argument must be a positive integer.");
      return;
    }
    double val = args[0].As<Number>()->Value();
    if (std::floor(val) != val || val <= 0 ||
        val > std::numeric_limits<int>::max()) {
      THROW_ERR_OUT_OF_RANGE(
          env->isolate(),
          "The \"maxSize\" argument must be a positive integer.");
      return;
    }
    capacity = static_cast<int>(val);
  }
  BaseObjectPtr<SQLTagStore> session =
      SQLTagStore::Create(env, BaseObjectWeakPtr<Database>(db), capacity);
  if (!session) {
    // Handle error if creation failed
    THROW_ERR_SQLITE_ERROR(env->isolate(), "Failed to create SQLTagStore");
    return;
  }
  args.GetReturnValue().Set(session->object());
}

// Reads an optional boolean property, leaving |out| unchanged when it is
// undefined. Returns false if a JavaScript exception is pending, either from
// the property getter or because the value is not a boolean.
template <typename T>
static bool ReadOptionalBool(Environment* env,
                             Local<Object> options,
                             const char* key,
                             T* out) {
  Local<String> key_string =
      OneByteString(env->isolate(), key, -1, NewStringType::kInternalized);
  Local<Value> value;
  if (!options->Get(env->context(), key_string).ToLocal(&value)) {
    return false;
  }
  if (value->IsUndefined()) {
    return true;
  }
  if (!value->IsBoolean()) {
    THROW_ERR_INVALID_ARG_TYPE(
        env->isolate(), "The \"options.%s\" argument must be a boolean.", key);
    return false;
  }
  *out = value->IsTrue();
  return true;
}

std::optional<std::string> ValidateDatabasePath(Environment* env,
                                                Local<Value> path,
                                                const std::string& field_name) {
  constexpr auto has_null_bytes = [](std::string_view str) {
    return str.find('\0') != std::string_view::npos;
  };
  if (path->IsString()) {
    Utf8Value location(env->isolate(), path.As<String>());
    if (!has_null_bytes(location.ToStringView())) {
      return location.ToString();
    }
  } else if (path->IsUint8Array()) {
    Local<Uint8Array> buffer = path.As<Uint8Array>();
    size_t byteOffset = buffer->ByteOffset();
    size_t byteLength = buffer->ByteLength();
    auto data =
        static_cast<const uint8_t*>(buffer->Buffer()->Data()) + byteOffset;
    if (std::find(data, data + byteLength, 0) == data + byteLength) {
      return std::string(reinterpret_cast<const char*>(data), byteLength);
    }
  } else if (path->IsObject()) {  // When is URL
    auto url = path.As<Object>();
    Local<Value> href;
    if (url->Get(env->context(), env->href_string()).ToLocal(&href) &&
        href->IsString()) {
      Utf8Value location_value(env->isolate(), href.As<String>());
      auto location = location_value.ToStringView();
      if (!has_null_bytes(location)) {
        if (!ada::can_parse(location)) {
          THROW_ERR_INVALID_URL(env->isolate(), "Invalid URL");
          return std::nullopt;
        }

        if (!location.starts_with("file:")) {
          THROW_ERR_INVALID_URL_SCHEME(env->isolate());
          return std::nullopt;
        }

        return location_value.ToString();
      }
    }
  }

  THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
                             "The \"%s\" argument must be a string, "
                             "Uint8Array, or URL without null bytes.",
                             field_name);

  return std::nullopt;
}

void Database::New(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  if (!args.IsConstructCall()) {
    THROW_ERR_CONSTRUCT_CALL_REQUIRED(env);
    return;
  }

  std::optional<std::string> location =
      ValidateDatabasePath(env, args[0], "path");
  if (!location.has_value()) {
    return;
  }

  DatabaseOpenConfiguration open_config(std::move(location.value()));
  bool open = true;
  bool allow_load_extension = false;
  if (args.Length() > 1) {
    if (!args[1]->IsObject()) {
      THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
                                 "The \"options\" argument must be an object.");
      return;
    }

    Local<Object> options = args[1].As<Object>();
    bool read_only = open_config.get_read_only();
    bool enable_foreign_keys = open_config.get_enable_foreign_keys();
    bool enable_dqs = open_config.get_enable_dqs();
    if (!ReadOptionalBool(env, options, "open", &open)) return;
    if (!ReadOptionalBool(env, options, "readOnly", &read_only)) return;
    if (!ReadOptionalBool(
            env, options, "enableForeignKeyConstraints", &enable_foreign_keys))
      return;
    if (!ReadOptionalBool(
            env, options, "enableDoubleQuotedStringLiterals", &enable_dqs))
      return;
    if (!ReadOptionalBool(
            env, options, "allowExtension", &allow_load_extension))
      return;
    open_config.set_read_only(read_only);
    open_config.set_enable_foreign_keys(enable_foreign_keys);
    open_config.set_enable_dqs(enable_dqs);

    Local<Value> timeout_v;
    if (!options->Get(env->context(), env->timeout_string())
             .ToLocal(&timeout_v)) {
      return;
    }

    if (!timeout_v->IsUndefined()) {
      if (!timeout_v->IsInt32()) {
        THROW_ERR_INVALID_ARG_TYPE(
            env->isolate(),
            "The \"options.timeout\" argument must be an integer.");
        return;
      }

      open_config.set_timeout(timeout_v.As<Int32>()->Value());
    }

    bool use_big_ints = open_config.get_use_big_ints();
    bool return_arrays = open_config.get_return_arrays();
    bool allow_bare_named_params = open_config.get_allow_bare_named_params();
    bool allow_unknown_named_params =
        open_config.get_allow_unknown_named_params();
    bool defensive = open_config.get_enable_defensive();
    if (!ReadOptionalBool(env, options, "readBigInts", &use_big_ints)) return;
    if (!ReadOptionalBool(env, options, "returnArrays", &return_arrays)) return;
    if (!ReadOptionalBool(
            env, options, "allowBareNamedParameters", &allow_bare_named_params))
      return;
    if (!ReadOptionalBool(env,
                          options,
                          "allowUnknownNamedParameters",
                          &allow_unknown_named_params))
      return;
    if (!ReadOptionalBool(env, options, "defensive", &defensive)) return;
    open_config.set_use_big_ints(use_big_ints);
    open_config.set_return_arrays(return_arrays);
    open_config.set_allow_bare_named_params(allow_bare_named_params);
    open_config.set_allow_unknown_named_params(allow_unknown_named_params);
    open_config.set_enable_defensive(defensive);

    // Parse limits option
    Local<Value> limits_v;
    if (!options->Get(env->context(), env->limits_string())
             .ToLocal(&limits_v)) {
      return;
    }
    if (!limits_v->IsUndefined()) {
      if (!limits_v->IsObject()) {
        THROW_ERR_INVALID_ARG_TYPE(
            env->isolate(),
            "The \"options.limits\" argument must be an object.");
        return;
      }

      Local<Object> limits_obj = limits_v.As<Object>();

      // Iterate through known limit names and extract values
      for (const auto& [js_name, sqlite_limit_id] : kLimitMapping) {
        Local<String> key;
        if (!String::NewFromUtf8(env->isolate(),
                                 js_name.data(),
                                 NewStringType::kNormal,
                                 js_name.size())
                 .ToLocal(&key)) {
          return;
        }

        Local<Value> val;
        if (!limits_obj->Get(env->context(), key).ToLocal(&val)) {
          return;
        }

        if (!val->IsUndefined()) {
          if (!val->IsInt32()) {
            std::string msg = "The \"options.limits." + std::string(js_name) +
                              "\" argument must be an integer.";
            THROW_ERR_INVALID_ARG_TYPE(env->isolate(), msg);
            return;
          }

          int limit_val = val.As<Int32>()->Value();
          if (limit_val < 0) {
            std::string msg = "The \"options.limits." + std::string(js_name) +
                              "\" argument must be non-negative.";
            THROW_ERR_OUT_OF_RANGE(env->isolate(), msg);
            return;
          }

          open_config.set_limit(sqlite_limit_id, limit_val);
        }
      }
    }
  }

  new Database(
      env, args.This(), std::move(open_config), open, allow_load_extension);
}

void Database::Open(const FunctionCallbackInfo<Value>& args) {
  Database* db;
  ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
  db->Open();
}

void Database::IsOpenGetter(const FunctionCallbackInfo<Value>& args) {
  Database* db;
  ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
  args.GetReturnValue().Set(db->IsOpen());
}

void Database::IsTransactionGetter(const FunctionCallbackInfo<Value>& args) {
  Database* db;
  ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
  Environment* env = Environment::GetCurrent(args);
  THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
  args.GetReturnValue().Set(sqlite3_get_autocommit(db->connection_.get()) == 0);
}

void Database::LimitsGetter(const FunctionCallbackInfo<Value>& args) {
  Database* db;
  ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
  Environment* env = Environment::GetCurrent(args);

  Local<Value> limits_val =
      db->object()->GetInternalField(kLimitsObject).template As<Value>();

  if (limits_val->IsUndefined()) {
    BaseObjectPtr<DatabaseLimits> limits =
        DatabaseLimits::Create(env, BaseObjectWeakPtr<Database>(db));
    if (limits) {
      db->object()->SetInternalField(kLimitsObject, limits->object());
      args.GetReturnValue().Set(limits->object());
    }
  } else {
    args.GetReturnValue().Set(limits_val);
  }
}

void Database::Close(const FunctionCallbackInfo<Value>& args) {
  Database* db;
  ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
  Environment* env = Environment::GetCurrent(args);
  THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
  THROW_AND_RETURN_ON_BAD_STATE(
      env, db->IsInCallback(), "database cannot be closed while in a callback");
  db->FinalizeStatements();
  db->DeleteSessions();
  // Close through the raw handle instead of reset() so that a failed close
  // keeps the handle, which the error below is read from.
  int r = sqlite3_close_v2(db->connection_.get());
  CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void());
  db->connection_.release();
  // Backups can defer SQLite destruction until after the connection is closed.
  db->user_defined_functions_.clear();
}

void Database::Dispose(const v8::FunctionCallbackInfo<v8::Value>& args) {
  v8::TryCatch try_catch(args.GetIsolate());
  Close(args);
  if (try_catch.HasCaught()) {
    CHECK(try_catch.CanContinue());
  }
}

void Database::Prepare(const FunctionCallbackInfo<Value>& args) {
  Database* db;
  ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
  Environment* env = Environment::GetCurrent(args);
  THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
  THROW_AND_RETURN_IF_IN_AUTHORIZER(env, db);

  if (!args[0]->IsString()) {
    THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
                               "The \"sql\" argument must be a string.");
    return;
  }

  std::optional<bool> return_arrays;
  std::optional<bool> use_big_ints;
  std::optional<bool> allow_bare_named_params;
  std::optional<bool> allow_unknown_named_params;
  std::optional<bool> persistent;

  if (args.Length() > 1 && !args[1]->IsUndefined()) {
    if (!args[1]->IsObject()) {
      THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
                                 "The \"options\" argument must be an object.");
      return;
    }
    Local<Object> options = args[1].As<Object>();
    if (!ReadOptionalBool(env, options, "returnArrays", &return_arrays)) return;
    if (!ReadOptionalBool(env, options, "readBigInts", &use_big_ints)) return;
    if (!ReadOptionalBool(
            env, options, "allowBareNamedParameters", &allow_bare_named_params))
      return;
    if (!ReadOptionalBool(env,
                          options,
                          "allowUnknownNamedParameters",
                          &allow_unknown_named_params))
      return;
    if (!ReadOptionalBool(env, options, "persistent", &persistent)) return;
  }

  // Reading the options bag above can run user JavaScript through a property
  // getter, which may have closed the database since it was checked.
  THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");

  Utf8Value sql(env->isolate(), args[0].As<String>());
  sqlite3_stmt* s = nullptr;

  unsigned int prep_flags =
      persistent.value_or(false) ? SQLITE_PREPARE_PERSISTENT : 0;

  int r = sqlite3_prepare_v3(
      db->connection_.get(), *sql, -1, prep_flags, &s, nullptr);

  StatementPtr stmt_ptr(s);

  CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void());

  // sqlite3_prepare_v2() reports success without producing a statement when
  // the input holds no SQL, such as a comment. Such a statement can never be
  // stepped, and tracking it would leave a dangling pointer in statements_
  // because its destructor treats a null statement as already finalized.
  if (s == nullptr) {
    THROW_ERR_INVALID_ARG_VALUE(env, "The SQL query contains no statements.");
    return;
  }

  BaseObjectPtr<Statement> stmt =
      Statement::Create(env, BaseObjectPtr<Database>(db), std::move(stmt_ptr));
  if (!stmt) {
    return;
  }

  if (return_arrays.has_value()) {
    stmt->return_arrays_ = return_arrays.value();
  }
  if (use_big_ints.has_value()) {
    stmt->use_big_ints_ = use_big_ints.value();
  }
  if (allow_bare_named_params.has_value()) {
    stmt->allow_bare_named_params_ = allow_bare_named_params.value();
  }
  if (allow_unknown_named_params.has_value()) {
    stmt->allow_unknown_named_params_ = allow_unknown_named_params.value();
  }

  args.GetReturnValue().Set(stmt->object());
}

void Database::Exec(const FunctionCallbackInfo<Value>& args) {
  Database* db;
  ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
  Environment* env = Environment::GetCurrent(args);
  THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
  THROW_AND_RETURN_IF_IN_AUTHORIZER(env, db);

  if (!args[0]->IsString()) {
    THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
                               "The \"sql\" argument must be a string.");
    return;
  }

  // Keep the database alive during sqlite3_exec(), which may call
  // user-defined SQLite functions that trigger JavaScript callbacks.
  // If the JavaScript callback drops all references to the database,
  // the Database could otherwise be garbage-collected while the
  // SQLite callback is still executing, causing a use-after-free.
  BaseObjectPtr<Database> guard(db);

  Utf8Value sql(env->isolate(), args[0].As<String>());
  int r = sqlite3_exec(db->connection_.get(), *sql, nullptr, nullptr, nullptr);
  CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void());
}

void Database::CustomFunction(const FunctionCallbackInfo<Value>& args) {
  Database* db;
  ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
  Environment* env = Environment::GetCurrent(args);
  THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
  THROW_AND_RETURN_IF_IN_AUTHORIZER(env, db);

  if (!args[0]->IsString()) {
    THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
                               "The \"name\" argument must be a string.");
    return;
  }

  int fn_index = args.Length() < 3 ? 1 : 2;
  bool use_bigint_args = false;
  bool varargs = false;
  bool deterministic = false;
  bool direct_only = false;

  if (fn_index > 1) {
    if (!args[1]->IsObject()) {
      THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
                                 "The \"options\" argument must be an object.");
      return;
    }

    Local<Object> options = args[1].As<Object>();
    if (!ReadOptionalBool(env, options, "useBigIntArguments", &use_bigint_args))
      return;
    if (!ReadOptionalBool(env, options, "varargs", &varargs)) return;
    if (!ReadOptionalBool(env, options, "deterministic", &deterministic))
      return;
    if (!ReadOptionalBool(env, options, "directOnly", &direct_only)) return;
  }

  if (!args[fn_index]->IsFunction()) {
    THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
                               "The \"function\" argument must be a function.");
    return;
  }

  Utf8Value name(env->isolate(), args[0].As<String>());
  Local<Function> fn = args[fn_index].As<Function>();

  int argc = 0;
  if (varargs) {
    argc = -1;
  } else {
    Local<Value> js_len;
    if (!fn->Get(env->context(), env->length_string()).ToLocal(&js_len)) {
      return;
    }

    if (!js_len->IsInt32()) {
      THROW_ERR_INVALID_ARG_TYPE(
          env->isolate(),
          "The \"function.length\" property must be an integer.");
      return;
    }

    argc = js_len.As<Int32>()->Value();
  }

  // Reading the options bag and "function.length" above can run user
  // JavaScript through a property getter, which may have closed the database
  // since it was checked.
  THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");

  UserDefinedFunction* user_data = new UserDefinedFunction(
      env, fn, BaseObjectWeakPtr<Database>(db), use_bigint_args);
  int text_rep = SQLITE_UTF8;

  if (deterministic) {
    text_rep |= SQLITE_DETERMINISTIC;
  }

  if (direct_only) {
    text_rep |= SQLITE_DIRECTONLY;
  }

  int r = sqlite3_create_function_v2(db->connection_.get(),
                                     *name,
                                     argc,
                                     text_rep,
                                     user_data,
                                     UserDefinedFunction::xFunc,
                                     nullptr,
                                     nullptr,
                                     UserDefinedFunction::xDestroy);
  CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void());
}

void Database::Location(const FunctionCallbackInfo<Value>& args) {
  Database* db;
  ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
  Environment* env = Environment::GetCurrent(args);
  THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");

  std::string db_name = "main";
  if (!args[0]->IsUndefined()) {
    if (!args[0]->IsString()) {
      THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
                                 "The \"dbName\" argument must be a string.");
      return;
    }

    db_name = Utf8Value(env->isolate(), args[0].As<String>()).ToString();
  }

  const char* db_filename =
      sqlite3_db_filename(db->connection_.get(), db_name.c_str());
  if (!db_filename || db_filename[0] == '\0') {
    args.GetReturnValue().Set(Null(env->isolate()));
    return;
  }

  Local<String> ret;
  if (String::NewFromUtf8(env->isolate(), db_filename).ToLocal(&ret)) {
    args.GetReturnValue().Set(ret);
  }
}

void Database::Serialize(const FunctionCallbackInfo<Value>& args) {
  Database* db;
  ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
  Environment* env = Environment::GetCurrent(args);
  THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
  THROW_AND_RETURN_IF_IN_AUTHORIZER(env, db);

  std::string db_name = "main";
  if (!args[0]->IsUndefined()) {
    if (!args[0]->IsString()) {
      THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
                                 "The \"dbName\" argument must be a string.");
      return;
    }
    db_name = Utf8Value(env->isolate(), args[0].As<String>()).ToString();
  }

  sqlite3_int64 size = 0;
  unsigned char* data =
      sqlite3_serialize(db->connection_.get(), db_name.c_str(), &size, 0);

  if (data == nullptr) {
    if (size == 0) {
      Local<ArrayBuffer> ab = ArrayBuffer::New(env->isolate(), 0);
      args.GetReturnValue().Set(Uint8Array::New(ab, 0, 0));
      return;
    }
    THROW_ERR_SQLITE_ERROR(env->isolate(), db);
    return;
  }

  auto store = AdoptIntoBackingStore(
      env->isolate(),
      data,
      size,
      [](void* ptr, size_t, void*) { sqlite3_free(ptr); },
      nullptr);
  if (!store) {
    THROW_ERR_MEMORY_ALLOCATION_FAILED(env);
    return;
  }
  Local<ArrayBuffer> ab = ArrayBuffer::New(env->isolate(), std::move(store));

  args.GetReturnValue().Set(Uint8Array::New(ab, 0, size));
}

void Database::Deserialize(const FunctionCallbackInfo<Value>& args) {
  Database* db;
  ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
  Environment* env = Environment::GetCurrent(args);
  THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
  THROW_AND_RETURN_ON_BAD_STATE(
      env,
      db->IsInCallback(),
      "database cannot be deserialized while in a callback");

  if (!args[0]->IsUint8Array()) {
    THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
                               "The \"buffer\" argument must be a Uint8Array.");
    return;
  }

  Local<Uint8Array> input = args[0].As<Uint8Array>();
  size_t byte_length = input->ByteLength();

  if (byte_length == 0) {
    THROW_ERR_INVALID_ARG_VALUE(env,
                                "The \"buffer\" argument must not be empty.");
    return;
  }

  std::string db_name = "main";
  if (args.Length() > 1 && !args[1]->IsUndefined()) {
    if (!args[1]->IsObject()) {
      THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
                                 "The \"options\" argument must be an object.");
      return;
    }

    Local<Object> options = args[1].As<Object>();
    Local<String> db_name_key = FIXED_ONE_BYTE_STRING(env->isolate(), "dbName");
    Local<Value> db_name_value;
    if (!options->Get(env->context(), db_name_key).ToLocal(&db_name_value)) {
      return;
    }

    if (!db_name_value->IsUndefined()) {
      if (!db_name_value->IsString()) {
        THROW_ERR_INVALID_ARG_TYPE(
            env->isolate(),
            "The \"options.dbName\" argument must be a string.");
        return;
      }
      db_name =
          Utf8Value(env->isolate(), db_name_value.As<String>()).ToString();
    }
  }

  // Reading the options bag above can run user JavaScript through a property
  // getter, which may have closed the database since it was checked.
  THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");

  // sqlite3_malloc64 is required because SQLITE_DESERIALIZE_FREEONCLOSE
  // transfers ownership to SQLite, which calls sqlite3_free() on close.
  // See: https://www.sqlite.org/c3ref/deserialize.html
  unsigned char* buf =
      static_cast<unsigned char*>(sqlite3_malloc64(byte_length));
  if (buf == nullptr) {
    THROW_ERR_MEMORY_ALLOCATION_FAILED(env);
    return;
  }

  // The same user JavaScript may also have shrunk or detached the backing
  // store, in which case byte_length is stale and CopyContents() leaves the
  // remainder of buf uninitialized. Handing that to SQLite would disclose it
  // through serialize().
  if (input->CopyContents(buf, byte_length) != byte_length) {
    sqlite3_free(buf);
    THROW_ERR_INVALID_STATE(
        env, "The \"buffer\" argument was resized while reading \"options\"");
    return;
  }

  db->FinalizeStatements();

  int r = sqlite3_deserialize(
      db->connection_.get(),
      db_name.c_str(),
      buf,
      byte_length,
      byte_length,
      SQLITE_DESERIALIZE_FREEONCLOSE | SQLITE_DESERIALIZE_RESIZEABLE);
  if (r != SQLITE_OK) {
    THROW_ERR_SQLITE_ERROR(env->isolate(), db);
    return;
  }
}

void Database::AggregateFunction(const FunctionCallbackInfo<Value>& args) {
  Database* db;
  ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
  Environment* env = Environment::GetCurrent(args);
  THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
  THROW_AND_RETURN_IF_IN_AUTHORIZER(env, db);

  if (!args[0]->IsString()) {
    THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
                               "The \"name\" argument must be a string.");
    return;
  }

  if (!args[1]->IsObject()) {
    THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
                               "The \"options\" argument must be an object.");
    return;
  }

  Utf8Value name(env->isolate(), args[0].As<String>());
  Local<Object> options = args[1].As<Object>();
  Local<Value> start_v;
  if (!options->Get(env->context(), env->start_string()).ToLocal(&start_v)) {
    return;
  }

  if (start_v->IsUndefined()) {
    THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
                               "The \"options.start\" argument must be a "
                               "function or a primitive value.");
    return;
  }

  Local<Value> step_v;
  if (!options->Get(env->context(), env->step_string()).ToLocal(&step_v)) {
    return;
  }

  if (!step_v->IsFunction()) {
    THROW_ERR_INVALID_ARG_TYPE(
        env->isolate(), "The \"options.step\" argument must be a function.");
    return;
  }

  Local<Value> result_v;
  if (!options->Get(env->context(), env->result_string()).ToLocal(&result_v)) {
    return;
  }

  bool use_bigint_args = false;
  bool varargs = false;
  bool direct_only = false;
  Local<Function> inverseFunc = Local<Function>();
  if (!ReadOptionalBool(env, options, "useBigIntArguments", &use_bigint_args))
    return;
  if (!ReadOptionalBool(env, options, "varargs", &varargs)) return;
  if (!ReadOptionalBool(env, options, "directOnly", &direct_only)) return;

  Local<Value> inverse_v;
  if (!options->Get(env->context(), env->inverse_string())
           .ToLocal(&inverse_v)) {
    return;
  }

  if (!inverse_v->IsUndefined()) {
    if (!inverse_v->IsFunction()) {
      THROW_ERR_INVALID_ARG_TYPE(
          env->isolate(),
          "The \"options.inverse\" argument must be a function.");
      return;
    }
    inverseFunc = inverse_v.As<Function>();
  }

  Local<Function> stepFunction = step_v.As<Function>();
  Local<Function> resultFunction =
      result_v->IsFunction() ? result_v.As<Function>() : Local<Function>();
  int argc = -1;
  if (!varargs) {
    Local<Value> js_len;
    if (!stepFunction->Get(env->context(), env->length_string())
             .ToLocal(&js_len)) {
      return;
    }

    if (!js_len->IsInt32()) {
      THROW_ERR_INVALID_ARG_TYPE(
          env->isolate(),
          "The \"options.step.length\" property must be an integer.");
      return;
    }

    // Subtract 1 because the first argument is the aggregate value.
    argc = js_len.As<Int32>()->Value() - 1;
    if (!inverseFunc.IsEmpty()) {
      if (!inverseFunc->Get(env->context(), env->length_string())
               .ToLocal(&js_len)) {
        return;
      }

      if (!js_len->IsInt32()) {
        THROW_ERR_INVALID_ARG_TYPE(
            env->isolate(),
            "The \"options.inverse.length\" property must be an integer.");
        return;
      }
    }

    argc = std::max({argc, js_len.As<Int32>()->Value() - 1, 0});
  }

  // Reading the options bag and the step/inverse "length" properties above can
  // run user JavaScript through a property getter, which may have closed the
  // database since it was checked.
  THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");

  int text_rep = SQLITE_UTF8;
  if (direct_only) {
    text_rep |= SQLITE_DIRECTONLY;
  }

  auto xInverse = !inverseFunc.IsEmpty() ? CustomAggregate::xInverse : nullptr;
  auto xValue = xInverse ? CustomAggregate::xValue : nullptr;
  int r = sqlite3_create_window_function(
      db->connection_.get(),
      *name,
      argc,
      text_rep,
      new CustomAggregate(env,
                          BaseObjectWeakPtr<Database>(db),
                          use_bigint_args,
                          start_v,
                          stepFunction,
                          inverseFunc,
                          resultFunction),
      CustomAggregate::xStep,
      CustomAggregate::xFinal,
      xValue,
      xInverse,
      CustomAggregate::xDestroy);
  CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void());
}

void Database::CreateModule(const FunctionCallbackInfo<Value>& args) {
  Database* db;
  ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
  Environment* env = Environment::GetCurrent(args);
  THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
  THROW_AND_RETURN_IF_IN_AUTHORIZER(env, db);

  if (!args[0]->IsString()) {
    THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
                               "The \"name\" argument must be a string.");
    return;
  }

  if (!args[1]->IsObject()) {
    THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
                               "The \"options\" argument must be an object.");
    return;
  }

  Utf8Value name(env->isolate(), args[0].As<String>());
  Local<Object> options = args[1].As<Object>();

  // Extract columns array.
  Local<Value> columns_v;
  if (!options
           ->Get(env->context(),
                 FIXED_ONE_BYTE_STRING(env->isolate(), "columns"))
           .ToLocal(&columns_v)) {
    return;
  }

  if (!columns_v->IsArray()) {
    THROW_ERR_INVALID_ARG_TYPE(
        env->isolate(), "The \"options.columns\" argument must be an array.");
    return;
  }

  Local<Array> columns = columns_v.As<Array>();
  uint32_t num_columns = columns->Length();

  if (num_columns == 0) {
    THROW_ERR_INVALID_ARG_VALUE(
        env->isolate(), "The \"options.columns\" array must not be empty.");
    return;
  }

  // Extract rows function.
  Local<Value> rows_v;
  if (!options
           ->Get(env->context(), FIXED_ONE_BYTE_STRING(env->isolate(), "rows"))
           .ToLocal(&rows_v)) {
    return;
  }

  if (!rows_v->IsFunction()) {
    THROW_ERR_INVALID_ARG_TYPE(
        env->isolate(), "The \"options.rows\" argument must be a function.");
    return;
  }

  Local<Function> rows_fn = rows_v.As<Function>();

  // Extract optional boolean options.
  bool direct_only = false;
  bool use_bigint_args = false;
  if (!ReadOptionalBool(env, options, "directOnly", &direct_only)) return;
  if (!ReadOptionalBool(env, options, "useBigIntArguments", &use_bigint_args))
    return;

  // Build CREATE TABLE schema SQL from columns.
  std::string schema_sql = "CREATE TABLE x(";
  std::vector<int> hidden_col_indices;

  for (uint32_t i = 0; i < num_columns; i++) {
    Local<Value> col_v;
    if (!columns->Get(env->context(), i).ToLocal(&col_v)) {
      return;
    }

    if (!col_v->IsObject()) {
      THROW_ERR_INVALID_ARG_TYPE(
          env->isolate(),
          "Each column in \"options.columns\" must be an object.");
      return;
    }

    Local<Object> col = col_v.As<Object>();

    // Get column name.
    Local<Value> col_name_v;
    if (!col->Get(env->context(), env->name_string()).ToLocal(&col_name_v)) {
      return;
    }

    if (!col_name_v->IsString()) {
      THROW_ERR_INVALID_ARG_TYPE(
          env->isolate(), "The column \"name\" property must be a string.");
      return;
    }

    Utf8Value col_name(env->isolate(), col_name_v.As<String>());

    // Get column type.
    Local<Value> col_type_v;
    if (!col->Get(env->context(), env->type_string()).ToLocal(&col_type_v)) {
      return;
    }

    if (!col_type_v->IsString()) {
      THROW_ERR_INVALID_ARG_TYPE(
          env->isolate(), "The column \"type\" property must be a string.");
      return;
    }

    Utf8Value col_type(env->isolate(), col_type_v.As<String>());

    // Get optional hidden flag.
    bool hidden = false;
    Local<Value> hidden_v;
    if (!col->Get(env->context(),
                  FIXED_ONE_BYTE_STRING(env->isolate(), "hidden"))
             .ToLocal(&hidden_v)) {
      return;
    }

    if (!hidden_v->IsUndefined()) {
      if (!hidden_v->IsBoolean()) {
        THROW_ERR_INVALID_ARG_TYPE(
            env->isolate(),
            "The column \"hidden\" property must be a boolean.");
        return;
      }
      hidden = hidden_v.As<Boolean>()->Value();
    }

    if (hidden) {
      hidden_col_indices.push_back(static_cast<int>(i));
    }

    if (i > 0) {
      schema_sql += ", ";
    }

    // Validate column type against allowed SQLite type names.
    std::string type_str = col_type.ToString();
    if (type_str != "INTEGER" && type_str != "TEXT" && type_str != "REAL" &&
        type_str != "BLOB" && type_str != "ANY") {
      THROW_ERR_INVALID_ARG_VALUE(
          env->isolate(),
          "The column \"type\" property must be one of "
          "'INTEGER', 'TEXT', 'REAL', 'BLOB', or 'ANY'.");
      return;
    }

    // Quote column name to prevent SQL injection.
    schema_sql += "\"";
    std::string name_str = col_name.ToString();
    for (char c : name_str) {
      if (c == '"') {
        schema_sql += "\"\"";
      } else {
        schema_sql += c;
      }
    }
    schema_sql += "\" ";
    schema_sql += type_str;

    if (hidden) {
      schema_sql += " HIDDEN";
    }
  }

  schema_sql += ")";

  // Reading the options bag and the column definitions above can run user
  // JavaScript through a property getter, which may have closed the database
  // since it was checked.
  THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");

  VirtualTableModule* vtab_mod =
      new VirtualTableModule(env,
                             BaseObjectWeakPtr<Database>(db),
                             rows_fn,
                             std::move(schema_sql),
                             num_columns,
                             std::move(hidden_col_indices),
                             use_bigint_args,
                             direct_only);

  int r = sqlite3_create_module_v2(db->connection_.get(),
                                   *name,
                                   &vtab_mod->module_def_,
                                   vtab_mod,
                                   VirtualTableModule::xDestroyModule);
  CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void());
}

void Database::CreateSession(const FunctionCallbackInfo<Value>& args) {
  Database* db;
  ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
  Environment* env = Environment::GetCurrent(args);
  THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
  THROW_AND_RETURN_IF_IN_AUTHORIZER(env, db);

  std::string table;
  std::string db_name = "main";

  if (args.Length() > 0) {
    if (!args[0]->IsObject()) {
      THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
                                 "The \"options\" argument must be an object.");
      return;
    }

    Local<Object> options = args[0].As<Object>();

    Local<String> table_key = env->table_string();
    bool hasIt;
    if (!options->HasOwnProperty(env->context(), table_key).To(&hasIt)) {
      return;
    }
    if (hasIt) {
      Local<Value> table_value;
      if (!options->Get(env->context(), table_key).ToLocal(&table_value)) {
        return;
      }

      if (table_value->IsString()) {
        table = Utf8Value(env->isolate(), table_value).ToString();
      } else {
        THROW_ERR_INVALID_ARG_TYPE(
            env->isolate(), "The \"options.table\" argument must be a string.");
        return;
      }
    }

    Local<String> db_key = FIXED_ONE_BYTE_STRING(env->isolate(), "db");

    if (!options->HasOwnProperty(env->context(), db_key).To(&hasIt)) {
      return;
    }
    if (hasIt) {
      Local<Value> db_value;
      if (!options->Get(env->context(), db_key).ToLocal(&db_value)) {
        // An error will have been scheduled.
        return;
      }
      if (db_value->IsString()) {
        db_name = Utf8Value(env->isolate(), db_value).ToString();
      } else {
        THROW_ERR_INVALID_ARG_TYPE(
            env->isolate(), "The \"options.db\" argument must be a string.");
        return;
      }
    }
  }

  // Reading the options bag above can run user JavaScript through a property
  // getter, which may have closed the database since it was checked.
  THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");

  sqlite3_session* raw_session;
  int r = sqlite3session_create(
      db->connection_.get(), db_name.c_str(), &raw_session);
  CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void());
  SessionPtr pSession(raw_session);

  r = sqlite3session_attach(pSession.get(),
                            table == "" ? nullptr : table.c_str());
  CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void());

  BaseObjectPtr<Session> session =
      Session::Create(env, BaseObjectPtr<Database>(db), std::move(pSession));
  if (!session) {
    return;
  }
  args.GetReturnValue().Set(session->object());
}

void Backup(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  if (!args[0]->IsObject() ||
      !env->sqlite_database_constructor_template()->HasInstance(args[0])) {
    THROW_ERR_INVALID_ARG_TYPE(
        env->isolate(),
        "The \"sourceDb\" argument must be an instance of Database.");
    return;
  }

  Database* db;
  ASSIGN_OR_RETURN_UNWRAP(&db, args[0].As<Object>());
  THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
  std::optional<std::string> dest_path =
      ValidateDatabasePath(env, args[1], "path");
  if (!dest_path.has_value()) {
    return;
  }

  int rate = 100;
  std::string source_db = "main";
  std::string dest_db = "main";
  Local<Function> progressFunc = Local<Function>();

  if (args.Length() > 2) {
    if (!args[2]->IsObject()) {
      THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
                                 "The \"options\" argument must be an object.");
      return;
    }

    Local<Object> options = args[2].As<Object>();
    Local<Value> rate_v;
    if (!options->Get(env->context(), env->rate_string()).ToLocal(&rate_v)) {
      return;
    }

    if (!rate_v->IsUndefined()) {
      if (!rate_v->IsInt32()) {
        THROW_ERR_INVALID_ARG_TYPE(
            env->isolate(),
            "The \"options.rate\" argument must be an integer.");
        return;
      }
      rate = rate_v.As<Int32>()->Value();
      if (rate <= 0) {
        THROW_ERR_OUT_OF_RANGE(
            env->isolate(),
            "The \"options.rate\" argument must be a positive integer.");
        return;
      }
    }

    Local<Value> source_v;
    if (!options->Get(env->context(), env->source_string())
             .ToLocal(&source_v)) {
      return;
    }

    if (!source_v->IsUndefined()) {
      if (!source_v->IsString()) {
        THROW_ERR_INVALID_ARG_TYPE(
            env->isolate(),
            "The \"options.source\" argument must be a string.");
        return;
      }

      source_db = Utf8Value(env->isolate(), source_v.As<String>()).ToString();
    }

    Local<Value> target_v;
    if (!options->Get(env->context(), env->target_string())
             .ToLocal(&target_v)) {
      return;
    }

    if (!target_v->IsUndefined()) {
      if (!target_v->IsString()) {
        THROW_ERR_INVALID_ARG_TYPE(
            env->isolate(),
            "The \"options.target\" argument must be a string.");
        return;
      }

      dest_db = Utf8Value(env->isolate(), target_v.As<String>()).ToString();
    }

    Local<Value> progress_v;
    if (!options->Get(env->context(), env->progress_string())
             .ToLocal(&progress_v)) {
      return;
    }

    if (!progress_v->IsUndefined()) {
      if (!progress_v->IsFunction()) {
        THROW_ERR_INVALID_ARG_TYPE(
            env->isolate(),
            "The \"options.progress\" argument must be a function.");
        return;
      }
      progressFunc = progress_v.As<Function>();
    }
  }

  // Reading the destination path and the options bag above can run user
  // JavaScript through a property getter, which may have closed the database
  // since it was checked.
  THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");

  Local<Promise::Resolver> resolver;
  if (!Promise::Resolver::New(env->context()).ToLocal(&resolver)) {
    return;
  }

  args.GetReturnValue().Set(resolver->GetPromise());
  BackupJob* job = new BackupJob(env,
                                 db,
                                 resolver,
                                 std::move(source_db),
                                 dest_path.value(),
                                 std::move(dest_db),
                                 rate,
                                 progressFunc);
  db->AddBackup(job);
  job->ScheduleBackup();
}

struct ConflictCallbackContext {
  std::function<bool(std::string_view)> filterCallback;
  std::function<int(int)> conflictCallback;
};

// the reason for using static functions here is that SQLite needs a
// function pointer

static int xConflict(void* pCtx, int eConflict, sqlite3_changeset_iter* pIter) {
  auto ctx = static_cast<ConflictCallbackContext*>(pCtx);
  if (!ctx->conflictCallback) return SQLITE_CHANGESET_ABORT;
  return ctx->conflictCallback(eConflict);
}

static int xFilter(void* pCtx, const char* zTab) {
  auto ctx = static_cast<ConflictCallbackContext*>(pCtx);
  return ctx->filterCallback(zTab) ? 1 : 0;
}

void Database::ApplyChangeset(const FunctionCallbackInfo<Value>& args) {
  ConflictCallbackContext context;

  Database* db;
  ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
  Environment* env = Environment::GetCurrent(args);
  THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
  THROW_AND_RETURN_IF_IN_AUTHORIZER(env, db);

  if (!args[0]->IsUint8Array()) {
    THROW_ERR_INVALID_ARG_TYPE(
        env->isolate(), "The \"changeset\" argument must be a Uint8Array.");
    return;
  }

  Local<Function> conflictFunc;
  Local<Function> filterFunc;
  bool filterCallbackFailed = false;
  if (args.Length() > 1 && !args[1]->IsUndefined()) {
    if (!args[1]->IsObject()) {
      THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
                                 "The \"options\" argument must be an object.");
      return;
    }

    Local<Object> options = args[1].As<Object>();
    Local<Value> conflictValue;
    if (!options->Get(env->context(), env->onconflict_string())
             .ToLocal(&conflictValue)) {
      // An error will have been scheduled.
      return;
    }

    if (!conflictValue->IsUndefined()) {
      if (!conflictValue->IsFunction()) {
        THROW_ERR_INVALID_ARG_TYPE(
            env->isolate(),
            "The \"options.onConflict\" argument must be a function.");
        return;
      }
      conflictFunc = conflictValue.As<Function>();
      context.conflictCallback = [env, &conflictFunc](int conflictType) -> int {
        Local<Value> argv[] = {Integer::New(env->isolate(), conflictType)};
        TryCatch try_catch(env->isolate());
        Local<Value> result =
            conflictFunc->Call(env->context(), Null(env->isolate()), 1, argv)
                .FromMaybe(Local<Value>());
        if (try_catch.HasCaught()) {
          try_catch.ReThrow();
          return SQLITE_CHANGESET_ABORT;
        }
        constexpr auto invalid_value = -1;
        if (!result->IsInt32()) return invalid_value;
        return result->Int32Value(env->context()).FromJust();
      };
    }

    bool hasIt;
    if (!options->HasOwnProperty(env->context(), env->filter_string())
             .To(&hasIt)) {
      return;
    }
    if (hasIt) {
      Local<Value> filterValue;
      if (!options->Get(env->context(), env->filter_string())
               .ToLocal(&filterValue)) {
        // An error will have been scheduled.
        return;
      }

      if (!filterValue->IsFunction()) {
        THROW_ERR_INVALID_ARG_TYPE(
            env->isolate(),
            "The \"options.filter\" argument must be a function.");
        return;
      }

      filterFunc = filterValue.As<Function>();

      context.filterCallback = [env, &filterFunc, &filterCallbackFailed](
                                   std::string_view item) -> bool {
        // If there was an error in the previous call to the filter's
        // callback, we skip calling it again.
        if (filterCallbackFailed) {
          return false;
        }

        Local<Value> argv[1];
        if (!ToV8Value(env->context(), item, env->isolate())
                 .ToLocal(&argv[0])) {
          filterCallbackFailed = true;
          return false;
        }

        Local<Value> result;
        if (!filterFunc->Call(env->context(), Null(env->isolate()), 1, argv)
                 .ToLocal(&result)) {
          filterCallbackFailed = true;
          return false;
        }

        return result->BooleanValue(env->isolate());
      };
    }
  }

  // Reading the options bag above can run user JavaScript through a property
  // getter, which may have closed the database since it was checked.
  THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");

  // Keep the database alive in case a callback drops all references to it,
  // which could otherwise let it be garbage-collected mid-callback.
  BaseObjectPtr<Database> guard(db);

  ArrayBufferViewContents<uint8_t> buf(args[0]);
  if (buf.length() > std::numeric_limits<int>::max()) {
    THROW_ERR_OUT_OF_RANGE(env, "The changeset is too large.");
    return;
  }

  // SQLite may invoke JavaScript through explicit callbacks or user-defined
  // SQL functions while applying the changeset. Copy the input so JavaScript
  // cannot detach or modify the memory while SQLite is still reading it.
  std::unique_ptr<BackingStore> changeset;
  if (buf.length() > 0) {
    changeset = ArrayBuffer::NewBackingStore(
        env->isolate(),
        buf.length(),
        BackingStoreInitializationMode::kUninitialized,
        BackingStoreOnFailureMode::kReturnNull);
    if (!changeset) {
      THROW_ERR_MEMORY_ALLOCATION_FAILED(env);
      return;
    }
    std::memcpy(changeset->Data(), buf.data(), buf.length());
  }

  int r;
  {
    CallbackDepthGuard guard(db);
    r = sqlite3changeset_apply(
        db->connection_.get(),
        static_cast<int>(buf.length()),
        changeset ? changeset->Data()
                  : const_cast<void*>(static_cast<const void*>(buf.data())),
        context.filterCallback ? xFilter : nullptr,
        xConflict,
        static_cast<void*>(&context));
  }
  if (r == SQLITE_OK) {
    args.GetReturnValue().Set(true);
    return;
  }
  if (r == SQLITE_ABORT) {
    // this is not an error, return false
    args.GetReturnValue().Set(false);
    return;
  }
  THROW_ERR_SQLITE_ERROR(env->isolate(), r);
}

void Database::EnableLoadExtension(const FunctionCallbackInfo<Value>& args) {
  Database* db;
  ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
  Environment* env = Environment::GetCurrent(args);
  THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
  THROW_AND_RETURN_IF_IN_AUTHORIZER(env, db);

  Isolate* isolate = env->isolate();
  if (!args[0]->IsBoolean()) {
    THROW_ERR_INVALID_ARG_TYPE(isolate,
                               "The \"allow\" argument must be a boolean.");
    return;
  }

  const int enable = args[0].As<Boolean>()->Value();

  if (db->allow_load_extension_ == false && enable == true) {
    THROW_ERR_INVALID_STATE(
        isolate,
        "Cannot enable extension loading because it was disabled at database "
        "creation.");
    return;
  }
  db->enable_load_extension_ = enable;
  const int load_extension_ret =
      sqlite3_db_config(db->connection_.get(),
                        SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION,
                        enable,
                        nullptr);
  CHECK_ERROR_OR_THROW(isolate, db, load_extension_ret, SQLITE_OK, void());
}

void Database::EnableDefensive(const FunctionCallbackInfo<Value>& args) {
  Database* db;
  ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
  Environment* env = Environment::GetCurrent(args);
  THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
  THROW_AND_RETURN_IF_IN_AUTHORIZER(env, db);

  Isolate* isolate = env->isolate();
  if (!args[0]->IsBoolean()) {
    THROW_ERR_INVALID_ARG_TYPE(isolate,
                               "The \"active\" argument must be a boolean.");
    return;
  }

  const int enable = args[0].As<Boolean>()->Value();
  int defensive_enabled;
  const int defensive_ret = sqlite3_db_config(db->connection_.get(),
                                              SQLITE_DBCONFIG_DEFENSIVE,
                                              enable,
                                              &defensive_enabled);
  CHECK_ERROR_OR_THROW(isolate, db, defensive_ret, SQLITE_OK, void());
  db->open_config_.set_enable_defensive(enable);
}

void Database::LoadExtension(const FunctionCallbackInfo<Value>& args) {
  Database* db;
  ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
  Environment* env = Environment::GetCurrent(args);
  THROW_AND_RETURN_ON_BAD_STATE(
      env, !db->connection_.get(), "database is not open");
  THROW_AND_RETURN_ON_BAD_STATE(
      env, !db->allow_load_extension_, "extension loading is not allowed");
  THROW_AND_RETURN_ON_BAD_STATE(
      env, !db->enable_load_extension_, "extension loading is not allowed");
  THROW_AND_RETURN_IF_IN_AUTHORIZER(env, db);

  if (!args[0]->IsString()) {
    THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
                               "The \"path\" argument must be a string.");
    return;
  }

  auto isolate = env->isolate();

  BufferValue path(isolate, args[0]);
  BufferValue entryPoint(isolate, args[1]);
  CHECK_NOT_NULL(*path);
  ToNamespacedPath(env, &path);
  THROW_IF_INSUFFICIENT_PERMISSIONS(
      env, permission::PermissionScope::kFileSystemRead, path.ToStringView());
  char* errmsg = nullptr;
  const int r = sqlite3_load_extension(
      db->connection_.get(), *path, *entryPoint, &errmsg);
  if (r != SQLITE_OK) {
    isolate->ThrowException(ERR_LOAD_SQLITE_EXTENSION(isolate, errmsg));
  }
}

void Database::SetAuthorizer(const FunctionCallbackInfo<Value>& args) {
  Database* db;
  ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
  Environment* env = Environment::GetCurrent(args);
  THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
  THROW_AND_RETURN_IF_IN_AUTHORIZER(env, db);

  Isolate* isolate = env->isolate();

  if (args[0]->IsNull()) {
    // Clear the authorizer
    sqlite3_set_authorizer(db->connection_.get(), nullptr, nullptr);
    db->object()->SetInternalField(kAuthorizerCallback, Null(isolate));
    return;
  }

  if (!args[0]->IsFunction()) {
    THROW_ERR_INVALID_ARG_TYPE(
        isolate, "The \"callback\" argument must be a function or null.");
    return;
  }

  Local<Function> fn = args[0].As<Function>();

  db->object()->SetInternalField(kAuthorizerCallback, fn);

  int r = sqlite3_set_authorizer(
      db->connection_.get(), Database::AuthorizerCallback, db);

  if (r != SQLITE_OK) {
    CHECK_ERROR_OR_THROW(isolate, db, r, SQLITE_OK, void());
  }
}

int Database::AuthorizerCallback(void* user_data,
                                 int action_code,
                                 const char* param1,
                                 const char* param2,
                                 const char* param3,
                                 const char* param4) {
  Database* db = static_cast<Database*>(user_data);
  CallbackDepthGuard guard(db);
  AuthorizerDepthGuard authorizer_guard(db);
  Environment* env = db->env();
  Isolate* isolate = env->isolate();
  HandleScope handle_scope(isolate);
  Local<Context> context = env->context();

  Local<Value> cb =
      db->object()->GetInternalField(kAuthorizerCallback).template As<Value>();

  CHECK(cb->IsFunction());

  Local<Function> callback = cb.As<Function>();

  LocalVector<Value> js_argv(isolate, {Integer::New(isolate, action_code)});
  for (const char* param : {param1, param2, param3, param4}) {
    Local<Value> arg;
    if (!NullableSQLiteStringToValue(isolate, param).ToLocal(&arg)) {
      db->SetIgnoreNextSQLiteError(true);
      return SQLITE_DENY;
    }
    js_argv.push_back(arg);
  }

  MaybeLocal<Value> retval = callback->Call(
      context, Undefined(isolate), js_argv.size(), js_argv.data());

  Local<Value> result;

  if (!retval.ToLocal(&result)) {
    db->SetIgnoreNextSQLiteError(true);
    return SQLITE_DENY;
  }

  Local<String> error_message;

  if (!result->IsInt32()) {
    if (!String::NewFromUtf8(
             isolate,
             "Authorizer callback must return an integer authorization code")
             .ToLocal(&error_message)) {
      return SQLITE_DENY;
    }

    Local<Value> err = Exception::TypeError(error_message);
    isolate->ThrowException(err);
    db->SetIgnoreNextSQLiteError(true);
    return SQLITE_DENY;
  }

  int32_t int_result = result.As<Int32>()->Value();
  if (int_result != SQLITE_OK && int_result != SQLITE_DENY &&
      int_result != SQLITE_IGNORE) {
    if (!String::NewFromUtf8(
             isolate,
             "Authorizer callback returned a invalid authorization code")
             .ToLocal(&error_message)) {
      return SQLITE_DENY;
    }

    Local<Value> err = Exception::RangeError(error_message);
    isolate->ThrowException(err);
    db->SetIgnoreNextSQLiteError(true);
    return SQLITE_DENY;
  }

  return int_result;
}

int Database::TraceCallback(unsigned int type,
                            void* user_data,
                            void* p,
                            void* x) {
  if (type != SQLITE_TRACE_PROFILE) {
    return 0;
  }

  Database* db = static_cast<Database*>(user_data);
  Environment* env = db->env();

  diagnostics_channel::Channel* ch = db->trace_channel_.get();
  // Checked before the channel, so that a suppressed callback does not
  // dereference it at all. Statement finalization suppresses trace events, and
  // that is the path SQLite takes during environment teardown.
  if (db->AreTraceEventsSuppressed() || ch == nullptr ||
      !ch->HasSubscribers()) {
    return 0;
  }

  // Entered before building the payload below, because allocating it can
  // trigger a garbage collection that SQLite is not prepared for.
  CallbackDepthGuard guard(db);

  Isolate* isolate = env->isolate();
  HandleScope handle_scope(isolate);

  char* expanded = sqlite3_expanded_sql(static_cast<sqlite3_stmt*>(p));
  Local<Value> sql_string;
  if (expanded != nullptr) {
    bool ok = String::NewFromUtf8(isolate, expanded).ToLocal(&sql_string);
    sqlite3_free(expanded);
    if (!ok) {
      return 0;
    }
  } else {
    // Fallback to source SQL if expanded is unavailable
    const char* source = sqlite3_sql(static_cast<sqlite3_stmt*>(p));
    if (source == nullptr ||
        !String::NewFromUtf8(isolate, source).ToLocal(&sql_string)) {
      return 0;
    }
  }

  // x points to the estimated statement run time in nanoseconds. A double is
  // sufficient since 2^53 ns (~104 days) exceeds any realistic query duration.
  sqlite3_int64 duration_ns = *static_cast<sqlite3_int64*>(x);

  Local<Name> keys[3] = {
      env->sql_string().As<Name>(),
      env->database_string().As<Name>(),
      env->duration_string().As<Name>(),
  };

  Local<Value> values[3] = {
      sql_string,
      db->object(),
      Number::New(isolate, static_cast<double>(duration_ns)),
  };

  Local<Object> payload = Object::New(isolate, Null(isolate), keys, values, 3);

  ch->Publish(env, payload);

  return 0;
}

Statement::Statement(Environment* env,
                     Local<Object> object,
                     BaseObjectPtr<Database> db,
                     StatementPtr stmt)
    : BaseObject(env, object), db_(std::move(db)), statement_(std::move(stmt)) {
  db_->TrackStatement(this);
  MakeWeak();
  use_big_ints_ = db_->use_big_ints();
  return_arrays_ = db_->return_arrays();
  allow_bare_named_params_ = db_->allow_bare_named_params();
  allow_unknown_named_params_ = db_->allow_unknown_named_params();

  bare_named_params_ = std::nullopt;
}

Statement::~Statement() {
  // Runs from a garbage collection callback, so finalizing the statement here
  // must not reach back into JavaScript. The scope has to live here rather than
  // in Close(), which is shared with the JS-facing close() and dispose().
  DestructorScope destructor_scope(db_.get());
  Close();
}

void Statement::Close() {
  db_->UntrackStatement(this);

  if (!IsFinalized()) {
    Finalize();
  }
}

void Statement::Finalize() {
  TraceEventSuppressionGuard trace_guard(db_.get());
  statement_.reset();
  InvalidateColumnNameCache();
}

void Statement::InvalidateColumnNameCache() {
  cached_column_names_.clear();
  cached_row_template_.Reset();
  cached_column_names_reprepare_count_ = -1;
}

inline bool Statement::IsFinalized() {
  return statement_ == nullptr;
}

void Statement::Close(const FunctionCallbackInfo<Value>& args) {
  Statement* stmt;
  ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This());
  Environment* env = Environment::GetCurrent(args);
  THROW_AND_RETURN_ON_BAD_STATE(
      env, stmt->IsFinalized(), "statement has been finalized");
  THROW_AND_RETURN_IF_STEPPING(env, stmt);
  THROW_AND_RETURN_IF_BUSY_IN_AUTHORIZER(env, stmt);
  stmt->Close();
}

void Statement::Dispose(const FunctionCallbackInfo<Value>& args) {
  Statement* stmt;
  ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This());
  Environment* env = Environment::GetCurrent(args);
  // Disposal is idempotent, so an already-finalized statement is a no-op even
  // inside a callback.
  if (stmt->IsFinalized()) {
    return;
  }
  THROW_AND_RETURN_IF_STEPPING(env, stmt);
  THROW_AND_RETURN_IF_BUSY_IN_AUTHORIZER(env, stmt);
  stmt->Close();
}

inline int Statement::ResetStatement() {
  reset_generation_++;
  return sqlite3_reset(statement_.get());
}

bool Statement::BindParams(const FunctionCallbackInfo<Value>& args) {
  int param_count = sqlite3_bind_parameter_count(statement_.get());
  // Parameter indexing starts at one.
  MaybeStackBuffer<bool, 32> bound(param_count + 1);
  std::fill_n(bound.out(), param_count + 1, false);

  int anon_idx = 1;
  int anon_start = 0;

  if (args[0]->IsObject() && !args[0]->IsArrayBufferView() &&
      !args[0]->IsArrayBuffer() && !args[0]->IsSharedArrayBuffer()) {
    Local<Object> obj = args[0].As<Object>();
    Local<Context> context = Isolate::GetCurrent()->GetCurrentContext();
    Local<Array> keys;
    if (!obj->GetOwnPropertyNames(context).ToLocal(&keys)) {
      return false;
    }

    if (allow_bare_named_params_ && !bare_named_params_.has_value()) {
      bare_named_params_.emplace();
      for (int i = 1; i <= param_count; ++i) {
        const char* name = sqlite3_bind_parameter_name(statement_.get(), i);
        if (name == nullptr) {
          continue;
        }

        auto bare_name = std::string(name + 1);
        auto full_name = std::string(name);
        auto insertion = bare_named_params_->insert({bare_name, full_name});
        if (insertion.second == false) {
          auto existing_full_name = (*insertion.first).second;
          if (full_name != existing_full_name) {
            THROW_ERR_INVALID_STATE(
                env(),
                "Cannot create bare named parameter '%s' because of "
                "conflicting names '%s' and '%s'.",
                bare_name,
                existing_full_name,
                full_name);
            return false;
          }
        }
      }
    }

    uint32_t len = keys->Length();
    for (uint32_t j = 0; j < len; j++) {
      Local<Value> key;
      if (!keys->Get(context, j).ToLocal(&key)) {
        return false;
      }

      Utf8Value utf8_key(env()->isolate(), key);
      int r = sqlite3_bind_parameter_index(statement_.get(), *utf8_key);
      if (r == 0) {
        if (allow_bare_named_params_) {
          auto lookup = bare_named_params_->find(std::string(*utf8_key));
          if (lookup != bare_named_params_->end()) {
            r = sqlite3_bind_parameter_index(statement_.get(),
                                             lookup->second.c_str());
          }
        }

        if (r == 0) {
          if (allow_unknown_named_params_) {
            continue;
          } else {
            THROW_ERR_INVALID_STATE(
                env(), "Unknown named parameter '%s'", utf8_key);
            return false;
          }
        }
      }

      Local<Value> value;
      if (!obj->Get(context, key).ToLocal(&value)) {
        return false;
      }

      if (!BindValue(value, r)) {
        return false;
      }
      bound[r] = true;
    }
    anon_start++;
  }

  for (int i = anon_start; i < args.Length(); ++i) {
    while (1) {
      const char* param =
          sqlite3_bind_parameter_name(statement_.get(), anon_idx);
      if (param == nullptr || param[0] == '?') break;
      anon_idx++;
    }

    if (!BindValue(args[i], anon_idx)) {
      return false;
    }
    bound[anon_idx] = true;

    anon_idx++;
  }

  // sqlite3_clear_bindings() would expire statements whose query plan depends
  // on a bound value, so only reset the parameters that were not bound.
  for (int i = 1; i <= param_count; ++i) {
    if (!bound[i]) {
      int r = sqlite3_bind_null(statement_.get(), i);
      CHECK_ERROR_OR_THROW(env()->isolate(), db_.get(), r, SQLITE_OK, false);
    }
  }

  return true;
}

bool Statement::BindValue(const Local<Value>& value, const int index) {
  // SQLite only supports a subset of JavaScript types. Some JS types such as
  // functions don't make sense to support. Other JS types such as
  // Dates could be supported by converting them to numbers. However, there
  // would not be a good way to read the values back from SQLite with the
  // original type. JS Boolean binds to 1 and 0 because SQLite maps true and
  // false keywords to 1 and 0. JS undefined binds to NULL so that passing it
  // explicitly matches omitting the parameter altogether.
  Isolate* isolate = env()->isolate();
  int r;
  if (value->IsNumber()) {
    const double val = value.As<Number>()->Value();
    r = sqlite3_bind_double(statement_.get(), index, val);
  } else if (value->IsString()) {
    Utf8Value val(isolate, value.As<String>());
    if (val.IsAllocated()) {
      // Avoid an extra SQLite copy for large strings by transferring ownership
      // of the malloc()'d buffer to SQLite.
      char* data = *val;
      const sqlite3_uint64 length = static_cast<sqlite3_uint64>(val.length());
      val.Release();
      r = sqlite3_bind_text64(
          statement_.get(), index, data, length, std::free, SQLITE_UTF8);
    } else {
      r = sqlite3_bind_text64(statement_.get(),
                              index,
                              *val,
                              static_cast<sqlite3_uint64>(val.length()),
                              SQLITE_TRANSIENT,
                              SQLITE_UTF8);
    }
  } else if (value->IsNullOrUndefined()) {
    r = sqlite3_bind_null(statement_.get(), index);
  } else if (value->IsArrayBufferView() || value->IsArrayBuffer() ||
             value->IsSharedArrayBuffer()) {
    ArrayBufferViewContents<uint8_t> buf(value);
    r = sqlite3_bind_blob64(statement_.get(),
                            index,
                            buf.data(),
                            static_cast<sqlite3_uint64>(buf.length()),
                            SQLITE_TRANSIENT);
  } else if (value->IsBoolean()) {
    r = sqlite3_bind_int(statement_.get(), index, value->IsTrue() ? 1 : 0);
  } else if (value->IsBigInt()) {
    bool lossless;
    int64_t as_int = value.As<BigInt>()->Int64Value(&lossless);
    if (!lossless) {
      THROW_ERR_INVALID_ARG_VALUE(env(), "BigInt value is too large to bind.");
      return false;
    }
    r = sqlite3_bind_int64(statement_.get(), index, as_int);
  } else {
    THROW_ERR_INVALID_ARG_TYPE(
        isolate,
        "Provided value cannot be bound to SQLite parameter %d.",
        index);
    return false;
  }

  CHECK_ERROR_OR_THROW(isolate, db_.get(), r, SQLITE_OK, false);
  return true;
}

MaybeLocal<Value> Statement::ColumnToValue(const int column) {
  return StatementExecutionHelper::ColumnToValue(
      env(), statement_.get(), column, use_big_ints_);
}

MaybeLocal<Name> Statement::ColumnNameToName(const int column) {
  const char* col_name = sqlite3_column_name(statement_.get(), column);
  if (col_name == nullptr) {
    THROW_ERR_INVALID_STATE(env(), "Cannot get name of column %d", column);
    return MaybeLocal<Name>();
  }

  return String::NewFromUtf8(
             env()->isolate(), col_name, NewStringType::kInternalized)
      .As<Name>();
}

// Size of the stack array RowToObject passes to the template.
static constexpr int kMaxRowTemplateColumns = 64;

// Templates read names as Latin-1 and reject indices and duplicates.
static bool CanUseInRowTemplate(Local<Context> context,
                                Local<Name> key,
                                std::string_view name,
                                const std::vector<std::string_view>& seen) {
  return simdutf::validate_ascii(name.data(), name.size()) &&
         std::find(seen.begin(), seen.end(), name) == seen.end() &&
         key->ToArrayIndex(context).IsEmpty();
}

// Fills either cached_row_template_ or cached_column_names_.
bool Statement::UpdateRowCache() {
  const int reprepare_count =
      sqlite3_stmt_status(statement_.get(), SQLITE_STMTSTATUS_REPREPARE, false);
  if (reprepare_count == cached_column_names_reprepare_count_) return true;

  InvalidateColumnNameCache();
  Isolate* isolate = env()->isolate();
  Local<Context> context = env()->context();
  const int num_cols = sqlite3_column_count(statement_.get());
  LocalVector<Name> keys(isolate);
  std::vector<std::string_view> names;
  keys.reserve(num_cols);
  names.reserve(num_cols);
  bool use_template = num_cols <= kMaxRowTemplateColumns;
  for (int i = 0; i < num_cols; ++i) {
    Local<Name> key;
    if (!ColumnNameToName(i).ToLocal(&key)) return false;
    std::string_view name = sqlite3_column_name(statement_.get(), i);
    use_template =
        use_template && CanUseInRowTemplate(context, key, name, names);
    keys.push_back(key);
    names.push_back(name);
  }

  if (use_template) {
    cached_row_template_.Reset(
        isolate,
        DictionaryTemplate::New(isolate, {names.data(), names.size()}));
  } else {
    cached_column_names_.reserve(num_cols);
    for (Local<Name> key : keys) {
      cached_column_names_.emplace_back(isolate, key);
    }
  }
  cached_column_names_reprepare_count_ = reprepare_count;
  return true;
}

MaybeLocal<Object> Statement::RowToObject(LocalVector<Value>* values) {
  DCHECK_EQ(
      sqlite3_stmt_status(statement_.get(), SQLITE_STMTSTATUS_REPREPARE, false),
      cached_column_names_reprepare_count_);
  Isolate* isolate = env()->isolate();
  if (cached_row_template_.IsEmpty()) {
    LocalVector<Name> keys(isolate);
    keys.reserve(cached_column_names_.size());
    for (const auto& name : cached_column_names_) {
      keys.emplace_back(name.Get(isolate));
    }
    DCHECK_EQ(keys.size(), values->size());
    return Object::New(
        isolate, Null(isolate), keys.data(), values->data(), keys.size());
  }
  DCHECK_LE(values->size(), kMaxRowTemplateColumns);
  MaybeLocal<Value> template_values[kMaxRowTemplateColumns];
  std::copy(values->begin(), values->end(), template_values);
  return NewDictionaryInstanceNullProto(env()->context(),
                                        cached_row_template_.Get(isolate),
                                        {template_values, values->size()});
}

MaybeLocal<Value> StatementExecutionHelper::ColumnToValue(Environment* env,
                                                          sqlite3_stmt* stmt,
                                                          const int column,
                                                          bool use_big_ints) {
  Isolate* isolate = env->isolate();
  MaybeLocal<Value> js_val = MaybeLocal<Value>();
  SQLITE_VALUE_TO_JS(column, isolate, use_big_ints, js_val, stmt, column);
  return js_val;
}

void Statement::MemoryInfo(MemoryTracker* tracker) const {}

Maybe<void> ExtractRowValues(Environment* env,
                             sqlite3_stmt* stmt,
                             int num_cols,
                             bool use_big_ints,
                             LocalVector<Value>* row_values) {
  row_values->clear();
  row_values->reserve(num_cols);
  for (int i = 0; i < num_cols; ++i) {
    Local<Value> val;
    if (!StatementExecutionHelper::ColumnToValue(env, stmt, i, use_big_ints)
             .ToLocal(&val)) {
      return Nothing<void>();
    }
    row_values->emplace_back(val);
  }
  return JustVoid();
}

MaybeLocal<Value> StatementExecutionHelper::All(Environment* env,
                                                Statement* statement) {
  Database* db = statement->db_.get();
  sqlite3_stmt* stmt = statement->statement_.get();
  const bool return_arrays = statement->return_arrays_;
  const bool use_big_ints = statement->use_big_ints_;
  Isolate* isolate = env->isolate();
  EscapableHandleScope scope(isolate);
  int r;
  int num_cols = 0;
  LocalVector<Value> rows(isolate);
  LocalVector<Value> row_values(isolate);

  SteppingStatementGuard stepping(db, stmt);
  while ((r = sqlite3_step(stmt)) == SQLITE_ROW) {
    if (num_cols == 0) {
      num_cols = sqlite3_column_count(stmt);
      if (!return_arrays && !statement->UpdateRowCache()) {
        return MaybeLocal<Value>();
      }
    }

    if (ExtractRowValues(env, stmt, num_cols, use_big_ints, &row_values)
            .IsNothing()) {
      return MaybeLocal<Value>();
    }

    if (return_arrays) {
      Local<Array> row_array =
          Array::New(isolate, row_values.data(), row_values.size());
      rows.emplace_back(row_array);
    } else {
      Local<Object> row_obj;
      if (!statement->RowToObject(&row_values).ToLocal(&row_obj)) {
        return MaybeLocal<Value>();
      }
      rows.emplace_back(row_obj);
    }
  }

  CHECK_ERROR_OR_THROW(isolate, db, r, SQLITE_DONE, MaybeLocal<Value>());
  return scope.Escape(Array::New(isolate, rows.data(), rows.size()));
}

MaybeLocal<Object> StatementExecutionHelper::Run(Environment* env,
                                                 Statement* statement) {
  Database* db = statement->db_.get();
  sqlite3_stmt* stmt = statement->statement_.get();
  const bool use_big_ints = statement->use_big_ints_;
  Isolate* isolate = env->isolate();
  EscapableHandleScope scope(isolate);
  // Declared before the reset below so that it outlives it: sqlite3_reset()
  // can run JavaScript through an aggregate's xFinal.
  SteppingStatementGuard stepping(db, stmt);
  bool needs_reset = true;
  auto reset = OnScopeLeave([&]() {
    if (needs_reset) sqlite3_reset(stmt);
  });

  int step_r = sqlite3_step(stmt);
  // SQLITE_ROW is accepted here (and discarded) so that run() can still be
  // used on RETURNING/SELECT statements, matching prior behavior of
  // ignoring the step result entirely.
  if (step_r != SQLITE_DONE && step_r != SQLITE_ROW) {
    THROW_ERR_SQLITE_ERROR(isolate, db);
    return MaybeLocal<Object>();
  }
  RESET_AND_CHECK(isolate, db, stmt, needs_reset, MaybeLocal<Object>());

  sqlite3_int64 last_insert_rowid = sqlite3_last_insert_rowid(db->Connection());
  sqlite3_int64 changes = sqlite3_changes64(db->Connection());
  Local<Value> last_insert_rowid_val;
  Local<Value> changes_val;

  if (!IntegerToValue(isolate, last_insert_rowid, use_big_ints)
           .ToLocal(&last_insert_rowid_val) ||
      !IntegerToValue(isolate, changes, use_big_ints).ToLocal(&changes_val)) {
    return MaybeLocal<Object>();
  }

  auto run_result_template = env->sqlite_run_result_template();
  if (run_result_template.IsEmpty()) {
    static constexpr std::string_view run_result_keys[] = {"changes",
                                                           "lastInsertRowid"};
    run_result_template = DictionaryTemplate::New(isolate, run_result_keys);
    env->set_sqlite_run_result_template(run_result_template);
  }

  MaybeLocal<Value> values[] = {changes_val, last_insert_rowid_val};
  Local<Object> result;
  if (!NewDictionaryInstance(env->context(), run_result_template, values)
           .ToLocal(&result)) {
    return MaybeLocal<Object>();
  }

  return scope.Escape(result);
}

BaseObjectPtr<StatementIterator> StatementExecutionHelper::Iterate(
    Environment* env, BaseObjectPtr<Statement> stmt) {
  Local<Context> context = env->context();
  Local<Object> global = context->Global();
  Local<Value> js_iterator;
  Local<Value> js_iterator_prototype;
  if (!global->Get(context, env->iterator_string()).ToLocal(&js_iterator)) {
    return BaseObjectPtr<StatementIterator>();
  }
  if (!js_iterator.As<Object>()
           ->Get(context, env->prototype_string())
           .ToLocal(&js_iterator_prototype)) {
    return BaseObjectPtr<StatementIterator>();
  }

  BaseObjectPtr<StatementIterator> iter = StatementIterator::Create(env, stmt);

  if (!iter) {
    // Error in iterator creation, likely already threw in Create
    return BaseObjectPtr<StatementIterator>();
  }

  if (iter->object()
          ->GetPrototype()
          .As<Object>()
          ->SetPrototype(context, js_iterator_prototype)
          .IsNothing()) {
    return BaseObjectPtr<StatementIterator>();
  }

  return iter;
}

MaybeLocal<Value> StatementExecutionHelper::Get(Environment* env,
                                                Statement* statement) {
  Database* db = statement->db_.get();
  sqlite3_stmt* stmt = statement->statement_.get();
  const bool return_arrays = statement->return_arrays_;
  const bool use_big_ints = statement->use_big_ints_;
  Isolate* isolate = env->isolate();
  EscapableHandleScope scope(isolate);
  // Declared before the reset below so that it outlives it: sqlite3_reset()
  // can run JavaScript through an aggregate's xFinal.
  SteppingStatementGuard stepping(db, stmt);
  bool needs_reset = true;
  auto reset = OnScopeLeave([&]() {
    if (needs_reset) sqlite3_reset(stmt);
  });

  int r = sqlite3_step(stmt);
  if (r == SQLITE_DONE) {
    RESET_AND_CHECK(isolate, db, stmt, needs_reset, MaybeLocal<Value>());
    return scope.Escape(Undefined(isolate));
  }
  if (r != SQLITE_ROW) {
    THROW_ERR_SQLITE_ERROR(isolate, db);
    return MaybeLocal<Value>();
  }

  int num_cols = sqlite3_column_count(stmt);
  if (num_cols == 0) {
    RESET_AND_CHECK(isolate, db, stmt, needs_reset, MaybeLocal<Value>());
    return scope.Escape(Undefined(isolate));
  }

  LocalVector<Value> row_values(isolate);
  if (ExtractRowValues(env, stmt, num_cols, use_big_ints, &row_values)
          .IsNothing()) {
    return MaybeLocal<Value>();
  }

  Local<Value> result;
  if (return_arrays) {
    result = Array::New(isolate, row_values.data(), row_values.size());
  } else if (!statement->UpdateRowCache() ||
             !statement->RowToObject(&row_values).ToLocal(&result)) {
    return MaybeLocal<Value>();
  }

  RESET_AND_CHECK(isolate, db, stmt, needs_reset, MaybeLocal<Value>());
  return scope.Escape(result);
}

void Statement::All(const FunctionCallbackInfo<Value>& args) {
  Statement* stmt;
  ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This());
  Environment* env = Environment::GetCurrent(args);
  THROW_AND_RETURN_ON_BAD_STATE(
      env, stmt->IsFinalized(), "statement has been finalized");
  THROW_AND_RETURN_IF_IN_AUTHORIZER(env, stmt->db_.get());
  THROW_AND_RETURN_IF_STEPPING(env, stmt);
  Isolate* isolate = env->isolate();
  SteppingStatementGuard stepping(stmt->db_.get(), stmt->statement_.get());
  int r = stmt->ResetStatement();
  CHECK_ERROR_OR_THROW(isolate, stmt->db_.get(), r, SQLITE_OK, void());

  if (!stmt->BindParams(args)) {
    return;
  }

  bool needs_reset = true;
  auto reset = OnScopeLeave([&]() {
    if (needs_reset) sqlite3_reset(stmt->statement_.get());
  });
  Local<Value> result;
  if (StatementExecutionHelper::All(env, stmt).ToLocal(&result)) {
    RESET_AND_CHECK(
        isolate, stmt->db_.get(), stmt->statement_.get(), needs_reset, void());
    args.GetReturnValue().Set(result);
  }
}

void Statement::Iterate(const FunctionCallbackInfo<Value>& args) {
  Statement* stmt;
  ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This());
  Environment* env = Environment::GetCurrent(args);
  THROW_AND_RETURN_ON_BAD_STATE(
      env, stmt->IsFinalized(), "statement has been finalized");
  THROW_AND_RETURN_IF_IN_AUTHORIZER(env, stmt->db_.get());
  THROW_AND_RETURN_IF_STEPPING(env, stmt);
  SteppingStatementGuard stepping(stmt->db_.get(), stmt->statement_.get());
  int r = stmt->ResetStatement();
  CHECK_ERROR_OR_THROW(env->isolate(), stmt->db_.get(), r, SQLITE_OK, void());

  if (!stmt->BindParams(args)) {
    return;
  }

  BaseObjectPtr<StatementIterator> iter =
      StatementExecutionHelper::Iterate(env, BaseObjectPtr<Statement>(stmt));

  if (!iter) {
    return;
  }

  args.GetReturnValue().Set(iter->object());
}

void Statement::Get(const FunctionCallbackInfo<Value>& args) {
  Statement* stmt;
  ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This());
  Environment* env = Environment::GetCurrent(args);
  THROW_AND_RETURN_ON_BAD_STATE(
      env, stmt->IsFinalized(), "statement has been finalized");
  THROW_AND_RETURN_IF_IN_AUTHORIZER(env, stmt->db_.get());
  THROW_AND_RETURN_IF_STEPPING(env, stmt);
  SteppingStatementGuard stepping(stmt->db_.get(), stmt->statement_.get());
  int r = stmt->ResetStatement();
  CHECK_ERROR_OR_THROW(env->isolate(), stmt->db_.get(), r, SQLITE_OK, void());

  if (!stmt->BindParams(args)) {
    return;
  }

  Local<Value> result;
  if (StatementExecutionHelper::Get(env, stmt).ToLocal(&result)) {
    args.GetReturnValue().Set(result);
  }
}

void Statement::Run(const FunctionCallbackInfo<Value>& args) {
  Statement* stmt;
  ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This());
  Environment* env = Environment::GetCurrent(args);
  THROW_AND_RETURN_ON_BAD_STATE(
      env, stmt->IsFinalized(), "statement has been finalized");
  THROW_AND_RETURN_IF_IN_AUTHORIZER(env, stmt->db_.get());
  THROW_AND_RETURN_IF_STEPPING(env, stmt);
  SteppingStatementGuard stepping(stmt->db_.get(), stmt->statement_.get());
  int r = stmt->ResetStatement();
  CHECK_ERROR_OR_THROW(env->isolate(), stmt->db_.get(), r, SQLITE_OK, void());

  if (!stmt->BindParams(args)) {
    return;
  }

  Local<Object> result;
  if (StatementExecutionHelper::Run(env, stmt).ToLocal(&result)) {
    args.GetReturnValue().Set(result);
  }
}

void Statement::Columns(const FunctionCallbackInfo<Value>& args) {
  Statement* stmt;
  ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This());
  Environment* env = Environment::GetCurrent(args);
  THROW_AND_RETURN_ON_BAD_STATE(
      env, stmt->IsFinalized(), "statement has been finalized");
  int num_cols = sqlite3_column_count(stmt->statement_.get());
  Isolate* isolate = env->isolate();
  LocalVector<Value> cols(isolate);
  auto sqlite_column_template = env->sqlite_column_template();
  if (sqlite_column_template.IsEmpty()) {
    static constexpr std::string_view col_keys[] = {
        "column", "database", "name", "table", "type"};
    sqlite_column_template = DictionaryTemplate::New(isolate, col_keys);
    env->set_sqlite_column_template(sqlite_column_template);
  }

  cols.reserve(num_cols);
  for (int i = 0; i < num_cols; ++i) {
    MaybeLocal<Value> values[] = {
        NullableSQLiteStringToValue(
            isolate, sqlite3_column_origin_name(stmt->statement_.get(), i)),
        NullableSQLiteStringToValue(
            isolate, sqlite3_column_database_name(stmt->statement_.get(), i)),
        stmt->ColumnNameToName(i),
        NullableSQLiteStringToValue(
            isolate, sqlite3_column_table_name(stmt->statement_.get(), i)),
        NullableSQLiteStringToValue(
            isolate, sqlite3_column_decltype(stmt->statement_.get(), i)),
    };

    Local<Object> col;
    if (!NewDictionaryInstanceNullProto(
             env->context(), sqlite_column_template, values)
             .ToLocal(&col)) {
      return;
    }
    cols.emplace_back(col);
  }

  args.GetReturnValue().Set(Array::New(isolate, cols.data(), cols.size()));
}

void Statement::SourceSQLGetter(const FunctionCallbackInfo<Value>& args) {
  Statement* stmt;
  ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This());
  Environment* env = Environment::GetCurrent(args);
  THROW_AND_RETURN_ON_BAD_STATE(
      env, stmt->IsFinalized(), "statement has been finalized");
  Local<String> sql;
  if (!String::NewFromUtf8(env->isolate(), sqlite3_sql(stmt->statement_.get()))
           .ToLocal(&sql)) {
    return;
  }
  args.GetReturnValue().Set(sql);
}

void Statement::ExpandedSQLGetter(const FunctionCallbackInfo<Value>& args) {
  Statement* stmt;
  ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This());
  Environment* env = Environment::GetCurrent(args);
  THROW_AND_RETURN_ON_BAD_STATE(
      env, stmt->IsFinalized(), "statement has been finalized");

  // sqlite3_expanded_sql may return nullptr without producing an error code.
  char* expanded = sqlite3_expanded_sql(stmt->statement_.get());
  if (expanded == nullptr) {
    return THROW_ERR_SQLITE_ERROR(
        env->isolate(), "Expanded SQL text would exceed configured limits");
  }
  auto maybe_expanded = String::NewFromUtf8(env->isolate(), expanded);
  sqlite3_free(expanded);
  Local<String> result;
  if (!maybe_expanded.ToLocal(&result)) {
    return;
  }
  args.GetReturnValue().Set(result);
}

void Statement::Stat(const FunctionCallbackInfo<Value>& args) {
  Statement* stmt;
  ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This());
  Environment* env = Environment::GetCurrent(args);
  THROW_AND_RETURN_ON_BAD_STATE(
      env, stmt->IsFinalized(), "statement has been finalized");
  Isolate* isolate = env->isolate();

  if (!args[0]->IsString()) {
    THROW_ERR_INVALID_ARG_TYPE(isolate,
                               "The \"counter\" argument must be a string.");
    return;
  }

  Utf8Value counter(isolate, args[0].As<String>());
  const StatusInfo* status_info =
      FindByJsName(kStatusMapping, counter.ToStringView());
  if (status_info == nullptr) {
    THROW_ERR_INVALID_ARG_VALUE(
        isolate, "The \"counter\" argument is not a valid statistic name.");
    return;
  }

  // The reset flag is always false; the counter is read without being cleared.
  int value = sqlite3_stmt_status(
      stmt->statement_.get(), status_info->sqlite_status_id, false);
  args.GetReturnValue().Set(Integer::New(isolate, value));
}

void Statement::ResetStats(const FunctionCallbackInfo<Value>& args) {
  Statement* stmt;
  ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This());
  Environment* env = Environment::GetCurrent(args);
  THROW_AND_RETURN_ON_BAD_STATE(
      env, stmt->IsFinalized(), "statement has been finalized");

  // sqlite3_stmt_status() resets a single counter per call, so every exposed
  // counter is visited. The returned value is the pre-reset one and is unused.
  // SQLITE_STMTSTATUS_MEMUSED is skipped: it reports current memory usage
  // rather than an accumulated counter, and SQLite ignores the reset flag for
  // it.
  for (const auto& info : kStatusMapping) {
    if (info.sqlite_status_id == SQLITE_STMTSTATUS_MEMUSED) {
      continue;
    }
    sqlite3_stmt_status(stmt->statement_.get(), info.sqlite_status_id, true);
  }

  // The column name cache is keyed on SQLITE_STMTSTATUS_REPREPARE, which was
  // just zeroed. Without invalidating, a later re-prepare can make the counter
  // match the cached generation again and the stale names would be reused.
  stmt->InvalidateColumnNameCache();
}

void Statement::SetAllowBareNamedParameters(
    const FunctionCallbackInfo<Value>& args) {
  Statement* stmt;
  ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This());
  Environment* env = Environment::GetCurrent(args);
  THROW_AND_RETURN_ON_BAD_STATE(
      env, stmt->IsFinalized(), "statement has been finalized");

  if (!args[0]->IsBoolean()) {
    THROW_ERR_INVALID_ARG_TYPE(
        env->isolate(),
        "The \"allowBareNamedParameters\" argument must be a boolean.");
    return;
  }

  stmt->allow_bare_named_params_ = args[0]->IsTrue();
}

void Statement::SetAllowUnknownNamedParameters(
    const FunctionCallbackInfo<Value>& args) {
  Statement* stmt;
  ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This());
  Environment* env = Environment::GetCurrent(args);
  THROW_AND_RETURN_ON_BAD_STATE(
      env, stmt->IsFinalized(), "statement has been finalized");

  if (!args[0]->IsBoolean()) {
    THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
                               "The \"enabled\" argument must be a boolean.");
    return;
  }

  stmt->allow_unknown_named_params_ = args[0]->IsTrue();
}

void Statement::SetReadBigInts(const FunctionCallbackInfo<Value>& args) {
  Statement* stmt;
  ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This());
  Environment* env = Environment::GetCurrent(args);
  THROW_AND_RETURN_ON_BAD_STATE(
      env, stmt->IsFinalized(), "statement has been finalized");

  if (!args[0]->IsBoolean()) {
    THROW_ERR_INVALID_ARG_TYPE(
        env->isolate(), "The \"readBigInts\" argument must be a boolean.");
    return;
  }

  stmt->use_big_ints_ = args[0]->IsTrue();
}

void Statement::SetReturnArrays(const FunctionCallbackInfo<Value>& args) {
  Statement* stmt;
  ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This());
  Environment* env = Environment::GetCurrent(args);
  THROW_AND_RETURN_ON_BAD_STATE(
      env, stmt->IsFinalized(), "statement has been finalized");

  if (!args[0]->IsBoolean()) {
    THROW_ERR_INVALID_ARG_TYPE(
        env->isolate(), "The \"returnArrays\" argument must be a boolean.");
    return;
  }

  stmt->return_arrays_ = args[0]->IsTrue();
}

void IllegalConstructor(const FunctionCallbackInfo<Value>& args) {
  THROW_ERR_ILLEGAL_CONSTRUCTOR(Environment::GetCurrent(args));
}

SQLTagStore::SQLTagStore(Environment* env,
                         Local<Object> object,
                         BaseObjectWeakPtr<Database> database,
                         int capacity)
    : BaseObject(env, object),
      database_(std::move(database)),
      sql_tags_(capacity) {
  MakeWeak();
}

static inline void SetSideEffectFreeGetter(
    Isolate* isolate,
    Local<FunctionTemplate> class_template,
    Local<String> name,
    FunctionCallback fn) {
  Local<FunctionTemplate> getter =
      FunctionTemplate::New(isolate,
                            fn,
                            Local<Value>(),
                            v8::Signature::New(isolate, class_template),
                            /* length */ 0,
                            ConstructorBehavior::kThrow,
                            SideEffectType::kHasNoSideEffect);
  class_template->InstanceTemplate()->SetAccessorProperty(
      name, getter, Local<FunctionTemplate>(), DontDelete);
}

SQLTagStore::~SQLTagStore() {}

Local<FunctionTemplate> SQLTagStore::GetConstructorTemplate(Environment* env) {
  Local<FunctionTemplate> tmpl = env->sqlite_tag_store_constructor_template();
  if (tmpl.IsEmpty()) {
    Isolate* isolate = env->isolate();
    tmpl = NewFunctionTemplate(isolate, IllegalConstructor);
    tmpl->SetClassName(FIXED_ONE_BYTE_STRING(isolate, "SQLTagStore"));
    tmpl->InstanceTemplate()->SetInternalFieldCount(
        SQLTagStore::kInternalFieldCount);
    SetProtoMethod(isolate, tmpl, "get", Get);
    SetProtoMethod(isolate, tmpl, "all", All);
    SetProtoMethod(isolate, tmpl, "iterate", Iterate);
    SetProtoMethod(isolate, tmpl, "run", Run);
    SetProtoMethod(isolate, tmpl, "clear", Clear);
    SetSideEffectFreeGetter(isolate,
                            tmpl,
                            FIXED_ONE_BYTE_STRING(isolate, "capacity"),
                            CapacityGetter);
    SetSideEffectFreeGetter(
        isolate, tmpl, FIXED_ONE_BYTE_STRING(isolate, "db"), DatabaseGetter);
    SetSideEffectFreeGetter(isolate, tmpl, env->size_string(), SizeGetter);
    env->set_sqlite_tag_store_constructor_template(tmpl);
  }
  return tmpl;
}

BaseObjectPtr<SQLTagStore> SQLTagStore::Create(
    Environment* env, BaseObjectWeakPtr<Database> database, int capacity) {
  Local<Object> obj;
  if (!GetConstructorTemplate(env)
           ->InstanceTemplate()
           ->NewInstance(env->context())
           .ToLocal(&obj)) {
    return nullptr;
  }
  obj->SetInternalField(kDatabaseObject, database->object());
  return MakeBaseObject<SQLTagStore>(env, obj, std::move(database), capacity);
}

void SQLTagStore::CapacityGetter(const FunctionCallbackInfo<Value>& args) {
  SQLTagStore* store;
  ASSIGN_OR_RETURN_UNWRAP(&store, args.This());
  args.GetReturnValue().Set(static_cast<double>(store->sql_tags_.Capacity()));
}

void SQLTagStore::DatabaseGetter(const FunctionCallbackInfo<Value>& args) {
  args.GetReturnValue().Set(
      args.This()->GetInternalField(kDatabaseObject).As<Value>());
}

void SQLTagStore::SizeGetter(const FunctionCallbackInfo<Value>& args) {
  SQLTagStore* store;
  ASSIGN_OR_RETURN_UNWRAP(&store, args.This());
  args.GetReturnValue().Set(static_cast<double>(store->sql_tags_.Size()));
}

bool SQLTagStore::ResetAndBindStatement(
    Environment* env,
    Statement* stmt,
    const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = env->isolate();
  int r = stmt->ResetStatement();
  CHECK_ERROR_OR_THROW(isolate, stmt->db_.get(), r, SQLITE_OK, false);

  uint32_t n_params = args.Length() - 1;
  int param_count = sqlite3_bind_parameter_count(stmt->statement_.get());
  if (param_count != static_cast<int>(n_params)) {
    THROW_ERR_INVALID_ARG_VALUE(
        env,
        "SQLite parameters must be bound using template literal placeholders.");
    return false;
  }

  for (int i = 0; i < param_count; ++i) {
    if (!stmt->BindValue(args[i + 1], i + 1)) {
      return false;
    }
  }

  return true;
}

void SQLTagStore::Run(const FunctionCallbackInfo<Value>& args) {
  SQLTagStore* session;
  ASSIGN_OR_RETURN_UNWRAP(&session, args.This());
  Environment* env = Environment::GetCurrent(args);

  THROW_AND_RETURN_ON_BAD_STATE(
      env, !session->database_->IsOpen(), "database is not open");
  THROW_AND_RETURN_IF_IN_AUTHORIZER(env, session->database_.get());

  BaseObjectPtr<Statement> stmt = PrepareStatement(args);

  if (!stmt) {
    return;
  }

  THROW_AND_RETURN_IF_STEPPING(env, stmt.get());

  if (!ResetAndBindStatement(env, stmt.get(), args)) {
    return;
  }

  Local<Object> result;
  if (StatementExecutionHelper::Run(env, stmt.get()).ToLocal(&result)) {
    args.GetReturnValue().Set(result);
  }
}

void SQLTagStore::Iterate(const FunctionCallbackInfo<Value>& args) {
  SQLTagStore* session;
  ASSIGN_OR_RETURN_UNWRAP(&session, args.This());
  Environment* env = Environment::GetCurrent(args);

  THROW_AND_RETURN_ON_BAD_STATE(
      env, !session->database_->IsOpen(), "database is not open");
  THROW_AND_RETURN_IF_IN_AUTHORIZER(env, session->database_.get());

  BaseObjectPtr<Statement> stmt = PrepareStatement(args);

  if (!stmt) {
    return;
  }

  THROW_AND_RETURN_IF_STEPPING(env, stmt.get());

  if (!ResetAndBindStatement(env, stmt.get(), args)) {
    return;
  }

  BaseObjectPtr<StatementIterator> iter =
      StatementExecutionHelper::Iterate(env, BaseObjectPtr<Statement>(stmt));

  if (!iter) {
    return;
  }

  args.GetReturnValue().Set(iter->object());
}

void SQLTagStore::Get(const FunctionCallbackInfo<Value>& args) {
  SQLTagStore* session;
  ASSIGN_OR_RETURN_UNWRAP(&session, args.This());
  Environment* env = Environment::GetCurrent(args);

  THROW_AND_RETURN_ON_BAD_STATE(
      env, !session->database_->IsOpen(), "database is not open");
  THROW_AND_RETURN_IF_IN_AUTHORIZER(env, session->database_.get());

  BaseObjectPtr<Statement> stmt = PrepareStatement(args);

  if (!stmt) {
    return;
  }

  THROW_AND_RETURN_IF_STEPPING(env, stmt.get());

  if (!ResetAndBindStatement(env, stmt.get(), args)) {
    return;
  }

  Local<Value> result;
  if (StatementExecutionHelper::Get(env, stmt.get()).ToLocal(&result)) {
    args.GetReturnValue().Set(result);
  }
}

void SQLTagStore::All(const FunctionCallbackInfo<Value>& args) {
  SQLTagStore* session;
  ASSIGN_OR_RETURN_UNWRAP(&session, args.This());
  Environment* env = Environment::GetCurrent(args);

  THROW_AND_RETURN_ON_BAD_STATE(
      env, !session->database_->IsOpen(), "database is not open");
  THROW_AND_RETURN_IF_IN_AUTHORIZER(env, session->database_.get());

  BaseObjectPtr<Statement> stmt = PrepareStatement(args);

  if (!stmt) {
    return;
  }

  THROW_AND_RETURN_IF_STEPPING(env, stmt.get());

  if (!ResetAndBindStatement(env, stmt.get(), args)) {
    return;
  }

  Isolate* isolate = env->isolate();
  bool needs_reset = true;
  auto reset = OnScopeLeave([&]() {
    if (needs_reset) sqlite3_reset(stmt->statement_.get());
  });
  Local<Value> result;
  if (StatementExecutionHelper::All(env, stmt.get()).ToLocal(&result)) {
    RESET_AND_CHECK(
        isolate, stmt->db_.get(), stmt->statement_.get(), needs_reset, void());
    args.GetReturnValue().Set(result);
  }
}

void SQLTagStore::Clear(const FunctionCallbackInfo<Value>& args) {
  SQLTagStore* store;
  ASSIGN_OR_RETURN_UNWRAP(&store, args.This());
  // Clearing the cache drops strong references to the cached statements but
  // never finalizes one synchronously, so it touches no SQLite state and stays
  // available from a callback. Invalidating the cache after a schema change is
  // a legitimate use of an authorizer.
  store->sql_tags_.Clear();
}

BaseObjectPtr<Statement> SQLTagStore::PrepareStatement(
    const FunctionCallbackInfo<Value>& args) {
  SQLTagStore* session = BaseObject::FromJSObject<SQLTagStore>(args.This());
  if (!session) {
    THROW_ERR_INVALID_ARG_TYPE(
        Environment::GetCurrent(args)->isolate(),
        "This method can only be called on SQLTagStore instances.");
    return BaseObjectPtr<Statement>();
  }
  Environment* env = Environment::GetCurrent(args);
  Isolate* isolate = env->isolate();
  Local<Context> context = isolate->GetCurrentContext();

  if (args.Length() < 1 || !args[0]->IsArray()) {
    THROW_ERR_INVALID_ARG_TYPE(
        isolate,
        "First argument must be an array of strings (template literal).");
    return BaseObjectPtr<Statement>();
  }

  Local<Array> strings = args[0].As<Array>();
  uint32_t n_strings = strings->Length();
  uint32_t n_params = args.Length() - 1;

  std::string sql;
  for (uint32_t i = 0; i < n_strings; ++i) {
    Local<Value> str_val;
    if (!strings->Get(context, i).ToLocal(&str_val) || !str_val->IsString()) {
      THROW_ERR_INVALID_ARG_TYPE(isolate,
                                 "Template literal parts must be strings.");
      return BaseObjectPtr<Statement>();
    }
    Utf8Value part(isolate, str_val);
    sql += part.ToStringView();
    if (i < n_params) {
      sql += "?";
    }
  }

  BaseObjectPtr<Statement> stmt = nullptr;
  if (session->sql_tags_.Exists(sql)) {
    stmt = session->sql_tags_.Get(sql);
    if (stmt->IsFinalized()) {
      session->sql_tags_.Erase(sql);
      stmt = nullptr;
    }
  }

  if (stmt == nullptr) {
    sqlite3_stmt* s = nullptr;
    int r = sqlite3_prepare_v3(session->database_->connection_.get(),
                               sql.data(),
                               sql.size(),
                               SQLITE_PREPARE_PERSISTENT,
                               &s,
                               nullptr);
    StatementPtr stmt_ptr(s);

    if (r != SQLITE_OK) {
      THROW_ERR_SQLITE_ERROR(isolate, session->database_.get());
      return BaseObjectPtr<Statement>();
    }

    // As in Database::Prepare(), reject input that holds no SQL rather
    // than caching a statement that can never be bound or stepped.
    if (s == nullptr) {
      THROW_ERR_INVALID_ARG_VALUE(env, "The SQL query contains no statements.");
      return BaseObjectPtr<Statement>();
    }

    BaseObjectPtr<Statement> stmt_obj = Statement::Create(
        env, BaseObjectPtr<Database>(session->database_), std::move(stmt_ptr));

    if (!stmt_obj) {
      THROW_ERR_SQLITE_ERROR(isolate, "Failed to create Statement");
      return BaseObjectPtr<Statement>();
    }

    session->sql_tags_.Put(sql, stmt_obj);
    stmt = stmt_obj;
  }

  return stmt;
}

void SQLTagStore::MemoryInfo(MemoryTracker* tracker) const {
  tracker->TrackFieldWithSize(MemoryInfoName(), SelfSize());
  tracker->TrackField("database", database_);
  size_t cache_content_size = 0;
  for (const auto& pair : sql_tags_) {
    cache_content_size += pair.first.capacity();
    cache_content_size += sizeof(pair.second);
  }
  tracker->TrackFieldWithSize("sql_tags_cache", cache_content_size);
}

Local<FunctionTemplate> Statement::GetConstructorTemplate(Environment* env) {
  Local<FunctionTemplate> tmpl =
      env->sqlite_statement_sync_constructor_template();
  if (tmpl.IsEmpty()) {
    Isolate* isolate = env->isolate();
    tmpl = NewFunctionTemplate(isolate, IllegalConstructor);
    tmpl->SetClassName(FIXED_ONE_BYTE_STRING(isolate, "Statement"));
    tmpl->InstanceTemplate()->SetInternalFieldCount(
        Statement::kInternalFieldCount);
    SetProtoMethod(isolate, tmpl, "iterate", Statement::Iterate);
    SetProtoMethod(isolate, tmpl, "all", Statement::All);
    SetProtoMethod(isolate, tmpl, "get", Statement::Get);
    SetProtoMethod(isolate, tmpl, "run", Statement::Run);
    SetProtoMethodNoSideEffect(isolate, tmpl, "columns", Statement::Columns);
    SetSideEffectFreeGetter(isolate,
                            tmpl,
                            FIXED_ONE_BYTE_STRING(isolate, "sourceSQL"),
                            Statement::SourceSQLGetter);
    SetSideEffectFreeGetter(isolate,
                            tmpl,
                            FIXED_ONE_BYTE_STRING(isolate, "expandedSQL"),
                            Statement::ExpandedSQLGetter);
    SetProtoMethodNoSideEffect(isolate, tmpl, "stat", Statement::Stat);
    SetProtoMethod(isolate, tmpl, "resetStats", Statement::ResetStats);
    SetProtoMethod(isolate,
                   tmpl,
                   "setAllowBareNamedParameters",
                   Statement::SetAllowBareNamedParameters);
    SetProtoMethod(isolate,
                   tmpl,
                   "setAllowUnknownNamedParameters",
                   Statement::SetAllowUnknownNamedParameters);
    SetProtoMethod(isolate, tmpl, "setReadBigInts", Statement::SetReadBigInts);
    SetProtoMethod(
        isolate, tmpl, "setReturnArrays", Statement::SetReturnArrays);
    SetProtoMethod(isolate, tmpl, "close", Statement::Close);
    SetProtoDispose(isolate, tmpl, Statement::Dispose);
    env->set_sqlite_statement_sync_constructor_template(tmpl);
  }
  return tmpl;
}

BaseObjectPtr<Statement> Statement::Create(Environment* env,
                                           BaseObjectPtr<Database> db,
                                           StatementPtr stmt) {
  Local<Object> obj;
  if (!GetConstructorTemplate(env)
           ->InstanceTemplate()
           ->NewInstance(env->context())
           .ToLocal(&obj)) {
    return nullptr;
  }

  return MakeBaseObject<Statement>(env, obj, std::move(db), std::move(stmt));
}

StatementIterator::StatementIterator(Environment* env,
                                     Local<Object> object,
                                     BaseObjectPtr<Statement> stmt)
    : BaseObject(env, object), stmt_(std::move(stmt)) {
  MakeWeak();
  done_ = false;
  statement_reset_generation_ = stmt_->reset_generation_;
}

StatementIterator::~StatementIterator() {}
void StatementIterator::MemoryInfo(MemoryTracker* tracker) const {}

Local<FunctionTemplate> StatementIterator::GetConstructorTemplate(
    Environment* env) {
  Local<FunctionTemplate> tmpl =
      env->sqlite_statement_sync_iterator_constructor_template();
  if (tmpl.IsEmpty()) {
    Isolate* isolate = env->isolate();
    tmpl = NewFunctionTemplate(isolate, IllegalConstructor);
    tmpl->SetClassName(FIXED_ONE_BYTE_STRING(isolate, "StatementIterator"));
    tmpl->InstanceTemplate()->SetInternalFieldCount(
        StatementIterator::kInternalFieldCount);
    SetProtoMethod(isolate, tmpl, "next", StatementIterator::Next);
    SetProtoMethod(isolate, tmpl, "return", StatementIterator::Return);
    env->set_sqlite_statement_sync_iterator_constructor_template(tmpl);
  }
  return tmpl;
}

BaseObjectPtr<StatementIterator> StatementIterator::Create(
    Environment* env, BaseObjectPtr<Statement> stmt) {
  Local<Object> obj;
  if (!GetConstructorTemplate(env)
           ->InstanceTemplate()
           ->NewInstance(env->context())
           .ToLocal(&obj)) {
    return BaseObjectPtr<StatementIterator>();
  }

  return MakeBaseObject<StatementIterator>(env, obj, std::move(stmt));
}

void StatementIterator::Next(const FunctionCallbackInfo<Value>& args) {
  StatementIterator* iter;
  ASSIGN_OR_RETURN_UNWRAP(&iter, args.This());
  Environment* env = Environment::GetCurrent(args);
  THROW_AND_RETURN_ON_BAD_STATE(
      env, iter->stmt_->IsFinalized(), "statement has been finalized");
  Isolate* isolate = env->isolate();

  auto iter_template = getLazyIterTemplate(env);

  // A drained iterator touches no SQLite state, so it stays usable from a
  // callback and is checked before the guards below.
  if (iter->done_) {
    MaybeLocal<Value> values[]{
        Boolean::New(isolate, true),
        Null(isolate),
    };
    Local<Object> result;
    if (NewDictionaryInstanceNullProto(env->context(), iter_template, values)
            .ToLocal(&result)) {
      args.GetReturnValue().Set(result);
    }
    return;
  }

  THROW_AND_RETURN_IF_IN_AUTHORIZER(env, iter->stmt_->db_.get());
  THROW_AND_RETURN_IF_STEPPING(env, iter->stmt_.get());

  THROW_AND_RETURN_ON_BAD_STATE(
      env,
      iter->statement_reset_generation_ != iter->stmt_->reset_generation_,
      "iterator was invalidated");

  // sqlite3_reset() can run JavaScript through an aggregate's xFinal, so it
  // stays inside the guard.
  SteppingStatementGuard stepping(iter->stmt_->db_.get(),
                                  iter->stmt_->statement_.get());
  int r = sqlite3_step(iter->stmt_->statement_.get());
  if (r != SQLITE_ROW) {
    CHECK_ERROR_OR_THROW(
        env->isolate(), iter->stmt_->db_.get(), r, SQLITE_DONE, void());
    iter->done_ = true;
    RESET_OR_THROW(env->isolate(),
                   iter->stmt_->db_.get(),
                   iter->stmt_->statement_.get(),
                   void());
    MaybeLocal<Value> values[] = {Boolean::New(isolate, true), Null(isolate)};
    Local<Object> result;
    if (NewDictionaryInstanceNullProto(env->context(), iter_template, values)
            .ToLocal(&result)) {
      args.GetReturnValue().Set(result);
    }
    return;
  }

  int num_cols = sqlite3_column_count(iter->stmt_->statement_.get());
  Local<Value> row_value;
  LocalVector<Value> row_values(isolate);

  if (ExtractRowValues(env,
                       iter->stmt_->statement_.get(),
                       num_cols,
                       iter->stmt_->use_big_ints_,
                       &row_values)
          .IsNothing()) {
    return;
  }

  if (iter->stmt_->return_arrays_) {
    row_value = Array::New(isolate, row_values.data(), row_values.size());
  } else if (!iter->stmt_->UpdateRowCache() ||
             !iter->stmt_->RowToObject(&row_values).ToLocal(&row_value)) {
    return;
  }

  MaybeLocal<Value> values[] = {Boolean::New(isolate, false), row_value};
  Local<Object> result;
  if (NewDictionaryInstanceNullProto(env->context(), iter_template, values)
          .ToLocal(&result)) {
    args.GetReturnValue().Set(result);
  }
}

void StatementIterator::Return(const FunctionCallbackInfo<Value>& args) {
  StatementIterator* iter;
  ASSIGN_OR_RETURN_UNWRAP(&iter, args.This());
  Environment* env = Environment::GetCurrent(args);
  THROW_AND_RETURN_ON_BAD_STATE(
      env, iter->stmt_->IsFinalized(), "statement has been finalized");
  Isolate* isolate = env->isolate();

  if (!iter->done_) {
    // A language-invoked return() cannot be reached mid-step, since the loop
    // body only runs after next() has returned, so these guards only reject an
    // explicit call from inside a callback.
    THROW_AND_RETURN_IF_IN_AUTHORIZER(env, iter->stmt_->db_.get());
    THROW_AND_RETURN_IF_STEPPING(env, iter->stmt_.get());
    // Unlike Next(), the reset result is intentionally ignored here: Return()
    // is invoked by the language during abrupt completion (e.g. a `throw`
    // inside a `for...of` body), and throwing on a deferred SQLite error
    // would discard the caller's already-pending exception.
    sqlite3_reset(iter->stmt_->statement_.get());
  }
  iter->done_ = true;

  auto iter_template = getLazyIterTemplate(env);
  MaybeLocal<Value> values[] = {Boolean::New(isolate, true), Null(isolate)};

  Local<Object> result;
  if (NewDictionaryInstanceNullProto(env->context(), iter_template, values)
          .ToLocal(&result)) {
    args.GetReturnValue().Set(result);
  }
}

Session::Session(Environment* env,
                 Local<Object> object,
                 BaseObjectPtr<Database> database,
                 SessionPtr session)
    : BaseObject(env, object),
      session_(std::move(session)),
      database_(std::move(database)) {
  database_->sessions_.insert(this);
  MakeWeak();
}

Session::~Session() {
  Delete();
}

BaseObjectPtr<Session> Session::Create(Environment* env,
                                       BaseObjectPtr<Database> database,
                                       SessionPtr session) {
  Local<Object> obj;
  if (!GetConstructorTemplate(env)
           ->InstanceTemplate()
           ->NewInstance(env->context())
           .ToLocal(&obj)) {
    return nullptr;
  }

  return MakeBaseObject<Session>(
      env, obj, std::move(database), std::move(session));
}

Local<FunctionTemplate> Session::GetConstructorTemplate(Environment* env) {
  Local<FunctionTemplate> tmpl = env->sqlite_session_constructor_template();
  if (tmpl.IsEmpty()) {
    Isolate* isolate = env->isolate();
    tmpl = NewFunctionTemplate(isolate, IllegalConstructor);
    tmpl->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "Session"));
    tmpl->InstanceTemplate()->SetInternalFieldCount(
        Session::kInternalFieldCount);
    SetProtoMethod(isolate,
                   tmpl,
                   "changeset",
                   Session::Changeset<sqlite3session_changeset>);
    SetProtoMethod(
        isolate, tmpl, "patchset", Session::Changeset<sqlite3session_patchset>);
    SetProtoMethod(isolate, tmpl, "close", Session::Close);
    SetProtoDispose(isolate, tmpl, Session::Dispose);
    env->set_sqlite_session_constructor_template(tmpl);
  }
  return tmpl;
}

void Session::MemoryInfo(MemoryTracker* tracker) const {
  tracker->TrackField("database", database_);
}

template <Sqlite3ChangesetGenFunc sqliteChangesetFunc>
void Session::Changeset(const FunctionCallbackInfo<Value>& args) {
  Session* session;
  ASSIGN_OR_RETURN_UNWRAP(&session, args.This());
  Environment* env = Environment::GetCurrent(args);
  THROW_AND_RETURN_ON_BAD_STATE(
      env, !session->database_->IsOpen(), "database is not open");
  THROW_AND_RETURN_ON_BAD_STATE(env, !session->session_, "session is not open");
  THROW_AND_RETURN_IF_IN_AUTHORIZER(env, session->database_.get());

  session->is_generating_changeset_ = true;
  auto changeset_guard =
      OnScopeLeave([&] { session->is_generating_changeset_ = false; });

  int nChangeset;
  void* pChangeset;
  int r =
      sqliteChangesetFunc(session->session_.get(), &nChangeset, &pChangeset);
  CHECK_ERROR_OR_THROW(
      env->isolate(), session->database_.get(), r, SQLITE_OK, void());

  DeleteFnPtr<void, sqlite3_free> changeset(pChangeset);

  Local<ArrayBuffer> buffer = ArrayBuffer::New(env->isolate(), nChangeset);
  if (nChangeset > 0) {
    std::memcpy(buffer->GetBackingStore()->Data(), pChangeset, nChangeset);
  }
  Local<Uint8Array> uint8Array = Uint8Array::New(buffer, 0, nChangeset);

  args.GetReturnValue().Set(uint8Array);
}

void Session::Close(const FunctionCallbackInfo<Value>& args) {
  Session* session;
  ASSIGN_OR_RETURN_UNWRAP(&session, args.This());
  Environment* env = Environment::GetCurrent(args);
  THROW_AND_RETURN_ON_BAD_STATE(
      env, !session->database_->IsOpen(), "database is not open");
  THROW_AND_RETURN_ON_BAD_STATE(
      env, session->session_ == nullptr, "session is not open");
  THROW_AND_RETURN_ON_BAD_STATE(
      env, session->is_generating_changeset_, "session is currently in use");
  // Checked last: changeset generation runs the authorizer, so both conditions
  // hold in that case and the more specific message above has to win.
  THROW_AND_RETURN_IF_SESSION_IN_CALLBACK(env, session);

  session->Delete();
}

void Session::Dispose(const FunctionCallbackInfo<Value>& args) {
  Session* session;
  ASSIGN_OR_RETURN_UNWRAP(&session, args.This());
  Environment* env = Environment::GetCurrent(args);
  if (session->session_ == nullptr) {
    return;
  }
  THROW_AND_RETURN_ON_BAD_STATE(
      env, session->is_generating_changeset_, "session is currently in use");
  THROW_AND_RETURN_IF_SESSION_IN_CALLBACK(env, session);

  session->Delete();
}

void Session::Delete() {
  if (!session_) return;
  session_.reset();
  if (database_) {
    database_->sessions_.erase(this);
  }
}

void DefineConstants(Local<Object> target) {
  NODE_DEFINE_CONSTANT(target, SQLITE_CHANGESET_OMIT);
  NODE_DEFINE_CONSTANT(target, SQLITE_CHANGESET_REPLACE);
  NODE_DEFINE_CONSTANT(target, SQLITE_CHANGESET_ABORT);

  NODE_DEFINE_CONSTANT(target, SQLITE_CHANGESET_DATA);
  NODE_DEFINE_CONSTANT(target, SQLITE_CHANGESET_NOTFOUND);
  NODE_DEFINE_CONSTANT(target, SQLITE_CHANGESET_CONFLICT);
  NODE_DEFINE_CONSTANT(target, SQLITE_CHANGESET_CONSTRAINT);
  NODE_DEFINE_CONSTANT(target, SQLITE_CHANGESET_FOREIGN_KEY);

  // Authorization result codes
  NODE_DEFINE_CONSTANT(target, SQLITE_OK);
  NODE_DEFINE_CONSTANT(target, SQLITE_DENY);
  NODE_DEFINE_CONSTANT(target, SQLITE_IGNORE);

  // Authorization action codes
  NODE_DEFINE_CONSTANT(target, SQLITE_CREATE_INDEX);
  NODE_DEFINE_CONSTANT(target, SQLITE_CREATE_TABLE);
  NODE_DEFINE_CONSTANT(target, SQLITE_CREATE_TEMP_INDEX);
  NODE_DEFINE_CONSTANT(target, SQLITE_CREATE_TEMP_TABLE);
  NODE_DEFINE_CONSTANT(target, SQLITE_CREATE_TEMP_TRIGGER);
  NODE_DEFINE_CONSTANT(target, SQLITE_CREATE_TEMP_VIEW);
  NODE_DEFINE_CONSTANT(target, SQLITE_CREATE_TRIGGER);
  NODE_DEFINE_CONSTANT(target, SQLITE_CREATE_VIEW);
  NODE_DEFINE_CONSTANT(target, SQLITE_DELETE);
  NODE_DEFINE_CONSTANT(target, SQLITE_DROP_INDEX);
  NODE_DEFINE_CONSTANT(target, SQLITE_DROP_TABLE);
  NODE_DEFINE_CONSTANT(target, SQLITE_DROP_TEMP_INDEX);
  NODE_DEFINE_CONSTANT(target, SQLITE_DROP_TEMP_TABLE);
  NODE_DEFINE_CONSTANT(target, SQLITE_DROP_TEMP_TRIGGER);
  NODE_DEFINE_CONSTANT(target, SQLITE_DROP_TEMP_VIEW);
  NODE_DEFINE_CONSTANT(target, SQLITE_DROP_TRIGGER);
  NODE_DEFINE_CONSTANT(target, SQLITE_DROP_VIEW);
  NODE_DEFINE_CONSTANT(target, SQLITE_INSERT);
  NODE_DEFINE_CONSTANT(target, SQLITE_PRAGMA);
  NODE_DEFINE_CONSTANT(target, SQLITE_READ);
  NODE_DEFINE_CONSTANT(target, SQLITE_SELECT);
  NODE_DEFINE_CONSTANT(target, SQLITE_TRANSACTION);
  NODE_DEFINE_CONSTANT(target, SQLITE_UPDATE);
  NODE_DEFINE_CONSTANT(target, SQLITE_ATTACH);
  NODE_DEFINE_CONSTANT(target, SQLITE_DETACH);
  NODE_DEFINE_CONSTANT(target, SQLITE_ALTER_TABLE);
  NODE_DEFINE_CONSTANT(target, SQLITE_REINDEX);
  NODE_DEFINE_CONSTANT(target, SQLITE_ANALYZE);
  NODE_DEFINE_CONSTANT(target, SQLITE_CREATE_VTABLE);
  NODE_DEFINE_CONSTANT(target, SQLITE_DROP_VTABLE);
  NODE_DEFINE_CONSTANT(target, SQLITE_FUNCTION);
  NODE_DEFINE_CONSTANT(target, SQLITE_SAVEPOINT);
  NODE_DEFINE_CONSTANT(target, SQLITE_COPY);
  NODE_DEFINE_CONSTANT(target, SQLITE_RECURSIVE);
}

static void Initialize(Local<Object> target,
                       Local<Value> unused,
                       Local<Context> context,
                       void* priv) {
  Environment* env = Environment::GetCurrent(context);
  Realm* realm = env->principal_realm();
  Isolate* isolate = env->isolate();

  // Set up the per-Environment database registry.
  BindingData::CreatePerContextProperties(target, unused, context, priv);

  // Register a native callback on the sqlite.db.query diagnostic channel so
  // that SQLite tracing is enabled/disabled as subscribers come and go.
  auto* diag_binding =
      realm->GetBindingData<diagnostics_channel::BindingData>();
  auto* sqlite_bd = realm->GetBindingData<BindingData>();
  if (diag_binding != nullptr && sqlite_bd != nullptr) {
    uint32_t idx = diag_binding->GetOrCreateChannelIndex("sqlite.db.query");
    BaseObjectPtr<BindingData> bd_ptr(sqlite_bd);
    diag_binding->SetChannelStatusCallback(idx, [bd_ptr](bool is_active) {
      BindingData* bd = bd_ptr.get();
      if (bd == nullptr) return;
      for (Database* db : bd->open_databases) {
        if (is_active)
          db->EnableTracing();
        else
          db->DisableTracing();
      }
    });
  }
  Local<Object> constants = Object::New(isolate);

  DefineConstants(constants);

  Local<FunctionTemplate> db_tmpl = env->sqlite_database_constructor_template();
  if (db_tmpl.IsEmpty()) {
    db_tmpl = NewFunctionTemplate(isolate, Database::New);
    db_tmpl->SetClassName(FIXED_ONE_BYTE_STRING(isolate, "Database"));
    db_tmpl->InstanceTemplate()->SetInternalFieldCount(
        Database::kInternalFieldCount);
    SetProtoMethod(isolate, db_tmpl, "open", Database::Open);
    SetProtoMethod(isolate, db_tmpl, "close", Database::Close);
    SetProtoDispose(isolate, db_tmpl, Database::Dispose);
    SetProtoMethod(isolate, db_tmpl, "prepare", Database::Prepare);
    SetProtoMethod(isolate, db_tmpl, "exec", Database::Exec);
    SetProtoMethod(isolate, db_tmpl, "function", Database::CustomFunction);
    SetProtoMethod(
        isolate, db_tmpl, "createTagStore", Database::CreateTagStore);
    SetProtoMethodNoSideEffect(
        isolate, db_tmpl, "location", Database::Location);
    SetProtoMethod(isolate, db_tmpl, "aggregate", Database::AggregateFunction);
    SetProtoMethod(isolate, db_tmpl, "createSession", Database::CreateSession);
    SetProtoMethod(
        isolate, db_tmpl, "applyChangeset", Database::ApplyChangeset);
    SetProtoMethod(
        isolate, db_tmpl, "enableLoadExtension", Database::EnableLoadExtension);
    SetProtoMethod(
        isolate, db_tmpl, "enableDefensive", Database::EnableDefensive);
    SetProtoMethod(isolate, db_tmpl, "loadExtension", Database::LoadExtension);
    SetProtoMethod(isolate, db_tmpl, "serialize", Database::Serialize);
    SetProtoMethod(isolate, db_tmpl, "deserialize", Database::Deserialize);
    SetProtoMethod(isolate, db_tmpl, "setAuthorizer", Database::SetAuthorizer);
    SetProtoMethod(isolate, db_tmpl, "createModule", Database::CreateModule);
    SetSideEffectFreeGetter(isolate,
                            db_tmpl,
                            FIXED_ONE_BYTE_STRING(isolate, "isOpen"),
                            Database::IsOpenGetter);
    SetSideEffectFreeGetter(isolate,
                            db_tmpl,
                            FIXED_ONE_BYTE_STRING(isolate, "isTransaction"),
                            Database::IsTransactionGetter);
    SetSideEffectFreeGetter(
        isolate, db_tmpl, env->limits_string(), Database::LimitsGetter);
    Local<String> sqlite_type_key =
        FIXED_ONE_BYTE_STRING(isolate, "sqlite-type");
    Local<v8::Symbol> sqlite_type_symbol =
        v8::Symbol::For(isolate, sqlite_type_key);
    Local<String> database_sync_string =
        FIXED_ONE_BYTE_STRING(isolate, "node:sqlite");
    db_tmpl->InstanceTemplate()->Set(sqlite_type_symbol, database_sync_string);
    env->set_sqlite_database_constructor_template(db_tmpl);
  }
  SetConstructorFunction(
      context, target, "Database", db_tmpl, SetConstructorFunctionFlag::NONE);
  SetConstructorFunction(context,
                         target,
                         "Statement",
                         Statement::GetConstructorTemplate(env),
                         SetConstructorFunctionFlag::NONE);
  SetConstructorFunction(context,
                         target,
                         "Session",
                         Session::GetConstructorTemplate(env),
                         SetConstructorFunctionFlag::NONE);

  target->Set(context, env->constants_string(), constants).Check();

  Local<Function> backup_function;

  if (!Function::New(context, Backup, Local<Value>(), 2)
           .ToLocal(&backup_function)) {
    return;
  }
  backup_function->SetName(env->backup_string());

  target->Set(context, env->backup_string(), backup_function).Check();
}

}  // namespace sqlite
}  // namespace node

NODE_BINDING_CONTEXT_AWARE_INTERNAL(sqlite, node::sqlite::Initialize)