diff options
Diffstat (limited to 'src/qt3support/sql/q3sqlcursor.cpp')
-rw-r--r-- | src/qt3support/sql/q3sqlcursor.cpp | 1519 |
1 files changed, 1519 insertions, 0 deletions
diff --git a/src/qt3support/sql/q3sqlcursor.cpp b/src/qt3support/sql/q3sqlcursor.cpp new file mode 100644 index 0000000..5bc23c1 --- /dev/null +++ b/src/qt3support/sql/q3sqlcursor.cpp @@ -0,0 +1,1519 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qplatformdefs.h> +#include "q3sqlcursor.h" + +#ifndef QT_NO_SQL + +#include "qsqldriver.h" +#include "qsqlresult.h" +#include "qdatetime.h" +#include "qsqldatabase.h" +#include "qsql.h" +#include "q3sqlrecordinfo.h" +#include "q3sqlfieldinfo.h" + +QT_BEGIN_NAMESPACE + +class Q3SqlCursorPrivate +{ +public: + + Q3SqlCursorPrivate(const QString& name, QSqlDatabase sdb) + : lastAt(QSql::BeforeFirst), nm(name), srt(name), md(0), db(sdb), q(0) + {} + ~Q3SqlCursorPrivate() + { + delete q; + } + + QSqlQuery* query() + { + if (!q) + q = new QSqlQuery(QString(), db); + return q; + } + + int lastAt; + QString nm; //name + QSqlIndex srt; //sort + QString ftr; //filter + int md; //mode + QSqlIndex priIndx; //primary index + QSqlRecord editBuffer; + // the primary index as it was before the user changed the values in editBuffer + QString editIndex; + Q3SqlRecordInfo infoBuffer; + QSqlDatabase db; + QSqlQuery *q; +}; + +QString qOrderByClause(const QSqlIndex & i, const QString& prefix = QString()) +{ + QString str; + int k = i.count(); + if(k == 0) + return QString(); + str = QLatin1String(" order by ") + i.toString(prefix); + return str; +} + +QString qWhereClause(const QString& prefix, QSqlField* field, const QSqlDriver* driver) +{ + QString f; + if (field && driver) { + if (!prefix.isEmpty()) + f += prefix + QLatin1Char('.'); + f += field->name(); + if (field->isNull()) { + f += QLatin1String(" IS NULL"); + } else { + f += QLatin1String(" = ") + driver->formatValue(field); + } + } + return f; +} + +QString qWhereClause(QSqlRecord* rec, const QString& prefix, const QString& sep, + const QSqlDriver* driver) +{ + static QString blank(QLatin1Char(' ')); + QString filter; + bool separator = false; + for (int j = 0; j < rec->count(); ++j) { + QSqlField f = rec->field(j); + if (rec->isGenerated(j)) { + if (separator) + filter += sep + blank; + filter += qWhereClause(prefix, &f, driver); + filter += blank; + separator = true; + } + } + return filter; +} + +/*! + \class Q3SqlCursor + \brief The Q3SqlCursor class provides browsing and editing of SQL + tables and views. + + \compat + + A Q3SqlCursor is a database record (see \l QSqlRecord) that + corresponds to a table or view within an SQL database (see \l + QSqlDatabase). There are two buffers in a cursor, one used for + browsing and one used for editing records. Each buffer contains a + list of fields which correspond to the fields in the table or + view. + + When positioned on a valid record, the browse buffer contains the + values of the current record's fields from the database. The edit + buffer is separate, and is used for editing existing records and + inserting new records. + + For browsing data, a cursor must first select() data from the + database. After a successful select() the cursor is active + (isActive() returns true), but is initially not positioned on a + valid record (isValid() returns false). To position the cursor on + a valid record, use one of the navigation functions, next(), + previous(), first(), last(), or seek(). Once positioned on a valid + record, data can be retrieved from the browse buffer using + value(). If a navigation function is not successful, it returns + false, the cursor will no longer be positioned on a valid record + and the values returned by value() are undefined. + + For example: + + \snippet doc/src/snippets/code/src_qt3support_sql_q3sqlcursor.cpp 0 + + In the above example, a cursor is created specifying a table or + view name in the database. Then, select() is called, which can be + optionally parameterised to filter and order the records + retrieved. Each record in the cursor is retrieved using next(). + When next() returns false, there are no more records to process, + and the loop terminates. + + For editing records (rows of data), a cursor contains a separate + edit buffer which is independent of the fields used when browsing. + The functions insert(), update() and del() operate on the edit + buffer. This allows the cursor to be repositioned to other + records while simultaneously maintaining a separate buffer for + edits. You can get a pointer to the edit buffer using + editBuffer(). The primeInsert(), primeUpdate() and primeDelete() + functions also return a pointer to the edit buffer and prepare it + for insert, update and delete respectively. Edit operations only + affect a single row at a time. Note that update() and del() + require that the table or view contain a primaryIndex() to ensure + that edit operations affect a unique record within the database. + + For example: + + \snippet doc/src/snippets/code/src_qt3support_sql_q3sqlcursor.cpp 1 + + To edit an existing database record, first move to the record you + wish to update. Call primeUpdate() to get the pointer to the + cursor's edit buffer. Then use this pointer to modify the values + in the edit buffer. Finally, call update() to save the changes to + the database. The values in the edit buffer will be used to + locate the appropriate record when updating the database (see + primaryIndex()). + + Similarly, when deleting an existing database record, first move + to the record you wish to delete. Then, call primeDelete() to get + the pointer to the edit buffer. Finally, call del() to delete the + record from the database. Again, the values in the edit buffer + will be used to locate and delete the appropriate record. + + To insert a new record, call primeInsert() to get the pointer to + the edit buffer. Use this pointer to populate the edit buffer + with new values and then insert() the record into the database. + + After calling insert(), update() or del(), the cursor is no longer + positioned on a valid record and can no longer be navigated + (isValid() return false). The reason for this is that any changes + made to the database will not be visible until select() is called + to refresh the cursor. You can change this behavior by passing + false to insert(), update() or del() which will prevent the cursor + from becoming invalid. The edits will still not be visible when + navigating the cursor until select() is called. + + Q3SqlCursor contains virtual methods which allow editing behavior + to be customized by subclasses. This allows custom cursors to be + created that encapsulate the editing behavior of a database table + for an entire application. For example, a cursor can be customized + to always auto-number primary index fields, or provide fields with + suitable default values, when inserting new records. Q3SqlCursor + generates SQL statements which are sent to the database engine; + you can control which fields are included in these statements + using setGenerated(). + + Note that Q3SqlCursor does not inherit from QObject. This means + that you are responsible for destroying instances of this class + yourself. However if you create a Q3SqlCursor and use it in a + \l Q3DataTable, \l Q3DataBrowser or a \l Q3DataView these classes will + usually take ownership of the cursor and destroy it when they + don't need it anymore. The documentation for Q3DataTable, + Q3DataBrowser and Q3DataView explicitly states which calls take + ownership of the cursor. +*/ + +/*! + \enum Q3SqlCursor::Mode + + This enum type describes how Q3SqlCursor operates on records in the + database. + + \value ReadOnly the cursor can only SELECT records from the + database. + + \value Insert the cursor can INSERT records into the database. + + \value Update the cursor can UPDATE records in the database. + + \value Delete the cursor can DELETE records from the database. + + \value Writable the cursor can INSERT, UPDATE and DELETE records + in the database. +*/ + +/*! + \fn QVariant Q3SqlCursor::value(const QString &name) const + + \overload + + Returns the value of the field named \a name. +*/ + +/*! + \fn void Q3SqlCursor::setValue(const QString &name, const QVariant &val) + + \overload + + Sets the value for the field named \a name to \a val. +*/ + +/*! + Constructs a cursor on database \a db using table or view \a name. + + If \a autopopulate is true (the default), the \a name of the + cursor must correspond to an existing table or view name in the + database so that field information can be automatically created. + If the table or view does not exist, the cursor will not be + functional. + + The cursor is created with an initial mode of Q3SqlCursor::Writable + (meaning that records can be inserted, updated or deleted using + the cursor). If the cursor does not have a unique primary index, + update and deletes cannot be performed. + + Note that \a autopopulate refers to populating the cursor with + meta-data, e.g. the names of the table's fields, not with + retrieving data. The select() function is used to populate the + cursor with data. + + \sa setName() setMode() +*/ + +Q3SqlCursor::Q3SqlCursor(const QString & name, bool autopopulate, QSqlDatabase db) + : QSqlRecord(), QSqlQuery(QString(), db) +{ + d = new Q3SqlCursorPrivate(name, db); + setMode(Writable); + if (!d->nm.isEmpty()) + setName(d->nm, autopopulate); +} + +/*! + Constructs a copy of \a other. +*/ + +Q3SqlCursor::Q3SqlCursor(const Q3SqlCursor & other) + : QSqlRecord(other), QSqlQuery(other) +{ + d = new Q3SqlCursorPrivate(other.d->nm, other.d->db); + d->lastAt = other.d->lastAt; + d->nm = other.d->nm; + d->srt = other.d->srt; + d->ftr = other.d->ftr; + d->priIndx = other.d->priIndx; + d->editBuffer = other.d->editBuffer; + d->infoBuffer = other.d->infoBuffer; + d->q = 0; // do not share queries + setMode(other.mode()); +} + +/*! + Destroys the object and frees any allocated resources. +*/ + +Q3SqlCursor::~Q3SqlCursor() +{ + delete d; +} + +/*! + Sets the cursor equal to \a other. +*/ + +Q3SqlCursor& Q3SqlCursor::operator=(const Q3SqlCursor& other) +{ + QSqlRecord::operator=(other); + QSqlQuery::operator=(other); + delete d; + d = new Q3SqlCursorPrivate(other.d->nm, other.d->db); + d->lastAt = other.d->lastAt; + d->nm = other.d->nm; + d->srt = other.d->srt; + d->ftr = other.d->ftr; + d->priIndx = other.d->priIndx; + d->editBuffer = other.d->editBuffer; + d->infoBuffer = other.d->infoBuffer; + d->q = 0; // do not share queries + setMode(other.mode()); + return *this; +} + +/*! + Sets the current sort to \a sort. Note that no new records are + selected. To select new records, use select(). The \a sort will + apply to any subsequent select() calls that do not explicitly + specify a sort. +*/ + +void Q3SqlCursor::setSort(const QSqlIndex& sort) +{ + d->srt = sort; +} + +/*! + Returns the current sort, or an empty index if there is no current + sort. +*/ +QSqlIndex Q3SqlCursor::sort() const +{ + return d->srt; +} + +/*! + Sets the current filter to \a filter. Note that no new records are + selected. To select new records, use select(). The \a filter will + apply to any subsequent select() calls that do not explicitly + specify a filter. + + The filter is a SQL \c WHERE clause without the keyword 'WHERE', + e.g. \c{name='Dave'} which will be processed by the DBMS. +*/ +void Q3SqlCursor::setFilter(const QString& filter) +{ + d->ftr = filter; +} + +/*! + Returns the current filter, or an empty string if there is no + current filter. +*/ +QString Q3SqlCursor::filter() const +{ + return d->ftr; +} + +/*! + Sets the name of the cursor to \a name. If \a autopopulate is true + (the default), the \a name must correspond to a valid table or + view name in the database. Also, note that all references to the + cursor edit buffer become invalidated when fields are + auto-populated. See the Q3SqlCursor constructor documentation for + more information. +*/ +void Q3SqlCursor::setName(const QString& name, bool autopopulate) +{ + d->nm = name; + if (autopopulate) { + if (driver()) { + d->infoBuffer = driver()->record(name); + *this = d->infoBuffer.toRecord(); + d->editBuffer = *this; + d->priIndx = driver()->primaryIndex(name); + } + if (isEmpty()) + qWarning("Q3SqlCursor::setName: unable to build record, does '%s' exist?", name.latin1()); + } +} + +/*! + Returns the name of the cursor. +*/ + +QString Q3SqlCursor::name() const +{ + return d->nm; +} + +/*! \internal +*/ + +QString Q3SqlCursor::toString(const QString& prefix, const QString& sep) const +{ + QString pflist; + QString pfix = prefix.isEmpty() ? prefix : prefix + QLatin1Char('.'); + bool comma = false; + + for (int i = 0; i < count(); ++i) { + const QString fname = fieldName(i); + if (isGenerated(i)) { + if(comma) + pflist += sep + QLatin1Char(' '); + pflist += pfix + driver()->escapeIdentifier(fname, QSqlDriver::FieldName); + comma = true; + } + } + return pflist; +} + +/*! + \internal + + Assigns the record \a list. + +*/ +QSqlRecord & Q3SqlCursor::operator=(const QSqlRecord & list) +{ + return QSqlRecord::operator=(list); +} + +/*! + Append a copy of field \a fieldInfo to the end of the cursor. Note + that all references to the cursor edit buffer become invalidated. +*/ + +void Q3SqlCursor::append(const Q3SqlFieldInfo& fieldInfo) +{ + d->editBuffer.append(fieldInfo.toField()); + d->infoBuffer.append(fieldInfo); + QSqlRecord::append(fieldInfo.toField()); +} + +/*! + Removes all fields from the cursor. Note that all references to + the cursor edit buffer become invalidated. +*/ +void Q3SqlCursor::clear() +{ + d->editBuffer.clear(); + d->infoBuffer.clear(); + QSqlRecord::clear(); +} + + +/*! + Insert a copy of \a fieldInfo at position \a pos. If a field + already exists at \a pos, it is removed. Note that all references + to the cursor edit buffer become invalidated. +*/ + +void Q3SqlCursor::insert(int pos, const Q3SqlFieldInfo& fieldInfo) +{ + d->editBuffer.replace(pos, fieldInfo.toField()); + d->infoBuffer[pos] = fieldInfo; + QSqlRecord::replace(pos, fieldInfo.toField()); +} + +/*! + Removes the field at \a pos. If \a pos does not exist, nothing + happens. Note that all references to the cursor edit buffer become + invalidated. +*/ + +void Q3SqlCursor::remove(int pos) +{ + d->editBuffer.remove(pos); + d->infoBuffer[pos] = Q3SqlFieldInfo(); + QSqlRecord::remove(pos); +} + +/*! + Sets the generated flag for the field \a name to \a generated. If + the field does not exist, nothing happens. Only fields that have + \a generated set to true are included in the SQL that is + generated by insert(), update() or del(). +*/ + +void Q3SqlCursor::setGenerated(const QString& name, bool generated) +{ + int pos = indexOf(name); + if (pos == -1) + return; + QSqlRecord::setGenerated(name, generated); + d->editBuffer.setGenerated(name, generated); + d->infoBuffer[pos].setGenerated(generated); +} + +/*! + \overload + + Sets the generated flag for the field \a i to \a generated. +*/ +void Q3SqlCursor::setGenerated(int i, bool generated) +{ + if (i < 0 || i >= (int)d->infoBuffer.count()) + return; + QSqlRecord::setGenerated(i, generated); + d->editBuffer.setGenerated(i, generated); + d->infoBuffer[i].setGenerated(generated); +} + +/*! + Returns the primary index associated with the cursor as defined in + the database, or an empty index if there is no primary index. If + \a setFromCursor is true (the default), the index fields are + populated with the corresponding values in the cursor's current + record. +*/ + +QSqlIndex Q3SqlCursor::primaryIndex(bool setFromCursor) const +{ + if (setFromCursor) { + for (int i = 0; i < d->priIndx.count(); ++i) { + const QString fn = d->priIndx.fieldName(i); + if (contains(fn)) + d->priIndx.setValue(i, QSqlRecord::value(fn)); + } + } + return d->priIndx; +} + +/*! + Sets the primary index associated with the cursor to the index \a + idx. Note that this index must contain a field or set of fields + which identify a unique record within the underlying database + table or view so that update() and del() will execute as expected. + + \sa update() del() +*/ + +void Q3SqlCursor::setPrimaryIndex(const QSqlIndex& idx) +{ + d->priIndx = idx; +} + + +/*! + Returns an index composed of \a fieldNames, all in ASCending + order. Note that all field names must exist in the cursor, + otherwise an empty index is returned. + + \sa QSqlIndex +*/ + +QSqlIndex Q3SqlCursor::index(const QStringList& fieldNames) const +{ + QSqlIndex idx; + for (QStringList::ConstIterator it = fieldNames.begin(); it != fieldNames.end(); ++it) { + QSqlField f = field((*it)); + if (!f.isValid()) { /* all fields must exist */ + idx.clear(); + break; + } + idx.append(f); + } + return idx; +} + +/*! + \overload + + Returns an index based on \a fieldName. +*/ + +QSqlIndex Q3SqlCursor::index(const QString& fieldName) const +{ + QStringList fl(fieldName); + return index(fl); +} + +/*! + Selects all fields in the cursor from the database matching the + filter criteria \a filter. The data is returned in the order + specified by the index \a sort. Returns true if the data was + successfully selected; otherwise returns false. + + The \a filter is a string containing a SQL \c WHERE clause but + without the 'WHERE' keyword. The cursor is initially positioned at + an invalid row after this function is called. To move to a valid + row, use seek(), first(), last(), previous() or next(). + + Example: + \snippet doc/src/snippets/code/src_qt3support_sql_q3sqlcursor.cpp 2 + + The filter will apply to any subsequent select() calls that do not + explicitly specify another filter. Similarly the sort will apply + to any subsequent select() calls that do not explicitly specify + another sort. + + \snippet doc/src/snippets/code/src_qt3support_sql_q3sqlcursor.cpp 3 + +*/ + +bool Q3SqlCursor::select(const QString & filter, const QSqlIndex & sort) +{ + QString fieldList(toString(d->nm)); + if (fieldList.isEmpty()) + return false; + QString str(QLatin1String("select ") + fieldList); + str += QLatin1String(" from ") + d->nm; + if (!filter.isEmpty()) { + d->ftr = filter; + str += QLatin1String(" where ") + filter; + } else + d->ftr.clear(); + if (sort.count() > 0) + str += QLatin1String(" order by ") + sort.toString(d->nm); + d->srt = sort; + return exec(str); +} + +/*! + \overload + + Selects all fields in the cursor from the database. The rows are + returned in the order specified by the last call to setSort() or + the last call to select() that specified a sort, whichever is the + most recent. If there is no current sort, the order in which the + rows are returned is undefined. The records are filtered according + to the filter specified by the last call to setFilter() or the + last call to select() that specified a filter, whichever is the + most recent. If there is no current filter, all records are + returned. The cursor is initially positioned at an invalid row. To + move to a valid row, use seek(), first(), last(), previous() or + next(). + + \sa setSort() setFilter() +*/ + +bool Q3SqlCursor::select() +{ + return select(filter(), sort()); +} + +/*! + \overload + + Selects all fields in the cursor from the database. The data is + returned in the order specified by the index \a sort. The records + are filtered according to the filter specified by the last call to + setFilter() or the last call to select() that specified a filter, + whichever is the most recent. The cursor is initially positioned + at an invalid row. To move to a valid row, use seek(), first(), + last(), previous() or next(). +*/ + +bool Q3SqlCursor::select(const QSqlIndex& sort) +{ + return select(filter(), sort); +} + +/*! + \overload + + Selects all fields in the cursor matching the filter index \a + filter. The data is returned in the order specified by the index + \a sort. The \a filter index works by constructing a WHERE clause + using the names of the fields from the \a filter and their values + from the current cursor record. The cursor is initially positioned + at an invalid row. To move to a valid row, use seek(), first(), + last(), previous() or next(). This function is useful, for example, + for retrieving data based upon a table's primary index: + + \snippet doc/src/snippets/code/src_qt3support_sql_q3sqlcursor.cpp 4 + + In this example the QSqlIndex, pk, is used for two different + purposes. When used as the filter (first) argument, the field + names it contains are used to construct the WHERE clause, each set + to the current cursor value, \c{WHERE id=10}, in this case. When + used as the sort (second) argument the field names it contains are + used for the ORDER BY clause, \c{ORDER BY id} in this example. +*/ + +bool Q3SqlCursor::select(const QSqlIndex & filter, const QSqlIndex & sort) +{ + return select(toString(filter, this, d->nm, QString(QLatin1Char('=')), QLatin1String("and")), sort); +} + +/*! + Sets the cursor mode to \a mode. This value can be an OR'ed + combination of \l Q3SqlCursor::Mode values. The default mode for a + cursor is Q3SqlCursor::Writable. + + \snippet doc/src/snippets/code/src_qt3support_sql_q3sqlcursor.cpp 5 +*/ + +void Q3SqlCursor::setMode(int mode) +{ + d->md = mode; +} + +/*! + Returns the current cursor mode. + + \sa setMode() +*/ + +int Q3SqlCursor::mode() const +{ + return d->md; +} + +/*! + Sets field \a name to \a calculated. If the field \a name does not + exist, nothing happens. The value of a calculated field is set by + the calculateField() virtual function which you must reimplement + (or the field value will be an invalid QVariant). Calculated + fields do not appear in generated SQL statements sent to the + database. + + \sa calculateField() +*/ + +void Q3SqlCursor::setCalculated(const QString& name, bool calculated) +{ + int pos = indexOf(name); + if (pos < 0) + return; + d->infoBuffer[pos].setCalculated(calculated); + if (calculated) + setGenerated(pos, false); +} + +/*! + Returns true if the field \a name exists and is calculated; + otherwise returns false. + + \sa setCalculated() +*/ + +bool Q3SqlCursor::isCalculated(const QString& name) const +{ + int pos = indexOf(name); + if (pos < 0) + return false; + return d->infoBuffer[pos].isCalculated(); +} + +/*! + Sets field \a{name}'s trimmed status to \a trim. If the field \a + name does not exist, nothing happens. + + When a trimmed field of type string is read from the + database any trailing (right-most) spaces are removed. + + \sa isTrimmed() QVariant +*/ + +void Q3SqlCursor::setTrimmed(const QString& name, bool trim) +{ + int pos = indexOf(name); + if (pos < 0) + return; + d->infoBuffer[pos].setTrim(trim); +} + +/*! + Returns true if the field \a name exists and is trimmed; otherwise + returns false. + + When a trimmed field of type string or cstring is read from the + database any trailing (right-most) spaces are removed. + + \sa setTrimmed() +*/ + +bool Q3SqlCursor::isTrimmed(const QString& name) const +{ + int pos = indexOf(name); + if (pos < 0) + return false; + return d->infoBuffer[pos].isTrim(); +} + +/*! + Returns true if the cursor is read-only; otherwise returns false. + The default is false. Read-only cursors cannot be edited using + insert(), update() or del(). + + \sa setMode() +*/ + +bool Q3SqlCursor::isReadOnly() const +{ + return d->md == 0; +} + +/*! + Returns true if the cursor will perform inserts; otherwise returns + false. + + \sa setMode() +*/ + +bool Q3SqlCursor::canInsert() const +{ + return ((d->md & Insert) == Insert) ; +} + + +/*! + Returns true if the cursor will perform updates; otherwise returns + false. + + \sa setMode() +*/ + +bool Q3SqlCursor::canUpdate() const +{ + return ((d->md & Update) == Update) ; +} + +/*! + Returns true if the cursor will perform deletes; otherwise returns + false. + + \sa setMode() +*/ + +bool Q3SqlCursor::canDelete() const +{ + return ((d->md & Delete) == Delete) ; +} + +/*! + \overload + + Returns a formatted string composed of the \a prefix (e.g. table + or view name), ".", the \a field name, the \a fieldSep and the + field value. If the \a prefix is empty then the string will begin + with the \a field name. This function is useful for generating SQL + statements. +*/ + +QString Q3SqlCursor::toString(const QString& prefix, QSqlField* field, const QString& fieldSep) const +{ + QString f; + if (field && driver()) { + f = (prefix.length() > 0 ? prefix + QLatin1Char('.') : QString()) + field->name(); + f += QLatin1Char(' ') + fieldSep + QLatin1Char(' '); + if (field->isNull()) { + f += QLatin1String("NULL"); + } else { + f += driver()->formatValue(field); + } + } + return f; +} + +/*! + Returns a formatted string composed of all the fields in \a rec. + Each field is composed of the \a prefix (e.g. table or view name), + ".", the field name, the \a fieldSep and the field value. If the + \a prefix is empty then each field will begin with the field name. + The fields are then joined together separated by \a sep. Fields + where isGenerated() returns false are not included. This function + is useful for generating SQL statements. +*/ + +QString Q3SqlCursor::toString(QSqlRecord* rec, const QString& prefix, const QString& fieldSep, + const QString& sep) const +{ + static QString blank(QLatin1Char(' ')); + QString filter; + bool separator = false; + for (int j = 0; j < count(); ++j) { + QSqlField f = rec->field(j); + if (rec->isGenerated(j)) { + if (separator) + filter += sep + blank; + filter += toString(prefix, &f, fieldSep); + filter += blank; + separator = true; + } + } + return filter; +} + +/*! + \overload + + Returns a formatted string composed of all the fields in the index + \a i. Each field is composed of the \a prefix (e.g. table or view + name), ".", the field name, the \a fieldSep and the field value. + If the \a prefix is empty then each field will begin with the field + name. The field values are taken from \a rec. The fields are then + joined together separated by \a sep. Fields where isGenerated() + returns false are ignored. This function is useful for generating + SQL statements. +*/ + +QString Q3SqlCursor::toString(const QSqlIndex& i, QSqlRecord* rec, const QString& prefix, + const QString& fieldSep, const QString& sep) const +{ + QString filter; + bool separator = false; + for(int j = 0; j < i.count(); ++j){ + if (rec->isGenerated(j)) { + if(separator) { + filter += QLatin1Char(' ') + sep + QLatin1Char(' ') ; + } + QString fn = i.fieldName(j); + QSqlField f = rec->field(fn); + filter += toString(prefix, &f, fieldSep); + separator = true; + } + } + return filter; +} + +/*! + Inserts the current contents of the cursor's edit record buffer + into the database, if the cursor allows inserts. Returns the + number of rows affected by the insert. For error information, use + lastError(). + + If \a invalidate is true (the default), the cursor will no longer + be positioned on a valid record and can no longer be navigated. A + new select() call must be made before navigating to a valid + record. + + \snippet doc/src/snippets/code/src_qt3support_sql_q3sqlcursor.cpp 6 + + In the above example, a cursor is created on the 'prices' table + and a pointer to the insert buffer is acquired using primeInsert(). + Each field's value is set to the desired value and then insert() + is called to insert the data into the database. Remember: all edit + operations (insert(), update() and delete()) operate on the + contents of the cursor edit buffer and not on the contents of the + cursor itself. + + \sa setMode() lastError() +*/ + +int Q3SqlCursor::insert(bool invalidate) +{ + if ((d->md & Insert) != Insert || !driver()) + return false; + int k = d->editBuffer.count(); + if (k == 0) + return 0; + + QString fList; + QString vList; + bool comma = false; + // use a prepared query if the driver supports it + if (driver()->hasFeature(QSqlDriver::PreparedQueries)) { + int cnt = 0; + bool oraStyle = driver()->hasFeature(QSqlDriver::NamedPlaceholders); + for(int j = 0; j < k; ++j) { + QSqlField f = d->editBuffer.field(j); + if (d->editBuffer.isGenerated(j)) { + if (comma) { + fList += QLatin1Char(','); + vList += QLatin1Char(','); + } + fList += driver()->escapeIdentifier(f.name(), QSqlDriver::FieldName); + vList += (oraStyle == true) ? QLatin1String(":f") + QString::number(cnt) : QString(QLatin1Char('?')); + cnt++; + comma = true; + } + } + if (!comma) { + return 0; + } + QString str; + str.append(QLatin1String("insert into ")).append(name()) + .append(QLatin1String(" (")).append(fList) + .append(QLatin1String(") values (")).append(vList). append(QLatin1Char(')')); + + return applyPrepared(str, invalidate); + } else { + for(int j = 0; j < k; ++j) { + QSqlField f = d->editBuffer.field(j); + if (d->editBuffer.isGenerated(j)) { + if (comma) { + fList += QLatin1Char(','); + vList += QLatin1Char(','); + } + fList += driver()->escapeIdentifier(f.name(), QSqlDriver::FieldName); + vList += driver()->formatValue(&f); + comma = true; + } + } + + if (!comma) { + // no valid fields found + return 0; + } + QString str; + str.append(QLatin1String("insert into ")).append(name()).append(QLatin1String(" (")) + .append(fList).append(QLatin1String(") values (")).append(vList). append (QLatin1String(")")); + return apply(str, invalidate); + } +} + +/*! + Returns the current internal edit buffer. If \a copy is true (the + default is false), the current cursor field values are first + copied into the edit buffer. The edit buffer is valid as long as + the cursor remains valid. The cursor retains ownership of the + returned pointer, so it must not be deleted or modified. + + \sa primeInsert(), primeUpdate() primeDelete() +*/ + +QSqlRecord* Q3SqlCursor::editBuffer(bool copy) +{ + sync(); + if (copy) { + for(int i = 0; i < d->editBuffer.count(); i++) { + if (QSqlRecord::isNull(i)) { + d->editBuffer.setNull(i); + } else { + d->editBuffer.setValue(i, value(i)); + } + } + } + return &d->editBuffer; +} + +/*! + This function primes the edit buffer's field values for update and + returns the edit buffer. The default implementation copies the + field values from the current cursor record into the edit buffer + (therefore, this function is equivalent to calling editBuffer( + true)). The cursor retains ownership of the returned pointer, so + it must not be deleted or modified. + + \sa editBuffer() update() +*/ + +QSqlRecord* Q3SqlCursor::primeUpdate() +{ + // memorize the primary keys as they were before the user changed the values in editBuffer + QSqlRecord* buf = editBuffer(true); + QSqlIndex idx = primaryIndex(false); + if (!idx.isEmpty()) + d->editIndex = toString(idx, buf, d->nm, QString(QLatin1Char('=')), QLatin1String("and")); + else + d->editIndex = qWhereClause(buf, d->nm, QLatin1String("and"), driver()); + return buf; +} + +/*! + This function primes the edit buffer's field values for delete and + returns the edit buffer. The default implementation copies the + field values from the current cursor record into the edit buffer + (therefore, this function is equivalent to calling editBuffer( + true)). The cursor retains ownership of the returned pointer, so + it must not be deleted or modified. + + \sa editBuffer() del() +*/ + +QSqlRecord* Q3SqlCursor::primeDelete() +{ + return editBuffer(true); +} + +/*! + This function primes the edit buffer's field values for insert and + returns the edit buffer. The default implementation clears all + field values in the edit buffer. The cursor retains ownership of + the returned pointer, so it must not be deleted or modified. + + \sa editBuffer() insert() +*/ + +QSqlRecord* Q3SqlCursor::primeInsert() +{ + d->editBuffer.clearValues(); + return &d->editBuffer; +} + + +/*! + Updates the database with the current contents of the edit buffer. + Returns the number of records which were updated. + For error information, use lastError(). + + Only records which meet the filter criteria specified by the + cursor's primary index are updated. If the cursor does not contain + a primary index, no update is performed and 0 is returned. + + If \a invalidate is true (the default), the current cursor can no + longer be navigated. A new select() call must be made before you + can move to a valid record. For example: + + \snippet doc/src/snippets/code/src_qt3support_sql_q3sqlcursor.cpp 7 + + In the above example, a cursor is created on the 'prices' table + and is positioned on the record to be updated. Then a pointer to + the cursor's edit buffer is acquired using primeUpdate(). A new + value is calculated and placed into the edit buffer with the + setValue() call. Finally, an update() call is made on the cursor + which uses the tables's primary index to update the record in the + database with the contents of the cursor's edit buffer. Remember: + all edit operations (insert(), update() and delete()) operate on + the contents of the cursor edit buffer and not on the contents of + the cursor itself. + + Note that if the primary index does not uniquely distinguish + records the database may be changed into an inconsistent state. + + \sa setMode() lastError() +*/ + +int Q3SqlCursor::update(bool invalidate) +{ + if (d->editIndex.isEmpty()) + return 0; + return update(d->editIndex, invalidate); +} + +/*! + \overload + + Updates the database with the current contents of the cursor edit + buffer using the specified \a filter. Returns the number of + records which were updated. + For error information, use lastError(). + + Only records which meet the filter criteria are updated, otherwise + all records in the table are updated. + + If \a invalidate is true (the default), the cursor can no longer + be navigated. A new select() call must be made before you can move + to a valid record. + + \sa primeUpdate() setMode() lastError() +*/ + +int Q3SqlCursor::update(const QString & filter, bool invalidate) +{ + if ((d->md & Update) != Update) { + return false; + } + int k = count(); + if (k == 0) { + return 0; + } + + // use a prepared query if the driver supports it + if (driver()->hasFeature(QSqlDriver::PreparedQueries)) { + QString fList; + bool comma = false; + int cnt = 0; + bool oraStyle = driver()->hasFeature(QSqlDriver::NamedPlaceholders); + for(int j = 0; j < k; ++j) { + QSqlField f = d->editBuffer.field(j); + if (d->editBuffer.isGenerated(j)) { + if (comma) { + fList += QLatin1Char(','); + } + fList += f.name() + QLatin1String(" = ") + (oraStyle == true ? QLatin1String(":f") + QString::number(cnt) : QString(QLatin1Char('?'))); + cnt++; + comma = true; + } + } + if (!comma) { + return 0; + } + QString str(QLatin1String("update ") + name() + QLatin1String(" set ") + fList); + if (filter.length()) { + str+= QLatin1String(" where ") + filter; + } + return applyPrepared(str, invalidate); + } else { + QString str = QLatin1String("update ") + name(); + str += QLatin1String(" set ") + toString(&d->editBuffer, QString(), QString(QLatin1Char('=')), QString(QLatin1Char(','))); + if (filter.length()) { + str+= QLatin1String(" where ") + filter; + } + return apply(str, invalidate); + } +} + +/*! + Deletes a record from the database using the cursor's primary + index and the contents of the cursor edit buffer. Returns the + number of records which were deleted. + For error information, use lastError(). + + Only records which meet the filter criteria specified by the + cursor's primary index are deleted. If the cursor does not contain + a primary index, no delete is performed and 0 is returned. If \a + invalidate is true (the default), the current cursor can no longer + be navigated. A new select() call must be made before you can move + to a valid record. For example: + + \snippet doc/src/snippets/code/src_qt3support_sql_q3sqlcursor.cpp 8 + + In the above example, a cursor is created on the 'prices' table + and positioned to the record to be deleted. First primeDelete() is + called to populate the edit buffer with the current cursor values, + e.g. with an id of 999, and then del() is called to actually + delete the record from the database. Remember: all edit operations + (insert(), update() and delete()) operate on the contents of the + cursor edit buffer and not on the contents of the cursor itself. + + \sa primeDelete() setMode() lastError() +*/ + +int Q3SqlCursor::del(bool invalidate) +{ + QSqlIndex idx = primaryIndex(false); + if (idx.isEmpty()) + return del(qWhereClause(&d->editBuffer, d->nm, QLatin1String("and"), driver()), invalidate); + return del(toString(primaryIndex(), &d->editBuffer, d->nm, QString(QLatin1Char('=')), QLatin1String("and")), invalidate); +} + +/*! + \overload + + Deletes the current cursor record from the database using the + filter \a filter. Only records which meet the filter criteria are + deleted. Returns the number of records which were deleted. If \a + invalidate is true (the default), the current cursor can no longer + be navigated. A new select() call must be made before you can move + to a valid record. For error information, use lastError(). + + The \a filter is an SQL \c WHERE clause, e.g. \c{id=500}. + + \sa setMode() lastError() +*/ + +int Q3SqlCursor::del(const QString & filter, bool invalidate) +{ + if ((d->md & Delete) != Delete) + return 0; + int k = count(); + if(k == 0) return 0; + QString str = QLatin1String("delete from ") + name(); + if (filter.length()) + str+= QLatin1String(" where ") + filter; + return apply(str, invalidate); +} + +/* + \internal +*/ + +int Q3SqlCursor::apply(const QString& q, bool invalidate) +{ + int ar = 0; + if (invalidate) { + if (exec(q)) + ar = numRowsAffected(); + } else if (driver()) { + QSqlQuery* sql = d->query(); + if (sql && sql->exec(q)) + ar = sql->numRowsAffected(); + } + return ar; +} + +/* + \internal +*/ + +int Q3SqlCursor::applyPrepared(const QString& q, bool invalidate) +{ + int ar = 0; + QSqlQuery* sql = 0; + + if (invalidate) { + sql = (QSqlQuery*)this; + d->lastAt = QSql::BeforeFirst; + } else { + sql = d->query(); + } + if (!sql) + return 0; + + if (invalidate || sql->lastQuery() != q) { + if (!sql->prepare(q)) + return 0; + } + + int cnt = 0; + int fieldCount = (int)count(); + for (int j = 0; j < fieldCount; ++j) { + const QSqlField f = d->editBuffer.field(j); + if (d->editBuffer.isGenerated(j)) { + if (f.type() == QVariant::ByteArray) + sql->bindValue(cnt, f.value(), QSql::In | QSql::Binary); + else + sql->bindValue(cnt, f.value()); + cnt++; + } + } + if (sql->exec()) { + ar = sql->numRowsAffected(); + } + return ar; +} + +/*! + Executes the SQL query \a sql. Returns true of the cursor is + active, otherwise returns false. +*/ +bool Q3SqlCursor::exec(const QString & sql) +{ + d->lastAt = QSql::BeforeFirst; + QSqlQuery::exec(sql); + return isActive(); +} + +/*! + Protected virtual function which is called whenever a field needs + to be calculated. If calculated fields are being used, derived + classes must reimplement this function and return the appropriate + value for field \a name. The default implementation returns an + invalid QVariant. + + \sa setCalculated() +*/ + +QVariant Q3SqlCursor::calculateField(const QString&) +{ + return QVariant(); +} + +/*! \internal + Ensure fieldlist is synced with query. + +*/ + +static QString qTrim(const QString& s) +{ + QString result = s; + int end = result.length() - 1; + while (end >= 0 && result[end].isSpace()) // skip white space from end + end--; + result.truncate(end + 1); + return result; +} + +/*! \internal + */ + +void Q3SqlCursor::sync() +{ + if (isActive() && isValid() && d->lastAt != at()) { + d->lastAt = at(); + int i = 0; + int j = 0; + bool haveCalculatedFields = false; + for (; i < count(); ++i) { + if (!haveCalculatedFields && d->infoBuffer[i].isCalculated()) { + haveCalculatedFields = true; + } + if (QSqlRecord::isGenerated(i)) { + QVariant v = QSqlQuery::value(j); + if ((v.type() == QVariant::String) && + d->infoBuffer[i].isTrim()) { + v = qTrim(v.toString()); + } + QSqlRecord::setValue(i, v); + if (QSqlQuery::isNull(j)) + QSqlRecord::field(i).clear(); + j++; + } + } + if (haveCalculatedFields) { + for (i = 0; i < count(); ++i) { + if (d->infoBuffer[i].isCalculated()) + QSqlRecord::setValue(i, calculateField(fieldName(i))); + } + } + } +} + +/*! + Returns the value of field number \a i. +*/ + +QVariant Q3SqlCursor::value(int i) const +{ + const_cast<Q3SqlCursor *>(this)->sync(); + return QSqlRecord::value(i); +} + +/*! \internal + cursors should be filled with Q3SqlFieldInfos... +*/ +void Q3SqlCursor::append(const QSqlField& field) +{ + append(Q3SqlFieldInfo(field)); +} + +/*! + Returns true if the field \a i is NULL or if there is no field at + position \a i; otherwise returns false. + + This is the same as calling QSqlRecord::isNull(\a i) +*/ +bool Q3SqlCursor::isNull(int i) const +{ + const_cast<Q3SqlCursor *>(this)->sync(); + return QSqlRecord::isNull(i); +} +/*! + \overload + + Returns true if the field called \a name is NULL or if there is no + field called \a name; otherwise returns false. + + This is the same as calling QSqlRecord::isNull(\a name) +*/ +bool Q3SqlCursor::isNull(const QString& name) const +{ + const_cast<Q3SqlCursor *>(this)->sync(); + return QSqlRecord::isNull(name); +} + +/*! \internal */ +void Q3SqlCursor::setValue(int i, const QVariant& val) +{ + sync(); +#ifdef QT_DEBUG + qDebug("Q3SqlCursor::setValue(): This will not affect actual database values. Use primeInsert(), primeUpdate() or primeDelete()."); +#endif + QSqlRecord::setValue(i, val); +} + +/*! \internal */ +bool Q3SqlCursor::seek(int i, bool relative) +{ + bool res = QSqlQuery::seek(i, relative); + sync(); + return res; +} + +/*! \internal */ +bool Q3SqlCursor::next() +{ + bool res = QSqlQuery::next(); + sync(); + return res; +} + +/*! + \fn Q3SqlCursor::previous() + + \internal +*/ + +/*! \internal */ +bool Q3SqlCursor::prev() +{ + bool res = QSqlQuery::previous(); + sync(); + return res; +} + +/*! \internal */ +bool Q3SqlCursor::first() +{ + bool res = QSqlQuery::first(); + sync(); + return res; +} + +/*! \internal */ +bool Q3SqlCursor::last() +{ + bool res = QSqlQuery::last(); + sync(); + return res; +} + +QT_END_NAMESPACE + +#endif |