Subversion Repositories public iLand

Rev

Rev 1221 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/********************************************************************************************
**    iLand - an individual based forest landscape and disturbance model
**    http://iland.boku.ac.at
**    Copyright (C) 2009-  Werner Rammer, Rupert Seidl
**
**    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 3 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, see <http://www.gnu.org/licenses/>.
********************************************************************************************/


#include "global.h"
#include "output.h"
#include <QtCore>
#include <QtSql>


/** @class Output
   The Output class abstracts output data (database, textbased, ...).
   To create a new output, create a class derived from Output and perform the following steps:
   - Overwrite constructor:
     Create columns and set fixed properties (e.g. table name)
   - overwrite setup()
     this function is called after the project file is read. You can access a XmlHelper calling settings()
     which is set to the top-node of the output (defined by tableName() which is set in the constructor). Access settings
     using relative xml-pathes (see example).
   - overwrite exec()
     add data using the stream operators or add() function of Output. Call writeRow() after each row. Each invokation
     of exec() is a database transaction.
   - Add the output to the constructor of @c OutputManager

   @par Example
   @code
   // (1) Overwrite constructor and set name, description and columns
   TreeOut::TreeOut()
    {
        setName("Tree Output", "tree");
        setDescription("Output of indivdual trees.");
        columns() << OutputColumn("id", "id of the tree", OutInteger)
                 << OutputColumn("name", "tree species name", OutString)
                 << OutputColumn("v1", "a double value", OutDouble);
     }
    // (2) optionally: some special settings (here: filter)
    void TreeOut::setup()
    {
        QString filter = settings().value(".filter","");
        if (filter!="")
            mFilter = QSharedPointer<Expression>(new Expression(filter));
    }

    // (3) the execution
    void TreeOut::exec()
    {
        AllTreeIterator at(GlobalSettings::instance()->model());
        while (Tree *t=at.next()) {
            if (mFilter && !mFilter->execute()) // skip if filter present
                continue;
            *this << t->id() << t->species()->id() << t->dbh(); // stream operators
            writeRow(); // executes DB insert
        }
    }
    // in outputmanager.cpp:
    OutputManager::OutputManager()
    {
        ...
        mOutputs.append(new TreeOut); // add the output
        ...
    }
    @endcode

*/

const GlobalSettings *Output::gl = GlobalSettings::instance();


void Output::exec()
{
    qDebug() << "Output::exec() called! (should be overrided!)";
}

void Output::setup()
{
}

Output::~Output()
{
    mInserter.clear();
}

Output::Output()
{
    mCount=0;
    mMode = OutDatabase;
    mOpen = false;
    mEnabled = false;
    newRow();
}


/** create the database table and opens up the output.
  */

void Output::openDatabase()
{
    QSqlDatabase db = GlobalSettings::instance()->dbout();
    // create the "create table" statement
    QString sql = "create table " +mTableName + "(";
    QString insert="insert into " + mTableName + " (";
    QString values;

    foreach(const OutputColumn &col, columns()) {
        switch(col.mDatatype) {
            case OutInteger: sql+=col.mName + " integer"; break;
            case OutDouble: sql+=col.mName + " real"; break;
            case OutString: sql+=col.mName + " text"; break;
        }
        insert+=col.mName+",";
        values+=QString(":")+col.mName+",";

        sql+=",";
    }
    sql[sql.length()-1]=')'; // replace last "," with )
    //qDebug()<< sql;
    if (mInserter.isValid())
        mInserter.clear();
    QSqlQuery creator(db);
    QString drop=QString("drop table if exists %1").arg(tableName());
    creator.exec(drop); // drop table (if exists)
    creator.exec(sql); // (re-)create table
    //creator.exec("delete from " + tableName()); // clear table??? necessary?

    if (creator.lastError().isValid()){
        throw IException(QString("Error creating output: %1").arg( creator.lastError().text()) );
    }
    insert[insert.length()-1]=')';
    values[values.length()-1]=')';
    insert += QString(" values (") + values;
    //qDebug() << insert;
    mInserter = QSqlQuery(db);
    mInserter.prepare(insert);
    if (mInserter.lastError().isValid()){
        throw IException(QString("Error creating output: %1").arg( mInserter.lastError().text()) );
    }
    for (int i=0;i<columns().count();i++)
        mInserter.bindValue(i,mRow[i]);

    mOpen = true;
}

void Output::newRow()
{
    mIndex = 0;
}



void Output::writeRow()
{
    DBG_IF(mIndex!=mCount, "Output::save()", "received invalid number of values!");
    if (!isOpen())
        open();
    switch(mMode) {
        case OutDatabase:
            saveDatabase(); break;
        default: throw IException("Invalid output mode");
    }

}
void Output::open()
{
    if (isOpen())
        return;
    // setup columns
    mCount = columns().count();
    mRow.resize(mCount);
    mOpen = true;
    newRow();
    // setup output
    switch(mMode) {
        case OutDatabase:
            openDatabase(); break;
        default: throw IException("Invalid output mode");
    }
}

void Output::close()
{
    if (!isOpen())
        return;
    mOpen = false;
    switch (mMode) {
        case OutDatabase:
            // calling finish() ensures, that the query and all locks are freed.
            // having (old) locks on database connections, degrades insert performance.
            if (mInserter.isValid())
                mInserter.finish();
            mInserter = QSqlQuery(); // clear inserter
         break;
        default:
         qWarning() << "Output::close with invalid mode";
    }

}


void Output::saveDatabase()
{
   for (int i=0;i<mCount;i++)
        mInserter.bindValue(i,mRow[i]);
    mInserter.exec();
    if (mInserter.lastError().isValid()){
        throw IException(QString("Error during saving of output tables: '%1'' (native code: '%2', driver: '%3')")
                         .arg( mInserter.lastError().text())
                         .arg(mInserter.lastError().nativeErrorCode())
                         .arg(mInserter.lastError().driverText()) );
    }

    newRow();
}

QString Output::wikiFormat() const
{
    QString result=QString("!!%1\nTable Name: %2\n%3\n\n").arg(name(), tableName(), description());
    // loop over columns...
    result += "||__caption__|__datatype__|__description__\n"; // table begin
    foreach(const OutputColumn &col, mColumns)
        result+=QString("%1|%2|%3\n").arg(col.name(), col.datatype(), col.description());
    result[result.length()-1]=' '; // clear last newline
    result+="||\n";
    return result;
}