summaryrefslogtreecommitdiffstats
path: root/bdep/database.cxx
blob: 7d72a1dd0d26ea6da0c9316b84e1aa4342b16c92 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// file      : bdep/database.cxx -*- C++ -*-
// copyright : Copyright (c) 2014-2018 Code Synthesis Ltd
// license   : MIT; see accompanying LICENSE file

#include <bdep/database.hxx>

#include <odb/schema-catalog.hxx>
#include <odb/sqlite/exceptions.hxx>

#include <bdep/diagnostics.hxx>

#include <bdep/database-views.hxx>
#include <bdep/database-views-odb.hxx>

using namespace std;

namespace bdep
{
  using namespace odb::sqlite;
  using odb::schema_catalog;

  database
  open (const dir_path& d, tracer& tr, bool create)
  {
    tracer trace ("open");

    path f (d / bdep_file);

    if (exists (f))
      create = false;
    else if (!create)
      fail << d << " does not look like an initialized project directory" <<
        info << "run 'bdep init' to initialize";

    try
    {
      // We don't need the thread pool.
      //
      unique_ptr<connection_factory> cf (new single_connection_factory);

      database db (f.string (),
                   SQLITE_OPEN_READWRITE | (create ? SQLITE_OPEN_CREATE : 0),
                   true,                  // Enable FKs.
                   "",                    // Default VFS.
                   move (cf));

      db.tracer (trace);

      // Lock the database for as long as the connection is active. First we
      // set locking_mode to EXCLUSIVE which instructs SQLite not to release
      // any locks until the connection is closed. Then we force SQLite to
      // acquire the write lock by starting exclusive transaction. See the
      // locking_mode pragma documentation for details. This will also fail if
      // the database is inaccessible (e.g., file does not exist, already used
      // by another process, etc).
      //
      try
      {
        connection_ptr c (db.connection ());
        c->execute ("PRAGMA locking_mode = EXCLUSIVE");
        transaction t (c->begin_exclusive ());

        if (create)
        {
          // Create the new schema.
          //
          if (db.schema_version () != 0)
            fail << f << ": already has database schema";

          schema_catalog::create_schema (db);

          // Make path comparison case-insensitive for certain platforms.
          //
          // For details on this technique see the SQLite's ALTER TABLE
          // documentation. Note that here we don't increment SQLite's
          // schema_version since we are in the same transaction as where we
          // have created the schema.
          //
#ifdef _WIN32
          db.execute ("PRAGMA writable_schema = ON");

          const char* where ("type == 'table' AND name == 'configuration'");

          sqlite_master t (db.query_value<sqlite_master> (where));

          auto set_nocase = [&t] (const char* name)
          {
            string n ('\"' + string (name) + '\"');
            size_t p (t.sql.find (n));
            assert (p != string::npos);
            p = t.sql.find_first_of (",)", p);
            assert (p != string::npos);
            t.sql.insert (p, " COLLATE NOCASE");
          };

          set_nocase ("path");
          set_nocase ("relative_path");

          db.execute ("UPDATE sqlite_master"
                      " SET sql = '" + t.sql + "'"
                      " WHERE " + where);

          db.execute ("PRAGMA writable_schema = OFF");
          db.execute ("PRAGMA integrity_check");
#endif
        }
        else
        {
          // Migrate the database if necessary.
          //
          schema_catalog::migrate (db);
        }

        t.commit ();
      }
      catch (odb::timeout&)
      {
        fail << "project " << d << " is already used by another process";
      }

      db.tracer (tr); // Switch to the caller's tracer.
      return db;
    }
    catch (const database_exception& e)
    {
      fail << f << ": " << e.message () << endf;
    }
  }
}