Optimize struct element initialization
[hiphop-php.git] / hphp / runtime / vm / repo-helpers.cpp
blobf4d6d24c717150a7f63857bc2d31c68ee001ca1d
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
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"
26 namespace HPHP {
28 TRACE_SET_MOD(hhbc);
30 //==============================================================================
31 // Debugging
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;
43 return 0;
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));
54 if (repoPath) {
55 report += folly::sformat("Path: '{}'\n", repoPath);
57 struct stat repoStat;
58 if (stat(repoPath, &repoStat) == 0) {
59 time_t now = time(nullptr);
60 report += folly::sformat("{} bytes, c_age: {}, m_age: {}\n",
61 repoStat.st_size,
62 now - repoStat.st_ctime,
63 now - repoStat.st_mtime);
64 } else {
65 report += "stat() failed\n";
67 } else {
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);",
74 repo.dbName(repoId))
75 .c_str(),
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]);
82 return 0;
84 &report, nullptr);
86 Logger::Error(report);
89 //==============================================================================
90 // RepoStmt.
92 RepoStmt::RepoStmt(Repo& repo)
93 : m_repo(repo), m_stmt(nullptr) {
96 RepoStmt::~RepoStmt() {
97 finalize();
100 void RepoStmt::finalize() {
101 if (m_stmt != nullptr) {
102 m_sql.clear();
103 sqlite3_finalize(m_stmt);
104 m_stmt = nullptr;
108 void RepoStmt::prepare(const std::string& sql) {
109 finalize();
110 m_sql = sql;
111 int rc = sqlite3_prepare_v2(m_repo.dbc(), sql.c_str(), sql.size(), &m_stmt,
112 nullptr);
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,
121 errmsg.c_str());
125 void RepoStmt::reset() {
126 sqlite3_reset(m_stmt);
127 sqlite3_clear_bindings(m_stmt);
130 //==============================================================================
131 // RepoTxn.
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() {
141 if (m_pending) {
142 rollback();
146 template<class F>
147 void RepoTxn::rollback_guard(const char* func, F f) {
148 try {
149 f();
150 } catch (const std::exception& e) {
151 TRACE(4, "RepoTxn::%s(repo=%p) caught '%s'\n", func, &m_repo, e.what());
152 rollback();
153 throw;
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) {
161 ROLLBACK_GUARD([&] {
162 stmt.prepare(sql);
166 void RepoTxn::exec(const std::string& sQuery) {
167 ROLLBACK_GUARD([&] {
168 m_repo.exec(sQuery);
172 void RepoTxn::commit() {
173 if (!m_pending) return;
174 ROLLBACK_GUARD([&] {
175 m_repo.commit();
176 // Mark pending false AFTER the commit finishes so if it fails we still
177 // attempt a rollback.
178 m_pending = false;
182 void RepoTxn::step(RepoQuery& query) {
183 ROLLBACK_GUARD([&] {
184 query.step();
188 void RepoTxn::exec(RepoQuery& query) {
189 ROLLBACK_GUARD([&] {
190 query.exec();
194 void RepoTxn::rollback() {
195 assertx(!m_error);
196 assertx(m_pending);
197 m_error = true;
198 m_pending = false;
199 m_repo.rollback();
202 #undef ROLLBACK_GUARD
204 //==============================================================================
205 // RepoQuery.
207 void RepoQuery::bindBlob(const char* paramName, const void* blob,
208 size_t size, bool isStatic /* = false */) {
209 sqlite3_stmt* stmt = m_stmt.get();
210 int rc UNUSED =
211 sqlite3_bind_blob(stmt,
212 sqlite3_bind_parameter_index(stmt, paramName),
213 blob, int(size),
214 isStatic ? SQLITE_STATIC : SQLITE_TRANSIENT);
215 assertx(rc == SQLITE_OK);
218 void RepoQuery::bindBlob(const char* paramName, const BlobEncoder& blob,
219 bool isStatic) {
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);
232 } else {
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();
241 int rc UNUSED =
242 sqlite3_bind_text(stmt,
243 sqlite3_bind_parameter_index(stmt, paramName),
244 text, int(size),
245 isStatic ? SQLITE_STATIC : SQLITE_TRANSIENT);
246 assertx(rc == SQLITE_OK);
249 void RepoQuery::bindStaticString(const char* paramName, const StringData* sd) {
250 if (sd == nullptr) {
251 bindNull(paramName);
252 } else {
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();
267 int rc UNUSED =
268 sqlite3_bind_double(stmt,
269 sqlite3_bind_parameter_index(stmt, paramName),
270 val);
271 assertx(rc == SQLITE_OK);
274 void RepoQuery::bindInt(const char* paramName, int val) {
275 sqlite3_stmt* stmt = m_stmt.get();
276 int rc UNUSED =
277 sqlite3_bind_int(stmt,
278 sqlite3_bind_parameter_index(stmt, paramName),
279 val);
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();
301 int rc UNUSED =
302 sqlite3_bind_int64(stmt,
303 sqlite3_bind_parameter_index(stmt, paramName),
304 val);
305 assertx(rc == SQLITE_OK);
308 void RepoQuery::bindNull(const char* paramName) {
309 sqlite3_stmt* stmt = m_stmt.get();
310 int rc UNUSED =
311 sqlite3_bind_null(stmt,
312 sqlite3_bind_parameter_index(stmt, paramName));
313 assertx(rc == SQLITE_OK);
316 void RepoQuery::step() {
317 if (m_done) {
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());
325 switch (rc) {
326 case SQLITE_DONE:
327 m_row = false;
328 m_done = true;
329 break;
330 case SQLITE_ROW:
331 m_row = true;
332 m_done = false;
333 break;
334 default:
335 m_row = false;
336 m_done = true;
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,
344 errmsg.c_str());
348 void RepoQuery::reset() {
349 m_stmt.reset();
350 m_row = false;
351 m_done = false;
354 void RepoQuery::exec() {
355 step();
356 reset();
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) {
389 if (!isBlob(iCol)) {
390 throw RepoExc(
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) {
399 const void* vp;
400 size_t sz;
401 getBlob(iCol, vp, sz);
402 return BlobDecoder(vp, sz, useGlobalIds);
405 void RepoQuery::getSha1(int iCol, SHA1& sha1) {
406 const void* blob;
407 size_t size;
408 getBlob(iCol, blob, size);
409 auto const sha1bytes = SHA1::kQNumWords * SHA1::kQWordLen;
410 if (size != sha1bytes) {
411 throw RepoExc(
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) {
420 const void* blob;
421 size_t size;
422 getBlob(iCol, blob, size);
423 tvWriteUninit(tv);
424 if (size > 0) {
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);
430 Variant v =
431 unserialize_from_string(s, VariableUnserializer::Type::Internal);
432 if (v.isString()) {
433 v = String(makeStaticString(v.asCStrRef().get()));
434 } else if (v.isArray()) {
435 v = Array(ArrayData::GetScalarArray(std::move(v)));
436 } else {
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) {
445 if (!isText(iCol)) {
446 throw RepoExc(
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) {
454 getText(iCol, text);
455 size = size_t(sqlite3_column_bytes(m_stmt.get(), iCol));
458 void RepoQuery::getStdString(int iCol, std::string& s) {
459 const char* text;
460 size_t size;
461 getText(iCol, text, size);
462 s = std::string(text, size);
465 void RepoQuery::getStaticString(int iCol, StringData*& s) {
466 if (isNull(iCol)) {
467 s = nullptr;
468 } else {
469 const char* text;
470 size_t size;
471 getText(iCol, text, size);
472 s = makeStaticString(text, size);
476 void RepoQuery::getDouble(int iCol, double& val) {
477 if (!isDouble(iCol)) {
478 throw RepoExc(
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) {
486 if (!isInt(iCol)) {
487 throw RepoExc(
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) {
495 int val;
496 getInt(iCol, val);
497 id = Id(val);
500 void RepoQuery::getOffset(int iCol, Offset& offset) {
501 int val;
502 getInt(iCol, val);
503 offset = Offset(val);
506 void RepoQuery::getAttr(int iCol, Attr& attrs) {
507 int val;
508 getInt(iCol, val);
509 attrs = Attr(val);
512 void RepoQuery::getBool(int iCol, bool& b) {
513 int val;
514 getInt(iCol, val);
515 b = bool(val);
518 void RepoQuery::getInt64(int iCol, int64_t& val) {
519 if (!isInt(iCol)) {
520 throw RepoExc(
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 //==============================================================================
528 // RepoTxnQuery.
530 void RepoTxnQuery::step() {
531 assertx(!m_txn.error());
532 m_txn.step(*this);
535 void RepoTxnQuery::exec() {
536 assertx(!m_txn.error());
537 m_txn.exec(*this);
540 } // HPHP::VM