diff --git a/LiteCore/Query/SQLiteQuery.cc b/LiteCore/Query/SQLiteQuery.cc index e5d97fdf2..f9038910d 100644 --- a/LiteCore/Query/SQLiteQuery.cc +++ b/LiteCore/Query/SQLiteQuery.cc @@ -110,11 +110,6 @@ namespace litecore { error::_throw(error::NoSuchIndex, "'match' test requires a full-text index"); } - // If expiration is queried, ensure the table(s) have the expiration column: - if ( qp.usesExpiration() ) { - for ( auto ks : _keyStores ) ks->addExpiration(); - } - LogTo(SQL, "Compiled {Query#%u}: %s", getObjectRef(), sql.c_str()); _statement = dataFile.compile(sql.c_str()); diff --git a/LiteCore/Storage/BothKeyStore.hh b/LiteCore/Storage/BothKeyStore.hh index 481d44789..20b3ee27b 100644 --- a/LiteCore/Storage/BothKeyStore.hh +++ b/LiteCore/Storage/BothKeyStore.hh @@ -67,11 +67,6 @@ namespace litecore { bool mayHaveExpiration() override { return _liveStore->mayHaveExpiration() || _deadStore->mayHaveExpiration(); } - void addExpiration() override { - _liveStore->addExpiration(); - _deadStore->addExpiration(); - } - bool setExpiration(slice key, expiration_t exp) override { return _liveStore->setExpiration(key, exp) || _deadStore->setExpiration(key, exp); } diff --git a/LiteCore/Storage/KeyStore.hh b/LiteCore/Storage/KeyStore.hh index 15a60b466..70493d24b 100644 --- a/LiteCore/Storage/KeyStore.hh +++ b/LiteCore/Storage/KeyStore.hh @@ -164,8 +164,6 @@ namespace litecore { /** Does this KeyStore potentially have records that expire? (May return false positives.) */ virtual bool mayHaveExpiration() = 0; - virtual void addExpiration() = 0; - /** Sets a record's expiration time. Zero means 'never'. @return true if the time was set, false if no record with that key exists. */ virtual bool setExpiration(slice key, expiration_t) = 0; @@ -210,7 +208,7 @@ namespace litecore { // public for complicated reasons; clients should never call it virtual ~KeyStore() = default; - KeyStore(const KeyStore&) = delete; // not copyable + KeyStore(const KeyStore&) = delete; // not copyable KeyStore& operator=(const KeyStore&) = delete; protected: diff --git a/LiteCore/Storage/SQLiteDataFile.cc b/LiteCore/Storage/SQLiteDataFile.cc index be64a692e..4806cb0a6 100644 --- a/LiteCore/Storage/SQLiteDataFile.cc +++ b/LiteCore/Storage/SQLiteDataFile.cc @@ -330,6 +330,19 @@ namespace litecore { error::_throw(error::CantUpgradeDatabase); } + (void)upgradeSchema(SchemaVersion::WithExpirationColumn, "Adding `expiration` column", [&] { + // Add the 'expiration' column to every KeyStore: + for ( string& name : allKeyStoreNames() ) { + if ( name.find("::") == string::npos ) { + // Only update data tables, not FTS index tables + _exec(format( + "ALTER TABLE \"kv_%s\" ADD COLUMN expiration INTEGER; " + "CREATE INDEX \"kv_%s_expiration\" ON \"kv_%s\" (expiration) WHERE expiration not null", + name.c_str(), name.c_str(), name.c_str())); + } + } + }); + (void)upgradeSchema(SchemaVersion::WithIndexesLastSeq, "Adding indexes.lastSeq column", [&] { string sql; if ( getSchema("indexes", "table", "indexes", sql) ) { @@ -602,9 +615,6 @@ namespace litecore { // Wrap the store in a BothKeyStore that manages it and the deleted store: auto deletedStore = new SQLiteKeyStore(*this, kDeletedKeyStorePrefix + name, options); - keyStore->addExpiration(); - deletedStore->addExpiration(); - // Create a SQLite view of a union of both stores, for use in queries: #define COLUMNS "key,sequence,flags,version,body,extra,expiration" // Invarient: keyStore->tablaName() == kv_ diff --git a/LiteCore/Storage/SQLiteDataFile.hh b/LiteCore/Storage/SQLiteDataFile.hh index 891d036b8..83ac92480 100644 --- a/LiteCore/Storage/SQLiteDataFile.hh +++ b/LiteCore/Storage/SQLiteDataFile.hh @@ -174,9 +174,10 @@ namespace litecore { WithNewDocs = 400, // New document/revision storage (CBL 3.0) - WithDeletedTable = 500, // Added 'deleted' KeyStore for deleted docs (CBL 3.0?) - WithIndexesLastSeq = 501, // Added 'lastSeq' column to 'indexes' table (CBL 3.2) - MaxReadable = 599, // Cannot open versions newer than this + WithExpirationColumn = 490, // Added 'expiration' column to KeyStore + WithDeletedTable = 500, // Added 'deleted' KeyStore for deleted docs (CBL 3.0?) + WithIndexesLastSeq = 501, // Added 'lastSeq' column to 'indexes' table (CBL 3.2) + MaxReadable = 599, // Cannot open versions newer than this Current = WithDeletedTable }; diff --git a/LiteCore/Storage/SQLiteKeyStore.cc b/LiteCore/Storage/SQLiteKeyStore.cc index 8154d0620..2cde48c4e 100644 --- a/LiteCore/Storage/SQLiteKeyStore.cc +++ b/LiteCore/Storage/SQLiteKeyStore.cc @@ -70,13 +70,16 @@ namespace litecore { // more efficient in SQLite to keep large columns at the end of a row. // Create the sequence and flags columns regardless of options, otherwise it's too // complicated to customize all the SQL queries to conditionally use them... - db().execWithLock(subst("CREATE TABLE IF NOT EXISTS kv_@ (" - " key TEXT PRIMARY KEY," - " sequence INTEGER," - " flags INTEGER DEFAULT 0," - " version BLOB," - " body BLOB," - " extra BLOB)")); + db().execWithLock( + subst("CREATE TABLE IF NOT EXISTS kv_@ (" + " key TEXT PRIMARY KEY," + " sequence INTEGER," + " flags INTEGER DEFAULT 0," + " version BLOB," + " body BLOB," + " expiration INTEGER," + " extra BLOB);" + "CREATE INDEX IF NOT EXISTS \"kv_@_expiration\" ON kv_@ (expiration) WHERE expiration not null")); _uncommitedTable = db().inTransaction(); } @@ -179,12 +182,10 @@ namespace litecore { _purgeCountValid = false; if ( !commit ) { - if ( _uncommittedExpirationColumn ) _hasExpirationColumn = false; if ( _uncommitedTable ) { close(); } } - _uncommittedExpirationColumn = false; - _uncommitedTable = false; + _uncommitedTable = false; } /*static*/ slice SQLiteKeyStore::columnAsSlice(const SQLite::Column& col) { @@ -520,22 +521,8 @@ namespace litecore { return _hasExpirationColumn; } - // Adds the 'expiration' column to the table. - void SQLiteKeyStore::addExpiration() { - if ( _hasExpirationColumn ) return; - db().withFileLock([=]() { - if ( mayHaveExpiration() ) return; - db()._logVerbose("Adding the `expiration` column & index to kv_%s", name().c_str()); - db().exec(subst("ALTER TABLE kv_@ ADD COLUMN expiration INTEGER; " - "CREATE INDEX \"kv_@_expiration\" ON kv_@ (expiration) WHERE expiration not null")); - _uncommittedExpirationColumn = true; - }); - _hasExpirationColumn = true; - } - bool SQLiteKeyStore::setExpiration(slice key, expiration_t expTime) { Assert(expTime >= expiration_t(0), "Invalid (negative) expiration time"); - addExpiration(); auto& stmt = compileCached("UPDATE kv_@ SET expiration=? WHERE key=?"); UsingStatement u(stmt); if ( expTime > expiration_t::None ) stmt.bind(1, (long long)expTime); diff --git a/LiteCore/Storage/SQLiteKeyStore.hh b/LiteCore/Storage/SQLiteKeyStore.hh index d1f3dd233..f9f398ede 100644 --- a/LiteCore/Storage/SQLiteKeyStore.hh +++ b/LiteCore/Storage/SQLiteKeyStore.hh @@ -90,9 +90,6 @@ namespace litecore { void createConflictsIndex(); void createBlobsIndex(); - /// Adds the `expiration` column to the table. Called only by SQLiteQuery. - void addExpiration() override; - void shareSequencesWith(KeyStore&) override; protected: @@ -161,7 +158,6 @@ namespace litecore { mutable std::optional _lastSequence; mutable std::atomic _purgeCount{0}; bool _hasExpirationColumn{false}; - bool _uncommittedExpirationColumn{false}; bool _uncommitedTable{false}; SQLiteKeyStore* _sequencesOwner{nullptr}; };