1 //////////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (c) 2004-2021 musikcube team
4 //
5 // All rights reserved.
6 //
7 // Redistribution and use in source and binary forms, with or without
8 // modification, are permitted provided that the following conditions are met:
9 //
10 //    * Redistributions of source code must retain the above copyright notice,
11 //      this list of conditions and the following disclaimer.
12 //
13 //    * Redistributions in binary form must reproduce the above copyright
14 //      notice, this list of conditions and the following disclaimer in the
15 //      documentation and/or other materials provided with the distribution.
16 //
17 //    * Neither the name of the author nor the names of other contributors may
18 //      be used to endorse or promote products derived from this software
19 //      without specific prior written permission.
20 //
21 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
25 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 // POSSIBILITY OF SUCH DAMAGE.
32 //
33 //////////////////////////////////////////////////////////////////////////////
34 
35 #include "pch.hpp"
36 
37 #include <musikcore/db/Connection.h>
38 #include <musikcore/db/SqliteExtensions.h>
39 
40 #pragma warning(push, 0)
41 #include <sqlite/sqlite3.h>
42 #pragma warning(pop)
43 
44 static std::mutex globalMutex;
45 
46 using namespace musik::core::db;
47 
Connection()48 Connection::Connection() noexcept
49 : connection(nullptr)
50 , transactionCounter(0) {
51     this->UpdateReferenceCount(true);
52 }
53 
~Connection()54 Connection::~Connection() {
55     this->Close();
56     this->UpdateReferenceCount(false);
57 }
58 
Open(const std::string & database,unsigned int options,unsigned int cache)59 int Connection::Open(const std::string &database, unsigned int options, unsigned int cache) {
60     int error;
61 
62     #ifdef WIN32
63         std::wstring wdatabase = u8to16(database);
64         error = sqlite3_open16(wdatabase.c_str(), &this->connection);
65     #else
66         error = sqlite3_open(database.c_str(), &this->connection);
67     #endif
68 
69     if (error == SQLITE_OK) {
70         this->Initialize(cache);
71     }
72 
73     return error;
74 }
75 
Close()76 int Connection::Close() noexcept {
77     if (sqlite3_close(this->connection) == SQLITE_OK) {
78         this->connection = 0;
79         return Okay;
80     }
81 
82     return Error;
83 }
84 
Execute(const char * sql)85 int Connection::Execute(const char* sql) {
86     sqlite3_stmt *stmt  = nullptr;
87 
88     /* prepare seems to give errors when interrupted */
89     {
90         std::unique_lock<std::mutex> lock(this->mutex);
91 
92         if (sqlite3_prepare_v2(this->connection, sql, -1, &stmt, nullptr) != SQLITE_OK) {
93             sqlite3_finalize(stmt);
94             return Error;
95         }
96     }
97 
98     const int error = this->StepStatement(stmt);
99     if (error != SQLITE_OK && error != SQLITE_DONE) {
100         sqlite3_finalize(stmt);
101         return Error;
102     }
103 
104     sqlite3_reset(stmt);
105     sqlite3_finalize(stmt);
106 
107     return Okay;
108 }
109 
Checkpoint()110 void Connection::Checkpoint() noexcept {
111     sqlite3_wal_checkpoint(this->connection, nullptr);
112 }
113 
LastInsertedId()114 int64_t Connection::LastInsertedId() noexcept {
115     return sqlite3_last_insert_rowid(this->connection);
116 }
117 
LastModifiedRowCount()118 int Connection::LastModifiedRowCount() noexcept {
119     return narrow_cast<int>(sqlite3_changes(this->connection));
120 }
121 
Initialize(unsigned int cache)122 void Connection::Initialize(unsigned int cache) {
123     SqliteExtensions::Register(this->connection);
124 
125     sqlite3_enable_shared_cache(1);
126     sqlite3_busy_timeout(this->connection, 10000);
127 
128     sqlite3_exec(this->connection, "PRAGMA optimize", nullptr, nullptr, nullptr);           // Optimize the database when applicable
129     sqlite3_exec(this->connection, "PRAGMA synchronous=NORMAL", nullptr, nullptr, nullptr); // NORMAL useful for auto-checkpointing with WAL
130     sqlite3_exec(this->connection, "PRAGMA page_size=4096", nullptr, nullptr, nullptr);	    // According to windows standard page size
131     sqlite3_exec(this->connection, "PRAGMA auto_vacuum=0", nullptr, nullptr, nullptr);	    // No autovaccum.
132     sqlite3_exec(this->connection, "PRAGMA journal_mode=WAL", nullptr, nullptr, nullptr);   // Allow reading while writing (write-ahead-logging)
133 
134     if (cache != 0) {
135         // Divide by 4 to since the page_size is 4096
136         // Total cache is the same as page_size*cache_size
137         cache = cache / 4;
138         std::string cacheSize("PRAGMA cache_size=" + std::to_string(cache));
139         sqlite3_exec(this->connection,cacheSize.c_str(), nullptr, nullptr, nullptr); // size * 1.5kb = 6Mb cache
140     }
141 
142     //sqlite3_exec(this->connection, "PRAGMA case_sensitive_like=0", nullptr, nullptr, nullptr);   // More speed if case insensitive
143     sqlite3_exec(this->connection, "PRAGMA count_changes=0", nullptr, nullptr, nullptr);         // If set it counts changes on SQL UPDATE. More speed when not.
144     sqlite3_exec(this->connection, "PRAGMA legacy_file_format=OFF", nullptr, nullptr, nullptr);  // No reason to be backwards compatible :)
145     sqlite3_exec(this->connection, "PRAGMA temp_store=MEMORY", nullptr, nullptr, nullptr);       // MEMORY, not file. More speed.
146 }
147 
Interrupt()148 void Connection::Interrupt() {
149     std::unique_lock<std::mutex> lock(this->mutex);
150     sqlite3_interrupt(this->connection);
151 }
152 
UpdateReferenceCount(bool init)153 void Connection::UpdateReferenceCount(bool init) {
154     std::unique_lock<std::mutex> lock(this->mutex);
155 
156     static int count = 0;
157 
158     if (init) {
159         if (count == 0) {
160             sqlite3_initialize();
161         }
162 
163         ++count;
164     }
165     else {
166         --count;
167         if (count <= 0) {
168             sqlite3_shutdown();
169             count = 0;
170         }
171     }
172 }
173 
StepStatement(sqlite3_stmt * stmt)174 int Connection::StepStatement(sqlite3_stmt *stmt) noexcept {
175     return sqlite3_step(stmt);
176 }
177