// Copyright (c) 2024 Cloudflare, Inc. // Licensed under the Apache 2.0 license found in the LICENSE file or at: // https://opensource.org/licenses/Apache-2.0 #include "sqlite-metadata.h" #include namespace workerd { SqliteMetadata::SqliteMetadata(SqliteDatabase& db): ResetListener(db) { auto q = db.run("SELECT name FROM sqlite_master WHERE type='table' AND name='_cf_METADATA'"); tableCreated = !q.isDone(); } kj::Maybe SqliteMetadata::getAlarm() { if (cacheState == kj::none) { cacheState = Cache{.alarmTime = getAlarmUncached()}; } return KJ_ASSERT_NONNULL(cacheState).alarmTime; } bool SqliteMetadata::setAlarm(kj::Maybe currentTime, bool allowUnconfirmed) { KJ_IF_SOME(c, cacheState) { if (c.alarmTime == currentTime) { return false; } } setAlarmUncached(currentTime, allowUnconfirmed); db.onRollback([this, oldCacheState = cacheState]() { cacheState = oldCacheState; }); cacheState = Cache{.alarmTime = currentTime}; return true; } kj::Maybe SqliteMetadata::getAlarmUncached() { if (!tableCreated) { return kj::none; } auto query = ensureInitialized(/*allowUnconfirmed=*/false).stmtGetAlarm.run(); if (query.isDone() || query.isNull(0)) { return kj::none; } else { return kj::UNIX_EPOCH + query.getInt64(0) * kj::NANOSECONDS; } } void SqliteMetadata::setAlarmUncached(kj::Maybe currentTime, bool allowUnconfirmed) { KJ_IF_SOME(t, currentTime) { ensureInitialized(allowUnconfirmed) .stmtSetAlarm.run( {.allowUnconfirmed = allowUnconfirmed}, (t - kj::UNIX_EPOCH) / kj::NANOSECONDS); } else { // Our getter code also allows representing an empty alarm value as a // missing row or table, but a null-value row seems efficient and simple. ensureInitialized(allowUnconfirmed) .stmtSetAlarm.run({.allowUnconfirmed = allowUnconfirmed}, nullptr); } } kj::Maybe SqliteMetadata::getLocalDevelopmentBookmark() { auto query = ensureInitialized(/*allowUnconfirmed=*/false).stmtGetLocalDevelopmentBookmark.run(); if (query.isDone() || query.isNull(0)) { return kj::none; } else { auto bookmark = query.getInt64(0); KJ_REQUIRE(bookmark >= 0); return bookmark; } } void SqliteMetadata::setLocalDevelopmentBookmark(uint64_t bookmark) { KJ_REQUIRE(bookmark <= static_cast(kj::maxValue)); ensureInitialized(/*allowUnconfirmed=*/false) .stmtSetLocalDevelopmentBookmark.run(static_cast(bookmark)); } SqliteMetadata::Initialized& SqliteMetadata::ensureInitialized(bool allowUnconfirmed) { if (!tableCreated) { db.run(SqliteDatabase::QueryOptions{.regulator = SqliteDatabase::TRUSTED, .allowUnconfirmed = allowUnconfirmed}, R"( CREATE TABLE IF NOT EXISTS _cf_METADATA ( key INTEGER PRIMARY KEY, value BLOB ); )"); tableCreated = true; db.onRollback([this]() { tableCreated = false; }); } KJ_SWITCH_ONEOF(dbState) { KJ_CASE_ONEOF(uninitialized, Uninitialized) { return dbState.init(db); } KJ_CASE_ONEOF(initialized, Initialized) { return initialized; } } KJ_UNREACHABLE; } void SqliteMetadata::beforeSqliteReset() { // We'll need to recreate the table on the next operation. tableCreated = false; cacheState = kj::none; } } // namespace workerd