executeStmt method

Future<QueryResult> executeStmt(
  1. Statement stmt
)

Implementation

Future<QueryResult> executeStmt(Statement stmt) async {
  // Statements that span a transaction boundary always need exclusive
  // access; otherwise SELECT-ish statements take the shared arm.
  final wantsWrite = inTransaction ||
      _isMutation(stmt) ||
      stmt is BeginStmt ||
      stmt is CommitStmt ||
      stmt is RollbackStmt ||
      stmt is SavepointStmt ||
      stmt is ReleaseSavepointStmt ||
      stmt is RollbackToSavepointStmt;

  Future<QueryResult> body() async {
    if (_readOnlySnapshot && _isMutation(stmt)) {
      throw StateError(
        'Cannot mutate inside a read-only snapshot transaction',
      );
    }
    final cb = authorizer;
    if (cb != null) {
      final outcome = cb(stmt, _statementTable(stmt));
      if (outcome == AuthorizerResult.deny) {
        throw StateError('not authorized');
      }
      if (outcome == AuthorizerResult.ignore) {
        return QueryResult.message('ignored by authorizer');
      }
    }
    _executionStack.add(this);
    final prevConnState = connStateLookup;
    connStateLookup = (which) {
      switch (which) {
        case 'last_insert_rowid':
          return _lastInsertRowid;
        case 'changes':
          return _changesCount;
        case 'total_changes':
          return _totalChangesCount;
        default:
          return 0;
      }
    };
    QueryResult result;
    try {
      // Paged-table fast path: a `CREATE TABLE … USING paged` lives
      // in [_pagedTables] rather than [_tables], and statements that
      // target one of those names use the async PagedTable API. Both
      // need to run before the synchronous [_dispatch] so they can
      // await disk I/O.
      final paged = await _maybeRunPagedStmt(stmt);
      if (paged != null) {
        result = paged;
      } else {
        // V48: async-only PRAGMAs (currently just `fts5_warm`) also
        // pre-empt `_dispatch` — they need to await work the sync
        // pragma handler can't.
        final asyncPragma = await _maybeRunAsyncPragma(stmt);
        if (asyncPragma != null) {
          result = asyncPragma;
        } else {
          result = _dispatch(stmt);
        }
      }
    } finally {
      _executionStack.removeLast();
      connStateLookup = prevConnState;
      // Drain any paged commit/rollback queued by a sync _commit /
      // _rollback. Rollbacks always run before commits — and run
      // unconditionally, including on the exception path — so a
      // deferred-FK-failed COMMIT (which calls _rollback internally
      // and rethrows) still undoes the paged side.
      if (_pendingPagedRollback.isNotEmpty) {
        final pending = List<PagedTable>.from(_pendingPagedRollback);
        _pendingPagedRollback.clear();
        for (final pt in pending) {
          try {
            await pt.rollback();
          } catch (_) {
            /* best-effort */
          }
        }
      }
      if (_pendingPagedCommit.isNotEmpty) {
        final pending = List<PagedTable>.from(_pendingPagedCommit);
        _pendingPagedCommit.clear();
        for (final pt in pending) {
          await pt.commit();
        }
      }
    }
    if (_isMutation(stmt)) {
      // Track changes()/total_changes() at the SQL function layer.
      if (stmt is InsertStmt || stmt is UpdateStmt || stmt is DeleteStmt) {
        _changesCount = result.affected;
        _totalChangesCount += result.affected;
      }
      // Conservatively drop FTS5 caches for the statement's target
      // table; the next ranking call will rebuild on demand.
      final tname = _statementTable(stmt);
      if (tname != null) {
        _invalidateFts5(tname);
        _invalidateRtree(tname);
        _invalidateVectorIndexes(stmt, tname);
        final t = _tables[tname];
        if (t != null) _refreshPartialIndexes(t);
      }
    }
    if (stmt is CommitStmt) {
      await _persist();
      return result;
    }
    if (!inTransaction && _isMutation(stmt)) {
      await _persist();
    }
    return result;
  }

  return wantsWrite ? _lock.write(body) : _lock.read(body);
}