2 +----------------------------------------------------------------------+
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"
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.
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");
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
);
63 void Repo::shutdown() {
67 SimpleMutex
Repo::s_lock
;
68 unsigned Repo::s_nRepos
= 0;
70 bool Repo::prefork() {
74 folly::SingletonVault::singleton()->destroyInstances();
83 void Repo::postfork(pid_t pid
) {
85 new (&s_lock
) SimpleMutex();
94 m_##o##Local(*this, RepoIdLocal), m_##o##Central(*this, RepoIdCentral),
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;
107 SimpleLock
lock(s_lock
);
116 SimpleLock
lock(s_lock
);
121 std::string
Repo::s_cliFile
;
122 void Repo::setCliFile(const std::string
& cliFile
) {
123 assert(s_cliFile
.empty());
124 assert(t_dh
.isNull());
128 void Repo::loadGlobalData(bool allowFailure
/* = false */) {
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
146 RepoStmt
stmt(*this);
147 const auto& tbl
= table(repoId
, "GlobalData");
150 "SELECT count(*), data from {};", tbl
154 RepoTxnQuery
query(txn
, stmt
);
157 throw RepoExc("Can't find table %s", tbl
.c_str());
160 query
.getInt(0, val
);
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
);
169 } catch (RepoExc
& e
) {
170 failures
.push_back(repoName(repoId
) + ": " + e
.msg());
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");
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());
197 void Repo::saveGlobalData(GlobalData newData
) {
198 s_globalData
= newData
;
200 auto const repoId
= repoIdForNewUnit(UnitOrigin::File
);
201 RepoStmt
stmt(*this);
204 "INSERT INTO {} VALUES(@data);", table(repoId
, "GlobalData")
208 RepoTxnQuery
query(txn
, stmt
);
210 encoder(s_globalData
);
211 query
.bindBlob("@data", encoder
, /* static */ true);
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
);
221 Unit
* Repo::loadUnit(const std::string
& name
, const MD5
& md5
) {
222 if (m_dbc
== 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
;
233 RepoStmt
stmt(*this);
234 stmt
.prepare(preloadOnly
?
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")) :
242 "SELECT path, md5 FROM {};",
243 table(repoId
, "FileMd5"))
246 RepoTxnQuery
query(txn
, stmt
);
248 for (query
.step(); query
.row(); query
.step()) {
252 query
.getStdString(0, path
);
253 query
.getMd5(1, md5
);
255 ret
.emplace_back(path
, md5
);
259 } catch (RepoExc
& e
) {
261 fprintf(stderr
, "failed to enumerate units: %s\n", e
.what());
268 void Repo::InsertFileHashStmt::insert(RepoTxn
& txn
, const StringData
* path
,
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
);
282 bool Repo::GetFileHashStmt::get(const char *path
, MD5
& md5
) {
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
));
300 query
.getMd5(0, md5
);
303 } catch (RepoExc
& re
) {
309 bool Repo::findFile(const char *path
, const std::string
&root
, MD5
& md5
) {
310 if (m_dbc
== nullptr) {
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());
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());
328 TRACE(3, "Repo file hash: error loading '%s'\n", path
);
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
) {
340 insertFileHash(repoId
).insert(txn
, path
, md5
);
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());
349 void Repo::commitMd5(UnitOrigin unitOrigin
, UnitEmitter
* ue
) {
352 bool err
= insertMd5(unitOrigin
, ue
, txn
);
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(),
366 std::string
Repo::table(int repoId
, const char* tablePrefix
) {
367 std::stringstream ss
;
368 ss
<< dbName(repoId
) << "." << tablePrefix
<< "_" << kRepoSchemaId
;
372 void Repo::exec(const std::string
& sQuery
) {
373 RepoStmt
stmt(*this);
374 stmt
.prepare(sQuery
);
375 RepoQuery
query(stmt
);
385 // Verify start state.
386 always_assert(m_txDepth
== 0);
387 always_assert(!m_rollback
);
389 // Bypass RepoQuery, in order to avoid triggering exceptions.
390 int rc
= sqlite3_step(m_rollbackStmt
.get());
399 bool rollbackFailed
= false;
401 RepoQuery
query(m_rollbackStmt
);
403 } catch (RepoExc
& re
) {
404 rollbackFailed
= true;
406 always_assert(rollbackFailed
);
409 RepoQuery
query(m_beginStmt
);
415 assert(m_txDepth
> 0);
421 RepoQuery
query(m_commitStmt
);
426 RepoQuery
query(m_rollbackStmt
);
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.
447 void Repo::rollback() {
452 void Repo::commit() {
456 bool Repo::insertUnit(UnitEmitter
* ue
, UnitOrigin unitOrigin
, RepoTxn
& txn
) {
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",
469 void Repo::commitUnit(UnitEmitter
* ue
, UnitOrigin unitOrigin
) {
470 if (!RuntimeOption::RepoCommit
) return;
473 commitMd5(unitOrigin
, ue
);
474 ue
->commit(unitOrigin
);
475 } catch (const std::exception
& e
) {
476 TRACE(0, "unexpected exception in commitUnit: %s\n",
482 void Repo::connect() {
485 if (!RuntimeOption::RepoEvalMode
.compare("local")) {
486 m_evalRepoId
= (m_localWritable
) ? RepoIdLocal
: RepoIdCentral
;
487 } else if (!RuntimeOption::RepoEvalMode
.compare("central")) {
488 m_evalRepoId
= RepoIdCentral
;
490 assert(!RuntimeOption::RepoEvalMode
.compare("readonly"));
491 m_evalRepoId
= RepoIdInvalid
;
493 TRACE(1, "Repo.Eval.Mode=%s\n",
494 (m_evalRepoId
== RepoIdLocal
)
496 : (m_evalRepoId
== RepoIdCentral
)
501 void Repo::disconnect() {
502 if (m_dbc
!= nullptr) {
503 sqlite3_close(m_dbc
);
505 m_localReadable
= false;
506 m_localWritable
= false;
507 m_evalRepoId
= RepoIdInvalid
;
511 void Repo::initCentral() {
514 assert(m_dbc
== nullptr);
515 auto tryPath
= [this, &error
](const char* path
) {
517 if (!openCentral(path
, subErr
)) {
518 folly::format(&error
, " {}\n", subErr
.empty() ? path
: subErr
);
524 // Try Repo.Central.Path
525 if (!RuntimeOption::RepoCentralPath
.empty()) {
526 if (tryPath(RuntimeOption::RepoCentralPath
.c_str())) {
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
)) {
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())) {
549 // Try the equivalent of "$HOME/.hhvm.hhbc", but look up the home directory
550 // in the password database.
553 struct passwd
* pwbufp
;
554 long bufsize
= sysconf(_SC_GETPW_R_SIZE_MAX
);
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())) {
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()) {
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
;
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());
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();
614 // Register a busy handler to avoid spurious SQLITE_BUSY errors.
615 sqlite3_busy_handler(m_dbc
, busyHandler
, (void*)this);
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();
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
) ||
635 TRACE(1, "Repo::initSchema() failed for candidate central repo '%s'\n",
637 errorMsg
= folly::format("Failed to initialize schema in {}: {}",
638 repoPath
, errorMsg
).str();
641 m_centralRepo
= repoPath
;
642 TRACE(1, "Central repo: '%s'\n", m_centralRepo
.c_str());
646 void Repo::initLocal() {
647 if (RuntimeOption::RepoLocalMode
.compare("--")) {
649 if (!RuntimeOption::RepoLocalMode
.compare("rw")) {
652 assert(!RuntimeOption::RepoLocalMode
.compare("r-"));
656 if (!RuntimeOption::RepoLocalPath
.empty()) {
657 attachLocal(RuntimeOption::RepoLocalPath
.c_str(), isWritable
);
659 if (RuntimeOption::ClientExecutionMode()) {
660 std::string cliRepo
= s_cliFile
;
661 if (!cliRepo
.empty()) {
664 attachLocal(cliRepo
.c_str(), isWritable
);
666 attachLocal("hhvm.hhbc", isWritable
);
672 void Repo::attachLocal(const char* path
, bool isWritable
) {
673 std::string repoPath
= insertSchema(path
);
675 // Make sure the repo exists before attaching it, in order to avoid
676 // creating a read-only repo.
678 if (!strchr(repoPath
.c_str(), ':') &&
679 stat(repoPath
.c_str(), &buf
) != 0) {
684 std::stringstream ssAttach
;
685 ssAttach
<< "ATTACH DATABASE '" << repoPath
<< "' as "
686 << dbName(RepoIdLocal
) << ";";
687 exec(ssAttach
.str());
688 pragmas(RepoIdLocal
);
689 } catch (RepoExc
& re
) {
694 if (initSchema(RepoIdLocal
, isWritable
, error
)) {
695 FTRACE(1, "Local repo {} failed to init schema: {}\n", repoPath
, error
);
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
) {
715 std::stringstream ssPragma
;
716 ssPragma
<< "PRAGMA " << dbName(repoId
) << "." << name
<< ";";
717 RepoStmt
stmt(*this);
718 stmt
.prepare(ssPragma
.str());
719 RepoTxnQuery
query(txn
, stmt
);
721 query
.getInt(0, val
);
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());
732 // Verify that the pragma had the desired effect.
734 getIntPragma(repoId
, name
, newval
);
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
) {
744 std::stringstream ssPragma
;
745 ssPragma
<< "PRAGMA " << dbName(repoId
) << "." << name
<< ";";
746 RepoStmt
stmt(*this);
747 stmt
.prepare(ssPragma
.str());
748 RepoTxnQuery
query(txn
, stmt
);
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());
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
)) {
782 // createSchema() successfully wrote to the database, so no further
783 // verification is necessary.
788 isWritable
= writable(repoId
);
793 bool Repo::schemaExists(int repoId
) {
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
);
802 const char* text
; /**/ query
.getText(0, text
);
803 if (strcmp(kMagicProduct
, text
)) {
807 } catch (RepoExc
& re
) {
813 bool Repo::createSchema(int repoId
, std::string
& errorMsg
) {
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
);
847 } catch (RepoExc
& re
) {
848 errorMsg
= re
.what();
854 bool Repo::writable(int repoId
) {
856 // Check whether database is writable by adding and removing a row in the
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());
867 } catch (RepoExc
& re
) {
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.
884 for (auto& ue
: ues
) {
885 if (repo
.insertUnit(ue
.get(), UnitOrigin::File
, txn
)) {
895 // Commit units individually if an error occurred during batch commit.
897 for (auto& ue
: ues
) {
898 repo
.commitUnit(ue
.get(), UnitOrigin::File
);
903 //////////////////////////////////////////////////////////////////////