/********************************************************************** * * sqlitedbclass.cpp -- * Copyright (C) 2008 DL Consulting Ltd * * A component of the Greenstone digital library software * from the New Zealand Digital Library Project at the * University of Waikato, New Zealand. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * *********************************************************************/ #include "sqlitedbclass.h" #include "gsdlunicode.h" #include "unitool.h" #ifdef __WIN32__ // for Sleep # include #else // for usleep # include #endif #define SQLITE_MAX_RETRIES 8 sqlitedbclass::sqlitedbclass() { sqlitefile = NULL; } sqlitedbclass::~sqlitedbclass() { closedatabase(); } // returns true if opened bool sqlitedbclass::opendatabase (const text_t &filename, int mode, int num_retrys, #ifdef __WIN32__ bool need_filelock #else bool #endif ) { // Check if we've already got the database open if (sqlitefile != NULL) { if (openfile == filename) return true; else closedatabase(); } char *filename_cstr = filename.getcstr(); sqlite3_open(filename_cstr, &sqlitefile); delete[] filename_cstr; if (sqlitefile == NULL) { (*logout) << "ERROR: sqlitedbclass::opendatabase() failed on: " << filename << "\n"; return false; } if ((mode == DB_WRITER || mode == DB_WRITER_CREATE) && !sqltableexists("data")) { sqlexec("CREATE TABLE data (key TEXT, value TEXT, PRIMARY KEY(key))"); } openfile = filename; return true; } void sqlitedbclass::closedatabase () { if (sqlitefile == NULL) return; sqlite3_close(sqlitefile); sqlitefile = NULL; openfile.clear(); } void sqlitedbclass::deletekey (const text_t &key) { text_t sql_cmd = "DELETE FROM data WHERE key='" + sql_safe(key) + "'"; sqlexec(sql_cmd); } // returns array of document OIDs text_tarray sqlitedbclass::get_documents_with_metadata_value (const text_tarray &metadata_element_names, const text_t &metadata_value, const text_t &sort_by_metadata_element_name) { text_tarray document_OIDs; // Check at least one metadata element and a metadata value has been specified if (metadata_element_names.empty() || metadata_value == "") { return document_OIDs; } // Get the entries in the "document_metadata" table where the element and value matches those specified text_t sql_cmd = "SELECT DISTINCT docOID FROM document_metadata WHERE element IN ('" + sql_safe(metadata_element_names[0]) + "'"; for (int i = 1; i < metadata_element_names.size(); i++) { sql_cmd += ",'" + sql_safe(metadata_element_names[i]) + "'"; } sql_cmd += ") AND value='" + sql_safe(metadata_value) + "'"; // If we're sorting the documents by a certain metadata element, extend the SQL command to do this if (sort_by_metadata_element_name != "") { sql_cmd = "SELECT docOID FROM (" + sql_cmd + ") LEFT JOIN (SELECT docOID,value from document_metadata WHERE element='" + sql_safe(sort_by_metadata_element_name) + "') USING (docOID) ORDER by value"; } // Perform the SQL request vector sql_results; if (!sqlgetarray(sql_cmd, sql_results) || sql_results.size() == 0) { return document_OIDs; } // Iterate through the documents and add them to the array to be returned vector::iterator sql_results_iterator = sql_results.begin(); while (sql_results_iterator != sql_results.end()) { text_tmap sql_result = (*sql_results_iterator); document_OIDs.push_back(sql_result["docOID"]); sql_results_iterator++; } return document_OIDs; } // returns file extension string text_t sqlitedbclass::getfileextension () { return ".db"; } // returns true on success bool sqlitedbclass::getkeydata (const text_t& key, text_t &data) { text_t sql_cmd = "SELECT value FROM data WHERE key='" + sql_safe(key) + "'"; vector sql_results; if (!sqlgetarray(sql_cmd, sql_results) || sql_results.size() == 0) { return false; } text_tmap sql_result = sql_results[0]; data = sql_result["value"]; return true; } // returns array of keys text_tarray sqlitedbclass::getkeys () { text_tarray keys; // Get all the entries in the "key" column of the table text_t sql_cmd = "SELECT key FROM data"; vector sql_results; if (!sqlgetarray(sql_cmd, sql_results) || sql_results.size() == 0) { return keys; } // Iterate through the keys and add them to the array to be returned vector::iterator sql_results_iterator = sql_results.begin(); while (sql_results_iterator != sql_results.end()) { text_tmap sql_result = (*sql_results_iterator); keys.push_back(sql_result["key"]); sql_results_iterator++; } return keys; } // returns array of values text_tarray sqlitedbclass::get_metadata_values (const text_tarray &metadata_element_names, const text_t &metadata_value_filter, const text_t &metadata_value_grouping_expression) { text_tarray metadata_values; // Check at least one metadata element has been specified if (metadata_element_names.empty()) { return metadata_values; } // Get the raw "value" field unless a grouping expression was provided (in this case an edited value is returned) text_t value_select_expression = "value"; if (metadata_value_grouping_expression != "") { value_select_expression = metadata_value_grouping_expression; } // Get the entries in the "document_metadata" table where the element matches that specified text_t sql_cmd = "SELECT DISTINCT docOID," + value_select_expression + " FROM document_metadata WHERE element IN ('" + sql_safe(metadata_element_names[0]) + "'"; for (int i = 1; i < metadata_element_names.size(); i++) { sql_cmd += ",'" + sql_safe(metadata_element_names[i]) + "'"; } sql_cmd += ")"; // Add value filter, if one has been defined if (metadata_value_filter != "") { sql_cmd += " AND value GLOB '" + sql_safe(metadata_value_filter) + "'"; } // Perform the SQL request vector sql_results; if (!sqlgetarray(sql_cmd, sql_results) || sql_results.size() == 0) { return metadata_values; } // Iterate through the values and add them to the array to be returned vector::iterator sql_results_iterator = sql_results.begin(); while (sql_results_iterator != sql_results.end()) { text_tmap sql_result = (*sql_results_iterator); metadata_values.push_back(sql_result[value_select_expression]); sql_results_iterator++; } return metadata_values; } // returns true on success bool sqlitedbclass::setkeydata (const text_t &key, const text_t &data) { // We need to do either an INSERT or UPDATE depending on whether the key already exists if (!exists(key)) { text_t sql_cmd = "INSERT INTO data (key, value) VALUES ('" + sql_safe(key) + "', '" + sql_safe(data) + "')"; return sqlexec(sql_cmd); } else { text_t sql_cmd = "UPDATE data SET value='" + sql_safe(data) + "' WHERE key='" + sql_safe(key) + "'"; return sqlexec(sql_cmd); } } // ---------------------------------------------------------------------------------------- // SQLITE-ONLY FUNCTIONS // ---------------------------------------------------------------------------------------- // sleep for the given number of milliseconds void sleep(int m) { #ifdef __WIN32__ Sleep(m); #else usleep(m); #endif } text_t sqlitedbclass::sql_safe (const text_t &value_arg) { text_t value = value_arg; value.replace("'", "''"); return value; } // sqlexec simply executes the given sql statement - it doesn't obtain a // result set - returns true if the sql statement was executed successfully bool sqlitedbclass::sqlexec(const text_t &sql_cmd) { if (sqlitefile == NULL) return false; char *sql_cmd_cstr = sql_cmd.getcstr(); int rv = 0; int tries = 0; while ((rv = sqlite3_exec(sqlitefile, sql_cmd_cstr, NULL, NULL, NULL)) == SQLITE_BUSY) { sleep(1000); tries++; if (tries > SQLITE_MAX_RETRIES) { outconvertclass text_t2ascii; (*logout) << text_t2ascii << "max_retries exceeded for sql query: " << sql_cmd << "\n"; break; } } delete[] sql_cmd_cstr; if (rv == SQLITE_OK) return true; // sqlite3_exec failed - return false outconvertclass text_t2ascii; (*logout) << text_t2ascii << "Error executing sql statement: " << sql_cmd << ", rv: " << rv << "\n"; return false; } // callback functions for sqlgetarray static int sqlgetarray_callback(void *res, int numcols, char **vals, char **columnnames) { vector *result = (vector*) res; text_tmap row; for (int i = 0; i < numcols; i++) { row[columnnames[i]] = ""; // vals[i] will be NULL if set to a NULL db value if (vals[i]) { row[columnnames[i]] = to_uni(vals[i]); } } result->push_back(row); return 0; } // sqlgetarray executes sql and returns the result set in sql_results bool sqlitedbclass::sqlgetarray(const text_t &sql_cmd, vector &sql_results) { if (sqlitefile == NULL) return false; char *sql_cmd_cstr = sql_cmd.getcstr(); sql_results.erase(sql_results.begin(), sql_results.end()); int rv = 0; int tries = 0; while ((rv = sqlite3_exec(sqlitefile, sql_cmd_cstr, sqlgetarray_callback, &sql_results, NULL)) == SQLITE_BUSY) { sleep(1000); tries++; if (tries > SQLITE_MAX_RETRIES) { outconvertclass text_t2ascii; (*logout) << text_t2ascii << "max_retries exceeded for sql query: " << sql_cmd << "\n"; break; } } delete[] sql_cmd_cstr; if (rv == SQLITE_OK) return true; // sqlite3_exec failed - return empty result set outconvertclass text_t2ascii; (*logout) << text_t2ascii << "Error executing sql statement: " << sql_cmd << "\n"; return false; } // returns true if exists bool sqlitedbclass::sqltableexists(const text_t &table_name) { text_t sql_cmd = "SELECT * FROM sqlite_master WHERE tbl_name='" + sql_safe(table_name) + "'"; vector sql_results; if (!sqlgetarray(sql_cmd, sql_results) || sql_results.size() == 0) { return false; } return true; }