blob: 53ab07d597ee9703bb0fe1c2768694f28f57361b [file] [log] [blame]
[email protected]e5ffd0e42009-09-11 21:30:561// Copyright (c) 2009 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "app/sql/connection.h"
6
7#include <string.h>
8
9#include "app/sql/statement.h"
10#include "base/file_path.h"
11#include "base/logging.h"
12#include "base/string_util.h"
13#include "third_party/sqlite/preprocessed/sqlite3.h"
14
15namespace sql {
16
17bool StatementID::operator<(const StatementID& other) const {
18 if (number_ != other.number_)
19 return number_ < other.number_;
20 return strcmp(str_, other.str_) < 0;
21}
22
23Connection::StatementRef::StatementRef()
24 : connection_(NULL),
25 stmt_(NULL) {
26}
27
28Connection::StatementRef::StatementRef(Connection* connection,
29 sqlite3_stmt* stmt)
30 : connection_(connection),
31 stmt_(stmt) {
32 connection_->StatementRefCreated(this);
33}
34
35Connection::StatementRef::~StatementRef() {
36 if (connection_)
37 connection_->StatementRefDeleted(this);
38 Close();
39}
40
41void Connection::StatementRef::Close() {
42 if (stmt_) {
43 sqlite3_finalize(stmt_);
44 stmt_ = NULL;
45 }
46 connection_ = NULL; // The connection may be getting deleted.
47}
48
49Connection::Connection()
50 : db_(NULL),
51 page_size_(0),
52 cache_size_(0),
53 exclusive_locking_(false),
54 transaction_nesting_(0),
55 needs_rollback_(false) {
56}
57
58Connection::~Connection() {
59 Close();
60}
61
62bool Connection::Init(const FilePath& path) {
63#if defined(OS_WIN)
64 // We want the default encoding to always be UTF-8, so we use the
65 // 8-bit version of open().
66 int err = sqlite3_open(WideToUTF8(path.value()).c_str(), &db_);
67#elif defined(OS_POSIX)
68 int err = sqlite3_open(path.value().c_str(), &db_);
69#endif
70
71 if (err != SQLITE_OK) {
72 db_ = NULL;
73 return false;
74 }
75
76 if (page_size_ != 0) {
77 if (!Execute(StringPrintf("PRAGMA page_size=%d", page_size_).c_str()))
78 NOTREACHED() << "Could not set page size";
79 }
80
81 if (cache_size_ != 0) {
82 if (!Execute(StringPrintf("PRAGMA cache_size=%d", cache_size_).c_str()))
83 NOTREACHED() << "Could not set page size";
84 }
85
86 if (exclusive_locking_) {
87 if (!Execute("PRAGMA locking_mode=EXCLUSIVE"))
88 NOTREACHED() << "Could not set locking mode.";
89 }
90
91 return true;
92}
93
94void Connection::Close() {
95 statement_cache_.clear();
96 DCHECK(open_statements_.empty());
97 if (db_) {
98 sqlite3_close(db_);
99 db_ = NULL;
100 }
101}
102
103void Connection::Preload() {
104 if (!db_) {
105 NOTREACHED();
106 return;
107 }
108
109 // A statement must be open for the preload command to work. If the meta
110 // table doesn't exist, it probably means this is a new database and there
111 // is nothing to preload (so it's OK we do nothing).
112 if (!DoesTableExist("meta"))
113 return;
114 Statement dummy(GetUniqueStatement("SELECT * FROM meta"));
115 if (!dummy || !dummy.Run())
116 return;
117
118 sqlite3Preload(db_);
119}
120
121bool Connection::BeginTransaction() {
122 if (needs_rollback_) {
123 DCHECK(transaction_nesting_ > 0);
124
125 // When we're going to rollback, fail on this begin and don't actually
126 // mark us as entering the nested transaction.
127 return false;
128 }
129
130 bool success = true;
131 if (!transaction_nesting_) {
132 needs_rollback_ = false;
133
134 Statement begin(GetCachedStatement(SQL_FROM_HERE, "BEGIN TRANSACTION"));
135 if (!begin || !begin.Run())
136 return false;
137 }
138 transaction_nesting_++;
139 return success;
140}
141
142void Connection::RollbackTransaction() {
143 if (!transaction_nesting_) {
144 NOTREACHED() << "Rolling back a nonexistant transaction";
145 return;
146 }
147
148 transaction_nesting_--;
149
150 if (transaction_nesting_ > 0) {
151 // Mark the outermost transaction as needing rollback.
152 needs_rollback_ = true;
153 return;
154 }
155
156 DoRollback();
157}
158
159bool Connection::CommitTransaction() {
160 if (!transaction_nesting_) {
161 NOTREACHED() << "Rolling back a nonexistant transaction";
162 return false;
163 }
164 transaction_nesting_--;
165
166 if (transaction_nesting_ > 0) {
167 // Mark any nested transactions as failing after we've already got one.
168 return !needs_rollback_;
169 }
170
171 if (needs_rollback_) {
172 DoRollback();
173 return false;
174 }
175
176 Statement commit(GetCachedStatement(SQL_FROM_HERE, "COMMIT"));
177 if (!commit)
178 return false;
179 return commit.Run();
180}
181
182bool Connection::Execute(const char* sql) {
183 if (!db_)
184 return false;
185 return sqlite3_exec(db_, sql, NULL, NULL, NULL) == SQLITE_OK;
186}
187
188bool Connection::HasCachedStatement(const StatementID& id) const {
189 return statement_cache_.find(id) != statement_cache_.end();
190}
191
192scoped_refptr<Connection::StatementRef> Connection::GetCachedStatement(
193 const StatementID& id,
194 const char* sql) {
195 CachedStatementMap::iterator i = statement_cache_.find(id);
196 if (i != statement_cache_.end()) {
197 // Statement is in the cache. It should still be active (we're the only
198 // one invalidating cached statements, and we'll remove it from the cache
199 // if we do that. Make sure we reset it before giving out the cached one in
200 // case it still has some stuff bound.
201 DCHECK(i->second->is_valid());
202 sqlite3_reset(i->second->stmt());
203 return i->second;
204 }
205
206 scoped_refptr<StatementRef> statement = GetUniqueStatement(sql);
207 if (statement->is_valid())
208 statement_cache_[id] = statement; // Only cache valid statements.
209 return statement;
210}
211
212scoped_refptr<Connection::StatementRef> Connection::GetUniqueStatement(
213 const char* sql) {
214 if (!db_)
215 return new StatementRef(this, NULL); // Return inactive statement.
216
217 sqlite3_stmt* stmt = NULL;
218 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, NULL) != SQLITE_OK) {
219 // Treat this as non-fatal, it can occur in a number of valid cases, and
220 // callers should be doing their own error handling.
221 DLOG(WARNING) << "SQL compile error " << GetErrorMessage();
222 return new StatementRef(this, NULL);
223 }
224 return new StatementRef(this, stmt);
225}
226
[email protected]1ed78a32009-09-15 20:24:17227bool Connection::DoesTableExist(const char* table_name) const {
228 // GetUniqueStatement can't be const since statements may modify the
229 // database, but we know ours doesn't modify it, so the cast is safe.
230 Statement statement(const_cast<Connection*>(this)->GetUniqueStatement(
[email protected]e5ffd0e42009-09-11 21:30:56231 "SELECT name FROM sqlite_master "
232 "WHERE type='table' AND name=?"));
233 if (!statement)
234 return false;
235 statement.BindString(0, table_name);
236 return statement.Step(); // Table exists if any row was returned.
237}
238
239bool Connection::DoesColumnExist(const char* table_name,
[email protected]1ed78a32009-09-15 20:24:17240 const char* column_name) const {
[email protected]e5ffd0e42009-09-11 21:30:56241 std::string sql("PRAGMA TABLE_INFO(");
242 sql.append(table_name);
243 sql.append(")");
244
[email protected]1ed78a32009-09-15 20:24:17245 // Our SQL is non-mutating, so this cast is OK.
246 Statement statement(const_cast<Connection*>(this)->GetUniqueStatement(
247 sql.c_str()));
[email protected]e5ffd0e42009-09-11 21:30:56248 if (!statement)
249 return false;
250
251 while (statement.Step()) {
252 if (!statement.ColumnString(1).compare(column_name))
253 return true;
254 }
255 return false;
256}
257
258int64 Connection::GetLastInsertRowId() const {
259 if (!db_) {
260 NOTREACHED();
261 return 0;
262 }
263 return sqlite3_last_insert_rowid(db_);
264}
265
[email protected]1ed78a32009-09-15 20:24:17266int Connection::GetLastChangeCount() const {
267 if (!db_) {
268 NOTREACHED();
269 return 0;
270 }
271 return sqlite3_changes(db_);
272}
273
[email protected]e5ffd0e42009-09-11 21:30:56274int Connection::GetErrorCode() const {
275 if (!db_)
276 return SQLITE_ERROR;
277 return sqlite3_errcode(db_);
278}
279
280const char* Connection::GetErrorMessage() const {
281 if (!db_)
282 return "sql::Connection has no connection.";
283 return sqlite3_errmsg(db_);
284}
285
286void Connection::DoRollback() {
287 Statement rollback(GetCachedStatement(SQL_FROM_HERE, "ROLLBACK"));
288 if (rollback)
289 rollback.Run();
290}
291
292void Connection::StatementRefCreated(StatementRef* ref) {
293 DCHECK(open_statements_.find(ref) == open_statements_.end());
294 open_statements_.insert(ref);
295}
296
297void Connection::StatementRefDeleted(StatementRef* ref) {
298 StatementRefSet::iterator i = open_statements_.find(ref);
299 if (i == open_statements_.end())
300 NOTREACHED();
301 else
302 open_statements_.erase(i);
303}
304
305void Connection::ClearCache() {
306 statement_cache_.clear();
307
308 // The cache clear will get most statements. There may be still be references
309 // to some statements that are held by others (including one-shot statements).
310 // This will deactivate them so they can't be used again.
311 for (StatementRefSet::iterator i = open_statements_.begin();
312 i != open_statements_.end(); ++i)
313 (*i)->Close();
314}
315
316} // namespace sql