#include "sqlitedb.h" #include "sqlbrowser_util.h" #include "MainWindow.h" #include #include #include #include #include #include #include void DBBrowserObject::addField(int order, const QString& wfield,const QString& wtype) { fldmap[order] = DBBrowserField(wfield,wtype); } bool DBBrowserDB::isOpen ( ) const { return _db!=0; } void DBBrowserDB::setDirty(bool dirtyval) { dirty = dirtyval; if(mainWindow) mainWindow->dbState(dirtyval); } bool DBBrowserDB::getDirty() const { return dirty; } void DBBrowserDB::setDefaultNewData( const QString & data ) { curNewData = data; } bool DBBrowserDB::open ( const QString & db) { bool ok=false; int err; if (isOpen()) close(); //try to verify the SQLite version 3 file header QFile dbfile(db); if ( dbfile.open( QIODevice::ReadOnly ) ) { char buffer[16+1]; dbfile.readLine(buffer, 16); QString contents = QString(buffer); dbfile.close(); if (!contents.startsWith("SQLite format 3")) { lastErrorMessage = QObject::tr("File is not a SQLite 3 database"); return false; } } else { lastErrorMessage = QObject::tr("File could not be read"); return false; } lastErrorMessage = QObject::tr("no error"); err = sqlite3_open_v2(db.toUtf8(), &_db, SQLITE_OPEN_READWRITE, NULL); if ( err ) { lastErrorMessage = QString::fromUtf8((const char*)sqlite3_errmsg(_db)); sqlite3_close(_db); _db = 0; return false; } if (_db){ // set preference defaults QSettings settings(QApplication::organizationName(), QApplication::organizationName()); settings.sync(); bool foreignkeys = settings.value( "/db/foreignkeys", false ).toBool(); setPragma("foreign_keys", foreignkeys ? "1" : "0"); if (SQLITE_OK==sqlite3_exec(_db,"PRAGMA empty_result_callbacks = ON;", NULL,NULL,NULL)){ if (SQLITE_OK==sqlite3_exec(_db,"PRAGMA show_datatypes = ON;", NULL,NULL,NULL)){ ok=true; setDirty(false); } curDBFilename = db; } } return ok; } bool DBBrowserDB::setRestorePoint() { if (!isOpen()) return false; if (dirty) return false; if (_db){ sqlite3_exec(_db,"SAVEPOINT RESTOREPOINT;", NULL,NULL,NULL); setDirty(true); } return true; } bool DBBrowserDB::save() { if (!isOpen()) return false; if (_db){ sqlite3_exec(_db,"RELEASE RESTOREPOINT;", NULL,NULL,NULL); setDirty(false); } return true; } bool DBBrowserDB::revert() { if (!isOpen()) return false; if (_db){ sqlite3_exec(_db,"ROLLBACK TO SAVEPOINT RESTOREPOINT;", NULL,NULL,NULL); sqlite3_exec(_db,"RELEASE RESTOREPOINT;", NULL,NULL,NULL); setDirty(false); } return true; } bool DBBrowserDB::create ( const QString & db) { bool ok=false; if (isOpen()) close(); lastErrorMessage = QObject::tr("no error"); // read encoding from settings and open with sqlite3_open for utf8 // and sqlite3_open16 for utf16 QSettings settings(QApplication::organizationName(), QApplication::organizationName()); QString sEncoding = settings.value("/db/defaultencoding", "UTF-8").toString(); int openresult = SQLITE_OK; if(sEncoding == "UTF-8" || sEncoding == "UTF8" || sEncoding == "Latin1") openresult = sqlite3_open(db.toUtf8(), &_db); else openresult = sqlite3_open16(db.utf16(), &_db); if( openresult != SQLITE_OK ){ lastErrorMessage = QString::fromUtf8((const char*)sqlite3_errmsg(_db)); sqlite3_close(_db); _db = 0; return false; } if (_db){ if (SQLITE_OK==sqlite3_exec(_db,"PRAGMA empty_result_callbacks = ON;", NULL,NULL,NULL)){ if (SQLITE_OK==sqlite3_exec(_db,"PRAGMA show_datatypes = ON;", NULL,NULL,NULL)){ ok=true; setDirty(false); } curDBFilename = db; } } return ok; } void DBBrowserDB::close (){ if (_db) { if (getDirty()) { QString msg = QObject::tr("Do you want to save the changes made to the database file %1?").arg(curDBFilename); if (QMessageBox::question( 0, QApplication::applicationName() ,msg, QMessageBox::Yes, QMessageBox::No)==QMessageBox::Yes) save(); else revert(); //not really necessary, I think... but will not hurt. } sqlite3_close(_db); } _db = 0; objMap.clear(); idmap.clear(); browseRecs.clear(); browseFields.clear(); hasValidBrowseSet = false; } bool DBBrowserDB::compact ( ) { char *errmsg; bool ok=false; if (!isOpen()) return false; if (_db){ save(); logSQL(QString("VACUUM;"), kLogMsg_App); if (SQLITE_OK==sqlite3_exec(_db,"VACUUM;", NULL,NULL,&errmsg)){ ok=true; setDirty(false); } } if (!ok){ lastErrorMessage = QString(errmsg); return false; }else{ return true; } } bool DBBrowserDB::reload( const QString & filename, int * lineErr) { /*to avoid a nested transaction error*/ sqlite3_exec(_db,"COMMIT;", NULL,NULL,NULL); FILE * cfile = fopen(filename.toUtf8(), (const char *) "r"); load_database(_db, cfile, lineErr); fclose(cfile); setDirty(false); if ((*lineErr)!=0) { return false; } return true; } bool DBBrowserDB::dump(const QString& filename) { // Open file QFile file(filename); if(file.open(QIODevice::WriteOnly)) { // Create progress dialog. For this count the number of all table rows to be exported first; this does neither take the table creation itself nor // indices, views or triggers into account but compared to the number of rows those should be neglectable unsigned int numRecordsTotal = 0, numRecordsCurrent = 0; QList tables = objMap.values("table"); for(QList::ConstIterator it=tables.begin();it!=tables.end();++it) numRecordsTotal += getFindResults(QString("SELECT COUNT(*) FROM `%1`;").arg((*it).getname())).value(0).toInt(); QProgressDialog progress(QObject::tr("Exporting database to SQL file..."), QObject::tr("Cancel"), 0, numRecordsTotal); progress.setWindowModality(Qt::ApplicationModal); // Regular expression to check for numeric strings QRegExp regexpIsNumeric("\\d*"); // Open text stream to the file QTextStream stream(&file); // Put the SQL commands in a transaction block stream << "BEGIN TRANSACTION;\n"; // Loop through all tables first as they are required to generate views, indices etc. later for(QList::ConstIterator it=tables.begin();it!=tables.end();++it) { // Write the SQL string used to create this table to the output file stream << (*it).getsql() << ";\n"; // Get data of this table browseTable((*it).getname()); // Dump all the content of the table rowList data = browseRecs; for(int row=0;rowprocessEvents(); if(progress.wasCanceled()) { file.close(); file.remove(); return false; } } } // Now dump all the other objects for(objectMap::ConstIterator it=objMap.begin();it!=objMap.end();++it) { // Make sure it's not a table again if(it.value().gettype() == "table") continue; // Write the SQL string used to create this object to the output file stream << (*it).getsql() << ";\n"; } // Done stream << "COMMIT;\n"; file.close(); return true; } else { return false; } } bool DBBrowserDB::executeSQL ( const QString & statement, bool dirtyDB, bool logsql) { char *errmsg; bool ok = false; if (!isOpen()) return false; if (_db){ if (logsql) logSQL(statement, kLogMsg_App); if (dirtyDB) setDirty(true); if (SQLITE_OK==sqlite3_exec(_db,statement.toUtf8(), NULL,NULL,&errmsg)){ ok=true; } } if (!ok){ lastErrorMessage = QString::fromUtf8(errmsg); qWarning() << "executeSQL: " << statement << "->" << lastErrorMessage; return false; } return true; } bool DBBrowserDB::addRecord ( ) { char *errmsg; if (!hasValidBrowseSet) return false; if (!isOpen()) return false; bool ok = false; int fields = browseFields.count(); QString emptyvalue = curNewData; QString statement = QString("INSERT INTO `%1` VALUES(").arg(curBrowseTableName); for ( int i=1; i<=fields; ++i ) { statement.append(emptyvalue); if (i0) {//table exists getTableRecords( tablename, orderby ); browseFields = testFields; hasValidBrowseSet = true; curBrowseTableName = tablename; } else { hasValidBrowseSet = false; curBrowseTableName = QString(" "); browseFields.clear(); browseRecs.clear(); idmap.clear(); } return hasValidBrowseSet; } bool DBBrowserDB::createTable(const QString& name, const QList& structure) { // Build SQL statement QString sql = QString("CREATE TABLE `%1` (").arg(name); for(int i=0;i new_table_structure; for(int i=0;i new_table_structure; QString select_cols; for(int i=0;i tmap = objMap.values("index"); QList::ConstIterator it; QStringList res; for ( it = tmap.begin(); it != tmap.end(); ++it ) { res.append( (*it).getname() ); } return res; } QStringList DBBrowserDB::getTableFields(const QString & tablename) const { objectMap::ConstIterator it; QStringList res; for ( it = objMap.begin(); it != objMap.end(); ++it ) { if((*it).getname() == tablename) { fieldMap::ConstIterator fit; for ( fit = (*it).fldmap.begin(); fit != (*it).fldmap.end(); ++fit ) { res.append( fit.value().getname() ); } } } return res; } QStringList DBBrowserDB::getTableTypes(const QString & tablename) const { objectMap::ConstIterator it; QStringList res; for ( it = objMap.begin(); it != objMap.end(); ++it ) { if((*it).getname() == tablename) { fieldMap::ConstIterator fit; for ( fit = (*it).fldmap.begin(); fit != (*it).fldmap.end(); ++fit ) { res.append( fit.value().gettype() ); } } } return res; } DBBrowserObject DBBrowserDB::getObjectByName(const QString& name) const { objectMap::ConstIterator it; QStringList res; for ( it = objMap.begin(); it != objMap.end(); ++it ) { if((*it).getname() == name) return *it; } return DBBrowserObject(); } int DBBrowserDB::getRecordCount() const { return browseRecs.count(); } void DBBrowserDB::logSQL(const QString& statement, int msgtype) { if(mainWindow) { /*limit log message to a sensible size, this will truncate some binary messages*/ int loglimit = 300; if ((statement.length() > loglimit)&&(msgtype==kLogMsg_App)) { QString logst = statement; logst.truncate(32); logst.append(QObject::tr("... ...")); mainWindow->logSql(logst, msgtype); } else { mainWindow->logSql(statement, msgtype); } } } void DBBrowserDB::updateSchema( ) { sqlite3_stmt *vm; const char *tail; int err=0; objMap.clear(); lastErrorMessage = QObject::tr("no error"); QString statement = "SELECT type, name, sql FROM sqlite_master;"; QByteArray utf8Statement = statement.toUtf8(); err=sqlite3_prepare(_db, utf8Statement, utf8Statement.length(), &vm, &tail); if (err == SQLITE_OK){ logSQL(statement, kLogMsg_App); while ( sqlite3_step(vm) == SQLITE_ROW ){ QString val1 = QString::fromUtf8((const char*)sqlite3_column_text(vm, 0)); QString val2 = QString::fromUtf8((const char*)sqlite3_column_text(vm, 1)); QString val3 = QString::fromUtf8((const char*)sqlite3_column_text(vm, 2)); if(val1 == "table" || val1 == "index" || val1 == "view" || val1 == "trigger") objMap.insert(val1, DBBrowserObject(val2, val3, val1)); else qDebug(QObject::tr("unknown object type %1").arg(val1).toStdString().c_str()); } sqlite3_finalize(vm); }else{ qDebug(QObject::tr("could not get list of db objects: %1, %2").arg(err).arg(sqlite3_errmsg(_db)).toStdString().c_str()); } //now get the field list for each table objectMap::Iterator it; for ( it = objMap.begin(); it != objMap.end(); ++it ) { if((*it).gettype() == "table" || (*it).gettype() == "view") { statement = QString("PRAGMA TABLE_INFO(`%1`);").arg((*it).getname()); logSQL(statement, kLogMsg_App); err=sqlite3_prepare(_db,statement.toUtf8(),statement.length(), &vm, &tail); if (err == SQLITE_OK){ (*it).fldmap.clear(); int e = 0; while ( sqlite3_step(vm) == SQLITE_ROW ){ if (sqlite3_column_count(vm)==6) { int ispk= 0; QString val1 = QString::fromUtf8((const char *) sqlite3_column_text(vm, 1)); QString val2 = QString::fromUtf8((const char *) sqlite3_column_text(vm, 2)); ispk = sqlite3_column_int(vm, 5); if(ispk==1) val2.append(QString(" PRIMARY KEY")); (*it).addField(e, val1, val2); e++; } } sqlite3_finalize(vm); } else{ lastErrorMessage = QObject::tr("could not get types"); } } } } QStringList DBBrowserDB::decodeCSV(const QString & csvfilename, char sep, char quote, int maxrecords, int * numfields) { QFile file(csvfilename); QStringList result; QString current = ""; bool inquotemode = false; bool inescapemode = false; int recs = 0; *numfields = 0; if ( file.open( QIODevice::ReadWrite ) ) { QProgressDialog progress(QObject::tr("Decoding CSV file..."), QObject::tr("Cancel"), 0, file.size()); progress.setWindowModality(Qt::ApplicationModal); char c=0; while(file.getChar(&c)) { if (c==quote){ if (inquotemode){ if (inescapemode){ inescapemode = false; //add the escaped char here current.append(c); } else { //are we escaping, or just finishing the quote? char d; file.getChar(&d); if (d==quote) { inescapemode = true; } else { inquotemode = false; } file.ungetChar(d); } } else { inquotemode = true; } } else if (c==sep) { if (inquotemode){ //add the sep here current.append(c); } else { //not quoting, start new record result << current; current = ""; } } else if (c==10) { if (inquotemode){ //add the newline current.append(c); } else { //not quoting, start new record result << current; current = ""; //for the first line, store the field count if (*numfields == 0){ *numfields = result.count(); } recs++; progress.setValue(file.pos()); qApp->processEvents(); if (progress.wasCanceled()) break; if ((recs>maxrecords)&&(maxrecords!=-1)) { break; } } } else if (c==13) { if (inquotemode){ //add the carrier return if in quote mode only current.append(c); } } else {//another character type current.append(c); } } file.close(); //do we still have a last result, not appended? //proper csv files should end with a linefeed , so this is not necessary //if (current.length()>0) result << current; } return result; } QString DBBrowserDB::getPragma(const QString& pragma) { if(!isOpen()) return ""; QString sql = QString("PRAGMA %1").arg(pragma); sqlite3_stmt* vm; const char* tail; QString retval = ""; // Get value from DB int err = sqlite3_prepare(_db, sql.toUtf8(), sql.toUtf8().length(), &vm, &tail); if(err == SQLITE_OK){ logSQL(sql, kLogMsg_App); if(sqlite3_step(vm) == SQLITE_ROW) { retval = QString::fromUtf8((const char *) sqlite3_column_text(vm, 0)); //qDebug((pragma + ": " + retval).toStdString().c_str()); } else { qDebug(QObject::tr("didn't receive any output from pragma %1").arg(pragma).toStdString().c_str()); } sqlite3_finalize(vm); } else { qDebug(QObject::tr("could not execute pragma command: %1, %2").arg(err).arg(sqlite3_errmsg(_db)).toStdString().c_str()); } // Return it return retval; } bool DBBrowserDB::setPragma(const QString& pragma, const QString& value) { // Set the pragma value QString sql = QString("PRAGMA %1 = \"%2\";").arg(pragma).arg(value); //qDebug(sql.toStdString().c_str()); save(); bool res = executeSQL(sql, false, true); // PRAGMA statements are usually not transaction bound, so we can't revert if( !res ) qDebug(QObject::tr("Error setting pragma %1 to %2: %3").arg(pragma).arg(value).arg(lastErrorMessage).toStdString().c_str()); return res; } bool DBBrowserDB::setPragma(const QString& pragma, const QString& value, QString& originalvalue) { if( originalvalue != value ) { if( setPragma(pragma, value)) { originalvalue = value; return true; } } return false; } bool DBBrowserDB::setPragma(const QString& pragma, int value, int& originalvalue) { if( originalvalue != value ) { QString val = QString::number(value); QString origval = QString::number(originalvalue); if( setPragma(pragma, val, origval)) { originalvalue = value; } } return false; }