diff --git a/native/cpp/CommonCpp/DatabaseManagers/DatabaseQueryExecutor.h b/native/cpp/CommonCpp/DatabaseManagers/DatabaseQueryExecutor.h --- a/native/cpp/CommonCpp/DatabaseManagers/DatabaseQueryExecutor.h +++ b/native/cpp/CommonCpp/DatabaseManagers/DatabaseQueryExecutor.h @@ -4,6 +4,7 @@ #include "entities/AuxUserInfo.h" #include "entities/CommunityInfo.h" #include "entities/Draft.h" +#include "entities/EntryInfo.h" #include "entities/InboundP2PMessage.h" #include "entities/IntegrityThreadHash.h" #include "entities/KeyserverInfo.h" @@ -108,6 +109,10 @@ virtual void removeAllThreadActivityEntries() const = 0; virtual std::vector getAllThreadActivityEntries() const = 0; + virtual void replaceEntry(const EntryInfo &entry_info) const = 0; + virtual void removeEntries(const std::vector &ids) const = 0; + virtual void removeAllEntries() const = 0; + virtual std::vector getAllEntries() const = 0; virtual void beginTransaction() const = 0; virtual void commitTransaction() const = 0; virtual void rollbackTransaction() const = 0; diff --git a/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.h b/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.h --- a/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.h +++ b/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.h @@ -120,6 +120,10 @@ const std::vector &ids) const override; void removeAllThreadActivityEntries() const override; std::vector getAllThreadActivityEntries() const override; + void replaceEntry(const EntryInfo &entry_info) const override; + void removeEntries(const std::vector &ids) const override; + void removeAllEntries() const override; + std::vector getAllEntries() const override; void beginTransaction() const override; void commitTransaction() const override; void rollbackTransaction() const override; diff --git a/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp b/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp --- a/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp +++ b/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp @@ -3,6 +3,7 @@ #include "entities/CommunityInfo.h" #include "entities/EntityQueryHelpers.h" +#include "entities/EntryInfo.h" #include "entities/IntegrityThreadHash.h" #include "entities/KeyserverInfo.h" #include "entities/Metadata.h" @@ -661,6 +662,15 @@ return create_table(db, query, "outbound_p2p_messages"); } +bool create_entries_table(sqlite3 *db) { + std::string query = + "CREATE TABLE IF NOT EXISTS entries (" + " id TEXT UNIQUE PRIMARY KEY NOT NULL," + " entry TEXT NOT NULL" + ");"; + return create_table(db, query, "entries"); +} + bool create_schema(sqlite3 *db) { char *error; sqlite3_exec( @@ -799,6 +809,11 @@ " status TEXT NOT NULL" ");" + "CREATE TABLE IF NOT EXISTS entries (" + " id TEXT UNIQUE PRIMARY KEY NOT NULL," + " entry TEXT NOT NULL" + ");" + "CREATE INDEX IF NOT EXISTS media_idx_container" " ON media (container);" @@ -1053,7 +1068,8 @@ {42, {add_version_column_to_olm_persist_sessions_table, true}}, {43, {create_thread_activity_table, true}}, {44, {create_received_messages_to_device, true}}, - {45, {recreate_outbound_p2p_messages_table, true}}}}; + {45, {recreate_outbound_p2p_messages_table, true}}, + {46, {create_entries_table, true}}}}; enum class MigrationResult { SUCCESS, FAILURE, NOT_APPLIED }; @@ -1952,6 +1968,43 @@ SQLiteQueryExecutor::getConnection(), getAllThreadActivityEntriesSQL); } +void SQLiteQueryExecutor::replaceEntry(const EntryInfo &entry_info) const { + static std::string replaceEntrySQL = + "REPLACE INTO entries (id, entry) " + "VALUES (?, ?);"; + replaceEntity( + SQLiteQueryExecutor::getConnection(), replaceEntrySQL, entry_info); +} + +void SQLiteQueryExecutor::removeAllEntries() const { + static std::string removeAllEntriesSQL = "DELETE FROM entries;"; + removeAllEntities(SQLiteQueryExecutor::getConnection(), removeAllEntriesSQL); +} + +void SQLiteQueryExecutor::removeEntries( + const std::vector &ids) const { + if (!ids.size()) { + return; + } + + std::stringstream removeEntriesByKeysSQLStream; + removeEntriesByKeysSQLStream << "DELETE FROM entries " + "WHERE id IN " + << getSQLStatementArray(ids.size()) << ";"; + removeEntitiesByKeys( + SQLiteQueryExecutor::getConnection(), + removeEntriesByKeysSQLStream.str(), + ids); +} + +std::vector SQLiteQueryExecutor::getAllEntries() const { + static std::string getAllEntriesSQL = + "SELECT * " + "FROM entries;"; + return getAllEntities( + SQLiteQueryExecutor::getConnection(), getAllEntriesSQL); +} + void SQLiteQueryExecutor::beginTransaction() const { executeQuery(SQLiteQueryExecutor::getConnection(), "BEGIN TRANSACTION;"); } diff --git a/native/cpp/CommonCpp/DatabaseManagers/entities/EntryInfo.h b/native/cpp/CommonCpp/DatabaseManagers/entities/EntryInfo.h new file mode 100644 --- /dev/null +++ b/native/cpp/CommonCpp/DatabaseManagers/entities/EntryInfo.h @@ -0,0 +1,23 @@ +#pragma once + +#include "SQLiteDataConverters.h" +#include +#include + +namespace comm { +struct EntryInfo { + std::string id; + std::string entry; + + static EntryInfo fromSQLResult(sqlite3_stmt *sqlRow, int idx) { + return EntryInfo{ + getStringFromSQLRow(sqlRow, idx), getStringFromSQLRow(sqlRow, idx + 1)}; + }; + + int bindToSQL(sqlite3_stmt *sql, int idx) const { + bindStringToSQL(id, sql, idx); + return bindStringToSQL(entry, sql, idx + 1); + } +}; + +} // namespace comm diff --git a/web/cpp/SQLiteQueryExecutorBindings.cpp b/web/cpp/SQLiteQueryExecutorBindings.cpp --- a/web/cpp/SQLiteQueryExecutorBindings.cpp +++ b/web/cpp/SQLiteQueryExecutorBindings.cpp @@ -68,6 +68,9 @@ .field( "threadActivityStoreEntry", &ThreadActivityEntry::thread_activity_store_entry); + value_object("EntryInfo") + .field("id", &EntryInfo::id) + .field("entry", &EntryInfo::entry); value_object("WebThread") .field("id", &WebThread::id) @@ -238,6 +241,10 @@ .function( "getAllThreadActivityEntries", &SQLiteQueryExecutor::getAllThreadActivityEntries) + .function("replaceEntry", &SQLiteQueryExecutor::replaceEntry) + .function("removeEntries", &SQLiteQueryExecutor::removeEntries) + .function("removeAllEntries", &SQLiteQueryExecutor::removeAllEntries) + .function("getAllEntries", &SQLiteQueryExecutor::getAllEntries) .function("beginTransaction", &SQLiteQueryExecutor::beginTransaction) .function("commitTransaction", &SQLiteQueryExecutor::commitTransaction) .function( diff --git a/web/shared-worker/_generated/comm_query_executor.wasm b/web/shared-worker/_generated/comm_query_executor.wasm index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc$@ { + let queryExecutor: ?SQLiteQueryExecutor = null; + let dbModule: ?EmscriptenModule = null; + + beforeAll(async () => { + dbModule = getDatabaseModule(); + }); + + beforeEach(() => { + if (!dbModule) { + throw new Error('Database module is missing'); + } + queryExecutor = new dbModule.SQLiteQueryExecutor(FILE_PATH); + if (!queryExecutor) { + throw new Error('SQLiteQueryExecutor is missing'); + } + queryExecutor?.replaceEntry( + convertEntryInfoIntoClientDBEntryInfo({ id: '0', entry: TEST_ENTRY_1 }), + ); + queryExecutor?.replaceEntry( + convertEntryInfoIntoClientDBEntryInfo({ id: '1', entry: TEST_ENTRY_2 }), + ); + }); + + afterEach(() => { + if (!dbModule || !queryExecutor) { + return; + } + clearSensitiveData(dbModule, FILE_PATH, queryExecutor); + }); + + it('should return all entries', () => { + const entries = queryExecutor?.getAllEntries(); + expect(entries?.length).toBe(2); + }); + + it('should remove all entries', () => { + queryExecutor?.removeAllEntries(); + const entries = queryExecutor?.getAllEntries(); + expect(entries?.length).toBe(0); + }); + + it('should update text property', () => { + const updatedText = 'updated_test_entry_text'; + const updatedTestEntry = { + ...TEST_ENTRY_2, + text: updatedText, + }; + queryExecutor?.replaceEntry( + convertEntryInfoIntoClientDBEntryInfo({ + id: '1', + entry: updatedTestEntry, + }), + ); + + const dbEntries = queryExecutor?.getAllEntries(); + if (!dbEntries) { + throw new Error('entries not defined'); + } + + const entries = entryStoreOpsHandlers.translateClientDBData(dbEntries); + expect(entries['1']).toBeDefined(); + expect(entries['1'].creatorID).toBe('1'); + expect(entries['1'].text).toBe(updatedText); + }); + + it('should remove entry', () => { + queryExecutor?.removeEntries(['1']); + + const entries = queryExecutor?.getAllEntries(); + if (!entries) { + throw new Error('entries not defined'); + } + expect(entries.length).toBe(1); + expect(entries[0].id).toBe('0'); + }); +}); diff --git a/web/shared-worker/types/sqlite-query-executor.js b/web/shared-worker/types/sqlite-query-executor.js --- a/web/shared-worker/types/sqlite-query-executor.js +++ b/web/shared-worker/types/sqlite-query-executor.js @@ -2,6 +2,7 @@ import type { ClientDBAuxUserInfo } from 'lib/ops/aux-user-store-ops.js'; import type { ClientDBCommunityInfo } from 'lib/ops/community-store-ops.js'; +import type { ClientDBEntryInfo } from 'lib/ops/entries-store-ops.js'; import type { ClientDBIntegrityThreadHash } from 'lib/ops/integrity-store-ops.js'; import type { ClientDBKeyserverInfo } from 'lib/ops/keyserver-store-ops.js'; import type { ClientDBReport } from 'lib/ops/report-store-ops.js'; @@ -139,6 +140,11 @@ removeAllThreadActivityEntries(): void; getAllThreadActivityEntries(): ClientDBThreadActivityEntry[]; + replaceEntry(entryInfo: ClientDBEntryInfo): void; + removeEntries(ids: $ReadOnlyArray): void; + removeAllEntries(): void; + getAllEntries(): $ReadOnlyArray; + beginTransaction(): void; commitTransaction(): void; rollbackTransaction(): void;