2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
17 #include "hphp/runtime/vm/repo-helpers.h"
19 #include "hphp/runtime/base/builtin-functions.h"
21 #include "hphp/runtime/vm/blob-helper.h"
22 #include "hphp/runtime/vm/repo.h"
24 #include "hphp/util/logger.h"
30 //==============================================================================
33 // Try to look at a SQL statement and figure out which repoId it's targeting.
34 static int debugComputeRepoIdFromSQL(Repo
& repo
, const std::string
& stmt
) {
35 for (int i
= 0; i
< repo
.numOpenRepos(); ++i
) {
36 auto const name
= std::string
{" "} + repo
.dbName(i
);
37 auto const pos
= stmt
.find(name
);
38 if (pos
!= std::string::npos
) {
39 auto const ch
= stmt
.c_str()[pos
+ name
.size()];
40 if (ch
== '.' || ch
== ';') return i
;
47 static void reportDbCorruption(Repo
& repo
, int repoId
,
48 const std::string
& where
) {
49 if (repoId
== RepoIdLocal
&& Repo::s_deleteLocalOnFailure
) return;
51 std::string report
= folly::sformat("{} returned SQLITE_CORRUPT.\n", where
);
53 auto repoPath
= sqlite3_db_filename(repo
.dbc(), repo
.dbName(repoId
));
55 report
+= folly::sformat("Path: '{}'\n", repoPath
);
58 if (stat(repoPath
, &repoStat
) == 0) {
59 time_t now
= time(nullptr);
60 report
+= folly::sformat("{} bytes, c_age: {}, m_age: {}\n",
62 now
- repoStat
.st_ctime
,
63 now
- repoStat
.st_mtime
);
65 report
+= "stat() failed\n";
68 report
+= "sqlite3_db_filename() returned nullptr\n";
71 // Use raw SQLite here because we just want to hit the raw DB itself.
72 sqlite3_exec(repo
.dbc(),
73 folly::sformat("PRAGMA {}.integrity_check(4);",
76 [](void* _report
, int columns
, char** text
, char** /*names*/) {
77 std::string
& report
= *reinterpret_cast<std::string
*>(_report
);
78 for (int column
= 0; column
< columns
; ++column
) {
79 report
+= folly::sformat("Integrity Check ({}): {}\n",
80 column
, text
[column
]);
86 Logger::Error(report
);
89 //==============================================================================
92 RepoStmt::RepoStmt(Repo
& repo
)
93 : m_repo(repo
), m_stmt(nullptr) {
96 RepoStmt::~RepoStmt() {
100 void RepoStmt::finalize() {
101 if (m_stmt
!= nullptr) {
103 sqlite3_finalize(m_stmt
);
108 void RepoStmt::prepare(const std::string
& sql
) {
111 int rc
= sqlite3_prepare_v2(m_repo
.dbc(), sql
.c_str(), sql
.size(), &m_stmt
,
113 if (rc
!= SQLITE_OK
) {
114 std::string errmsg
= sqlite3_errmsg(m_repo
.dbc());
115 if (rc
== SQLITE_CORRUPT
) {
116 auto repoId
= debugComputeRepoIdFromSQL(repo(), sql
);
117 reportDbCorruption(m_repo
, repoId
, "sqlite3_prepare_v2()");
119 throw RepoExc("RepoStmt::%s(repo=%p) error: '%s' --> (%d) %s\n",
120 __func__
, &m_repo
, sql
.c_str(), rc
,
125 void RepoStmt::reset() {
126 sqlite3_reset(m_stmt
);
127 sqlite3_clear_bindings(m_stmt
);
130 //==============================================================================
134 * This constructor should only be called from Repo::begin
136 RepoTxn::RepoTxn(Repo
& repo
)
137 : m_repo(repo
), m_pending(true), m_error(false) {
140 RepoTxn::~RepoTxn() {
147 void RepoTxn::rollback_guard(const char* func
, F f
) {
150 } catch (const std::exception
& e
) {
151 TRACE(4, "RepoTxn::%s(repo=%p) caught '%s'\n", func
, &m_repo
, e
.what());
157 // Convienence wrapper to provide __func__ to rollback_guard().
158 #define ROLLBACK_GUARD(f) rollback_guard(__func__, f)
160 void RepoTxn::prepare(RepoStmt
& stmt
, const std::string
& sql
) {
166 void RepoTxn::exec(const std::string
& sQuery
) {
172 void RepoTxn::commit() {
173 if (!m_pending
) return;
176 // Mark pending false AFTER the commit finishes so if it fails we still
177 // attempt a rollback.
182 void RepoTxn::step(RepoQuery
& query
) {
188 void RepoTxn::exec(RepoQuery
& query
) {
194 void RepoTxn::rollback() {
202 #undef ROLLBACK_GUARD
204 //==============================================================================
207 void RepoQuery::bindBlob(const char* paramName
, const void* blob
,
208 size_t size
, bool isStatic
/* = false */) {
209 sqlite3_stmt
* stmt
= m_stmt
.get();
211 sqlite3_bind_blob(stmt
,
212 sqlite3_bind_parameter_index(stmt
, paramName
),
214 isStatic
? SQLITE_STATIC
: SQLITE_TRANSIENT
);
215 assertx(rc
== SQLITE_OK
);
218 void RepoQuery::bindBlob(const char* paramName
, const BlobEncoder
& blob
,
220 return bindBlob(paramName
, blob
.data(), blob
.size(), isStatic
);
223 void RepoQuery::bindSha1(const char* paramName
, const SHA1
& sha1
) {
224 char sha1nbo
[SHA1::kQNumWords
* SHA1::kQWordLen
];
225 sha1
.nbo((void*)sha1nbo
);
226 bindBlob(paramName
, sha1nbo
, sizeof(sha1nbo
));
229 void RepoQuery::bindTypedValue(const char* paramName
, const TypedValue
& tv
) {
230 if (tv
.m_type
== KindOfUninit
) {
231 bindBlob(paramName
, "", 0, true);
233 String blob
= internal_serialize(tvAsCVarRef(&tv
));
234 bindBlob(paramName
, blob
.data(), blob
.size());
238 void RepoQuery::bindText(const char* paramName
, const char* text
,
239 size_t size
, bool isStatic
/* = false */) {
240 sqlite3_stmt
* stmt
= m_stmt
.get();
242 sqlite3_bind_text(stmt
,
243 sqlite3_bind_parameter_index(stmt
, paramName
),
245 isStatic
? SQLITE_STATIC
: SQLITE_TRANSIENT
);
246 assertx(rc
== SQLITE_OK
);
249 void RepoQuery::bindStaticString(const char* paramName
, const StringData
* sd
) {
253 bindText(paramName
, sd
->data(), sd
->size(), true);
257 void RepoQuery::bindStdString(const char* paramName
, const std::string
& s
) {
258 bindText(paramName
, s
.data(), s
.size(), true);
261 void RepoQuery::bindStringPiece(const char* paramName
, folly::StringPiece s
) {
262 bindText(paramName
, s
.data(), s
.size(), true);
265 void RepoQuery::bindDouble(const char* paramName
, double val
) {
266 sqlite3_stmt
* stmt
= m_stmt
.get();
268 sqlite3_bind_double(stmt
,
269 sqlite3_bind_parameter_index(stmt
, paramName
),
271 assertx(rc
== SQLITE_OK
);
274 void RepoQuery::bindInt(const char* paramName
, int val
) {
275 sqlite3_stmt
* stmt
= m_stmt
.get();
277 sqlite3_bind_int(stmt
,
278 sqlite3_bind_parameter_index(stmt
, paramName
),
280 assertx(rc
== SQLITE_OK
);
283 void RepoQuery::bindId(const char* paramName
, Id id
) {
284 bindInt(paramName
, int(id
));
287 void RepoQuery::bindOffset(const char* paramName
, Offset offset
) {
288 bindInt(paramName
, int(offset
));
291 void RepoQuery::bindAttr(const char* paramName
, Attr attrs
) {
292 bindInt(paramName
, int(attrs
));
295 void RepoQuery::bindBool(const char* paramName
, bool b
) {
296 bindInt(paramName
, int(b
));
299 void RepoQuery::bindInt64(const char* paramName
, int64_t val
) {
300 sqlite3_stmt
* stmt
= m_stmt
.get();
302 sqlite3_bind_int64(stmt
,
303 sqlite3_bind_parameter_index(stmt
, paramName
),
305 assertx(rc
== SQLITE_OK
);
308 void RepoQuery::bindNull(const char* paramName
) {
309 sqlite3_stmt
* stmt
= m_stmt
.get();
311 sqlite3_bind_null(stmt
,
312 sqlite3_bind_parameter_index(stmt
, paramName
));
313 assertx(rc
== SQLITE_OK
);
316 void RepoQuery::step() {
318 throw RepoExc("RepoQuery::%s(repo=%p) error: Query done",
319 __func__
, &m_stmt
.repo());
321 // Avoid changing errno, which is visible from php land via `posix_errno()`.
322 auto errno_bak
= errno
;
323 SCOPE_EXIT
{ errno
= errno_bak
; };
324 int rc
= sqlite3_step(m_stmt
.get());
337 std::string errmsg
= sqlite3_errmsg(m_stmt
.repo().dbc());
338 if (rc
== SQLITE_CORRUPT
) {
339 auto repoId
= debugComputeRepoIdFromSQL(m_stmt
.repo(), m_stmt
.sql());
340 reportDbCorruption(m_stmt
.repo(), repoId
, "sqlite3_step()");
342 throw RepoExc("RepoQuery::%s(repo=%p) error: '%s' --> (%d) %s",
343 __func__
, &m_stmt
.repo(), m_stmt
.sql().c_str(), rc
,
348 void RepoQuery::reset() {
354 void RepoQuery::exec() {
359 int64_t RepoQuery::getInsertedRowid() {
360 return sqlite3_last_insert_rowid(m_stmt
.repo().dbc());
363 bool RepoQuery::isBlob(int iCol
) {
364 return (iCol
< sqlite3_data_count(m_stmt
.get())
365 && sqlite3_column_type(m_stmt
.get(), iCol
) == SQLITE_BLOB
);
368 bool RepoQuery::isText(int iCol
) {
369 return (iCol
< sqlite3_data_count(m_stmt
.get())
370 && sqlite3_column_type(m_stmt
.get(), iCol
) == SQLITE_TEXT
);
373 bool RepoQuery::isDouble(int iCol
) {
374 return (iCol
< sqlite3_data_count(m_stmt
.get())
375 && sqlite3_column_type(m_stmt
.get(), iCol
) == SQLITE_FLOAT
);
378 bool RepoQuery::isInt(int iCol
) {
379 return (iCol
< sqlite3_data_count(m_stmt
.get())
380 && sqlite3_column_type(m_stmt
.get(), iCol
) == SQLITE_INTEGER
);
383 bool RepoQuery::isNull(int iCol
) {
384 return (iCol
< sqlite3_data_count(m_stmt
.get())
385 && sqlite3_column_type(m_stmt
.get(), iCol
) == SQLITE_NULL
);
388 void RepoQuery::getBlob(int iCol
, const void*& blob
, size_t& size
) {
391 "RepoQuery::%s(repo=%p) error: Column %d is not a blob in '%s'",
392 __func__
, &m_stmt
.repo(), iCol
, m_stmt
.sql().c_str());
394 blob
= sqlite3_column_blob(m_stmt
.get(), iCol
);
395 size
= size_t(sqlite3_column_bytes(m_stmt
.get(), iCol
));
398 BlobDecoder
RepoQuery::getBlob(int iCol
, bool useGlobalIds
) {
401 getBlob(iCol
, vp
, sz
);
402 return BlobDecoder(vp
, sz
, useGlobalIds
);
405 void RepoQuery::getSha1(int iCol
, SHA1
& sha1
) {
408 getBlob(iCol
, blob
, size
);
409 auto const sha1bytes
= SHA1::kQNumWords
* SHA1::kQWordLen
;
410 if (size
!= sha1bytes
) {
412 "RepoQuery::%s(repo=%p) error: Column %d is the wrong size"
413 " (expected %zu, got %zu) in '%s'",
414 __func__
, &m_stmt
.repo(), iCol
, sha1bytes
, size
, m_stmt
.sql().c_str());
416 new (&sha1
) SHA1(blob
, size
);
419 void RepoQuery::getTypedValue(int iCol
, TypedValue
& tv
) {
422 getBlob(iCol
, blob
, size
);
425 // We check that arrays do not exceed a configurable maximum size in the
426 // assembler, so just assume that they're okay here.
427 MemoryManager::SuppressOOM
so(*tl_heap
);
429 String s
= String((const char*)blob
, size
, CopyString
);
431 unserialize_from_string(s
, VariableUnserializer::Type::Internal
);
433 v
= String(makeStaticString(v
.asCStrRef().get()));
434 } else if (v
.isArray()) {
435 v
= Array(ArrayData::GetScalarArray(std::move(v
)));
437 // Serialized variants and objects shouldn't ever make it into the repo.
438 assertx(!isRefcountedType(v
.getType()));
440 tvAsVariant(&tv
) = v
;
444 void RepoQuery::getText(int iCol
, const char*& text
) {
447 "RepoQuery::%s(repo=%p) error: Column %d is not text in '%s'",
448 __func__
, &m_stmt
.repo(), iCol
, m_stmt
.sql().c_str());
450 text
= (const char*)sqlite3_column_text(m_stmt
.get(), iCol
);
453 void RepoQuery::getText(int iCol
, const char*& text
, size_t& size
) {
455 size
= size_t(sqlite3_column_bytes(m_stmt
.get(), iCol
));
458 void RepoQuery::getStdString(int iCol
, std::string
& s
) {
461 getText(iCol
, text
, size
);
462 s
= std::string(text
, size
);
465 void RepoQuery::getStaticString(int iCol
, StringData
*& s
) {
471 getText(iCol
, text
, size
);
472 s
= makeStaticString(text
, size
);
476 void RepoQuery::getDouble(int iCol
, double& val
) {
477 if (!isDouble(iCol
)) {
479 "RepoQuery::%s(repo=%p) error: Column %d is not a double in '%s'",
480 __func__
, &m_stmt
.repo(), iCol
, m_stmt
.sql().c_str());
482 val
= sqlite3_column_double(m_stmt
.get(), iCol
);
485 void RepoQuery::getInt(int iCol
, int& val
) {
488 "RepoQuery::%s(repo=%p) error: Column %d is not an integer in '%s'",
489 __func__
, &m_stmt
.repo(), iCol
, m_stmt
.sql().c_str());
491 val
= sqlite3_column_int(m_stmt
.get(), iCol
);
494 void RepoQuery::getId(int iCol
, Id
& id
) {
500 void RepoQuery::getOffset(int iCol
, Offset
& offset
) {
503 offset
= Offset(val
);
506 void RepoQuery::getAttr(int iCol
, Attr
& attrs
) {
512 void RepoQuery::getBool(int iCol
, bool& b
) {
518 void RepoQuery::getInt64(int iCol
, int64_t& val
) {
521 "RepoQuery::%s(repo=%p) error: Column %d is not an integer in '%s'",
522 __func__
, &m_stmt
.repo(), iCol
, m_stmt
.sql().c_str());
524 val
= sqlite3_column_int64(m_stmt
.get(), iCol
);
527 //==============================================================================
530 void RepoTxnQuery::step() {
531 assertx(!m_txn
.error());
535 void RepoTxnQuery::exec() {
536 assertx(!m_txn
.error());