Fix refcounting in arReturn() and stop leaking static strings.
[hiphop-php.git] / hphp / runtime / vm / repo.cpp
blob9ff972f526f023f17b897cacba74497262fd69ba
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-2014 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 +----------------------------------------------------------------------+
16 #include "hphp/runtime/vm/repo.h"
18 #include <folly/Format.h>
19 #include <folly/experimental/Singleton.h>
21 #include "hphp/util/logger.h"
22 #include "hphp/util/trace.h"
23 #include "hphp/util/repo-schema.h"
24 #include "hphp/util/assertions.h"
25 #include "hphp/util/process.h"
26 #include "hphp/runtime/vm/blob-helper.h"
27 #include "hphp/runtime/vm/repo-global-data.h"
29 namespace HPHP {
31 TRACE_SET_MOD(hhbc);
33 const char* Repo::kMagicProduct =
34 "facebook.com HipHop Virtual Machine bytecode repository";
35 const char* Repo::kSchemaPlaceholder = "%{schema}";
36 const char* Repo::kDbs[RepoIdCount] = { "main", // Central.
37 "local"}; // Local.
38 Repo::GlobalData Repo::s_globalData;
40 void initialize_repo() {
41 if (!sqlite3_threadsafe()) {
42 TRACE(0, "SQLite was compiled without thread support; aborting\n");
43 abort();
45 if (sqlite3_config(SQLITE_CONFIG_MULTITHREAD) != SQLITE_OK) {
46 TRACE(1, "Failed to set default SQLite multi-threading mode\n");
48 if (sqlite3_config(SQLITE_CONFIG_MEMSTATUS, 0) != SQLITE_OK) {
49 TRACE(1, "Failed to disable SQLite memory statistics\n");
51 if (const char* schemaOverride = getenv("HHVM_RUNTIME_REPO_SCHEMA")) {
52 TRACE(1, "Schema override: HHVM_RUNTIME_REPO_SCHEMA=%s\n", schemaOverride);
53 kRepoSchemaId = schemaOverride;
57 IMPLEMENT_THREAD_LOCAL(Repo, t_dh);
59 Repo& Repo::get() {
60 return *t_dh.get();
63 void Repo::shutdown() {
64 t_dh.destroy();
67 SimpleMutex Repo::s_lock;
68 unsigned Repo::s_nRepos = 0;
70 bool Repo::prefork() {
71 if (!t_dh.isNull()) {
72 t_dh.destroy();
74 folly::SingletonVault::singleton()->destroyInstances();
75 s_lock.lock();
76 if (s_nRepos > 0) {
77 s_lock.unlock();
78 return true;
80 return false;
83 void Repo::postfork(pid_t pid) {
84 if (pid == 0) {
85 new (&s_lock) SimpleMutex();
86 } else {
87 s_lock.unlock();
91 Repo::Repo()
92 : RepoProxy(*this),
93 #define RP_OP(c, o) \
94 m_##o##Local(*this, RepoIdLocal), m_##o##Central(*this, RepoIdCentral),
95 RP_OPS
96 #undef RP_OP
97 m_dbc(nullptr), m_localReadable(false), m_localWritable(false),
98 m_evalRepoId(-1), m_txDepth(0), m_rollback(false), m_beginStmt(*this),
99 m_rollbackStmt(*this), m_commitStmt(*this), m_urp(*this), m_pcrp(*this),
100 m_frp(*this), m_lsrp(*this) {
101 #define RP_OP(c, o) \
102 m_##o[RepoIdLocal] = &m_##o##Local; \
103 m_##o[RepoIdCentral] = &m_##o##Central;
104 RP_OPS
105 #undef RP_OP
107 SimpleLock lock(s_lock);
108 s_nRepos++;
110 connect();
113 Repo::~Repo() {
114 disconnect();
116 SimpleLock lock(s_lock);
117 s_nRepos--;
121 std::string Repo::s_cliFile;
122 void Repo::setCliFile(const std::string& cliFile) {
123 assert(s_cliFile.empty());
124 assert(t_dh.isNull());
125 s_cliFile = cliFile;
128 void Repo::loadGlobalData(bool allowFailure /* = false */) {
129 m_lsrp.load();
131 if (!RuntimeOption::RepoAuthoritative) return;
133 std::vector<std::string> failures;
136 * This should probably just go to the Local repo always, except
137 * that our unit test suite is currently running RepoAuthoritative
138 * tests with the compiled repo as the Central repo.
140 for (int repoId = RepoIdCount - 1; repoId >= 0; --repoId) {
141 if (repoName(repoId).empty()) {
142 // The repo wasn't loadable
143 continue;
145 try {
146 RepoStmt stmt(*this);
147 const auto& tbl = table(repoId, "GlobalData");
148 stmt.prepare(
149 folly::format(
150 "SELECT count(*), data from {};", tbl
151 ).str()
153 RepoTxn txn(*this);
154 RepoTxnQuery query(txn, stmt);
155 query.step();
156 if (!query.row()) {
157 throw RepoExc("Can't find table %s", tbl.c_str());
159 int val;
160 query.getInt(0, val);
161 if (val == 0) {
162 throw RepoExc("No rows in %s. Did you forget to compile that file with "
163 "this HHVM version?", tbl.c_str());
165 BlobDecoder decoder = query.getBlob(1);
166 decoder(s_globalData);
168 txn.commit();
169 } catch (RepoExc& e) {
170 failures.push_back(repoName(repoId) + ": " + e.msg());
171 continue;
174 return;
177 if (allowFailure) return;
179 if (failures.empty()) {
180 std::fprintf(stderr, "No repo was loadable. Check all the possible repo "
181 "locations (Repo.Central.Path, HHVM_REPO_CENTRAL_PATH, and "
182 "$HOME/.hhvm.hhbc) to make sure one of them is a valid "
183 "sqlite3 HHVM repo built with this exact HHVM version.\n");
184 } else {
185 // We should always have a global data section in RepoAuthoritative
186 // mode, or the repo is messed up.
187 std::fprintf(stderr, "Failed to load Repo::GlobalData:\n");
188 for (auto& f : failures) {
189 std::fprintf(stderr, " %s\n", f.c_str());
193 assert(Process::IsInMainThread());
194 exit(1);
197 void Repo::saveGlobalData(GlobalData newData) {
198 s_globalData = newData;
200 auto const repoId = repoIdForNewUnit(UnitOrigin::File);
201 RepoStmt stmt(*this);
202 stmt.prepare(
203 folly::format(
204 "INSERT INTO {} VALUES(@data);", table(repoId, "GlobalData")
205 ).str()
207 RepoTxn txn(*this);
208 RepoTxnQuery query(txn, stmt);
209 BlobEncoder encoder;
210 encoder(s_globalData);
211 query.bindBlob("@data", encoder, /* static */ true);
212 query.exec();
214 // TODO(#3521039): we could just put the litstr table in the same
215 // blob as the above and delete LitstrRepoProxy.
216 LitstrTable::get().insert(txn);
218 txn.commit();
221 Unit* Repo::loadUnit(const std::string& name, const MD5& md5) {
222 if (m_dbc == nullptr) {
223 return nullptr;
225 return m_urp.load(name, md5);
228 std::vector<std::pair<std::string,MD5>>
229 Repo::enumerateUnits(int repoId, bool preloadOnly, bool warn) {
230 std::vector<std::pair<std::string,MD5>> ret;
232 try {
233 RepoStmt stmt(*this);
234 stmt.prepare(preloadOnly ?
235 folly::sformat(
236 "SELECT path, {0}.md5 FROM {0} "
237 "LEFT JOIN {1} ON ({0}.md5={1}.md5) WHERE preload != 0 "
238 "ORDER BY preload DESC;",
239 table(repoId, "FileMd5"),
240 table(repoId, "Unit")) :
241 folly::sformat(
242 "SELECT path, md5 FROM {};",
243 table(repoId, "FileMd5"))
245 RepoTxn txn(*this);
246 RepoTxnQuery query(txn, stmt);
248 for (query.step(); query.row(); query.step()) {
249 std::string path;
250 MD5 md5;
252 query.getStdString(0, path);
253 query.getMd5(1, md5);
255 ret.emplace_back(path, md5);
258 txn.commit();
259 } catch (RepoExc& e) {
260 if (warn) {
261 fprintf(stderr, "failed to enumerate units: %s\n", e.what());
265 return ret;
268 void Repo::InsertFileHashStmt::insert(RepoTxn& txn, const StringData* path,
269 const MD5& md5) {
270 if (!prepared()) {
271 std::stringstream ssInsert;
272 ssInsert << "INSERT INTO " << m_repo.table(m_repoId, "FileMd5")
273 << " VALUES(@path, @md5);";
274 txn.prepare(*this, ssInsert.str());
276 RepoTxnQuery query(txn, *this);
277 query.bindStaticString("@path", path);
278 query.bindMd5("@md5", md5);
279 query.exec();
282 bool Repo::GetFileHashStmt::get(const char *path, MD5& md5) {
283 try {
284 RepoTxn txn(m_repo);
285 if (!prepared()) {
286 std::stringstream ssSelect;
287 ssSelect << "SELECT f.md5 FROM "
288 << m_repo.table(m_repoId, "FileMd5")
289 << " AS f, " << m_repo.table(m_repoId, "Unit")
290 << " AS u WHERE path == @path AND f.md5 == u.md5"
291 << " ORDER BY unitSn DESC LIMIT 1;";
292 txn.prepare(*this, ssSelect.str());
294 RepoTxnQuery query(txn, *this);
295 query.bindText("@path", path, strlen(path));
296 query.step();
297 if (!query.row()) {
298 return false;
300 query.getMd5(0, md5);
301 txn.commit();
302 return true;
303 } catch (RepoExc& re) {
304 return false;
306 return false;
309 bool Repo::findFile(const char *path, const std::string &root, MD5& md5) {
310 if (m_dbc == nullptr) {
311 return false;
313 int repoId;
314 for (repoId = RepoIdCount - 1; repoId >= 0; --repoId) {
315 if (*path == '/' && !root.empty() &&
316 !strncmp(root.c_str(), path, root.size()) &&
317 getFileHash(repoId).get(path + root.size(), md5)) {
318 TRACE(3, "Repo loaded file hash for '%s' from '%s'\n",
319 path + root.size(), repoName(repoId).c_str());
320 return true;
322 if (getFileHash(repoId).get(path, md5)) {
323 TRACE(3, "Repo loaded file hash for '%s' from '%s'\n",
324 path, repoName(repoId).c_str());
325 return true;
328 TRACE(3, "Repo file hash: error loading '%s'\n", path);
329 return false;
332 bool Repo::insertMd5(UnitOrigin unitOrigin, UnitEmitter* ue, RepoTxn& txn) {
333 const StringData* path = ue->m_filepath;
334 const MD5& md5 = ue->md5();
335 int repoId = repoIdForNewUnit(unitOrigin);
336 if (repoId == RepoIdInvalid) {
337 return true;
339 try {
340 insertFileHash(repoId).insert(txn, path, md5);
341 return false;
342 } catch(RepoExc& re) {
343 TRACE(3, "Failed to commit md5 for '%s' to '%s': %s\n",
344 path->data(), repoName(repoId).c_str(), re.msg().c_str());
345 return true;
349 void Repo::commitMd5(UnitOrigin unitOrigin, UnitEmitter* ue) {
350 try {
351 RepoTxn txn(*this);
352 bool err = insertMd5(unitOrigin, ue, txn);
353 if (!err) {
354 txn.commit();
356 } catch(RepoExc& re) {
357 int repoId = repoIdForNewUnit(unitOrigin);
358 if (repoId != RepoIdInvalid) {
359 TRACE(3, "Failed to commit md5 for '%s' to '%s': %s\n",
360 ue->m_filepath->data(), repoName(repoId).c_str(),
361 re.msg().c_str());
366 std::string Repo::table(int repoId, const char* tablePrefix) {
367 std::stringstream ss;
368 ss << dbName(repoId) << "." << tablePrefix << "_" << kRepoSchemaId;
369 return ss.str();
372 void Repo::exec(const std::string& sQuery) {
373 RepoStmt stmt(*this);
374 stmt.prepare(sQuery);
375 RepoQuery query(stmt);
376 query.exec();
379 void Repo::begin() {
380 if (m_txDepth > 0) {
381 m_txDepth++;
382 return;
384 if (debug) {
385 // Verify start state.
386 always_assert(m_txDepth == 0);
387 always_assert(!m_rollback);
388 if (true) {
389 // Bypass RepoQuery, in order to avoid triggering exceptions.
390 int rc = sqlite3_step(m_rollbackStmt.get());
391 switch (rc) {
392 case SQLITE_DONE:
393 case SQLITE_ROW:
394 not_reached();
395 default:
396 break;
398 } else {
399 bool rollbackFailed = false;
400 try {
401 RepoQuery query(m_rollbackStmt);
402 query.exec();
403 } catch (RepoExc& re) {
404 rollbackFailed = true;
406 always_assert(rollbackFailed);
409 RepoQuery query(m_beginStmt);
410 query.exec();
411 m_txDepth++;
414 void Repo::txPop() {
415 assert(m_txDepth > 0);
416 if (m_txDepth > 1) {
417 m_txDepth--;
418 return;
420 if (!m_rollback) {
421 RepoQuery query(m_commitStmt);
422 query.exec();
423 } else {
424 m_rollback = false;
425 try {
426 RepoQuery query(m_rollbackStmt);
427 query.exec();
428 } catch (RepoExc& ex) {
430 * Having a rollback fail is actually a normal, expected case,
431 * so just swallow this.
433 * In particular, according to the docs, if we got an I/O error
434 * while doing a commit, the rollback will often fail with "no
435 * transaction in progress", because the commit will have
436 * automatically been rolled back. Recommended practice is
437 * still to execute a rollback statement and ignore the error.
439 TRACE(4, "repo rollback failure: %s\n", ex.what());
442 // Decrement depth after query execution, in case an exception occurs during
443 // commit. This allows for subsequent rollback.
444 m_txDepth--;
447 void Repo::rollback() {
448 m_rollback = true;
449 txPop();
452 void Repo::commit() {
453 txPop();
456 bool Repo::insertUnit(UnitEmitter* ue, UnitOrigin unitOrigin, RepoTxn& txn) {
457 try {
458 if (insertMd5(unitOrigin, ue, txn)) return true;
459 if (ue->insert(unitOrigin, txn)) return true;
460 } catch (const std::exception& e) {
461 TRACE(0, "unexpected exception in insertUnit: %s\n",
462 e.what());
463 assert(false);
464 return true;
466 return false;
469 void Repo::commitUnit(UnitEmitter* ue, UnitOrigin unitOrigin) {
470 if (!RuntimeOption::RepoCommit) return;
472 try {
473 commitMd5(unitOrigin, ue);
474 ue->commit(unitOrigin);
475 } catch (const std::exception& e) {
476 TRACE(0, "unexpected exception in commitUnit: %s\n",
477 e.what());
478 assert(false);
482 void Repo::connect() {
483 initCentral();
484 initLocal();
485 if (!RuntimeOption::RepoEvalMode.compare("local")) {
486 m_evalRepoId = (m_localWritable) ? RepoIdLocal : RepoIdCentral;
487 } else if (!RuntimeOption::RepoEvalMode.compare("central")) {
488 m_evalRepoId = RepoIdCentral;
489 } else {
490 assert(!RuntimeOption::RepoEvalMode.compare("readonly"));
491 m_evalRepoId = RepoIdInvalid;
493 TRACE(1, "Repo.Eval.Mode=%s\n",
494 (m_evalRepoId == RepoIdLocal)
495 ? "local"
496 : (m_evalRepoId == RepoIdCentral)
497 ? "central"
498 : "readonly");
501 void Repo::disconnect() {
502 if (m_dbc != nullptr) {
503 sqlite3_close(m_dbc);
504 m_dbc = nullptr;
505 m_localReadable = false;
506 m_localWritable = false;
507 m_evalRepoId = RepoIdInvalid;
511 void Repo::initCentral() {
512 std::string error;
514 assert(m_dbc == nullptr);
515 auto tryPath = [this, &error](const char* path) {
516 std::string subErr;
517 if (!openCentral(path, subErr)) {
518 folly::format(&error, " {}\n", subErr.empty() ? path : subErr);
519 return false;
521 return true;
524 // Try Repo.Central.Path
525 if (!RuntimeOption::RepoCentralPath.empty()) {
526 if (tryPath(RuntimeOption::RepoCentralPath.c_str())) {
527 return;
531 // Try HHVM_REPO_CENTRAL_PATH
532 const char* HHVM_REPO_CENTRAL_PATH = getenv("HHVM_REPO_CENTRAL_PATH");
533 if (HHVM_REPO_CENTRAL_PATH != nullptr) {
534 if (tryPath(HHVM_REPO_CENTRAL_PATH)) {
535 return;
539 // Try "$HOME/.hhvm.hhbc".
540 char* HOME = getenv("HOME");
541 if (HOME != nullptr) {
542 std::string centralPath = HOME;
543 centralPath += "/.hhvm.hhbc";
544 if (tryPath(centralPath.c_str())) {
545 return;
549 // Try the equivalent of "$HOME/.hhvm.hhbc", but look up the home directory
550 // in the password database.
552 struct passwd pwbuf;
553 struct passwd* pwbufp;
554 long bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
555 if (bufsize != -1) {
556 char buf[size_t(bufsize)];
557 if (!getpwuid_r(getuid(), &pwbuf, buf, size_t(bufsize), &pwbufp)
558 && (HOME == nullptr || strcmp(HOME, pwbufp->pw_dir))) {
559 std::string centralPath = pwbufp->pw_dir;
560 centralPath += "/.hhvm.hhbc";
561 if (tryPath(centralPath.c_str())) {
562 return;
568 error = "Failed to initialize central HHBC repository:\n" + error;
569 // Database initialization failed; this is an unrecoverable state.
570 Logger::Error("%s", error.c_str());
572 if (Process::IsInMainThread()) {
573 exit(1);
575 always_assert_log(false, [&error] { return error; });
578 static int busyHandler(void* opaque, int nCalls) {
579 Repo* repo UNUSED = static_cast<Repo*>(opaque);
580 // yield to allow other threads access to the machine
581 // spin-wait can starve other threads.
582 usleep(1000 * nCalls);
583 return 1; // Tell SQLite to retry.
586 std::string Repo::insertSchema(const char* path) {
587 assert(strstr(kRepoSchemaId, kSchemaPlaceholder) == nullptr);
588 std::string result = path;
589 size_t idx;
590 if ((idx = result.find(kSchemaPlaceholder)) != std::string::npos) {
591 result.replace(idx, strlen(kSchemaPlaceholder), kRepoSchemaId);
593 TRACE(2, "Repo::%s() transformed %s into %s\n",
594 __func__, path, result.c_str());
595 return result;
598 bool Repo::openCentral(const char* rawPath, std::string& errorMsg) {
599 std::string repoPath = insertSchema(rawPath);
600 // SQLITE_OPEN_NOMUTEX specifies that the connection be opened such
601 // that no mutexes are used to protect the database connection from other
602 // threads. However, multiple connections can still be used concurrently,
603 // because SQLite as a whole is thread-safe.
604 if (int err = sqlite3_open_v2(repoPath.c_str(), &m_dbc,
605 SQLITE_OPEN_NOMUTEX |
606 SQLITE_OPEN_READWRITE |
607 SQLITE_OPEN_CREATE, nullptr)) {
608 TRACE(1, "Repo::%s() failed to open candidate central repo '%s'\n",
609 __func__, repoPath.c_str());
610 errorMsg = folly::format("Failed to open {}: {} - {}",
611 repoPath, err, sqlite3_errmsg(m_dbc)).str();
612 return false;
614 // Register a busy handler to avoid spurious SQLITE_BUSY errors.
615 sqlite3_busy_handler(m_dbc, busyHandler, (void*)this);
616 try {
617 m_beginStmt.prepare("BEGIN TRANSACTION;");
618 m_rollbackStmt.prepare("ROLLBACK;");
619 m_commitStmt.prepare("COMMIT;");
620 pragmas(RepoIdCentral);
621 } catch (RepoExc& re) {
622 TRACE(1, "Repo::%s() failed to initialize connection to canditate repo"
623 " '%s': %s\n", __func__, repoPath.c_str(), re.what());
624 errorMsg = folly::format("Failed to initialize connection to {}: {}",
625 repoPath, re.what()).str();
626 return false;
628 // sqlite3_open_v2() will silently open in read-only mode if file permissions
629 // prevent writing, and there is no apparent way to detect this other than to
630 // attempt writing to the database. Therefore, tell initSchema() to verify
631 // that the database is writable.
632 bool centralWritable = true;
633 if (initSchema(RepoIdCentral, centralWritable, errorMsg) ||
634 !centralWritable) {
635 TRACE(1, "Repo::initSchema() failed for candidate central repo '%s'\n",
636 repoPath.c_str());
637 errorMsg = folly::format("Failed to initialize schema in {}: {}",
638 repoPath, errorMsg).str();
639 return false;
641 m_centralRepo = repoPath;
642 TRACE(1, "Central repo: '%s'\n", m_centralRepo.c_str());
643 return true;
646 void Repo::initLocal() {
647 if (RuntimeOption::RepoLocalMode.compare("--")) {
648 bool isWritable;
649 if (!RuntimeOption::RepoLocalMode.compare("rw")) {
650 isWritable = true;
651 } else {
652 assert(!RuntimeOption::RepoLocalMode.compare("r-"));
653 isWritable = false;
656 if (!RuntimeOption::RepoLocalPath.empty()) {
657 attachLocal(RuntimeOption::RepoLocalPath.c_str(), isWritable);
658 } else {
659 if (RuntimeOption::ClientExecutionMode()) {
660 std::string cliRepo = s_cliFile;
661 if (!cliRepo.empty()) {
662 cliRepo += ".hhbc";
664 attachLocal(cliRepo.c_str(), isWritable);
665 } else {
666 attachLocal("hhvm.hhbc", isWritable);
672 void Repo::attachLocal(const char* path, bool isWritable) {
673 std::string repoPath = insertSchema(path);
674 if (!isWritable) {
675 // Make sure the repo exists before attaching it, in order to avoid
676 // creating a read-only repo.
677 struct stat buf;
678 if (!strchr(repoPath.c_str(), ':') &&
679 stat(repoPath.c_str(), &buf) != 0) {
680 return;
683 try {
684 std::stringstream ssAttach;
685 ssAttach << "ATTACH DATABASE '" << repoPath << "' as "
686 << dbName(RepoIdLocal) << ";";
687 exec(ssAttach.str());
688 pragmas(RepoIdLocal);
689 } catch (RepoExc& re) {
690 return;
693 std::string error;
694 if (initSchema(RepoIdLocal, isWritable, error)) {
695 FTRACE(1, "Local repo {} failed to init schema: {}\n", repoPath, error);
696 return;
698 m_localRepo = repoPath;
699 m_localReadable = true;
700 m_localWritable = isWritable;
701 TRACE(1, "Local repo: '%s' (read%s)\n",
702 m_localRepo.c_str(), m_localWritable ? "-write" : "-only");
705 void Repo::pragmas(int repoId) {
706 // Valid synchronous values: 0 (OFF), 1 (NORMAL), 2 (FULL).
707 static const int synchronous = 0;
708 setIntPragma(repoId, "synchronous", synchronous);
709 // Valid journal_mode values: delete, truncate, persist, memory, wal, off.
710 setTextPragma(repoId, "journal_mode", RuntimeOption::RepoJournal.c_str());
713 void Repo::getIntPragma(int repoId, const char* name, int& val) {
714 RepoTxn txn(*this);
715 std::stringstream ssPragma;
716 ssPragma << "PRAGMA " << dbName(repoId) << "." << name << ";";
717 RepoStmt stmt(*this);
718 stmt.prepare(ssPragma.str());
719 RepoTxnQuery query(txn, stmt);
720 query.step();
721 query.getInt(0, val);
722 txn.commit();
725 void Repo::setIntPragma(int repoId, const char* name, int val) {
726 // Pragma writes must be executed outside transactions, since they may change
727 // transaction behavior.
728 std::stringstream ssPragma;
729 ssPragma << "PRAGMA " << dbName(repoId) << "." << name << " = " << val << ";";
730 exec(ssPragma.str());
731 if (debug) {
732 // Verify that the pragma had the desired effect.
733 int newval = -1;
734 getIntPragma(repoId, name, newval);
735 if (newval != val) {
736 throw RepoExc("Unexpected PRAGMA %s.%s value: %d\n",
737 dbName(repoId), name, newval);
742 void Repo::getTextPragma(int repoId, const char* name, std::string& val) {
743 RepoTxn txn(*this);
744 std::stringstream ssPragma;
745 ssPragma << "PRAGMA " << dbName(repoId) << "." << name << ";";
746 RepoStmt stmt(*this);
747 stmt.prepare(ssPragma.str());
748 RepoTxnQuery query(txn, stmt);
749 const char* s;
750 query.step();
751 query.getText(0, s);
752 val = s;
753 txn.commit();
756 void Repo::setTextPragma(int repoId, const char* name, const char* val) {
757 // Pragma writes must be executed outside transactions, since they may change
758 // transaction behavior.
759 std::stringstream ssPragma;
760 ssPragma << "PRAGMA " << dbName(repoId) << "." << name << " = " << val << ";";
761 exec(ssPragma.str());
762 if (debug) {
763 // Verify that the pragma had the desired effect.
764 std::string newval = "?";
765 getTextPragma(repoId, name, newval);
766 if (strcmp(newval.c_str(), val)) {
767 throw RepoExc("Unexpected PRAGMA %s.%s value: %s\n",
768 dbName(repoId), name, newval.c_str());
773 bool Repo::initSchema(int repoId, bool& isWritable, std::string& errorMsg) {
774 if (!schemaExists(repoId)) {
775 if (createSchema(repoId, errorMsg)) {
776 // Check whether this failure is due to losing the schema
777 // initialization race with another process.
778 if (!schemaExists(repoId)) {
779 return true;
781 } else {
782 // createSchema() successfully wrote to the database, so no further
783 // verification is necessary.
784 return false;
787 if (isWritable) {
788 isWritable = writable(repoId);
790 return false;
793 bool Repo::schemaExists(int repoId) {
794 try {
795 RepoTxn txn(*this);
796 std::stringstream ssSelect;
797 ssSelect << "SELECT product FROM " << table(repoId, "magic") << ";";
798 RepoStmt stmt(*this);
799 stmt.prepare(ssSelect.str());
800 RepoTxnQuery query(txn, stmt);
801 query.step();
802 const char* text; /**/ query.getText(0, text);
803 if (strcmp(kMagicProduct, text)) {
804 return false;
806 txn.commit();
807 } catch (RepoExc& re) {
808 return false;
810 return true;
813 bool Repo::createSchema(int repoId, std::string& errorMsg) {
814 try {
815 RepoTxn txn(*this);
817 std::stringstream ssCreate;
818 ssCreate << "CREATE TABLE " << table(repoId, "magic")
819 << "(product TEXT);";
820 txn.exec(ssCreate.str());
822 std::stringstream ssInsert;
823 ssInsert << "INSERT INTO " << table(repoId, "magic")
824 << " VALUES('" << kMagicProduct << "');";
825 txn.exec(ssInsert.str());
828 std::stringstream ssCreate;
829 ssCreate << "CREATE TABLE " << table(repoId, "writable")
830 << "(canary INTEGER);";
831 txn.exec(ssCreate.str());
834 std::stringstream ssCreate;
835 ssCreate << "CREATE TABLE " << table(repoId, "FileMd5")
836 << "(path TEXT, md5 BLOB, UNIQUE(path, md5));";
837 txn.exec(ssCreate.str());
839 txn.exec(folly::format("CREATE TABLE {} (data BLOB);",
840 table(repoId, "GlobalData")).str());
841 m_urp.createSchema(repoId, txn);
842 m_pcrp.createSchema(repoId, txn);
843 m_frp.createSchema(repoId, txn);
844 m_lsrp.createSchema(repoId, txn);
846 txn.commit();
847 } catch (RepoExc& re) {
848 errorMsg = re.what();
849 return true;
851 return false;
854 bool Repo::writable(int repoId) {
855 try {
856 // Check whether database is writable by adding and removing a row in the
857 // 'writable' table.
858 RepoTxn txn(*this);
859 std::stringstream ssInsert;
860 ssInsert << "INSERT INTO " << table(repoId, "writable") << " VALUES(0);";
861 txn.exec(ssInsert.str());
862 std::stringstream ssDelete;
863 ssDelete << "DELETE FROM " << table(repoId, "writable")
864 << " WHERE canary == 0;";
865 txn.exec(ssDelete.str());
866 txn.commit();
867 } catch (RepoExc& re) {
868 return false;
870 return true;
873 //////////////////////////////////////////////////////////////////////
875 void batchCommit(std::vector<std::unique_ptr<UnitEmitter>> ues) {
876 auto& repo = Repo::get();
878 // Attempt batch commit. This can legitimately fail due to multiple input
879 // files having identical contents.
880 bool err = false;
882 RepoTxn txn(repo);
884 for (auto& ue : ues) {
885 if (repo.insertUnit(ue.get(), UnitOrigin::File, txn)) {
886 err = true;
887 break;
890 if (!err) {
891 txn.commit();
895 // Commit units individually if an error occurred during batch commit.
896 if (err) {
897 for (auto& ue : ues) {
898 repo.commitUnit(ue.get(), UnitOrigin::File);
903 //////////////////////////////////////////////////////////////////////