Skip to content
File

Blob: src/workerd/util/sqlite-metadata.c++

3.3 KB
1// Copyright (c) 2024 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4 
5#include "sqlite-metadata.h"
6 
7#include <kj/debug.h>
8 
9namespace workerd {
10 
11SqliteMetadata::SqliteMetadata(SqliteDatabase& db): ResetListener(db) {
12 auto q = db.run("SELECT name FROM sqlite_master WHERE type='table' AND name='_cf_METADATA'");
13 tableCreated = !q.isDone();
14}
15 
16kj::Maybe<kj::Date> SqliteMetadata::getAlarm() {
17 if (cacheState == kj::none) {
18 cacheState = Cache{.alarmTime = getAlarmUncached()};
19 }
20 return KJ_ASSERT_NONNULL(cacheState).alarmTime;
21}
22 
23bool SqliteMetadata::setAlarm(kj::Maybe<kj::Date> currentTime, bool allowUnconfirmed) {
24 KJ_IF_SOME(c, cacheState) {
25 if (c.alarmTime == currentTime) {
26 return false;
27 }
28 }
29 setAlarmUncached(currentTime, allowUnconfirmed);
30 db.onRollback([this, oldCacheState = cacheState]() { cacheState = oldCacheState; });
31 cacheState = Cache{.alarmTime = currentTime};
32 return true;
33}
34 
35kj::Maybe<kj::Date> SqliteMetadata::getAlarmUncached() {
36 if (!tableCreated) {
37 return kj::none;
38 }
39 
40 auto query = ensureInitialized(/*allowUnconfirmed=*/false).stmtGetAlarm.run();
41 if (query.isDone() || query.isNull(0)) {
42 return kj::none;
43 } else {
44 return kj::UNIX_EPOCH + query.getInt64(0) * kj::NANOSECONDS;
45 }
46}
47 
48void SqliteMetadata::setAlarmUncached(kj::Maybe<kj::Date> currentTime, bool allowUnconfirmed) {
49 KJ_IF_SOME(t, currentTime) {
50 ensureInitialized(allowUnconfirmed)
51 .stmtSetAlarm.run(
52 {.allowUnconfirmed = allowUnconfirmed}, (t - kj::UNIX_EPOCH) / kj::NANOSECONDS);
53 } else {
54 // Our getter code also allows representing an empty alarm value as a
55 // missing row or table, but a null-value row seems efficient and simple.
56 ensureInitialized(allowUnconfirmed)
57 .stmtSetAlarm.run({.allowUnconfirmed = allowUnconfirmed}, nullptr);
58 }
59}
60 
61kj::Maybe<uint64_t> SqliteMetadata::getLocalDevelopmentBookmark() {
62 auto query = ensureInitialized(/*allowUnconfirmed=*/false).stmtGetLocalDevelopmentBookmark.run();
63 if (query.isDone() || query.isNull(0)) {
64 return kj::none;
65 } else {
66 auto bookmark = query.getInt64(0);
67 KJ_REQUIRE(bookmark >= 0);
68 return bookmark;
69 }
70}
71 
72void SqliteMetadata::setLocalDevelopmentBookmark(uint64_t bookmark) {
73 KJ_REQUIRE(bookmark <= static_cast<int64_t>(kj::maxValue));
74 ensureInitialized(/*allowUnconfirmed=*/false)
75 .stmtSetLocalDevelopmentBookmark.run(static_cast<int64_t>(bookmark));
76}
77 
78SqliteMetadata::Initialized& SqliteMetadata::ensureInitialized(bool allowUnconfirmed) {
79 if (!tableCreated) {
80 db.run(SqliteDatabase::QueryOptions{.regulator = SqliteDatabase::TRUSTED,
81 .allowUnconfirmed = allowUnconfirmed},
82 R"(
83 CREATE TABLE IF NOT EXISTS _cf_METADATA (
84 key INTEGER PRIMARY KEY,
85 value BLOB
86 );
87 )");
88 tableCreated = true;
89 db.onRollback([this]() { tableCreated = false; });
90 }
91 
92 KJ_SWITCH_ONEOF(dbState) {
93 KJ_CASE_ONEOF(uninitialized, Uninitialized) {
94 return dbState.init<Initialized>(db);
95 }
96 KJ_CASE_ONEOF(initialized, Initialized) {
97 return initialized;
98 }
99 }
100 KJ_UNREACHABLE;
101}
102 
103void SqliteMetadata::beforeSqliteReset() {
104 // We'll need to recreate the table on the next operation.
105 tableCreated = false;
106 cacheState = kj::none;
107}
108 
109} // namespace workerd