xref: /freebsd/contrib/kyua/store/read_transaction.cpp (revision b0d29bc47dba79f6f38e67eabadfb4b32ffd9390)
1*b0d29bc4SBrooks Davis // Copyright 2011 The Kyua Authors.
2*b0d29bc4SBrooks Davis // All rights reserved.
3*b0d29bc4SBrooks Davis //
4*b0d29bc4SBrooks Davis // Redistribution and use in source and binary forms, with or without
5*b0d29bc4SBrooks Davis // modification, are permitted provided that the following conditions are
6*b0d29bc4SBrooks Davis // met:
7*b0d29bc4SBrooks Davis //
8*b0d29bc4SBrooks Davis // * Redistributions of source code must retain the above copyright
9*b0d29bc4SBrooks Davis //   notice, this list of conditions and the following disclaimer.
10*b0d29bc4SBrooks Davis // * Redistributions in binary form must reproduce the above copyright
11*b0d29bc4SBrooks Davis //   notice, this list of conditions and the following disclaimer in the
12*b0d29bc4SBrooks Davis //   documentation and/or other materials provided with the distribution.
13*b0d29bc4SBrooks Davis // * Neither the name of Google Inc. nor the names of its contributors
14*b0d29bc4SBrooks Davis //   may be used to endorse or promote products derived from this software
15*b0d29bc4SBrooks Davis //   without specific prior written permission.
16*b0d29bc4SBrooks Davis //
17*b0d29bc4SBrooks Davis // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18*b0d29bc4SBrooks Davis // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19*b0d29bc4SBrooks Davis // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20*b0d29bc4SBrooks Davis // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21*b0d29bc4SBrooks Davis // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22*b0d29bc4SBrooks Davis // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23*b0d29bc4SBrooks Davis // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24*b0d29bc4SBrooks Davis // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25*b0d29bc4SBrooks Davis // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26*b0d29bc4SBrooks Davis // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27*b0d29bc4SBrooks Davis // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28*b0d29bc4SBrooks Davis 
29*b0d29bc4SBrooks Davis #include "store/read_transaction.hpp"
30*b0d29bc4SBrooks Davis 
31*b0d29bc4SBrooks Davis extern "C" {
32*b0d29bc4SBrooks Davis #include <stdint.h>
33*b0d29bc4SBrooks Davis }
34*b0d29bc4SBrooks Davis 
35*b0d29bc4SBrooks Davis #include <map>
36*b0d29bc4SBrooks Davis #include <utility>
37*b0d29bc4SBrooks Davis 
38*b0d29bc4SBrooks Davis #include "model/context.hpp"
39*b0d29bc4SBrooks Davis #include "model/metadata.hpp"
40*b0d29bc4SBrooks Davis #include "model/test_case.hpp"
41*b0d29bc4SBrooks Davis #include "model/test_program.hpp"
42*b0d29bc4SBrooks Davis #include "model/test_result.hpp"
43*b0d29bc4SBrooks Davis #include "store/dbtypes.hpp"
44*b0d29bc4SBrooks Davis #include "store/exceptions.hpp"
45*b0d29bc4SBrooks Davis #include "store/read_backend.hpp"
46*b0d29bc4SBrooks Davis #include "utils/datetime.hpp"
47*b0d29bc4SBrooks Davis #include "utils/format/macros.hpp"
48*b0d29bc4SBrooks Davis #include "utils/fs/path.hpp"
49*b0d29bc4SBrooks Davis #include "utils/logging/macros.hpp"
50*b0d29bc4SBrooks Davis #include "utils/noncopyable.hpp"
51*b0d29bc4SBrooks Davis #include "utils/optional.ipp"
52*b0d29bc4SBrooks Davis #include "utils/sanity.hpp"
53*b0d29bc4SBrooks Davis #include "utils/sqlite/database.hpp"
54*b0d29bc4SBrooks Davis #include "utils/sqlite/exceptions.hpp"
55*b0d29bc4SBrooks Davis #include "utils/sqlite/statement.ipp"
56*b0d29bc4SBrooks Davis #include "utils/sqlite/transaction.hpp"
57*b0d29bc4SBrooks Davis 
58*b0d29bc4SBrooks Davis namespace datetime = utils::datetime;
59*b0d29bc4SBrooks Davis namespace fs = utils::fs;
60*b0d29bc4SBrooks Davis namespace sqlite = utils::sqlite;
61*b0d29bc4SBrooks Davis 
62*b0d29bc4SBrooks Davis using utils::optional;
63*b0d29bc4SBrooks Davis 
64*b0d29bc4SBrooks Davis 
65*b0d29bc4SBrooks Davis namespace {
66*b0d29bc4SBrooks Davis 
67*b0d29bc4SBrooks Davis 
68*b0d29bc4SBrooks Davis /// Retrieves the environment variables of the context.
69*b0d29bc4SBrooks Davis ///
70*b0d29bc4SBrooks Davis /// \param db The SQLite database.
71*b0d29bc4SBrooks Davis ///
72*b0d29bc4SBrooks Davis /// \return The environment variables of the specified context.
73*b0d29bc4SBrooks Davis ///
74*b0d29bc4SBrooks Davis /// \throw sqlite::error If there is a problem loading the variables.
75*b0d29bc4SBrooks Davis static std::map< std::string, std::string >
get_env_vars(sqlite::database & db)76*b0d29bc4SBrooks Davis get_env_vars(sqlite::database& db)
77*b0d29bc4SBrooks Davis {
78*b0d29bc4SBrooks Davis     std::map< std::string, std::string > env;
79*b0d29bc4SBrooks Davis 
80*b0d29bc4SBrooks Davis     sqlite::statement stmt = db.create_statement(
81*b0d29bc4SBrooks Davis         "SELECT var_name, var_value FROM env_vars");
82*b0d29bc4SBrooks Davis 
83*b0d29bc4SBrooks Davis     while (stmt.step()) {
84*b0d29bc4SBrooks Davis         const std::string name = stmt.safe_column_text("var_name");
85*b0d29bc4SBrooks Davis         const std::string value = stmt.safe_column_text("var_value");
86*b0d29bc4SBrooks Davis         env[name] = value;
87*b0d29bc4SBrooks Davis     }
88*b0d29bc4SBrooks Davis 
89*b0d29bc4SBrooks Davis     return env;
90*b0d29bc4SBrooks Davis }
91*b0d29bc4SBrooks Davis 
92*b0d29bc4SBrooks Davis 
93*b0d29bc4SBrooks Davis /// Retrieves a metadata object.
94*b0d29bc4SBrooks Davis ///
95*b0d29bc4SBrooks Davis /// \param db The SQLite database.
96*b0d29bc4SBrooks Davis /// \param metadata_id The identifier of the metadata.
97*b0d29bc4SBrooks Davis ///
98*b0d29bc4SBrooks Davis /// \return A new metadata object.
99*b0d29bc4SBrooks Davis static model::metadata
get_metadata(sqlite::database & db,const int64_t metadata_id)100*b0d29bc4SBrooks Davis get_metadata(sqlite::database& db, const int64_t metadata_id)
101*b0d29bc4SBrooks Davis {
102*b0d29bc4SBrooks Davis     model::metadata_builder builder;
103*b0d29bc4SBrooks Davis 
104*b0d29bc4SBrooks Davis     sqlite::statement stmt = db.create_statement(
105*b0d29bc4SBrooks Davis         "SELECT * FROM metadatas WHERE metadata_id == :metadata_id");
106*b0d29bc4SBrooks Davis     stmt.bind(":metadata_id", metadata_id);
107*b0d29bc4SBrooks Davis     while (stmt.step()) {
108*b0d29bc4SBrooks Davis         const std::string name = stmt.safe_column_text("property_name");
109*b0d29bc4SBrooks Davis         const std::string value = stmt.safe_column_text("property_value");
110*b0d29bc4SBrooks Davis         builder.set_string(name, value);
111*b0d29bc4SBrooks Davis     }
112*b0d29bc4SBrooks Davis 
113*b0d29bc4SBrooks Davis     return builder.build();
114*b0d29bc4SBrooks Davis }
115*b0d29bc4SBrooks Davis 
116*b0d29bc4SBrooks Davis 
117*b0d29bc4SBrooks Davis /// Gets a file from the database.
118*b0d29bc4SBrooks Davis ///
119*b0d29bc4SBrooks Davis /// \param db The database to query the file from.
120*b0d29bc4SBrooks Davis /// \param file_id The identifier of the file to be queried.
121*b0d29bc4SBrooks Davis ///
122*b0d29bc4SBrooks Davis /// \return A textual representation of the file contents.
123*b0d29bc4SBrooks Davis ///
124*b0d29bc4SBrooks Davis /// \throw integrity_error If there is any problem in the loaded data or if the
125*b0d29bc4SBrooks Davis ///     file cannot be found.
126*b0d29bc4SBrooks Davis static std::string
get_file(sqlite::database & db,const int64_t file_id)127*b0d29bc4SBrooks Davis get_file(sqlite::database& db, const int64_t file_id)
128*b0d29bc4SBrooks Davis {
129*b0d29bc4SBrooks Davis     sqlite::statement stmt = db.create_statement(
130*b0d29bc4SBrooks Davis         "SELECT contents FROM files WHERE file_id == :file_id");
131*b0d29bc4SBrooks Davis     stmt.bind(":file_id", file_id);
132*b0d29bc4SBrooks Davis     if (!stmt.step())
133*b0d29bc4SBrooks Davis         throw store::integrity_error(F("Cannot find referenced file %s") %
134*b0d29bc4SBrooks Davis                                      file_id);
135*b0d29bc4SBrooks Davis 
136*b0d29bc4SBrooks Davis     try {
137*b0d29bc4SBrooks Davis         const sqlite::blob raw_contents = stmt.safe_column_blob("contents");
138*b0d29bc4SBrooks Davis         const std::string contents(
139*b0d29bc4SBrooks Davis             static_cast< const char *>(raw_contents.memory), raw_contents.size);
140*b0d29bc4SBrooks Davis 
141*b0d29bc4SBrooks Davis         const bool more = stmt.step();
142*b0d29bc4SBrooks Davis         INV(!more);
143*b0d29bc4SBrooks Davis 
144*b0d29bc4SBrooks Davis         return contents;
145*b0d29bc4SBrooks Davis     } catch (const sqlite::error& e) {
146*b0d29bc4SBrooks Davis         throw store::integrity_error(e.what());
147*b0d29bc4SBrooks Davis     }
148*b0d29bc4SBrooks Davis }
149*b0d29bc4SBrooks Davis 
150*b0d29bc4SBrooks Davis 
151*b0d29bc4SBrooks Davis /// Gets all the test cases within a particular test program.
152*b0d29bc4SBrooks Davis ///
153*b0d29bc4SBrooks Davis /// \param db The database to query the information from.
154*b0d29bc4SBrooks Davis /// \param test_program_id The identifier of the test program whose test cases
155*b0d29bc4SBrooks Davis ///     to query.
156*b0d29bc4SBrooks Davis ///
157*b0d29bc4SBrooks Davis /// \return The collection of loaded test cases.
158*b0d29bc4SBrooks Davis ///
159*b0d29bc4SBrooks Davis /// \throw integrity_error If there is any problem in the loaded data.
160*b0d29bc4SBrooks Davis static model::test_cases_map
get_test_cases(sqlite::database & db,const int64_t test_program_id)161*b0d29bc4SBrooks Davis get_test_cases(sqlite::database& db, const int64_t test_program_id)
162*b0d29bc4SBrooks Davis {
163*b0d29bc4SBrooks Davis     model::test_cases_map_builder test_cases;
164*b0d29bc4SBrooks Davis 
165*b0d29bc4SBrooks Davis     sqlite::statement stmt = db.create_statement(
166*b0d29bc4SBrooks Davis         "SELECT name, metadata_id "
167*b0d29bc4SBrooks Davis         "FROM test_cases WHERE test_program_id == :test_program_id");
168*b0d29bc4SBrooks Davis     stmt.bind(":test_program_id", test_program_id);
169*b0d29bc4SBrooks Davis     while (stmt.step()) {
170*b0d29bc4SBrooks Davis         const std::string name = stmt.safe_column_text("name");
171*b0d29bc4SBrooks Davis         const int64_t metadata_id = stmt.safe_column_int64("metadata_id");
172*b0d29bc4SBrooks Davis 
173*b0d29bc4SBrooks Davis         const model::metadata metadata = get_metadata(db, metadata_id);
174*b0d29bc4SBrooks Davis         LD(F("Loaded test case '%s'") % name);
175*b0d29bc4SBrooks Davis         test_cases.add(name, metadata);
176*b0d29bc4SBrooks Davis     }
177*b0d29bc4SBrooks Davis 
178*b0d29bc4SBrooks Davis     return test_cases.build();
179*b0d29bc4SBrooks Davis }
180*b0d29bc4SBrooks Davis 
181*b0d29bc4SBrooks Davis 
182*b0d29bc4SBrooks Davis /// Retrieves a result from the database.
183*b0d29bc4SBrooks Davis ///
184*b0d29bc4SBrooks Davis /// \param stmt The statement with the data for the result to load.
185*b0d29bc4SBrooks Davis /// \param type_column The name of the column containing the type of the result.
186*b0d29bc4SBrooks Davis /// \param reason_column The name of the column containing the reason for the
187*b0d29bc4SBrooks Davis ///     result, if any.
188*b0d29bc4SBrooks Davis ///
189*b0d29bc4SBrooks Davis /// \return The loaded result.
190*b0d29bc4SBrooks Davis ///
191*b0d29bc4SBrooks Davis /// \throw integrity_error If the data in the database is invalid.
192*b0d29bc4SBrooks Davis static model::test_result
parse_result(sqlite::statement & stmt,const char * type_column,const char * reason_column)193*b0d29bc4SBrooks Davis parse_result(sqlite::statement& stmt, const char* type_column,
194*b0d29bc4SBrooks Davis              const char* reason_column)
195*b0d29bc4SBrooks Davis {
196*b0d29bc4SBrooks Davis     try {
197*b0d29bc4SBrooks Davis         const model::test_result_type type =
198*b0d29bc4SBrooks Davis             store::column_test_result_type(stmt, type_column);
199*b0d29bc4SBrooks Davis         if (type == model::test_result_passed) {
200*b0d29bc4SBrooks Davis             if (stmt.column_type(stmt.column_id(reason_column)) !=
201*b0d29bc4SBrooks Davis                 sqlite::type_null)
202*b0d29bc4SBrooks Davis                 throw store::integrity_error("Result of type 'passed' has a "
203*b0d29bc4SBrooks Davis                                              "non-NULL reason");
204*b0d29bc4SBrooks Davis             return model::test_result(type);
205*b0d29bc4SBrooks Davis         } else {
206*b0d29bc4SBrooks Davis             return model::test_result(type,
207*b0d29bc4SBrooks Davis                                       stmt.safe_column_text(reason_column));
208*b0d29bc4SBrooks Davis         }
209*b0d29bc4SBrooks Davis     } catch (const sqlite::error& e) {
210*b0d29bc4SBrooks Davis         throw store::integrity_error(e.what());
211*b0d29bc4SBrooks Davis     }
212*b0d29bc4SBrooks Davis }
213*b0d29bc4SBrooks Davis 
214*b0d29bc4SBrooks Davis 
215*b0d29bc4SBrooks Davis }  // anonymous namespace
216*b0d29bc4SBrooks Davis 
217*b0d29bc4SBrooks Davis 
218*b0d29bc4SBrooks Davis /// Loads a specific test program from the database.
219*b0d29bc4SBrooks Davis ///
220*b0d29bc4SBrooks Davis /// \param backend_ The store backend we are dealing with.
221*b0d29bc4SBrooks Davis /// \param id The identifier of the test program to load.
222*b0d29bc4SBrooks Davis ///
223*b0d29bc4SBrooks Davis /// \return The instantiated test program.
224*b0d29bc4SBrooks Davis ///
225*b0d29bc4SBrooks Davis /// \throw integrity_error If the data read from the database cannot be properly
226*b0d29bc4SBrooks Davis ///     interpreted.
227*b0d29bc4SBrooks Davis model::test_program_ptr
get_test_program(read_backend & backend_,const int64_t id)228*b0d29bc4SBrooks Davis store::detail::get_test_program(read_backend& backend_, const int64_t id)
229*b0d29bc4SBrooks Davis {
230*b0d29bc4SBrooks Davis     sqlite::database& db = backend_.database();
231*b0d29bc4SBrooks Davis 
232*b0d29bc4SBrooks Davis     model::test_program_ptr test_program;
233*b0d29bc4SBrooks Davis     sqlite::statement stmt = db.create_statement(
234*b0d29bc4SBrooks Davis         "SELECT * FROM test_programs WHERE test_program_id == :id");
235*b0d29bc4SBrooks Davis     stmt.bind(":id", id);
236*b0d29bc4SBrooks Davis     stmt.step();
237*b0d29bc4SBrooks Davis     const std::string interface = stmt.safe_column_text("interface");
238*b0d29bc4SBrooks Davis     test_program.reset(new model::test_program(
239*b0d29bc4SBrooks Davis         interface,
240*b0d29bc4SBrooks Davis         fs::path(stmt.safe_column_text("relative_path")),
241*b0d29bc4SBrooks Davis         fs::path(stmt.safe_column_text("root")),
242*b0d29bc4SBrooks Davis         stmt.safe_column_text("test_suite_name"),
243*b0d29bc4SBrooks Davis         get_metadata(db, stmt.safe_column_int64("metadata_id")),
244*b0d29bc4SBrooks Davis         get_test_cases(db, id)));
245*b0d29bc4SBrooks Davis     const bool more = stmt.step();
246*b0d29bc4SBrooks Davis     INV(!more);
247*b0d29bc4SBrooks Davis 
248*b0d29bc4SBrooks Davis     LD(F("Loaded test program '%s'") % test_program->relative_path());
249*b0d29bc4SBrooks Davis     return test_program;
250*b0d29bc4SBrooks Davis }
251*b0d29bc4SBrooks Davis 
252*b0d29bc4SBrooks Davis 
253*b0d29bc4SBrooks Davis /// Internal implementation for a results iterator.
254*b0d29bc4SBrooks Davis struct store::results_iterator::impl : utils::noncopyable {
255*b0d29bc4SBrooks Davis     /// The store backend we are dealing with.
256*b0d29bc4SBrooks Davis     store::read_backend _backend;
257*b0d29bc4SBrooks Davis 
258*b0d29bc4SBrooks Davis     /// The statement to iterate on.
259*b0d29bc4SBrooks Davis     sqlite::statement _stmt;
260*b0d29bc4SBrooks Davis 
261*b0d29bc4SBrooks Davis     /// A cache for the last loaded test program.
262*b0d29bc4SBrooks Davis     optional< std::pair< int64_t, model::test_program_ptr > >
263*b0d29bc4SBrooks Davis         _last_test_program;
264*b0d29bc4SBrooks Davis 
265*b0d29bc4SBrooks Davis     /// Whether the iterator is still valid or not.
266*b0d29bc4SBrooks Davis     bool _valid;
267*b0d29bc4SBrooks Davis 
268*b0d29bc4SBrooks Davis     /// Constructor.
269*b0d29bc4SBrooks Davis     ///
270*b0d29bc4SBrooks Davis     /// \param backend_ The store backend implementation.
implstore::results_iterator::impl271*b0d29bc4SBrooks Davis     impl(store::read_backend& backend_) :
272*b0d29bc4SBrooks Davis         _backend(backend_),
273*b0d29bc4SBrooks Davis         _stmt(backend_.database().create_statement(
274*b0d29bc4SBrooks Davis             "SELECT test_programs.test_program_id, "
275*b0d29bc4SBrooks Davis             "    test_programs.interface, "
276*b0d29bc4SBrooks Davis             "    test_cases.test_case_id, test_cases.name, "
277*b0d29bc4SBrooks Davis             "    test_results.result_type, test_results.result_reason, "
278*b0d29bc4SBrooks Davis             "    test_results.start_time, test_results.end_time "
279*b0d29bc4SBrooks Davis             "FROM test_programs "
280*b0d29bc4SBrooks Davis             "    JOIN test_cases "
281*b0d29bc4SBrooks Davis             "    ON test_programs.test_program_id = test_cases.test_program_id "
282*b0d29bc4SBrooks Davis             "    JOIN test_results "
283*b0d29bc4SBrooks Davis             "    ON test_cases.test_case_id = test_results.test_case_id "
284*b0d29bc4SBrooks Davis             "ORDER BY test_programs.absolute_path, test_cases.name"))
285*b0d29bc4SBrooks Davis     {
286*b0d29bc4SBrooks Davis         _valid = _stmt.step();
287*b0d29bc4SBrooks Davis     }
288*b0d29bc4SBrooks Davis };
289*b0d29bc4SBrooks Davis 
290*b0d29bc4SBrooks Davis 
291*b0d29bc4SBrooks Davis /// Constructor.
292*b0d29bc4SBrooks Davis ///
293*b0d29bc4SBrooks Davis /// \param pimpl_ The internal implementation details of the iterator.
results_iterator(std::shared_ptr<impl> pimpl_)294*b0d29bc4SBrooks Davis store::results_iterator::results_iterator(
295*b0d29bc4SBrooks Davis     std::shared_ptr< impl > pimpl_) :
296*b0d29bc4SBrooks Davis     _pimpl(pimpl_)
297*b0d29bc4SBrooks Davis {
298*b0d29bc4SBrooks Davis }
299*b0d29bc4SBrooks Davis 
300*b0d29bc4SBrooks Davis 
301*b0d29bc4SBrooks Davis /// Destructor.
~results_iterator(void)302*b0d29bc4SBrooks Davis store::results_iterator::~results_iterator(void)
303*b0d29bc4SBrooks Davis {
304*b0d29bc4SBrooks Davis }
305*b0d29bc4SBrooks Davis 
306*b0d29bc4SBrooks Davis 
307*b0d29bc4SBrooks Davis /// Moves the iterator forward by one result.
308*b0d29bc4SBrooks Davis ///
309*b0d29bc4SBrooks Davis /// \return The iterator itself.
310*b0d29bc4SBrooks Davis store::results_iterator&
operator ++(void)311*b0d29bc4SBrooks Davis store::results_iterator::operator++(void)
312*b0d29bc4SBrooks Davis {
313*b0d29bc4SBrooks Davis     _pimpl->_valid = _pimpl->_stmt.step();
314*b0d29bc4SBrooks Davis     return *this;
315*b0d29bc4SBrooks Davis }
316*b0d29bc4SBrooks Davis 
317*b0d29bc4SBrooks Davis 
318*b0d29bc4SBrooks Davis /// Checks whether the iterator is still valid.
319*b0d29bc4SBrooks Davis ///
320*b0d29bc4SBrooks Davis /// \return True if there is more elements to iterate on, false otherwise.
operator bool(void) const321*b0d29bc4SBrooks Davis store::results_iterator::operator bool(void) const
322*b0d29bc4SBrooks Davis {
323*b0d29bc4SBrooks Davis     return _pimpl->_valid;
324*b0d29bc4SBrooks Davis }
325*b0d29bc4SBrooks Davis 
326*b0d29bc4SBrooks Davis 
327*b0d29bc4SBrooks Davis /// Gets the test program this result belongs to.
328*b0d29bc4SBrooks Davis ///
329*b0d29bc4SBrooks Davis /// \return The representation of a test program.
330*b0d29bc4SBrooks Davis const model::test_program_ptr
test_program(void) const331*b0d29bc4SBrooks Davis store::results_iterator::test_program(void) const
332*b0d29bc4SBrooks Davis {
333*b0d29bc4SBrooks Davis     const int64_t id = _pimpl->_stmt.safe_column_int64("test_program_id");
334*b0d29bc4SBrooks Davis     if (!_pimpl->_last_test_program ||
335*b0d29bc4SBrooks Davis         _pimpl->_last_test_program.get().first != id)
336*b0d29bc4SBrooks Davis     {
337*b0d29bc4SBrooks Davis         const model::test_program_ptr tp = detail::get_test_program(
338*b0d29bc4SBrooks Davis             _pimpl->_backend, id);
339*b0d29bc4SBrooks Davis         _pimpl->_last_test_program = std::make_pair(id, tp);
340*b0d29bc4SBrooks Davis     }
341*b0d29bc4SBrooks Davis     return _pimpl->_last_test_program.get().second;
342*b0d29bc4SBrooks Davis }
343*b0d29bc4SBrooks Davis 
344*b0d29bc4SBrooks Davis 
345*b0d29bc4SBrooks Davis /// Gets the name of the test case pointed by the iterator.
346*b0d29bc4SBrooks Davis ///
347*b0d29bc4SBrooks Davis /// The caller can look up the test case data by using the find() method on the
348*b0d29bc4SBrooks Davis /// test program returned by test_program().
349*b0d29bc4SBrooks Davis ///
350*b0d29bc4SBrooks Davis /// \return A test case name, unique within the test program.
351*b0d29bc4SBrooks Davis std::string
test_case_name(void) const352*b0d29bc4SBrooks Davis store::results_iterator::test_case_name(void) const
353*b0d29bc4SBrooks Davis {
354*b0d29bc4SBrooks Davis     return _pimpl->_stmt.safe_column_text("name");
355*b0d29bc4SBrooks Davis }
356*b0d29bc4SBrooks Davis 
357*b0d29bc4SBrooks Davis 
358*b0d29bc4SBrooks Davis /// Gets the result of the test case pointed by the iterator.
359*b0d29bc4SBrooks Davis ///
360*b0d29bc4SBrooks Davis /// \return A test case result.
361*b0d29bc4SBrooks Davis model::test_result
result(void) const362*b0d29bc4SBrooks Davis store::results_iterator::result(void) const
363*b0d29bc4SBrooks Davis {
364*b0d29bc4SBrooks Davis     return parse_result(_pimpl->_stmt, "result_type", "result_reason");
365*b0d29bc4SBrooks Davis }
366*b0d29bc4SBrooks Davis 
367*b0d29bc4SBrooks Davis 
368*b0d29bc4SBrooks Davis /// Gets the start time of the test case execution.
369*b0d29bc4SBrooks Davis ///
370*b0d29bc4SBrooks Davis /// \return The time when the test started execution.
371*b0d29bc4SBrooks Davis datetime::timestamp
start_time(void) const372*b0d29bc4SBrooks Davis store::results_iterator::start_time(void) const
373*b0d29bc4SBrooks Davis {
374*b0d29bc4SBrooks Davis     return column_timestamp(_pimpl->_stmt, "start_time");
375*b0d29bc4SBrooks Davis }
376*b0d29bc4SBrooks Davis 
377*b0d29bc4SBrooks Davis 
378*b0d29bc4SBrooks Davis /// Gets the end time of the test case execution.
379*b0d29bc4SBrooks Davis ///
380*b0d29bc4SBrooks Davis /// \return The time when the test finished execution.
381*b0d29bc4SBrooks Davis datetime::timestamp
end_time(void) const382*b0d29bc4SBrooks Davis store::results_iterator::end_time(void) const
383*b0d29bc4SBrooks Davis {
384*b0d29bc4SBrooks Davis     return column_timestamp(_pimpl->_stmt, "end_time");
385*b0d29bc4SBrooks Davis }
386*b0d29bc4SBrooks Davis 
387*b0d29bc4SBrooks Davis 
388*b0d29bc4SBrooks Davis /// Gets a file from a test case.
389*b0d29bc4SBrooks Davis ///
390*b0d29bc4SBrooks Davis /// \param db The database to query the file from.
391*b0d29bc4SBrooks Davis /// \param test_case_id The identifier of the test case.
392*b0d29bc4SBrooks Davis /// \param filename The name of the file to be retrieved.
393*b0d29bc4SBrooks Davis ///
394*b0d29bc4SBrooks Davis /// \return A textual representation of the file contents.
395*b0d29bc4SBrooks Davis ///
396*b0d29bc4SBrooks Davis /// \throw integrity_error If there is any problem in the loaded data or if the
397*b0d29bc4SBrooks Davis ///     file cannot be found.
398*b0d29bc4SBrooks Davis static std::string
get_test_case_file(sqlite::database & db,const int64_t test_case_id,const char * filename)399*b0d29bc4SBrooks Davis get_test_case_file(sqlite::database& db, const int64_t test_case_id,
400*b0d29bc4SBrooks Davis                    const char* filename)
401*b0d29bc4SBrooks Davis {
402*b0d29bc4SBrooks Davis     sqlite::statement stmt = db.create_statement(
403*b0d29bc4SBrooks Davis         "SELECT file_id FROM test_case_files "
404*b0d29bc4SBrooks Davis         "WHERE test_case_id == :test_case_id AND file_name == :file_name");
405*b0d29bc4SBrooks Davis     stmt.bind(":test_case_id", test_case_id);
406*b0d29bc4SBrooks Davis     stmt.bind(":file_name", filename);
407*b0d29bc4SBrooks Davis     if (stmt.step())
408*b0d29bc4SBrooks Davis         return get_file(db, stmt.safe_column_int64("file_id"));
409*b0d29bc4SBrooks Davis     else
410*b0d29bc4SBrooks Davis         return "";
411*b0d29bc4SBrooks Davis }
412*b0d29bc4SBrooks Davis 
413*b0d29bc4SBrooks Davis 
414*b0d29bc4SBrooks Davis /// Gets the contents of stdout of a test case.
415*b0d29bc4SBrooks Davis ///
416*b0d29bc4SBrooks Davis /// \return A textual representation of the stdout contents of the test case.
417*b0d29bc4SBrooks Davis /// This may of course be empty if the test case didn't print anything.
418*b0d29bc4SBrooks Davis std::string
stdout_contents(void) const419*b0d29bc4SBrooks Davis store::results_iterator::stdout_contents(void) const
420*b0d29bc4SBrooks Davis {
421*b0d29bc4SBrooks Davis     return get_test_case_file(_pimpl->_backend.database(),
422*b0d29bc4SBrooks Davis                               _pimpl->_stmt.safe_column_int64("test_case_id"),
423*b0d29bc4SBrooks Davis                               "__STDOUT__");
424*b0d29bc4SBrooks Davis }
425*b0d29bc4SBrooks Davis 
426*b0d29bc4SBrooks Davis 
427*b0d29bc4SBrooks Davis /// Gets the contents of stderr of a test case.
428*b0d29bc4SBrooks Davis ///
429*b0d29bc4SBrooks Davis /// \return A textual representation of the stderr contents of the test case.
430*b0d29bc4SBrooks Davis /// This may of course be empty if the test case didn't print anything.
431*b0d29bc4SBrooks Davis std::string
stderr_contents(void) const432*b0d29bc4SBrooks Davis store::results_iterator::stderr_contents(void) const
433*b0d29bc4SBrooks Davis {
434*b0d29bc4SBrooks Davis     return get_test_case_file(_pimpl->_backend.database(),
435*b0d29bc4SBrooks Davis                               _pimpl->_stmt.safe_column_int64("test_case_id"),
436*b0d29bc4SBrooks Davis                               "__STDERR__");
437*b0d29bc4SBrooks Davis }
438*b0d29bc4SBrooks Davis 
439*b0d29bc4SBrooks Davis 
440*b0d29bc4SBrooks Davis /// Internal implementation for a store read-only transaction.
441*b0d29bc4SBrooks Davis struct store::read_transaction::impl : utils::noncopyable {
442*b0d29bc4SBrooks Davis     /// The backend instance.
443*b0d29bc4SBrooks Davis     store::read_backend& _backend;
444*b0d29bc4SBrooks Davis 
445*b0d29bc4SBrooks Davis     /// The SQLite database this transaction deals with.
446*b0d29bc4SBrooks Davis     sqlite::database _db;
447*b0d29bc4SBrooks Davis 
448*b0d29bc4SBrooks Davis     /// The backing SQLite transaction.
449*b0d29bc4SBrooks Davis     sqlite::transaction _tx;
450*b0d29bc4SBrooks Davis 
451*b0d29bc4SBrooks Davis     /// Opens a transaction.
452*b0d29bc4SBrooks Davis     ///
453*b0d29bc4SBrooks Davis     /// \param backend_ The backend this transaction is connected to.
implstore::read_transaction::impl454*b0d29bc4SBrooks Davis     impl(read_backend& backend_) :
455*b0d29bc4SBrooks Davis         _backend(backend_),
456*b0d29bc4SBrooks Davis         _db(backend_.database()),
457*b0d29bc4SBrooks Davis         _tx(backend_.database().begin_transaction())
458*b0d29bc4SBrooks Davis     {
459*b0d29bc4SBrooks Davis     }
460*b0d29bc4SBrooks Davis };
461*b0d29bc4SBrooks Davis 
462*b0d29bc4SBrooks Davis 
463*b0d29bc4SBrooks Davis /// Creates a new read-only transaction.
464*b0d29bc4SBrooks Davis ///
465*b0d29bc4SBrooks Davis /// \param backend_ The backend this transaction belongs to.
read_transaction(read_backend & backend_)466*b0d29bc4SBrooks Davis store::read_transaction::read_transaction(read_backend& backend_) :
467*b0d29bc4SBrooks Davis     _pimpl(new impl(backend_))
468*b0d29bc4SBrooks Davis {
469*b0d29bc4SBrooks Davis }
470*b0d29bc4SBrooks Davis 
471*b0d29bc4SBrooks Davis 
472*b0d29bc4SBrooks Davis /// Destructor.
~read_transaction(void)473*b0d29bc4SBrooks Davis store::read_transaction::~read_transaction(void)
474*b0d29bc4SBrooks Davis {
475*b0d29bc4SBrooks Davis }
476*b0d29bc4SBrooks Davis 
477*b0d29bc4SBrooks Davis 
478*b0d29bc4SBrooks Davis /// Finishes the transaction.
479*b0d29bc4SBrooks Davis ///
480*b0d29bc4SBrooks Davis /// This actually commits the result of the transaction, but because the
481*b0d29bc4SBrooks Davis /// transaction is read-only, we use a different term to denote that there is no
482*b0d29bc4SBrooks Davis /// distinction between commit and rollback.
483*b0d29bc4SBrooks Davis ///
484*b0d29bc4SBrooks Davis /// \throw error If there is any problem when talking to the database.
485*b0d29bc4SBrooks Davis void
finish(void)486*b0d29bc4SBrooks Davis store::read_transaction::finish(void)
487*b0d29bc4SBrooks Davis {
488*b0d29bc4SBrooks Davis     try {
489*b0d29bc4SBrooks Davis         _pimpl->_tx.commit();
490*b0d29bc4SBrooks Davis     } catch (const sqlite::error& e) {
491*b0d29bc4SBrooks Davis         throw error(e.what());
492*b0d29bc4SBrooks Davis     }
493*b0d29bc4SBrooks Davis }
494*b0d29bc4SBrooks Davis 
495*b0d29bc4SBrooks Davis 
496*b0d29bc4SBrooks Davis /// Retrieves an context from the database.
497*b0d29bc4SBrooks Davis ///
498*b0d29bc4SBrooks Davis /// \return The retrieved context.
499*b0d29bc4SBrooks Davis ///
500*b0d29bc4SBrooks Davis /// \throw error If there is a problem loading the context.
501*b0d29bc4SBrooks Davis model::context
get_context(void)502*b0d29bc4SBrooks Davis store::read_transaction::get_context(void)
503*b0d29bc4SBrooks Davis {
504*b0d29bc4SBrooks Davis     try {
505*b0d29bc4SBrooks Davis         sqlite::statement stmt = _pimpl->_db.create_statement(
506*b0d29bc4SBrooks Davis             "SELECT cwd FROM contexts");
507*b0d29bc4SBrooks Davis         if (!stmt.step())
508*b0d29bc4SBrooks Davis             throw error("Error loading context: no data");
509*b0d29bc4SBrooks Davis 
510*b0d29bc4SBrooks Davis         return model::context(fs::path(stmt.safe_column_text("cwd")),
511*b0d29bc4SBrooks Davis                               get_env_vars(_pimpl->_db));
512*b0d29bc4SBrooks Davis     } catch (const sqlite::error& e) {
513*b0d29bc4SBrooks Davis         throw error(F("Error loading context: %s") % e.what());
514*b0d29bc4SBrooks Davis     }
515*b0d29bc4SBrooks Davis }
516*b0d29bc4SBrooks Davis 
517*b0d29bc4SBrooks Davis 
518*b0d29bc4SBrooks Davis /// Creates a new iterator to scan tests results.
519*b0d29bc4SBrooks Davis ///
520*b0d29bc4SBrooks Davis /// \return The constructed iterator.
521*b0d29bc4SBrooks Davis ///
522*b0d29bc4SBrooks Davis /// \throw error If there is any problem constructing the iterator.
523*b0d29bc4SBrooks Davis store::results_iterator
get_results(void)524*b0d29bc4SBrooks Davis store::read_transaction::get_results(void)
525*b0d29bc4SBrooks Davis {
526*b0d29bc4SBrooks Davis     try {
527*b0d29bc4SBrooks Davis         return results_iterator(std::shared_ptr< results_iterator::impl >(
528*b0d29bc4SBrooks Davis            new results_iterator::impl(_pimpl->_backend)));
529*b0d29bc4SBrooks Davis     } catch (const sqlite::error& e) {
530*b0d29bc4SBrooks Davis         throw error(e.what());
531*b0d29bc4SBrooks Davis     }
532*b0d29bc4SBrooks Davis }
533