platform/databases/SQLiteDatabase.cpp

00001 // ------------------------------------------------------------------------
00002 // Pion is a development platform for building Reactors that process Events
00003 // ------------------------------------------------------------------------
00004 // Copyright (C) 2007-2008 Atomic Labs, Inc.  (http://www.atomiclabs.com)
00005 //
00006 // Pion is free software: you can redistribute it and/or modify it under the
00007 // terms of the GNU Affero General Public License as published by the Free
00008 // Software Foundation, either version 3 of the License, or (at your option)
00009 // any later version.
00010 //
00011 // Pion is distributed in the hope that it will be useful, but WITHOUT ANY
00012 // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
00013 // FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for
00014 // more details.
00015 //
00016 // You should have received a copy of the GNU Affero General Public License
00017 // along with Pion.  If not, see <http://www.gnu.org/licenses/>.
00018 //
00019 
00020 #include <set>
00021 #include <boost/filesystem/operations.hpp>
00022 #include <boost/regex.hpp>
00023 #include <pion/platform/ConfigManager.hpp>
00024 #include <pion/platform/DatabaseManager.hpp>
00025 #include "SQLiteDatabase.hpp"
00026 
00027 using namespace pion::platform;
00028 
00029 
00030 namespace pion {        // begin namespace pion
00031 namespace plugins {     // begin namespace plugins
00032 
00033 
00034 // static members of SQLiteDatabase
00035 
00036 const std::string           SQLiteDatabase::BACKUP_FILE_EXTENSION = ".bak";
00037 const std::string           SQLiteDatabase::FILENAME_ELEMENT_NAME = "Filename";
00038 
00039 
00040 // SQLiteDatabase member functions
00041 
00042 void SQLiteDatabase::setConfig(const Vocabulary& v, const xmlNodePtr config_ptr)
00043 {
00044     Database::setConfig(v, config_ptr);
00045 
00046     readConfig(config_ptr, "SQLite");
00047 
00048     // get the Filename of the database
00049     if (! ConfigManager::getConfigOption(FILENAME_ELEMENT_NAME, m_database_name, config_ptr))
00050         throw EmptyFilenameException(getId());
00051 
00052     // resolve paths relative to the platform DataDirectory
00053     m_database_name = getDatabaseManager().resolveRelativeDataPath(m_database_name);
00054 }
00055 
00056 DatabasePtr SQLiteDatabase::clone(void) const
00057 {
00058     SQLiteDatabase *db_ptr(new SQLiteDatabase());
00059     db_ptr->copyDatabase(*this);
00060     db_ptr->m_database_name = m_database_name;
00061     return DatabasePtr(db_ptr);
00062 }
00063 
00064 void SQLiteDatabase::open(unsigned partition)
00065 {
00066     // create a backup copy of the database before opening it
00067 /* "no backups no more" since it's not supported by Enterprise database either...
00068     const bool is_new_database = ! boost::filesystem::exists(m_database_name);
00069     if (! is_new_database && create_backup) {
00070         const std::string backup_filename(m_database_name + BACKUP_FILE_EXTENSION);
00071         if (boost::filesystem::exists(backup_filename))
00072             boost::filesystem::remove(backup_filename);
00073         boost::filesystem::copy_file(m_database_name, backup_filename);
00074     }
00075 */
00076     m_partition = partition;
00077     // If Partitioning: Change "name.db" into "name_001.db" or, "name" into "name_001.db"
00078     std::string dbname = dbPartition(m_database_name, partition);
00079 
00080     // We'll try to enable the shared cache
00081     if (sqlite3_enable_shared_cache(1) != SQLITE_OK)
00082         throw SQLiteAPIException(getSQLiteError());
00083 
00084     // open up the database
00085 //  if (sqlite3_open_v2(m_database_name.c_str(), &m_sqlite_db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, NULL) != SQLITE_OK) {
00086     if (sqlite3_open_v2(dbname.c_str(), &m_sqlite_db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX, NULL) != SQLITE_OK) {
00087         // prevent memory leak (sqlite3 assigns handle even if error)
00088         if (m_sqlite_db != NULL) {
00089             sqlite3_close(m_sqlite_db);
00090             m_sqlite_db = NULL;
00091         }
00092         throw OpenDatabaseException(dbname);
00093     }
00094 
00095     // set a 10s busy timeout to deal with db locking
00096     sqlite3_busy_timeout(m_sqlite_db, 10000);
00097 
00098     // Set up defaults for page & cache size
00099     boost::uint64_t page_size = 1024;
00100     boost::uint64_t cache_size = 2000;
00101     boost::regex regex_cache_size("pragma\\s+cache_size\\s+=\\s*([0-9]+)", boost::regex::extended | boost::regex::icase);
00102     boost::regex regex_default_cache_size("pragma\\s+default_cache_size\\s+=\\s*([0-9]+)", boost::regex::extended | boost::regex::icase);
00103     boost::regex regex_page_size("pragma\\s+page_size\\s+=\\s*([0-9]+)", boost::regex::extended | boost::regex::icase);
00104     // execute all PreSQL (if any)
00105     for (unsigned i = 0; i < m_pre_sql.size(); i++) {
00106         if (sqlite3_exec(m_sqlite_db, m_pre_sql[i].c_str(), NULL, NULL, &m_error_ptr) != SQLITE_OK)
00107             throw SQLiteAPIException(getSQLiteError());
00108         std::string s;
00109         if (regex_search(m_pre_sql[i], regex_cache_size))
00110             s = regex_replace(m_pre_sql[i], regex_cache_size, "$1", boost::format_all | boost::format_first_only | boost::format_no_copy);
00111         if (regex_search(m_pre_sql[i], regex_default_cache_size))
00112             s = regex_replace(m_pre_sql[i], regex_default_cache_size, "$1", boost::format_all | boost::format_first_only | boost::format_no_copy);
00113         if (!s.empty())
00114             cache_size = boost::lexical_cast<boost::uint64_t>(s);
00115         if (regex_search(m_pre_sql[i], regex_page_size)) {
00116             s = regex_replace(m_pre_sql[i], regex_page_size, "$1", boost::format_all | boost::format_first_only | boost::format_no_copy);
00117             if (!s.empty())
00118                 page_size = boost::lexical_cast<boost::uint64_t>(s);
00119         }
00120     }
00121     m_cache_size = page_size * cache_size;
00122 }
00123 
00124 void SQLiteDatabase::close(void)
00125 {
00126     if (m_sqlite_db != NULL) {
00127         // This should never happen -- but let's close all prepared statements that might have been left open, so close will succeed
00128         sqlite3_stmt *pStmt;
00129         while ((pStmt = sqlite3_next_stmt(m_sqlite_db, 0)) != 0)
00130             sqlite3_finalize(pStmt);
00131         sqlite3_close(m_sqlite_db);
00132     }
00133     m_sqlite_db = NULL;
00134 }
00135 
00136 void SQLiteDatabase::runQuery(const std::string& sql_query, const boost::regex& suppress)
00137 {
00138     // sanity checks
00139     PION_ASSERT(is_open());
00140     PION_ASSERT(! sql_query.empty());
00141 
00142     // execute the query
00143     if (sqlite3_exec(m_sqlite_db, sql_query.c_str(), NULL, NULL, &m_error_ptr) != SQLITE_OK && !suppress.empty())
00144         throw SQLiteAPIException(getSQLiteError());
00145 }
00146 
00147 QueryPtr SQLiteDatabase::addQuery(QueryID query_id,
00148                                   const std::string& sql_query)
00149 {
00150     // sanity checks
00151     PION_ASSERT(is_open());
00152     PION_ASSERT(! query_id.empty());
00153     PION_ASSERT(! sql_query.empty());
00154 
00155     // generate a new database query object
00156     QueryPtr query_ptr(new SQLiteQuery(sql_query, m_sqlite_db));
00157 
00158     // add the query to our query map
00159     m_query_map.insert(std::make_pair(query_id, query_ptr));
00160 
00161     // return the new database query object
00162     return query_ptr;
00163 }
00164 
00165 void SQLiteDatabase::createTable(const Query::FieldMap& field_map,
00166                                 std::string& table_name,
00167                                 const Query::IndexMap& index_map,
00168                                 unsigned partition)
00169 {
00170     PION_ASSERT(is_open());
00171     // If partition is defined, change table_name
00172     if (partition) {
00173         char buff[10];
00174         sprintf(buff, "_%03u", partition);
00175         table_name += buff;
00176     }
00177     PION_LOG_DEBUG(m_logger, "createTable " + table_name);
00178 
00179     bool DidItExist = boost::filesystem::exists(dbPartition(m_database_name, partition));
00180 
00181     PION_LOG_DEBUG(m_logger, "createTable/exists (" << DidItExist << ")" << dbPartition(m_database_name, partition));
00182 
00183     // build a SQL query to create the output table if it doesn't yet exist
00184     std::string create_table_sql = m_create_log;
00185     stringSubstitutes(create_table_sql, field_map, table_name);
00186 
00187     // run the SQL query to create the table
00188     runQuery(create_table_sql, m_create_log_attr);
00189 
00190     std::string Sql = "PRAGMA table_info(" + table_name + ')';
00191     try {
00192         sqlite3_stmt *pStmt;
00193         if (sqlite3_prepare_v2(m_sqlite_db, Sql.c_str(), Sql.size(), &pStmt, NULL) == SQLITE_OK) {
00194             // In theory, we got the schema... let's play with it
00195 
00196             std::set<std::string> columns_found;    // A set of column names found in the schema
00197             while (sqlite3_step(pStmt) == SQLITE_ROW) {
00198                 // cid (0), name (1), type (2), notnull (3), dftl_value (4), pk (5)
00199                 // 0|epoch_time|INTEGER|0||0
00200                 const char *col = (const char *)sqlite3_column_text(pStmt, 1);  // get the "name" column (1)
00201                 if (col && *col) {
00202                     columns_found.insert(col);  // Add the col name into the set
00203                     PION_LOG_DEBUG(m_logger, "createTable/schemaCheck, found: " << col);
00204                 }
00205             }
00206             sqlite3_finalize(pStmt);
00207 
00208             // We'll find out if any column (in field_map) is missing... add via ALTER TABLE ADD COLUMN
00209             // Need to do this, before adding/checking indexes, in case some columns are missing
00210             for (unsigned p = 0; p < field_map.size(); p++) {
00211 
00212                 // Only try to add (ALTER TABLE ADD) if not found in the set of columns in schema
00213                 if (columns_found.find(field_map[p].first) == columns_found.end()) {
00214                     // SQLite3/ALTER: ALTER TABLE [db.]table ADD [COLUMN] coldef;
00215                     // SQLite3/coldef: colname [typename] n*[colconstraint]
00216                     Sql = "ALTER TABLE " + table_name + " ADD COLUMN " + field_map[p].first + ' ' +
00217                         m_sql_affinity[field_map[p].second.term_type];
00218                     PION_LOG_DEBUG(m_logger, "createTable, add column " + Sql);
00219                     // This would be the "right" way, checking columns, etc...
00220                     // if (sqlite3_exec(m_sqlite_db, Sql.c_str(), NULL, NULL, &m_error_ptr) != SQLITE_OK)
00221                     //  throw SQLiteAPIException(getSQLiteError());
00222                     // But, we're going to just "cram it in"
00223                     sqlite3_exec(m_sqlite_db, Sql.c_str(), NULL, NULL, &m_error_ptr);
00224                 }
00225             }
00226         } else
00227             sqlite3_finalize(pStmt);    // finalize away, if something went wrong
00228     } catch (...) {
00229     }
00230 
00231     // Find out if there's 1 or more rows (in which case, we won't touch the indexes)
00232     bool RowsInTable = true;    // We'll assume true, just in case anything goes wrong with detection, we're not going to munge the db
00233     try {
00234         Sql = "SELECT * FROM " + table_name + " LIMIT 1";
00235         sqlite3_stmt *pStmt;
00236         if (sqlite3_prepare_v2(m_sqlite_db, Sql.c_str(), Sql.size(), &pStmt, NULL) == SQLITE_OK)
00237             RowsInTable =(sqlite3_step(pStmt) == SQLITE_ROW);   // _step either found a row, or not...
00238         sqlite3_finalize(pStmt);                                // finalize the prepared statement away
00239     } catch (...) {
00240     }
00241 
00242 
00243     // CREATE [UNIQUE] INDEX [IF NOT EXISTS] [dbname.] indexname ON tablename ( indexcolumn [, indexcolumn] )
00244     //      indexcolumn:  indexcolumn [COLLATE collatename] [ ASC | DESC]
00245     // DROP INDEX [IF EXISTS] [dbname.] indexname
00246 
00247     if (!RowsInTable)   // Don't index/un-index if the table has rows...
00248         for (unsigned i = 0; i < index_map.size(); i++) {
00249             const std::string idxname = table_name + "_" + field_map[i].first + "_idx";
00250             if (index_map[i] == "false" || index_map[i].empty())
00251                 Sql = "DROP INDEX IF EXISTS " + idxname;
00252             else if (index_map[i] == "true")
00253                 Sql = "CREATE INDEX IF NOT EXISTS " + idxname + " ON " + table_name + " ( " + field_map[i].first + " )";
00254             else if (index_map[i] == "unique")
00255                 Sql = "CREATE UNIQUE INDEX IF NOT EXISTS " + idxname + " ON " + table_name + " ( " + field_map[i].first + " )";
00256             else
00257                 Sql = "CREATE INDEX IF NOT EXISTS " + idxname + " ON " + table_name + " ( " + index_map[i] + " )";
00258 
00259             PION_LOG_DEBUG(m_logger, "createTable/index: " + Sql);
00260             if (sqlite3_exec(m_sqlite_db, Sql.c_str(), NULL, NULL, &m_error_ptr) != SQLITE_OK)
00261                 throw SQLiteAPIException(getSQLiteError());
00262         }
00263 }
00264 
00265 void SQLiteDatabase::dropTable(std::string& table_name, unsigned partition)
00266 {
00267     m_partition = partition;
00268     if (m_sqlite_db) {
00269         close();
00270 //      if (unlink(dbPartition(table_name, partition).c_str()))
00271         if (unlink(dbPartition(m_database_name, partition).c_str()))
00272           throw SQLiteAPIException(strerror(errno) + dbPartition(m_database_name, partition));
00273 //        throw SQLiteAPIException(strerror(errno) + ":" + dbPartition(table_name, partition));
00274         open(partition);
00275     } else {
00276         if (unlink(dbPartition(m_database_name, partition).c_str()))
00277           // throw SQLiteAPIException(strerror(errno) + dbPartition(table_name, partition));
00278           ;
00279     }
00280 }
00281 
00282 // Testing whether a partitioned database/table exists...
00283 bool SQLiteDatabase::tableExists(std::string& table_name, unsigned partition)
00284 {
00285     m_partition = partition;    // We'll try to grab the partition number for later
00286     if (m_sqlite_db)            // Is a database handle open already?
00287         return true;            //  yes -> then there's a database... (too late to ask)
00288     else                        //  otherwise -> just find out if the database file exists
00289         return boost::filesystem::exists(dbPartition(m_database_name, partition));
00290 }
00291 
00292 QueryPtr SQLiteDatabase::prepareInsertQuery(const Query::FieldMap& field_map,
00293                                             const std::string& table_name)
00294 {
00295     PION_ASSERT(is_open());
00296 
00297     // exit early if it already exists
00298     QueryMap::const_iterator query_it = m_query_map.find(INSERT_QUERY_ID);
00299     if (query_it != m_query_map.end())
00300         return query_it->second;
00301 
00302     // build a SQL query that can be used to insert a new record
00303     std::string insert_sql = m_insert_log;
00304     stringSubstitutes(insert_sql, field_map, table_name);
00305 
00306     // compile the SQL query into a prepared statement
00307     return addQuery(Database::INSERT_QUERY_ID, insert_sql);
00308 }
00309 
00310 QueryPtr SQLiteDatabase::prepareInsertIgnoreQuery(const Query::FieldMap& field_map,
00311                                             const std::string& table_name)
00312 {
00313     PION_ASSERT(is_open());
00314 
00315     // exit early if it already exists
00316     QueryMap::const_iterator query_it = m_query_map.find(INSERT_IGNORE_QUERY_ID);
00317     if (query_it != m_query_map.end())
00318         return query_it->second;
00319 
00320     // build a SQL query that can be used to insert a new record
00321     std::string insert_sql = m_insert_ignore;
00322     stringSubstitutes(insert_sql, field_map, table_name);
00323 
00324     // compile the SQL query into a prepared statement
00325     return addQuery(Database::INSERT_IGNORE_QUERY_ID, insert_sql);
00326 }
00327 
00328 QueryPtr SQLiteDatabase::getBeginTransactionQuery(void)
00329 {
00330     PION_ASSERT(is_open());
00331     QueryMap::const_iterator i = m_query_map.find(BEGIN_QUERY_ID);
00332     if (i == m_query_map.end())
00333         return addQuery(BEGIN_QUERY_ID, m_begin_insert);
00334     return i->second;
00335 }
00336 
00337 QueryPtr SQLiteDatabase::getCommitTransactionQuery(void)
00338 {
00339     PION_ASSERT(is_open());
00340     QueryMap::const_iterator i = m_query_map.find(COMMIT_QUERY_ID);
00341     if (i == m_query_map.end())
00342         return addQuery(COMMIT_QUERY_ID, m_commit_insert);
00343     return i->second;
00344 }
00345 
00346 QueryPtr SQLiteDatabase::prepareFullQuery(const std::string& query, const boost::regex& suppress)
00347 {
00348     PION_ASSERT(is_open());
00349 
00350     QueryPtr query_ptr(new SQLiteQuery(query, m_sqlite_db));
00351     return query_ptr;
00352 }
00353 
00354 bool SQLiteDatabase::SQLiteQuery::runFullQuery(const pion::platform::Query::FieldMap& ins, const pion::platform::EventPtr& src,
00355     const pion::platform::Query::FieldMap& outs, pion::platform::EventPtr& dest, unsigned int limit, const boost::regex& suppress)
00356 {
00357     bool changes = false;
00358     sqlite3_reset(m_sqlite_stmt);
00359     bindEvent(ins, *src);
00360     while (sqlite3_step(m_sqlite_stmt) == SQLITE_ROW) {
00361         fetchEvent(outs, dest);
00362         changes = true;
00363         if (!--limit) return changes;
00364     }
00365     return changes;
00366 }
00367 
00368 bool SQLiteDatabase::SQLiteQuery::runFullGetMore(const pion::platform::Query::FieldMap& outs, pion::platform::EventPtr& dest,
00369     unsigned int limit)
00370 {
00371     bool changes = false;
00372     while (sqlite3_step(m_sqlite_stmt) == SQLITE_ROW) {
00373         fetchEvent(outs, dest);
00374         changes = true;
00375         if (!--limit) return changes;
00376     }
00377     return changes;
00378 }
00379 
00380 
00381 // SQLiteDatabase::SQLiteQuery member functions
00382 
00383 
00384 SQLiteDatabase::SQLiteQuery::SQLiteQuery(const std::string& sql_query, sqlite3 *db_ptr)
00385     : Query(sql_query), m_sqlite_db(db_ptr), m_sqlite_stmt(NULL)
00386 {
00387     PION_ASSERT(db_ptr != NULL);
00388     if (sqlite3_prepare_v2(m_sqlite_db, sql_query.c_str(), sql_query.size(),
00389                         &m_sqlite_stmt, NULL) != SQLITE_OK)
00390         SQLiteDatabase::throwAPIException(m_sqlite_db);
00391     PION_ASSERT(m_sqlite_stmt != NULL);
00392 }
00393 
00394 bool SQLiteDatabase::SQLiteQuery::run(void)
00395 {
00396     // step forward to the next row in the query (if there are any)
00397     bool row_available = false;
00398     switch (sqlite3_step(m_sqlite_stmt)) {
00399         case SQLITE_LOCKED:
00400         case SQLITE_BUSY:
00401             throw Database::DatabaseBusyException();
00402             break;
00403         case SQLITE_ROW:
00404             // a new result row is available
00405             row_available = true;
00406             break;
00407         case SQLITE_DONE:
00408             // query is finished; no more rows to return
00409             row_available = false;
00410             break;
00411         default:
00412             SQLiteDatabase::throwAPIException(m_sqlite_db);
00413             break;
00414     }
00415     return row_available;
00416 }
00417 
00418 
00419 bool SQLiteDatabase::SQLiteQuery::fetchRow(const FieldMap& field_map, EventPtr e)
00420 {
00421     bool row_available = false;
00422     switch (sqlite3_step(m_sqlite_stmt)) {
00423         case SQLITE_BUSY:
00424             throw SQLiteDatabase::DatabaseBusyException();
00425             break;
00426         case SQLITE_ROW:
00427             // a new result row is available
00428             fetchEvent(field_map, e);
00429             row_available = true;
00430             break;
00431         case SQLITE_DONE:
00432             // query is finished; no more rows to return
00433 //          row_available = false;
00434             break;
00435         default:
00436             SQLiteDatabase::throwAPIException(m_sqlite_db);
00437             break;
00438     }
00439     return row_available;
00440 }
00441 
00442 
00443 }   // end namespace plugins
00444 }   // end namespace pion
00445 
00446 
00448 extern "C" PION_PLUGIN_API pion::platform::Database *pion_create_SQLiteDatabase(void) {
00449     return new pion::plugins::SQLiteDatabase();
00450 }
00451 
00453 extern "C" PION_PLUGIN_API void pion_destroy_SQLiteDatabase(pion::plugins::SQLiteDatabase *database_ptr) {
00454     delete database_ptr;
00455 }

Generated on Wed Apr 13 16:38:34 2011 for pion-platform by  doxygen 1.4.7