diff --git a/src/storage/backend/sqlite/SQLite.cc b/src/storage/backend/sqlite/SQLite.cc index 5e479f0e0c..c6bf31b811 100644 --- a/src/storage/backend/sqlite/SQLite.cc +++ b/src/storage/backend/sqlite/SQLite.cc @@ -295,6 +295,33 @@ OperationResult SQLite::DoPut(ResultCallback* cb, ValPtr key, ValPtr value, bool if ( ! key_data ) return {ReturnCode::SERIALIZATION_FAILED, "Failed to serialize key"}; + // If we are not already in overwrite mode check if an expired entry exists + // in the database. Such entries would not be visible to the user, but even + // outside of overwrite mode would need to be overwritten. + if ( ! overwrite ) { + auto stmt = unique_stmt_ptr(get_stmt.get(), sqlite3_reset); + + if ( auto res = CheckError(sqlite3_bind_blob(stmt.get(), 1, key_data->data(), key_data->size(), SQLITE_STATIC)); + res.code != ReturnCode::SUCCESS ) { + return res; + } + + int step_status = sqlite3_step(stmt.get()); + if ( step_status == SQLITE_ROW ) { + // If an expired entry exists, switch to overwrite mode. + overwrite = + sqlite3_column_type(stmt.get(), 1) != SQLITE_NULL && is_expired(sqlite3_column_double(stmt.get(), 0)); + } + else if ( step_status == SQLITE_DONE ) { + // Nothing currently exists. + } + else if ( step_status == SQLITE_BUSY || step_status == SQLITE_LOCKED ) + // TODO: this could retry a number of times instead of just failing + return {ReturnCode::TIMEOUT}; + else + return {ReturnCode::OPERATION_FAILED}; + } + unique_stmt_ptr stmt; if ( ! overwrite ) stmt = unique_stmt_ptr(put_stmt.get(), sqlite3_reset); diff --git a/testing/btest/Baseline/scripts.base.frameworks.storage.sqlite-expiration-implicit/output b/testing/btest/Baseline/scripts.base.frameworks.storage.sqlite-expiration-implicit/output index 0730873d8c..829eded2c7 100644 --- a/testing/btest/Baseline/scripts.base.frameworks.storage.sqlite-expiration-implicit/output +++ b/testing/btest/Baseline/scripts.base.frameworks.storage.sqlite-expiration-implicit/output @@ -1,3 +1,4 @@ ### 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=, value=v] AFTER, [code=Storage::KEY_NOT_FOUND, error_str=, value=] +OVERWRITE, [code=Storage::SUCCESS, error_str=, value=vv] diff --git a/testing/btest/scripts/base/frameworks/storage/sqlite-expiration-implicit.zeek b/testing/btest/scripts/base/frameworks/storage/sqlite-expiration-implicit.zeek index aa9e4d9a98..f6197ea9ad 100644 --- a/testing/btest/scripts/base/frameworks/storage/sqlite-expiration-implicit.zeek +++ b/testing/btest/scripts/base/frameworks/storage/sqlite-expiration-implicit.zeek @@ -47,4 +47,15 @@ event zeek_init() # An expired value does not exist. get = Storage::Sync::get(h, key); 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; }