@@ -26,7 +26,6 @@ namespace mostly_harmless::data {
2626 DoubleIndex = 5
2727 };
2828
29-
3029 template <DatabaseStorageType T>
3130 auto databaseQueryCallback (void * ud, int count, char ** data, char ** /* columns*/ ) -> int {
3231 auto * result = static_cast <std::optional<T>*>(ud);
@@ -48,6 +47,10 @@ namespace mostly_harmless::data {
4847 }
4948 } // namespace
5049
50+ /* *
51+ * \brief A std::variant containing all types satisfying the DatabaseStorageType concept.
52+ */
53+ using DatabaseValueVariant = std::variant<std::string, bool , int , float , double >;
5154 /* *
5255 * \brief Represents a connection to a sqlite database.
5356 *
@@ -68,22 +71,68 @@ namespace mostly_harmless::data {
6871 /* *
6972 * @private
7073 */
71- DatabaseState (Private, const std::filesystem::path& location) {
74+ DatabaseState (Private, const std::filesystem::path& location, const std::vector<std::pair<std::string, DatabaseValueVariant>>& initialValues ) {
7275 const auto checkResult = [](int response) -> void {
7376 if (response != SQLITE_OK) {
7477 throw std::exception{};
7578 }
7679 };
77- auto resultCode = sqlite3_open (location.string ().c_str (), &m_databaseHandle);
78- checkResult (resultCode);
79- const std::string enableWalCommand{ " PRAGMA journal_mode=WAL" };
80- resultCode = sqlite3_exec (m_databaseHandle, enableWalCommand.c_str (), nullptr , nullptr , nullptr );
81- checkResult (resultCode);
82- const std::string command{
83- " CREATE TABLE IF NOT EXISTS DATA (NAME text UNIQUE, TEXT_VALUE text, BOOL_VALUE bool, INT_VALUE int, FLOAT_VALUE float, DOUBLE_VALUE double);"
84- };
85- resultCode = sqlite3_exec (m_databaseHandle, command.c_str (), nullptr , nullptr , nullptr );
86- checkResult (resultCode);
80+
81+ // Try open existing
82+ if (sqlite3_open_v2 (location.string ().c_str (), &m_databaseHandle, SQLITE_OPEN_READWRITE, nullptr ) != SQLITE_OK) {
83+ checkResult (sqlite3_open (location.string ().c_str (), &m_databaseHandle)); // Didn't exist, try create
84+ }
85+ // Enable WAL
86+ checkResult (sqlite3_exec (m_databaseHandle, " PRAGMA journal_mode=WAL" , nullptr , nullptr , nullptr ));
87+ // Create Table if not present
88+ checkResult (sqlite3_exec (m_databaseHandle,
89+ " CREATE TABLE IF NOT EXISTS DATA (NAME text UNIQUE, TEXT_VALUE text, BOOL_VALUE bool, INT_VALUE int, FLOAT_VALUE float, DOUBLE_VALUE double);" ,
90+ nullptr ,
91+ nullptr ,
92+ nullptr ));
93+ // Populate with initial values, if key is present already, skip set
94+ for (const auto & [key, value] : initialValues) {
95+ std::visit ([this , &key](auto && arg) {
96+ using T = std::decay_t <decltype (arg)>;
97+ if (get<T>(key)) {
98+ return ;
99+ }
100+ set (key, std::forward<decltype (arg)>(arg));
101+ },
102+ value);
103+ }
104+ }
105+
106+ /* *
107+ * Non Copyable, as the database connection pointer will be closed on destruction...
108+ */
109+ DatabaseState (const DatabaseState& /* other*/ ) = delete ;
110+
111+ /* *
112+ * Moveable, nulls `other`'s connection pointer
113+ * @param other The moved-from DatabaseState instance
114+ */
115+ DatabaseState (DatabaseState&& other) noexcept {
116+ std::swap (m_databaseHandle, other.m_databaseHandle );
117+ }
118+
119+ /* *
120+ *
121+ * Non Copyable, as the database connection pointer will be closed on destruction...
122+ *
123+ */
124+ DatabaseState& operator =(const DatabaseState& /* other*/ ) = delete ;
125+
126+ /* *
127+ * Moveable, nulls `other`'s connection pointer
128+ * @param other The moved-from DatabaseState instance
129+ * @return *this
130+ */
131+ DatabaseState& operator =(DatabaseState&& other) noexcept {
132+ if (this != &other) {
133+ std::swap (m_databaseHandle, other.m_databaseHandle );
134+ }
135+ return *this ;
87136 }
88137
89138 /* *
@@ -92,20 +141,22 @@ namespace mostly_harmless::data {
92141 * If it doesn't exist, creates the database, and a table to store user data in.
93142 *
94143 * \param location A path to the database to create or open.
144+ * \param initialValues A vector containing the initial values to add to the database if it didn't exist. If the database DID exist, but any of the keys in the vector aren't present, they'll be added with the values specified
145+ * and existing items will be skipped.
95146 * \return A DatabaseState instance on success, nullopt otherwise.
96147 */
97- [[nodiscard]] static auto try_create (const std::filesystem::path& location) -> std::optional<DatabaseState> {
148+ [[nodiscard]] static auto try_create (const std::filesystem::path& location, const std::vector<std::pair<std::string, DatabaseValueVariant>>& initialValues ) -> std::optional<DatabaseState> {
98149 try {
99- DatabaseState state{ {}, location };
100- return state;
150+ DatabaseState state{ {}, location, initialValues };
151+ return std::move ( state) ;
101152 } catch (...) {
102153 assert (false );
103154 return {};
104155 }
105156 }
106157
107158 /* *
108- * @private
159+ * The internal database handle is closed if not null.
109160 */
110161 ~DatabaseState () noexcept {
111162 if (!m_databaseHandle) return ;
@@ -119,8 +170,8 @@ namespace mostly_harmless::data {
119170 * @param toSet The value to set.
120171 */
121172 template <DatabaseStorageType T>
122- auto set (std::string_view name, T& & toSet) -> void {
123- struct Properties {
173+ auto set (std::string_view name, const T & toSet) -> void {
174+ struct {
124175 std::string textValue{};
125176 bool boolValue{ false };
126177 int intValue{ 0 };
0 commit comments