xref: /freebsd/contrib/kyua/store/write_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/write_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 <fstream>
36*b0d29bc4SBrooks Davis #include <map>
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 "model/types.hpp"
44*b0d29bc4SBrooks Davis #include "store/dbtypes.hpp"
45*b0d29bc4SBrooks Davis #include "store/exceptions.hpp"
46*b0d29bc4SBrooks Davis #include "store/write_backend.hpp"
47*b0d29bc4SBrooks Davis #include "utils/datetime.hpp"
48*b0d29bc4SBrooks Davis #include "utils/format/macros.hpp"
49*b0d29bc4SBrooks Davis #include "utils/fs/path.hpp"
50*b0d29bc4SBrooks Davis #include "utils/logging/macros.hpp"
51*b0d29bc4SBrooks Davis #include "utils/noncopyable.hpp"
52*b0d29bc4SBrooks Davis #include "utils/optional.ipp"
53*b0d29bc4SBrooks Davis #include "utils/sanity.hpp"
54*b0d29bc4SBrooks Davis #include "utils/stream.hpp"
55*b0d29bc4SBrooks Davis #include "utils/sqlite/database.hpp"
56*b0d29bc4SBrooks Davis #include "utils/sqlite/exceptions.hpp"
57*b0d29bc4SBrooks Davis #include "utils/sqlite/statement.ipp"
58*b0d29bc4SBrooks Davis #include "utils/sqlite/transaction.hpp"
59*b0d29bc4SBrooks Davis 
60*b0d29bc4SBrooks Davis namespace datetime = utils::datetime;
61*b0d29bc4SBrooks Davis namespace fs = utils::fs;
62*b0d29bc4SBrooks Davis namespace sqlite = utils::sqlite;
63*b0d29bc4SBrooks Davis 
64*b0d29bc4SBrooks Davis using utils::none;
65*b0d29bc4SBrooks Davis using utils::optional;
66*b0d29bc4SBrooks Davis 
67*b0d29bc4SBrooks Davis 
68*b0d29bc4SBrooks Davis namespace {
69*b0d29bc4SBrooks Davis 
70*b0d29bc4SBrooks Davis 
71*b0d29bc4SBrooks Davis /// Stores the environment variables of a context.
72*b0d29bc4SBrooks Davis ///
73*b0d29bc4SBrooks Davis /// \param db The SQLite database.
74*b0d29bc4SBrooks Davis /// \param env The environment variables to store.
75*b0d29bc4SBrooks Davis ///
76*b0d29bc4SBrooks Davis /// \throw sqlite::error If there is a problem storing the variables.
77*b0d29bc4SBrooks Davis static void
put_env_vars(sqlite::database & db,const std::map<std::string,std::string> & env)78*b0d29bc4SBrooks Davis put_env_vars(sqlite::database& db,
79*b0d29bc4SBrooks Davis              const std::map< std::string, std::string >& env)
80*b0d29bc4SBrooks Davis {
81*b0d29bc4SBrooks Davis     sqlite::statement stmt = db.create_statement(
82*b0d29bc4SBrooks Davis         "INSERT INTO env_vars (var_name, var_value) "
83*b0d29bc4SBrooks Davis         "VALUES (:var_name, :var_value)");
84*b0d29bc4SBrooks Davis     for (std::map< std::string, std::string >::const_iterator iter =
85*b0d29bc4SBrooks Davis              env.begin(); iter != env.end(); iter++) {
86*b0d29bc4SBrooks Davis         stmt.bind(":var_name", (*iter).first);
87*b0d29bc4SBrooks Davis         stmt.bind(":var_value", (*iter).second);
88*b0d29bc4SBrooks Davis         stmt.step_without_results();
89*b0d29bc4SBrooks Davis         stmt.reset();
90*b0d29bc4SBrooks Davis     }
91*b0d29bc4SBrooks Davis }
92*b0d29bc4SBrooks Davis 
93*b0d29bc4SBrooks Davis 
94*b0d29bc4SBrooks Davis /// Calculates the last rowid of a table.
95*b0d29bc4SBrooks Davis ///
96*b0d29bc4SBrooks Davis /// \param db The SQLite database.
97*b0d29bc4SBrooks Davis /// \param table Name of the table.
98*b0d29bc4SBrooks Davis ///
99*b0d29bc4SBrooks Davis /// \return The last rowid; 0 if the table is empty.
100*b0d29bc4SBrooks Davis static int64_t
last_rowid(sqlite::database & db,const std::string & table)101*b0d29bc4SBrooks Davis last_rowid(sqlite::database& db, const std::string& table)
102*b0d29bc4SBrooks Davis {
103*b0d29bc4SBrooks Davis     sqlite::statement stmt = db.create_statement(
104*b0d29bc4SBrooks Davis         F("SELECT MAX(ROWID) AS max_rowid FROM %s") % table);
105*b0d29bc4SBrooks Davis     stmt.step();
106*b0d29bc4SBrooks Davis     if (stmt.column_type(0) == sqlite::type_null) {
107*b0d29bc4SBrooks Davis         return 0;
108*b0d29bc4SBrooks Davis     } else {
109*b0d29bc4SBrooks Davis         INV(stmt.column_type(0) == sqlite::type_integer);
110*b0d29bc4SBrooks Davis         return stmt.column_int64(0);
111*b0d29bc4SBrooks Davis     }
112*b0d29bc4SBrooks Davis }
113*b0d29bc4SBrooks Davis 
114*b0d29bc4SBrooks Davis 
115*b0d29bc4SBrooks Davis /// Stores a metadata object.
116*b0d29bc4SBrooks Davis ///
117*b0d29bc4SBrooks Davis /// \param db The database into which to store the information.
118*b0d29bc4SBrooks Davis /// \param md The metadata to store.
119*b0d29bc4SBrooks Davis ///
120*b0d29bc4SBrooks Davis /// \return The identifier of the new metadata object.
121*b0d29bc4SBrooks Davis static int64_t
put_metadata(sqlite::database & db,const model::metadata & md)122*b0d29bc4SBrooks Davis put_metadata(sqlite::database& db, const model::metadata& md)
123*b0d29bc4SBrooks Davis {
124*b0d29bc4SBrooks Davis     const model::properties_map props = md.to_properties();
125*b0d29bc4SBrooks Davis 
126*b0d29bc4SBrooks Davis     const int64_t metadata_id = last_rowid(db, "metadatas");
127*b0d29bc4SBrooks Davis 
128*b0d29bc4SBrooks Davis     sqlite::statement stmt = db.create_statement(
129*b0d29bc4SBrooks Davis         "INSERT INTO metadatas (metadata_id, property_name, property_value) "
130*b0d29bc4SBrooks Davis         "VALUES (:metadata_id, :property_name, :property_value)");
131*b0d29bc4SBrooks Davis     stmt.bind(":metadata_id", metadata_id);
132*b0d29bc4SBrooks Davis 
133*b0d29bc4SBrooks Davis     for (model::properties_map::const_iterator iter = props.begin();
134*b0d29bc4SBrooks Davis          iter != props.end(); ++iter) {
135*b0d29bc4SBrooks Davis         stmt.bind(":property_name", (*iter).first);
136*b0d29bc4SBrooks Davis         stmt.bind(":property_value", (*iter).second);
137*b0d29bc4SBrooks Davis         stmt.step_without_results();
138*b0d29bc4SBrooks Davis         stmt.reset();
139*b0d29bc4SBrooks Davis     }
140*b0d29bc4SBrooks Davis 
141*b0d29bc4SBrooks Davis     return metadata_id;
142*b0d29bc4SBrooks Davis }
143*b0d29bc4SBrooks Davis 
144*b0d29bc4SBrooks Davis 
145*b0d29bc4SBrooks Davis /// Stores an arbitrary file into the database as a BLOB.
146*b0d29bc4SBrooks Davis ///
147*b0d29bc4SBrooks Davis /// \param db The database into which to store the file.
148*b0d29bc4SBrooks Davis /// \param path Path to the file to be stored.
149*b0d29bc4SBrooks Davis ///
150*b0d29bc4SBrooks Davis /// \return The identifier of the stored file, or none if the file was empty.
151*b0d29bc4SBrooks Davis ///
152*b0d29bc4SBrooks Davis /// \throw sqlite::error If there are problems writing to the database.
153*b0d29bc4SBrooks Davis static optional< int64_t >
put_file(sqlite::database & db,const fs::path & path)154*b0d29bc4SBrooks Davis put_file(sqlite::database& db, const fs::path& path)
155*b0d29bc4SBrooks Davis {
156*b0d29bc4SBrooks Davis     std::ifstream input(path.c_str());
157*b0d29bc4SBrooks Davis     if (!input)
158*b0d29bc4SBrooks Davis         throw store::error(F("Cannot open file %s") % path);
159*b0d29bc4SBrooks Davis 
160*b0d29bc4SBrooks Davis     try {
161*b0d29bc4SBrooks Davis         if (utils::stream_length(input) == 0)
162*b0d29bc4SBrooks Davis             return none;
163*b0d29bc4SBrooks Davis     } catch (const std::runtime_error& e) {
164*b0d29bc4SBrooks Davis         // Skipping empty files is an optimization.  If we fail to calculate the
165*b0d29bc4SBrooks Davis         // size of the file, just ignore the problem.  If there are real issues
166*b0d29bc4SBrooks Davis         // with the file, the read below will fail anyway.
167*b0d29bc4SBrooks Davis         LD(F("Cannot determine if file is empty: %s") % e.what());
168*b0d29bc4SBrooks Davis     }
169*b0d29bc4SBrooks Davis 
170*b0d29bc4SBrooks Davis     // TODO(jmmv): This will probably cause an unreasonable amount of memory
171*b0d29bc4SBrooks Davis     // consumption if we decide to store arbitrary files in the database (other
172*b0d29bc4SBrooks Davis     // than stdout or stderr).  Should this happen, we need to investigate a
173*b0d29bc4SBrooks Davis     // better way to feel blobs into SQLite.
174*b0d29bc4SBrooks Davis     const std::string contents = utils::read_stream(input);
175*b0d29bc4SBrooks Davis 
176*b0d29bc4SBrooks Davis     sqlite::statement stmt = db.create_statement(
177*b0d29bc4SBrooks Davis         "INSERT INTO files (contents) VALUES (:contents)");
178*b0d29bc4SBrooks Davis     stmt.bind(":contents", sqlite::blob(contents.c_str(), contents.length()));
179*b0d29bc4SBrooks Davis     stmt.step_without_results();
180*b0d29bc4SBrooks Davis 
181*b0d29bc4SBrooks Davis     return optional< int64_t >(db.last_insert_rowid());
182*b0d29bc4SBrooks Davis }
183*b0d29bc4SBrooks Davis 
184*b0d29bc4SBrooks Davis 
185*b0d29bc4SBrooks Davis }  // anonymous namespace
186*b0d29bc4SBrooks Davis 
187*b0d29bc4SBrooks Davis 
188*b0d29bc4SBrooks Davis /// Internal implementation for a store write-only transaction.
189*b0d29bc4SBrooks Davis struct store::write_transaction::impl : utils::noncopyable {
190*b0d29bc4SBrooks Davis     /// The backend instance.
191*b0d29bc4SBrooks Davis     store::write_backend& _backend;
192*b0d29bc4SBrooks Davis 
193*b0d29bc4SBrooks Davis     /// The SQLite database this transaction deals with.
194*b0d29bc4SBrooks Davis     sqlite::database _db;
195*b0d29bc4SBrooks Davis 
196*b0d29bc4SBrooks Davis     /// The backing SQLite transaction.
197*b0d29bc4SBrooks Davis     sqlite::transaction _tx;
198*b0d29bc4SBrooks Davis 
199*b0d29bc4SBrooks Davis     /// Opens a transaction.
200*b0d29bc4SBrooks Davis     ///
201*b0d29bc4SBrooks Davis     /// \param backend_ The backend this transaction is connected to.
implstore::write_transaction::impl202*b0d29bc4SBrooks Davis     impl(write_backend& backend_) :
203*b0d29bc4SBrooks Davis         _backend(backend_),
204*b0d29bc4SBrooks Davis         _db(backend_.database()),
205*b0d29bc4SBrooks Davis         _tx(backend_.database().begin_transaction())
206*b0d29bc4SBrooks Davis     {
207*b0d29bc4SBrooks Davis     }
208*b0d29bc4SBrooks Davis };
209*b0d29bc4SBrooks Davis 
210*b0d29bc4SBrooks Davis 
211*b0d29bc4SBrooks Davis /// Creates a new write-only transaction.
212*b0d29bc4SBrooks Davis ///
213*b0d29bc4SBrooks Davis /// \param backend_ The backend this transaction belongs to.
write_transaction(write_backend & backend_)214*b0d29bc4SBrooks Davis store::write_transaction::write_transaction(write_backend& backend_) :
215*b0d29bc4SBrooks Davis     _pimpl(new impl(backend_))
216*b0d29bc4SBrooks Davis {
217*b0d29bc4SBrooks Davis }
218*b0d29bc4SBrooks Davis 
219*b0d29bc4SBrooks Davis 
220*b0d29bc4SBrooks Davis /// Destructor.
~write_transaction(void)221*b0d29bc4SBrooks Davis store::write_transaction::~write_transaction(void)
222*b0d29bc4SBrooks Davis {
223*b0d29bc4SBrooks Davis }
224*b0d29bc4SBrooks Davis 
225*b0d29bc4SBrooks Davis 
226*b0d29bc4SBrooks Davis /// Commits the transaction.
227*b0d29bc4SBrooks Davis ///
228*b0d29bc4SBrooks Davis /// \throw error If there is any problem when talking to the database.
229*b0d29bc4SBrooks Davis void
commit(void)230*b0d29bc4SBrooks Davis store::write_transaction::commit(void)
231*b0d29bc4SBrooks Davis {
232*b0d29bc4SBrooks Davis     try {
233*b0d29bc4SBrooks Davis         _pimpl->_tx.commit();
234*b0d29bc4SBrooks Davis     } catch (const sqlite::error& e) {
235*b0d29bc4SBrooks Davis         throw error(e.what());
236*b0d29bc4SBrooks Davis     }
237*b0d29bc4SBrooks Davis }
238*b0d29bc4SBrooks Davis 
239*b0d29bc4SBrooks Davis 
240*b0d29bc4SBrooks Davis /// Rolls the transaction back.
241*b0d29bc4SBrooks Davis ///
242*b0d29bc4SBrooks Davis /// \throw error If there is any problem when talking to the database.
243*b0d29bc4SBrooks Davis void
rollback(void)244*b0d29bc4SBrooks Davis store::write_transaction::rollback(void)
245*b0d29bc4SBrooks Davis {
246*b0d29bc4SBrooks Davis     try {
247*b0d29bc4SBrooks Davis         _pimpl->_tx.rollback();
248*b0d29bc4SBrooks Davis     } catch (const sqlite::error& e) {
249*b0d29bc4SBrooks Davis         throw error(e.what());
250*b0d29bc4SBrooks Davis     }
251*b0d29bc4SBrooks Davis }
252*b0d29bc4SBrooks Davis 
253*b0d29bc4SBrooks Davis 
254*b0d29bc4SBrooks Davis /// Puts a context into the database.
255*b0d29bc4SBrooks Davis ///
256*b0d29bc4SBrooks Davis /// \pre The context has not been put yet.
257*b0d29bc4SBrooks Davis /// \post The context is stored into the database with a new identifier.
258*b0d29bc4SBrooks Davis ///
259*b0d29bc4SBrooks Davis /// \param context The context to put.
260*b0d29bc4SBrooks Davis ///
261*b0d29bc4SBrooks Davis /// \throw error If there is any problem when talking to the database.
262*b0d29bc4SBrooks Davis void
put_context(const model::context & context)263*b0d29bc4SBrooks Davis store::write_transaction::put_context(const model::context& context)
264*b0d29bc4SBrooks Davis {
265*b0d29bc4SBrooks Davis     try {
266*b0d29bc4SBrooks Davis         sqlite::statement stmt = _pimpl->_db.create_statement(
267*b0d29bc4SBrooks Davis             "INSERT INTO contexts (cwd) VALUES (:cwd)");
268*b0d29bc4SBrooks Davis         stmt.bind(":cwd", context.cwd().str());
269*b0d29bc4SBrooks Davis         stmt.step_without_results();
270*b0d29bc4SBrooks Davis 
271*b0d29bc4SBrooks Davis         put_env_vars(_pimpl->_db, context.env());
272*b0d29bc4SBrooks Davis     } catch (const sqlite::error& e) {
273*b0d29bc4SBrooks Davis         throw error(e.what());
274*b0d29bc4SBrooks Davis     }
275*b0d29bc4SBrooks Davis }
276*b0d29bc4SBrooks Davis 
277*b0d29bc4SBrooks Davis 
278*b0d29bc4SBrooks Davis /// Puts a test program into the database.
279*b0d29bc4SBrooks Davis ///
280*b0d29bc4SBrooks Davis /// \pre The test program has not been put yet.
281*b0d29bc4SBrooks Davis /// \post The test program is stored into the database with a new identifier.
282*b0d29bc4SBrooks Davis ///
283*b0d29bc4SBrooks Davis /// \param test_program The test program to put.
284*b0d29bc4SBrooks Davis ///
285*b0d29bc4SBrooks Davis /// \return The identifier of the inserted test program.
286*b0d29bc4SBrooks Davis ///
287*b0d29bc4SBrooks Davis /// \throw error If there is any problem when talking to the database.
288*b0d29bc4SBrooks Davis int64_t
put_test_program(const model::test_program & test_program)289*b0d29bc4SBrooks Davis store::write_transaction::put_test_program(
290*b0d29bc4SBrooks Davis     const model::test_program& test_program)
291*b0d29bc4SBrooks Davis {
292*b0d29bc4SBrooks Davis     try {
293*b0d29bc4SBrooks Davis         const int64_t metadata_id = put_metadata(
294*b0d29bc4SBrooks Davis             _pimpl->_db, test_program.get_metadata());
295*b0d29bc4SBrooks Davis 
296*b0d29bc4SBrooks Davis         sqlite::statement stmt = _pimpl->_db.create_statement(
297*b0d29bc4SBrooks Davis             "INSERT INTO test_programs (absolute_path, "
298*b0d29bc4SBrooks Davis             "                           root, relative_path, test_suite_name, "
299*b0d29bc4SBrooks Davis             "                           metadata_id, interface) "
300*b0d29bc4SBrooks Davis             "VALUES (:absolute_path, :root, :relative_path, "
301*b0d29bc4SBrooks Davis             "        :test_suite_name, :metadata_id, :interface)");
302*b0d29bc4SBrooks Davis         stmt.bind(":absolute_path", test_program.absolute_path().str());
303*b0d29bc4SBrooks Davis         // TODO(jmmv): The root is not necessarily absolute.  We need to ensure
304*b0d29bc4SBrooks Davis         // that we can recover the absolute path of the test program.  Maybe we
305*b0d29bc4SBrooks Davis         // need to change the test_program to always ensure root is absolute?
306*b0d29bc4SBrooks Davis         stmt.bind(":root", test_program.root().str());
307*b0d29bc4SBrooks Davis         stmt.bind(":relative_path", test_program.relative_path().str());
308*b0d29bc4SBrooks Davis         stmt.bind(":test_suite_name", test_program.test_suite_name());
309*b0d29bc4SBrooks Davis         stmt.bind(":metadata_id", metadata_id);
310*b0d29bc4SBrooks Davis         stmt.bind(":interface", test_program.interface_name());
311*b0d29bc4SBrooks Davis         stmt.step_without_results();
312*b0d29bc4SBrooks Davis         return _pimpl->_db.last_insert_rowid();
313*b0d29bc4SBrooks Davis     } catch (const sqlite::error& e) {
314*b0d29bc4SBrooks Davis         throw error(e.what());
315*b0d29bc4SBrooks Davis     }
316*b0d29bc4SBrooks Davis }
317*b0d29bc4SBrooks Davis 
318*b0d29bc4SBrooks Davis 
319*b0d29bc4SBrooks Davis /// Puts a test case into the database.
320*b0d29bc4SBrooks Davis ///
321*b0d29bc4SBrooks Davis /// \pre The test case has not been put yet.
322*b0d29bc4SBrooks Davis /// \post The test case is stored into the database with a new identifier.
323*b0d29bc4SBrooks Davis ///
324*b0d29bc4SBrooks Davis /// \param test_program The program containing the test case to be stored.
325*b0d29bc4SBrooks Davis /// \param test_case_name The name of the test case to put.
326*b0d29bc4SBrooks Davis /// \param test_program_id The test program this test case belongs to.
327*b0d29bc4SBrooks Davis ///
328*b0d29bc4SBrooks Davis /// \return The identifier of the inserted test case.
329*b0d29bc4SBrooks Davis ///
330*b0d29bc4SBrooks Davis /// \throw error If there is any problem when talking to the database.
331*b0d29bc4SBrooks Davis int64_t
put_test_case(const model::test_program & test_program,const std::string & test_case_name,const int64_t test_program_id)332*b0d29bc4SBrooks Davis store::write_transaction::put_test_case(const model::test_program& test_program,
333*b0d29bc4SBrooks Davis                                         const std::string& test_case_name,
334*b0d29bc4SBrooks Davis                                         const int64_t test_program_id)
335*b0d29bc4SBrooks Davis {
336*b0d29bc4SBrooks Davis     const model::test_case& test_case = test_program.find(test_case_name);
337*b0d29bc4SBrooks Davis 
338*b0d29bc4SBrooks Davis     try {
339*b0d29bc4SBrooks Davis         const int64_t metadata_id = put_metadata(
340*b0d29bc4SBrooks Davis             _pimpl->_db, test_case.get_raw_metadata());
341*b0d29bc4SBrooks Davis 
342*b0d29bc4SBrooks Davis         sqlite::statement stmt = _pimpl->_db.create_statement(
343*b0d29bc4SBrooks Davis             "INSERT INTO test_cases (test_program_id, name, metadata_id) "
344*b0d29bc4SBrooks Davis             "VALUES (:test_program_id, :name, :metadata_id)");
345*b0d29bc4SBrooks Davis         stmt.bind(":test_program_id", test_program_id);
346*b0d29bc4SBrooks Davis         stmt.bind(":name", test_case.name());
347*b0d29bc4SBrooks Davis         stmt.bind(":metadata_id", metadata_id);
348*b0d29bc4SBrooks Davis         stmt.step_without_results();
349*b0d29bc4SBrooks Davis         return _pimpl->_db.last_insert_rowid();
350*b0d29bc4SBrooks Davis     } catch (const sqlite::error& e) {
351*b0d29bc4SBrooks Davis         throw error(e.what());
352*b0d29bc4SBrooks Davis     }
353*b0d29bc4SBrooks Davis }
354*b0d29bc4SBrooks Davis 
355*b0d29bc4SBrooks Davis 
356*b0d29bc4SBrooks Davis /// Stores a file generated by a test case into the database as a BLOB.
357*b0d29bc4SBrooks Davis ///
358*b0d29bc4SBrooks Davis /// \param name The name of the file to store in the database.  This needs to be
359*b0d29bc4SBrooks Davis ///     unique per test case.  The caller is free to decide what names to use
360*b0d29bc4SBrooks Davis ///     for which files.  For example, it might make sense to always call
361*b0d29bc4SBrooks Davis ///     __STDOUT__ the stdout of the test case so that it is easy to locate.
362*b0d29bc4SBrooks Davis /// \param path The path to the file to be stored.
363*b0d29bc4SBrooks Davis /// \param test_case_id The identifier of the test case this file belongs to.
364*b0d29bc4SBrooks Davis ///
365*b0d29bc4SBrooks Davis /// \return The identifier of the stored file, or none if the file was empty.
366*b0d29bc4SBrooks Davis ///
367*b0d29bc4SBrooks Davis /// \throw store::error If there are problems writing to the database.
368*b0d29bc4SBrooks Davis optional< int64_t >
put_test_case_file(const std::string & name,const fs::path & path,const int64_t test_case_id)369*b0d29bc4SBrooks Davis store::write_transaction::put_test_case_file(const std::string& name,
370*b0d29bc4SBrooks Davis                                              const fs::path& path,
371*b0d29bc4SBrooks Davis                                              const int64_t test_case_id)
372*b0d29bc4SBrooks Davis {
373*b0d29bc4SBrooks Davis     LD(F("Storing %s (%s) of test case %s") % name % path % test_case_id);
374*b0d29bc4SBrooks Davis     try {
375*b0d29bc4SBrooks Davis         const optional< int64_t > file_id = put_file(_pimpl->_db, path);
376*b0d29bc4SBrooks Davis         if (!file_id) {
377*b0d29bc4SBrooks Davis             LD("Not storing empty file");
378*b0d29bc4SBrooks Davis             return none;
379*b0d29bc4SBrooks Davis         }
380*b0d29bc4SBrooks Davis 
381*b0d29bc4SBrooks Davis         sqlite::statement stmt = _pimpl->_db.create_statement(
382*b0d29bc4SBrooks Davis             "INSERT INTO test_case_files (test_case_id, file_name, file_id) "
383*b0d29bc4SBrooks Davis             "VALUES (:test_case_id, :file_name, :file_id)");
384*b0d29bc4SBrooks Davis         stmt.bind(":test_case_id", test_case_id);
385*b0d29bc4SBrooks Davis         stmt.bind(":file_name", name);
386*b0d29bc4SBrooks Davis         stmt.bind(":file_id", file_id.get());
387*b0d29bc4SBrooks Davis         stmt.step_without_results();
388*b0d29bc4SBrooks Davis 
389*b0d29bc4SBrooks Davis         return optional< int64_t >(_pimpl->_db.last_insert_rowid());
390*b0d29bc4SBrooks Davis     } catch (const sqlite::error& e) {
391*b0d29bc4SBrooks Davis         throw error(e.what());
392*b0d29bc4SBrooks Davis     }
393*b0d29bc4SBrooks Davis }
394*b0d29bc4SBrooks Davis 
395*b0d29bc4SBrooks Davis 
396*b0d29bc4SBrooks Davis /// Puts a result into the database.
397*b0d29bc4SBrooks Davis ///
398*b0d29bc4SBrooks Davis /// \pre The result has not been put yet.
399*b0d29bc4SBrooks Davis /// \post The result is stored into the database with a new identifier.
400*b0d29bc4SBrooks Davis ///
401*b0d29bc4SBrooks Davis /// \param result The result to put.
402*b0d29bc4SBrooks Davis /// \param test_case_id The test case this result corresponds to.
403*b0d29bc4SBrooks Davis /// \param start_time The time when the test started to run.
404*b0d29bc4SBrooks Davis /// \param end_time The time when the test finished running.
405*b0d29bc4SBrooks Davis ///
406*b0d29bc4SBrooks Davis /// \return The identifier of the inserted result.
407*b0d29bc4SBrooks Davis ///
408*b0d29bc4SBrooks Davis /// \throw error If there is any problem when talking to the database.
409*b0d29bc4SBrooks Davis int64_t
put_result(const model::test_result & result,const int64_t test_case_id,const datetime::timestamp & start_time,const datetime::timestamp & end_time)410*b0d29bc4SBrooks Davis store::write_transaction::put_result(const model::test_result& result,
411*b0d29bc4SBrooks Davis                                      const int64_t test_case_id,
412*b0d29bc4SBrooks Davis                                      const datetime::timestamp& start_time,
413*b0d29bc4SBrooks Davis                                      const datetime::timestamp& end_time)
414*b0d29bc4SBrooks Davis {
415*b0d29bc4SBrooks Davis     try {
416*b0d29bc4SBrooks Davis         sqlite::statement stmt = _pimpl->_db.create_statement(
417*b0d29bc4SBrooks Davis             "INSERT INTO test_results (test_case_id, result_type, "
418*b0d29bc4SBrooks Davis             "                          result_reason, start_time, "
419*b0d29bc4SBrooks Davis             "                          end_time) "
420*b0d29bc4SBrooks Davis             "VALUES (:test_case_id, :result_type, :result_reason, "
421*b0d29bc4SBrooks Davis             "        :start_time, :end_time)");
422*b0d29bc4SBrooks Davis         stmt.bind(":test_case_id", test_case_id);
423*b0d29bc4SBrooks Davis 
424*b0d29bc4SBrooks Davis         store::bind_test_result_type(stmt, ":result_type", result.type());
425*b0d29bc4SBrooks Davis         if (result.reason().empty())
426*b0d29bc4SBrooks Davis             stmt.bind(":result_reason", sqlite::null());
427*b0d29bc4SBrooks Davis         else
428*b0d29bc4SBrooks Davis             stmt.bind(":result_reason", result.reason());
429*b0d29bc4SBrooks Davis 
430*b0d29bc4SBrooks Davis         store::bind_timestamp(stmt, ":start_time", start_time);
431*b0d29bc4SBrooks Davis         store::bind_timestamp(stmt, ":end_time", end_time);
432*b0d29bc4SBrooks Davis 
433*b0d29bc4SBrooks Davis         stmt.step_without_results();
434*b0d29bc4SBrooks Davis         const int64_t result_id = _pimpl->_db.last_insert_rowid();
435*b0d29bc4SBrooks Davis 
436*b0d29bc4SBrooks Davis         return result_id;
437*b0d29bc4SBrooks Davis     } catch (const sqlite::error& e) {
438*b0d29bc4SBrooks Davis         throw error(e.what());
439*b0d29bc4SBrooks Davis     }
440*b0d29bc4SBrooks Davis }
441