Make PUT on SQLite backend implicitly overwrite expired entries

The backend does not serve expired but still present entries so to a
user they do not exist. When they put new data over such an entry their
expecation is that the value is overwritten, even if not explicitly
requested.
This commit is contained in:
Benjamin Bannier 2025-07-01 08:14:33 +02:00 committed by Tim Wojtulewicz
parent 2f67539c0f
commit 16c40f4f3a
3 changed files with 52 additions and 21 deletions

View file

@ -182,30 +182,38 @@ OperationResult SQLite::DoOpen(OpenResultCallback* cb, RecordValPtr options) {
sqlite3_free(errorMsg); sqlite3_free(errorMsg);
static std::array<std::pair<std::string, sqlite3*>, 8> statements = static std::array<std::pair<std::string, sqlite3*>, 8> statements =
{std::make_pair(util::fmt("insert into %s (key_str, value_str, expire_time) values(?, ?, ?)", {// Normal put
std::make_pair(util::fmt("insert into %s (key_str, value_str, expire_time) values(?, ?, ?) "
"ON CONFLICT(key_str) DO UPDATE SET value_str=?, expire_time=? "
"WHERE expire_time > 0.0 AND expire_time < ?",
table_name.c_str()), table_name.c_str()),
db), db),
std::make_pair(util:: // Put with forced overwrite
fmt("insert into %s (key_str, value_str, expire_time) values(?, ?, ?) ON CONFLICT(key_str) " std::make_pair(util::fmt("insert into %s (key_str, value_str, expire_time) values(?, ?, ?) "
"DO UPDATE SET value_str=?, expire_time=?", "ON CONFLICT(key_str) DO UPDATE SET value_str=?, expire_time=?",
table_name.c_str()), table_name.c_str()),
db), db),
// Get
std::make_pair(util::fmt("select value_str, expire_time from %s where key_str=? and " std::make_pair(util::fmt("select value_str, expire_time from %s where key_str=? and "
"(expire_time > ? OR expire_time == 0.0)", "(expire_time > ? OR expire_time == 0.0)",
table_name.c_str()), table_name.c_str()),
db), db),
// Erase
std::make_pair(util::fmt("delete from %s where key_str=?", table_name.c_str()), db), std::make_pair(util::fmt("delete from %s where key_str=?", table_name.c_str()), db),
// Check for expired entries
std::make_pair( std::make_pair(
util::fmt("select count(*) from %s where expire_time > 0 and expire_time != 0 and expire_time <= ?", util::fmt("select count(*) from %s where expire_time > 0 and expire_time != 0 and expire_time <= ?",
table_name.c_str()), table_name.c_str()),
expire_db), expire_db),
// Remove expired entries
std::make_pair(util::fmt("delete from %s where expire_time > 0 and expire_time != 0 and expire_time <= ?", std::make_pair(util::fmt("delete from %s where expire_time > 0 and expire_time != 0 and expire_time <= ?",
table_name.c_str()), table_name.c_str()),
expire_db), expire_db),
// Get the last time expiry ran
std::make_pair(util::fmt("select last_run from zeek_storage_expiry_runs where ukey = '%s'", std::make_pair(util::fmt("select last_run from zeek_storage_expiry_runs where ukey = '%s'",
table_name.c_str()), table_name.c_str()),
expire_db), expire_db),
// Update the last time expiry ran
std::make_pair(util::fmt("update zeek_storage_expiry_runs set last_run = ? where ukey = '%s'", std::make_pair(util::fmt("update zeek_storage_expiry_runs set last_run = ? where ukey = '%s'",
table_name.c_str()), table_name.c_str()),
expire_db)}; expire_db)};
@ -294,10 +302,10 @@ OperationResult SQLite::DoPut(ResultCallback* cb, ValPtr key, ValPtr value, bool
return {ReturnCode::SERIALIZATION_FAILED, "Failed to serialize key"}; return {ReturnCode::SERIALIZATION_FAILED, "Failed to serialize key"};
unique_stmt_ptr stmt; unique_stmt_ptr stmt;
if ( ! overwrite ) if ( overwrite )
stmt = unique_stmt_ptr(put_stmt.get(), sqlite3_reset);
else
stmt = unique_stmt_ptr(put_update_stmt.get(), sqlite3_reset); stmt = unique_stmt_ptr(put_update_stmt.get(), sqlite3_reset);
else
stmt = unique_stmt_ptr(put_stmt.get(), sqlite3_reset);
if ( auto res = CheckError(sqlite3_bind_blob(stmt.get(), 1, key_data->data(), key_data->size(), SQLITE_STATIC)); if ( auto res = CheckError(sqlite3_bind_blob(stmt.get(), 1, key_data->data(), key_data->size(), SQLITE_STATIC));
res.code != ReturnCode::SUCCESS ) { res.code != ReturnCode::SUCCESS ) {
@ -317,20 +325,31 @@ OperationResult SQLite::DoPut(ResultCallback* cb, ValPtr key, ValPtr value, bool
return res; return res;
} }
if ( overwrite ) {
if ( auto res = CheckError(sqlite3_bind_blob(stmt.get(), 4, val_data->data(), val_data->size(), SQLITE_STATIC)); if ( auto res = CheckError(sqlite3_bind_blob(stmt.get(), 4, val_data->data(), val_data->size(), SQLITE_STATIC));
res.code != ReturnCode::SUCCESS ) { res.code != ReturnCode::SUCCESS ) {
return res; return res;
} }
// This duplicates the above binding, but it's to overwrite the expiration time on the entry. // This duplicates the above binding, but it's to overwrite the expiration time on the entry.
if ( auto res = CheckError(sqlite3_bind_double(stmt.get(), 5, expiration_time)); if ( auto res = CheckError(sqlite3_bind_double(stmt.get(), 5, expiration_time)); res.code != ReturnCode::SUCCESS ) {
return res;
}
if ( ! overwrite )
if ( auto res = CheckError(sqlite3_bind_double(stmt.get(), 6, run_state::network_time));
res.code != ReturnCode::SUCCESS ) { res.code != ReturnCode::SUCCESS ) {
return res; return res;
} }
auto step_result = Step(stmt.get(), false);
if ( ! overwrite )
if ( step_result.code == ReturnCode::SUCCESS ) {
int changed = sqlite3_changes(db);
if ( changed == 0 )
step_result.code = ReturnCode::KEY_EXISTS;
} }
return Step(stmt.get(), false); return step_result;
} }
/** /**

View file

@ -1,3 +1,4 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
BEFORE, [code=Storage::SUCCESS, error_str=<uninitialized>, value=v] BEFORE, [code=Storage::SUCCESS, error_str=<uninitialized>, value=v]
AFTER, [code=Storage::KEY_NOT_FOUND, error_str=<uninitialized>, value=<uninitialized>] AFTER, [code=Storage::KEY_NOT_FOUND, error_str=<uninitialized>, value=<uninitialized>]
OVERWRITE, [code=Storage::SUCCESS, error_str=<uninitialized>, value=vv]

View file

@ -47,4 +47,15 @@ event zeek_init()
# An expired value does not exist. # An expired value does not exist.
get = Storage::Sync::get(h, key); get = Storage::Sync::get(h, key);
print "AFTER", get; print "AFTER", get;
# Even though the entry still exists in the backend we can put a
# new value in its place without specifying overwrite.
Storage::Sync::put(
h,
Storage::PutArgs(
$key=key,
$value=value+value));
get = Storage::Sync::get(h, key);
print "OVERWRITE", get;
} }