-
Notifications
You must be signed in to change notification settings - Fork 304
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add SqliteMetadata helper class for reading/writing sqlite alarm time
Analogous to SqliteKv helper class; not used yet.
- Loading branch information
Showing
4 changed files
with
170 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
// 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 <kj/test.h> | ||
|
||
namespace workerd { | ||
namespace { | ||
|
||
KJ_TEST("SQLite-METADATA") { | ||
auto dir = kj::newInMemoryDirectory(kj::nullClock()); | ||
SqliteDatabase::Vfs vfs(*dir); | ||
SqliteDatabase db(vfs, kj::Path({"foo"}), kj::WriteMode::CREATE | kj::WriteMode::MODIFY); | ||
SqliteMetadata metadata(db); | ||
|
||
// Initial state has empty alarm | ||
KJ_EXPECT(metadata.getAlarm() == kj::none); | ||
|
||
// Can set alarm to an explicit time | ||
constexpr kj::Date anAlarmTime1 = | ||
kj::UNIX_EPOCH + 1734099316 * kj::SECONDS + 987654321 * kj::NANOSECONDS; | ||
metadata.setAlarm(anAlarmTime1); | ||
|
||
// Can get the set alarm time | ||
KJ_EXPECT(metadata.getAlarm() == anAlarmTime1); | ||
|
||
// Can overwrite the alarm time | ||
constexpr kj::Date anAlarmTime2 = anAlarmTime1 + 1 * kj::NANOSECONDS; | ||
metadata.setAlarm(anAlarmTime2); | ||
KJ_EXPECT(metadata.getAlarm() != anAlarmTime1); | ||
KJ_EXPECT(metadata.getAlarm() == anAlarmTime2); | ||
|
||
// Can clear alarm | ||
metadata.setAlarm(kj::none); | ||
KJ_EXPECT(metadata.getAlarm() == kj::none); | ||
|
||
// Zero alarm is distinct from unset (probably not important, but just checking) | ||
metadata.setAlarm(kj::UNIX_EPOCH); | ||
KJ_EXPECT(metadata.getAlarm() == kj::UNIX_EPOCH); | ||
} | ||
|
||
} // namespace | ||
} // namespace workerd |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
// 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" | ||
|
||
namespace workerd { | ||
|
||
SqliteMetadata::SqliteMetadata(SqliteDatabase& db) { | ||
auto q = db.run("SELECT name FROM sqlite_master WHERE type='table' AND name='_cf_METADATA'"); | ||
if (q.isDone()) { | ||
// The _cf_METADATA table doesn't exist. Defer initialization. | ||
state = Uninitialized{db}; | ||
} else { | ||
// The metadata table was initialized in the past. We can go ahead and prepare our statements. | ||
// (We don't call ensureInitialized() here because the `CREATE TABLE IF NOT EXISTS` query it | ||
// executes would be redundant.) | ||
state = Initialized(db); | ||
} | ||
} | ||
|
||
kj::Maybe<kj::Date> SqliteMetadata::getAlarm() { | ||
auto& stmts = KJ_UNWRAP_OR(state.tryGet<Initialized>(), return kj::none); | ||
|
||
auto query = stmts.stmtGetAlarm.run(); | ||
if (query.isDone() || query.isNull(0)) { | ||
return kj::none; | ||
} else { | ||
return kj::UNIX_EPOCH + query.getInt64(0) * kj::NANOSECONDS; | ||
} | ||
} | ||
|
||
void SqliteMetadata::setAlarm(kj::Maybe<kj::Date> currentTime) { | ||
KJ_IF_SOME(t, currentTime) { | ||
ensureInitialized().stmtSetAlarm.run((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().stmtSetAlarm.run(nullptr); | ||
} | ||
} | ||
|
||
SqliteMetadata::Initialized& SqliteMetadata::ensureInitialized() { | ||
KJ_SWITCH_ONEOF(state) { | ||
KJ_CASE_ONEOF(uninitialized, Uninitialized) { | ||
auto& db = uninitialized.db; | ||
|
||
db.run(R"( | ||
CREATE TABLE IF NOT EXISTS _cf_METADATA ( | ||
key INTEGER PRIMARY KEY, | ||
value BLOB | ||
); | ||
)"); | ||
|
||
return state.init<Initialized>(db); | ||
} | ||
KJ_CASE_ONEOF(initialized, Initialized) { | ||
return initialized; | ||
} | ||
} | ||
KJ_UNREACHABLE; | ||
} | ||
|
||
} // namespace workerd |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
// 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 | ||
|
||
#pragma once | ||
|
||
#include "sqlite.h" | ||
|
||
namespace workerd { | ||
|
||
// Class which implements a simple metadata kv storage on top of SQLite. Currently only used to | ||
// store Durable Object alarm times (hardcoded as key = 1), but could later be used for other | ||
// properties. | ||
// | ||
// The table is named `_cf_METADATA`. The naming is designed so that if the application is allowed to | ||
// perform direct SQL queries, we can block it from accessing any table prefixed with `_cf_`. | ||
class SqliteMetadata { | ||
public: | ||
explicit SqliteMetadata(SqliteDatabase& db); | ||
|
||
// Return currently set alarm time, or none. | ||
kj::Maybe<kj::Date> getAlarm(); | ||
|
||
// Sets current alarm time, or none. | ||
void setAlarm(kj::Maybe<kj::Date> currentTime); | ||
|
||
private: | ||
struct Uninitialized { | ||
SqliteDatabase& db; | ||
}; | ||
|
||
struct Initialized { | ||
SqliteDatabase& db; | ||
|
||
SqliteDatabase::Statement stmtGetAlarm = db.prepare(R"( | ||
SELECT value FROM _cf_METADATA WHERE key = 1 | ||
)"); | ||
SqliteDatabase::Statement stmtSetAlarm = db.prepare(R"( | ||
INSERT INTO _cf_METADATA VALUES(1, ?) | ||
ON CONFLICT DO UPDATE SET value = excluded.value; | ||
)"); | ||
|
||
Initialized(SqliteDatabase& db): db(db) {} | ||
}; | ||
|
||
kj::OneOf<Uninitialized, Initialized> state; | ||
|
||
Initialized& ensureInitialized(); | ||
// Make sure the metadata table is created and prepared statements are ready. Not called until the | ||
// first write. | ||
}; | ||
|
||
} // namespace workerd |