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