File indexing completed on 2024-06-23 11:12:20

0001 /* This file is part of the KDE project
0002    Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at>
0003    Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org>
0004    Copyright (C) 2003-2017 Jarosław Staniek <staniek@kde.org>
0005    Copyright (C) 2014 Michał Poteralski <michalpoteralskikde@gmail.com>
0006 
0007    This program is free software; you can redistribute it and/or
0008    modify it under the terms of the GNU Library General Public
0009    License as published by the Free Software Foundation; either
0010    version 2 of the License, or (at your option) any later version.
0011 
0012    This program is distributed in the hope that it will be useful,
0013    but WITHOUT ANY WARRANTY; without even the implied warranty of
0014    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0015    Library General Public License for more details.
0016 
0017    You should have received a copy of the GNU Library General Public License
0018    along with this program; see the file COPYING.  If not, write to
0019    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0020  * Boston, MA 02110-1301, USA.
0021 
0022    Original Author:  Till Busch <till@bux.at>
0023    Original Project: buX (www.bux.at)
0024 */
0025 
0026 #include "KDbTableViewData.h"
0027 #include "KDbConnection.h"
0028 #include "KDbConnectionOptions.h"
0029 #include "KDbCursor.h"
0030 #include "KDbError.h"
0031 #include "KDb.h"
0032 #include "KDbOrderByColumn.h"
0033 #include "KDbQuerySchema.h"
0034 #include "KDbRecordEditBuffer.h"
0035 #include "KDbTableViewColumn.h"
0036 #include "kdb_debug.h"
0037 
0038 #include <QApplication>
0039 
0040 #include <unicode/coll.h>
0041 
0042 // #define TABLEVIEW_NO_PROCESS_EVENTS
0043 
0044 static unsigned short charTable[] = {
0045 #include "chartable.txt"
0046 };
0047 
0048 //-------------------------------
0049 
0050 //! @internal Unicode-aware collator for comparing strings
0051 class CollatorInstance
0052 {
0053 public:
0054     CollatorInstance() {
0055         UErrorCode status = U_ZERO_ERROR;
0056         m_collator = icu::Collator::createInstance(status);
0057         if (U_FAILURE(status)) {
0058             kdbWarning() << "Could not create instance of collator:" << status;
0059             m_collator = nullptr;
0060         } else {
0061             // enable normalization by default
0062             m_collator->setAttribute(UCOL_NORMALIZATION_MODE, UCOL_ON, status);
0063             if (U_FAILURE(status)) {
0064                 kdbWarning() << "Could not set collator attribute:" << status;
0065             }
0066         }
0067     }
0068 
0069     icu::Collator* getCollator() {
0070         return m_collator;
0071     }
0072 
0073     ~CollatorInstance() {
0074         delete m_collator;
0075     }
0076 
0077 private:
0078     icu::Collator *m_collator;
0079 };
0080 
0081 Q_GLOBAL_STATIC(CollatorInstance, KDb_collator)
0082 
0083 //! @internal A functor used in std::sort() in order to sort by a given column
0084 class LessThanFunctor
0085 {
0086 private:
0087     KDbOrderByColumn::SortOrder m_order;
0088     QVariant m_leftTmp, m_rightTmp;
0089     int m_sortColumn;
0090 
0091     bool (*m_lessThanFunction)(const QVariant&, const QVariant&);
0092 
0093 #define CAST_AND_COMPARE(casting) \
0094     return left.casting() < right.casting()
0095 
0096     static bool cmpInt(const QVariant& left, const QVariant& right) {
0097         CAST_AND_COMPARE(toInt);
0098     }
0099 
0100     static bool cmpUInt(const QVariant& left, const QVariant& right) {
0101         CAST_AND_COMPARE(toUInt);
0102     }
0103 
0104     static bool cmpLongLong(const QVariant& left, const QVariant& right) {
0105         CAST_AND_COMPARE(toLongLong);
0106     }
0107 
0108     static bool cmpULongLong(const QVariant& left, const QVariant& right) {
0109         CAST_AND_COMPARE(toULongLong);
0110     }
0111 
0112     static bool cmpDouble(const QVariant& left, const QVariant& right) {
0113         CAST_AND_COMPARE(toDouble);
0114     }
0115 
0116     static bool cmpDate(const QVariant& left, const QVariant& right) {
0117         CAST_AND_COMPARE(toDate);
0118     }
0119 
0120     static bool cmpDateTime(const QVariant& left, const QVariant& right) {
0121         CAST_AND_COMPARE(toDateTime);
0122     }
0123 
0124     static bool cmpTime(const QVariant& left, const QVariant& right) {
0125         CAST_AND_COMPARE(toDate);
0126     }
0127 
0128     static bool cmpString(const QVariant& left, const QVariant& right) {
0129         const QString &as = left.toString();
0130         const QString &bs = right.toString();
0131 
0132         const QChar *a = as.isEmpty() ? nullptr : as.unicode();
0133         const QChar *b = bs.isEmpty() ? nullptr : bs.unicode();
0134 
0135         if (a == nullptr) {
0136             return b != nullptr;
0137         }
0138         if (a == b || b == nullptr) {
0139             return false;
0140         }
0141 
0142         int len = qMin(as.length(), bs.length());
0143         forever {
0144             unsigned short au = a->unicode();
0145             unsigned short bu = b->unicode();
0146             au = (au <= 0x17e ? charTable[au] : 0xffff);
0147             bu = (bu <= 0x17e ? charTable[bu] : 0xffff);
0148 
0149             if (len <= 0)
0150                 return false;
0151             len--;
0152 
0153             if (au != bu)
0154                 return au < bu;
0155             a++;
0156             b++;
0157         }
0158         return false;
0159     }
0160 
0161     static bool cmpStringWithCollator(const QVariant& left, const QVariant& right) {
0162         const QString &as = left.toString();
0163         const QString &bs = right.toString();
0164         return icu::Collator::LESS == KDb_collator->getCollator()->compare(
0165                                         (const UChar *)as.constData(), as.size(),
0166                                         (const UChar *)bs.constData(), bs.size());
0167     }
0168 
0169     //! Compare function for BLOB data (QByteArray). Uses size as the weight.
0170     static bool cmpBLOB(const QVariant& left, const QVariant& right) {
0171         return left.toByteArray().size() < right.toByteArray().size();
0172     }
0173 
0174 public:
0175     LessThanFunctor()
0176             : m_order(KDbOrderByColumn::SortOrder::Ascending)
0177             , m_sortColumn(-1)
0178             , m_lessThanFunction(nullptr)
0179     {
0180     }
0181 
0182     void setColumnType(const KDbField& field) {
0183         const KDbField::Type t = field.type();
0184         if (field.isTextType())
0185             m_lessThanFunction = &cmpString;
0186         if (KDbField::isFPNumericType(t))
0187             m_lessThanFunction = &cmpDouble;
0188         else if (t == KDbField::Integer && field.isUnsigned())
0189             m_lessThanFunction = &cmpUInt;
0190         else if (t == KDbField::Boolean || KDbField::isNumericType(t))
0191             m_lessThanFunction = &cmpInt; //other integers
0192         else if (t == KDbField::BigInteger) {
0193             if (field.isUnsigned())
0194                 m_lessThanFunction = &cmpULongLong;
0195             else
0196                 m_lessThanFunction = &cmpLongLong;
0197         } else if (t == KDbField::Date)
0198             m_lessThanFunction = &cmpDate;
0199         else if (t == KDbField::Time)
0200             m_lessThanFunction = &cmpTime;
0201         else if (t == KDbField::DateTime)
0202             m_lessThanFunction = &cmpDateTime;
0203         else if (t == KDbField::BLOB)
0204             //! @todo allow users to define BLOB sorting function?
0205             m_lessThanFunction = &cmpBLOB;
0206         else { // anything else
0207             // check if CollatorInstance is not destroyed and has valid collator
0208             if (!KDb_collator.isDestroyed() && KDb_collator->getCollator()) {
0209                 m_lessThanFunction = &cmpStringWithCollator;
0210             }
0211             else {
0212                 m_lessThanFunction = &cmpString;
0213             }
0214         }
0215     }
0216 
0217     void setSortOrder(KDbOrderByColumn::SortOrder order) {
0218         m_order = order;
0219     }
0220 
0221     void setSortColumn(int column) {
0222         m_sortColumn = column;
0223     }
0224 
0225 #define _IIF(a,b) ((a) ? (b) : !(b))
0226 
0227     //! Main comparison operator that takes column number, type and order into account
0228     bool operator()(KDbRecordData* record1, KDbRecordData* record2) {
0229         // compare NULLs : NULL is smaller than everything
0230         if ((m_leftTmp = record1->at(m_sortColumn)).isNull())
0231             return _IIF(m_order == KDbOrderByColumn::SortOrder::Ascending,
0232                         !record2->at(m_sortColumn).isNull());
0233         if ((m_rightTmp = record2->at(m_sortColumn)).isNull())
0234             return m_order == KDbOrderByColumn::SortOrder::Descending;
0235 
0236         return _IIF(m_order == KDbOrderByColumn::SortOrder::Ascending,
0237                     m_lessThanFunction(m_leftTmp, m_rightTmp));
0238     }
0239 };
0240 #undef _IIF
0241 #undef CAST_AND_COMPARE
0242 
0243 //! @internal
0244 class Q_DECL_HIDDEN KDbTableViewData::Private
0245 {
0246 public:
0247     Private()
0248             : sortColumn(0)
0249             , realSortColumn(0)
0250             , sortOrder(KDbOrderByColumn::SortOrder::Ascending)
0251             , type(1)
0252             , pRecordEditBuffer(nullptr)
0253             , readOnly(false)
0254             , insertingEnabled(true)
0255             , containsRecordIdInfo(false)
0256             , autoIncrementedColumn(-2) {
0257     }
0258 
0259     ~Private() {
0260         delete pRecordEditBuffer;
0261     }
0262 
0263     //! Number of physical columns
0264     int realColumnCount;
0265 
0266     /*! Columns information */
0267     QList<KDbTableViewColumn*> columns;
0268 
0269     /*! Visible columns information */
0270     QList<KDbTableViewColumn*> visibleColumns;
0271 
0272     //! (Logical) sorted column number, set by setSorting().
0273     //! Can differ from realSortColumn if there's lookup column used.
0274     int sortColumn;
0275 
0276     //! Real sorted column number, set by setSorting(), used by cmp*() methods
0277     int realSortColumn;
0278 
0279     //! Specifies sorting order
0280     KDbOrderByColumn::SortOrder sortOrder;
0281 
0282     LessThanFunctor lessThanFunctor;
0283 
0284     short type;
0285 
0286     KDbRecordEditBuffer *pRecordEditBuffer;
0287 
0288     KDbCursor *cursor;
0289 
0290     KDbResultInfo result;
0291 
0292     QList<int> visibleColumnIDs;
0293     QList<int> globalColumnIDs;
0294 
0295     bool readOnly;
0296 
0297     bool insertingEnabled;
0298 
0299     //! @see KDbTableViewData::containsRecordIdInfo()
0300     bool containsRecordIdInfo;
0301 
0302     mutable int autoIncrementedColumn;
0303 };
0304 
0305 //-------------------------------
0306 
0307 KDbTableViewData::KDbTableViewData()
0308         : QObject()
0309         , KDbTableViewDataBase()
0310         , d(new Private)
0311 {
0312     d->realColumnCount = 0;
0313     d->cursor = nullptr;
0314 }
0315 
0316 // db-aware ctor
0317 KDbTableViewData::KDbTableViewData(KDbCursor *c)
0318         : QObject()
0319         , KDbTableViewDataBase()
0320         , d(new Private)
0321 {
0322     d->cursor = c;
0323     d->containsRecordIdInfo = d->cursor->containsRecordIdInfo();
0324     if (d->cursor && d->cursor->query()) {
0325         const KDbQuerySchema::FieldsExpandedMode fieldsExpandedMode
0326         = d->containsRecordIdInfo ? KDbQuerySchema::FieldsExpandedMode::WithInternalFieldsAndRecordId
0327                                   : KDbQuerySchema::FieldsExpandedMode::WithInternalFields;
0328         d->realColumnCount = d->cursor->query()->fieldsExpanded(
0329                     d->cursor->connection(), fieldsExpandedMode).count();
0330     } else {
0331         d->realColumnCount = d->columns.count() + (d->containsRecordIdInfo ? 1 : 0);
0332     }
0333 
0334     // Allocate KDbTableViewColumn objects for each visible query column
0335     const KDbQueryColumnInfo::Vector fields
0336             = d->cursor->query()->fieldsExpanded(d->cursor->connection());
0337     const int fieldCount = fields.count();
0338     for (int i = 0;i < fieldCount;i++) {
0339         KDbQueryColumnInfo *ci = fields[i];
0340         if (ci->isVisible()) {
0341             KDbQueryColumnInfo *visibleLookupColumnInfo = nullptr;
0342             if (ci->indexForVisibleLookupValue() != -1) {
0343                 // Lookup field is defined
0344                 visibleLookupColumnInfo = d->cursor->query()->expandedOrInternalField(
0345                     d->cursor->connection(), ci->indexForVisibleLookupValue());
0346             }
0347             KDbTableViewColumn* col = new KDbTableViewColumn(*d->cursor->query(), ci, visibleLookupColumnInfo);
0348             addColumn(col);
0349         }
0350     }
0351 }
0352 
0353 KDbTableViewData::KDbTableViewData(const QList<QVariant> &keys, const QList<QVariant> &values,
0354                                    KDbField::Type keyType, KDbField::Type valueType)
0355     : KDbTableViewData()
0356 {
0357     KDbField *keyField = new KDbField(QLatin1String("key"), keyType);
0358     keyField->setPrimaryKey(true);
0359     KDbTableViewColumn *keyColumn
0360         = new KDbTableViewColumn(keyField, KDbTableViewColumn::FieldIsOwned::Yes);
0361     keyColumn->setVisible(false);
0362     addColumn(keyColumn);
0363 
0364     KDbField *valueField = new KDbField(QLatin1String("value"), valueType);
0365     KDbTableViewColumn *valueColumn
0366         = new KDbTableViewColumn(valueField, KDbTableViewColumn::FieldIsOwned::Yes);
0367     addColumn(valueColumn);
0368 
0369     int cnt = qMin(keys.count(), values.count());
0370     QList<QVariant>::ConstIterator it_keys = keys.constBegin();
0371     QList<QVariant>::ConstIterator it_values = values.constBegin();
0372     for (;cnt > 0;++it_keys, ++it_values, cnt--) {
0373         KDbRecordData *record = new KDbRecordData(2);
0374         (*record)[0] = (*it_keys);
0375         (*record)[1] = (*it_values);
0376         append(record);
0377     }
0378 }
0379 
0380 KDbTableViewData::KDbTableViewData(KDbField::Type keyType, KDbField::Type valueType)
0381     : KDbTableViewData(QList<QVariant>(), QList<QVariant>(), keyType, valueType)
0382 {
0383 }
0384 
0385 KDbTableViewData::~KDbTableViewData()
0386 {
0387     emit destroying();
0388     clearInternal(false /* !processEvents */);
0389     qDeleteAll(d->columns);
0390     delete d;
0391 }
0392 
0393 void KDbTableViewData::deleteLater()
0394 {
0395     d->cursor = nullptr;
0396     QObject::deleteLater();
0397 }
0398 
0399 void KDbTableViewData::addColumn(KDbTableViewColumn* col)
0400 {
0401     d->columns.append(col);
0402     col->setData(this);
0403     if (col->isVisible()) {
0404         d->visibleColumns.append(col);
0405         d->visibleColumnIDs.append(d->visibleColumns.count() - 1);
0406         d->globalColumnIDs.append(d->columns.count() - 1);
0407     } else {
0408         d->visibleColumnIDs.append(-1);
0409     }
0410     d->autoIncrementedColumn = -2; //clear cache;
0411     if (!d->cursor || !d->cursor->query()) {
0412         d->realColumnCount = d->columns.count() + (d->containsRecordIdInfo ? 1 : 0);
0413     }
0414 }
0415 
0416 void KDbTableViewData::columnVisibilityChanged(const KDbTableViewColumn &column)
0417 {
0418     if (column.isVisible()) { // column made visible
0419         int indexInGlobal = d->columns.indexOf(const_cast<KDbTableViewColumn*>(&column));
0420         // find previous column that is visible
0421         int prevIndexInGlobal = indexInGlobal - 1;
0422         while (prevIndexInGlobal >= 0 && d->visibleColumnIDs[prevIndexInGlobal] == -1) {
0423             prevIndexInGlobal--;
0424         }
0425         int indexInVisible = prevIndexInGlobal + 1;
0426         // update
0427         d->visibleColumns.insert(indexInVisible, const_cast<KDbTableViewColumn*>(&column));
0428         d->visibleColumnIDs[indexInGlobal] = indexInVisible;
0429         d->globalColumnIDs.insert(indexInVisible, indexInGlobal);
0430         for (int i = indexInGlobal + 1; i < d->columns.count(); i++) { // increment ids of the rest
0431             if (d->visibleColumnIDs[i] >= 0) {
0432                 d->visibleColumnIDs[i]++;
0433             }
0434         }
0435     }
0436     else { // column made invisible
0437         int indexInVisible = d->visibleColumns.indexOf(const_cast<KDbTableViewColumn*>(&column));
0438         d->visibleColumns.removeAt(indexInVisible);
0439         int indexInGlobal = globalIndexOfVisibleColumn(indexInVisible);
0440         d->visibleColumnIDs[indexInGlobal] = -1;
0441         d->globalColumnIDs.removeAt(indexInVisible);
0442     }
0443 }
0444 
0445 int KDbTableViewData::globalIndexOfVisibleColumn(int visibleIndex) const
0446 {
0447     return d->globalColumnIDs.value(visibleIndex, -1);
0448 }
0449 
0450 int KDbTableViewData::visibleColumnIndex(int globalIndex) const
0451 {
0452     return d->visibleColumnIDs.value(globalIndex, -1);
0453 }
0454 
0455 int KDbTableViewData::columnCount() const
0456 {
0457     return d->columns.count();
0458 }
0459 
0460 int KDbTableViewData::visibleColumnCount() const
0461 {
0462     return d->visibleColumns.count();
0463 }
0464 
0465 QList<KDbTableViewColumn*>* KDbTableViewData::columns()
0466 {
0467     return &d->columns;
0468 }
0469 
0470 QList<KDbTableViewColumn*>* KDbTableViewData::visibleColumns()
0471 {
0472     return &d->visibleColumns;
0473 }
0474 
0475 KDbTableViewColumn* KDbTableViewData::column(int index)
0476 {
0477     return d->columns.value(index);
0478 }
0479 
0480 KDbTableViewColumn* KDbTableViewData::visibleColumn(int index)
0481 {
0482     return d->visibleColumns.value(index);
0483 }
0484 
0485 bool KDbTableViewData::isDBAware() const
0486 {
0487     return d->cursor != nullptr;
0488 }
0489 
0490 KDbCursor* KDbTableViewData::cursor() const
0491 {
0492     return d->cursor;
0493 }
0494 
0495 bool KDbTableViewData::isInsertingEnabled() const
0496 {
0497     return d->insertingEnabled;
0498 }
0499 
0500 KDbRecordEditBuffer* KDbTableViewData::recordEditBuffer() const
0501 {
0502     return d->pRecordEditBuffer;
0503 }
0504 
0505 const KDbResultInfo& KDbTableViewData::result() const
0506 {
0507     return d->result;
0508 }
0509 
0510 bool KDbTableViewData::containsRecordIdInfo() const
0511 {
0512     return d->containsRecordIdInfo;
0513 }
0514 
0515 KDbRecordData* KDbTableViewData::createItem() const
0516 {
0517     return new KDbRecordData(d->realColumnCount);
0518 }
0519 
0520 QString KDbTableViewData::dbTableName() const
0521 {
0522     if (d->cursor && d->cursor->query() && d->cursor->query()->masterTable())
0523         return d->cursor->query()->masterTable()->name();
0524     return QString();
0525 }
0526 
0527 void KDbTableViewData::setSorting(int column, KDbOrderByColumn::SortOrder order)
0528 {
0529     d->sortOrder = order;
0530     if (column < 0 || column >= d->columns.count()) {
0531         d->sortColumn = -1;
0532         d->realSortColumn = -1;
0533         return;
0534     }
0535     // find proper column information for sorting (lookup column points to alternate column with visible data)
0536     const KDbTableViewColumn *tvcol = d->columns.at(column);
0537     const KDbQueryColumnInfo* visibleLookupColumnInfo = tvcol->visibleLookupColumnInfo();
0538     const KDbField *field = visibleLookupColumnInfo ? visibleLookupColumnInfo->field() : tvcol->field();
0539     d->sortColumn = column;
0540     d->realSortColumn = tvcol->columnInfo()->indexForVisibleLookupValue() != -1
0541                           ? tvcol->columnInfo()->indexForVisibleLookupValue() : d->sortColumn;
0542 
0543     // setup compare functor
0544     d->lessThanFunctor.setColumnType(*field);
0545     d->lessThanFunctor.setSortOrder(d->sortOrder);
0546     d->lessThanFunctor.setSortColumn(column);
0547 }
0548 
0549 int KDbTableViewData::sortColumn() const
0550 {
0551     return d->sortColumn;
0552 }
0553 
0554 KDbOrderByColumn::SortOrder KDbTableViewData::sortOrder() const
0555 {
0556     return d->sortOrder;
0557 }
0558 
0559 void KDbTableViewData::sort()
0560 {
0561     if (d->sortColumn < 0 || d->sortColumn >= d->columns.count()) {
0562         return;
0563     }
0564     std::sort(begin(), end(), d->lessThanFunctor);
0565 }
0566 
0567 void KDbTableViewData::setReadOnly(bool set)
0568 {
0569     if (d->readOnly == set)
0570         return;
0571     d->readOnly = set;
0572     if (d->readOnly)
0573         setInsertingEnabled(false);
0574 }
0575 
0576 void KDbTableViewData::setInsertingEnabled(bool set)
0577 {
0578     if (d->insertingEnabled == set)
0579         return;
0580     d->insertingEnabled = set;
0581     if (d->insertingEnabled)
0582         setReadOnly(false);
0583 }
0584 
0585 void KDbTableViewData::clearRecordEditBuffer()
0586 {
0587     //init record edit buffer
0588     if (!d->pRecordEditBuffer)
0589         d->pRecordEditBuffer = new KDbRecordEditBuffer(isDBAware());
0590     else
0591         d->pRecordEditBuffer->clear();
0592 }
0593 
0594 bool KDbTableViewData::updateRecordEditBufferRef(KDbRecordData *record,
0595         int colnum, KDbTableViewColumn* col, QVariant* newval, bool allowSignals,
0596         QVariant *visibleValueForLookupField)
0597 {
0598     if (!record || !newval) {
0599         return false;
0600     }
0601     d->result.clear();
0602     if (allowSignals)
0603         emit aboutToChangeCell(record, colnum, newval, &d->result);
0604     if (!d->result.success)
0605         return false;
0606 
0607     //kdbDebug() << "column #" << colnum << " = " << newval.toString();
0608     if (!col) {
0609         kdbWarning() << "column #" << colnum << "not found! col==0";
0610         return false;
0611     }
0612     if (!d->pRecordEditBuffer)
0613         d->pRecordEditBuffer = new KDbRecordEditBuffer(isDBAware());
0614     if (d->pRecordEditBuffer->isDBAware()) {
0615         if (!(col->columnInfo())) {
0616             kdbWarning() << "column #" << colnum << " not found!";
0617             return false;
0618         }
0619         d->pRecordEditBuffer->insert(col->columnInfo(), *newval);
0620 
0621         if (col->visibleLookupColumnInfo() && visibleValueForLookupField) {
0622             //this is value for lookup table: update visible value as well
0623             d->pRecordEditBuffer->insert(col->visibleLookupColumnInfo(), *visibleValueForLookupField);
0624         }
0625         return true;
0626     }
0627     if (!(col->field())) {
0628         kdbWarning() << "column #" << colnum << "not found!";
0629         return false;
0630     }
0631     //not db-aware:
0632     const QString colname = col->field()->name();
0633     if (colname.isEmpty()) {
0634         kdbWarning() << "column #" << colnum << "not found!";
0635         return false;
0636     }
0637     d->pRecordEditBuffer->insert(colname, *newval);
0638     return true;
0639 }
0640 
0641 bool KDbTableViewData::updateRecordEditBuffer(KDbRecordData *record, int colnum,
0642                                               KDbTableViewColumn* col,
0643                                               const QVariant &newval, bool allowSignals)
0644 {
0645     QVariant newv(newval);
0646     return updateRecordEditBufferRef(record, colnum, col, &newv, allowSignals);
0647 }
0648 
0649 bool KDbTableViewData::updateRecordEditBuffer(KDbRecordData *record, int colnum,
0650                                               const QVariant &newval, bool allowSignals)
0651 {
0652     KDbTableViewColumn* col = d->columns.value(colnum);
0653     QVariant newv(newval);
0654     return col ? updateRecordEditBufferRef(record, colnum, col, &newv, allowSignals) : false;
0655 }
0656 
0657 //! Get a new value (if present in the buffer), or the old one (taken here for optimization)
0658 static inline void saveRecordGetValue(const QVariant **pval, KDbCursor *cursor,
0659                                       KDbRecordEditBuffer *pRecordEditBuffer,
0660                                       QList<KDbTableViewColumn*>::ConstIterator* it_f,
0661                                       KDbRecordData *record, KDbField *f, QVariant* val, int col)
0662 {
0663     if (!*pval) {
0664         *pval = cursor
0665                 ? pRecordEditBuffer->at( (**it_f)->columnInfo(),
0666                                          record->at(col).isNull() /* useDefaultValueIfPossible */ )
0667                 : pRecordEditBuffer->at( *f );
0668         *val = *pval ? **pval : record->at(col); /* get old value */
0669         //kdbDebug() << col << *(**it_f)->columnInfo() << "val:" << *val;
0670     }
0671 }
0672 
0673 //! @todo if there're multiple views for this data, we need multiple buffers!
0674 bool KDbTableViewData::saveRecord(KDbRecordData *record, bool insert, bool repaint)
0675 {
0676     if (!d->pRecordEditBuffer)
0677         return true; //nothing to do
0678 
0679     //check constraints:
0680     //-check if every NOT NULL and NOT EMPTY field is filled
0681     QList<KDbTableViewColumn*>::ConstIterator it_f(d->columns.constBegin());
0682     int colIndex = 0;
0683     const QVariant *pval = nullptr;
0684     QVariant val;
0685     for (;it_f != d->columns.constEnd() && colIndex < record->count();++it_f, ++colIndex) {
0686         KDbField *f = (*it_f)->field();
0687         if (f->isNotNull()) {
0688             saveRecordGetValue(&pval, d->cursor, d->pRecordEditBuffer, &it_f, record, f, &val, colIndex);
0689             //check it
0690             if (val.isNull() && !f->isAutoIncrement()) {
0691                 //NOT NULL violated
0692                 d->result.message = tr("\"%1\" column requires a value to be entered.").arg(f->captionOrName())
0693                                 + QLatin1String("\n\n") + KDbTableViewData::messageYouCanImproveData();
0694                 d->result.description = tr("The column's constraint is declared as NOT NULL.");
0695                 d->result.column = colIndex;
0696                 return false;
0697             }
0698         }
0699         if (f->isNotEmpty()) {
0700             saveRecordGetValue(&pval, d->cursor, d->pRecordEditBuffer, &it_f, record, f, &val, colIndex);
0701             if (!f->isAutoIncrement() && (val.isNull() || KDb::isEmptyValue(f->type(), val))) {
0702                 //NOT EMPTY violated
0703                 d->result.message = tr("\"%1\" column requires a value to be entered.").arg(f->captionOrName())
0704                                 + QLatin1String("\n\n") + KDbTableViewData::messageYouCanImproveData();
0705                 d->result.description = tr("The column's constraint is declared as NOT EMPTY.");
0706                 d->result.column = colIndex;
0707                 return false;
0708             }
0709         }
0710     }
0711 
0712     if (d->cursor) {//db-aware
0713         if (insert) {
0714             if (!d->cursor->insertRecord(record, d->pRecordEditBuffer,
0715                                          d->containsRecordIdInfo /*also retrieve ROWID*/))
0716             {
0717                 d->result.message = tr("Record inserting failed.") + QLatin1String("\n\n")
0718                                 + KDbTableViewData::messageYouCanImproveData();
0719                 KDb::getHTMLErrorMesage(*d->cursor, &d->result);
0720 
0721                 /*   if (desc)
0722                       *desc = */
0723                 /*! @todo use KexiMainWindow::showErrorMessage(const QString &title, KDbObject *obj)
0724                   after it will be moved somewhere to KDb (this will require moving other
0725                     showErrorMessage() methods from KexiMainWindow to libkexiutils....)
0726                   then: just call: *desc = KDb::errorMessage(d->cursor); */
0727                 return false;
0728             }
0729         } else { // record updating
0730             if (!d->cursor->updateRecord(static_cast<KDbRecordData*>(record), d->pRecordEditBuffer,
0731                                          d->containsRecordIdInfo /*use ROWID*/))
0732             {
0733                 d->result.message = tr("Record changing failed.") + QLatin1String("\n\n")
0734                                 + KDbTableViewData::messageYouCanImproveData();
0735 //! @todo set d->result.column if possible
0736                 KDb::getHTMLErrorMesage(*d->cursor, &d->result.description);
0737                 return false;
0738             }
0739         }
0740     } else {//not db-aware version
0741         KDbRecordEditBuffer::SimpleMap b = d->pRecordEditBuffer->simpleBuffer();
0742         for (KDbRecordEditBuffer::SimpleMap::ConstIterator it = b.constBegin();it != b.constEnd();++it) {
0743             int i = -1;
0744             foreach(KDbTableViewColumn *col, d->columns) {
0745                 i++;
0746                 if (col->field()->name() == it.key()) {
0747                     kdbDebug() << col->field()->name() << ": " << record->at(i).toString()
0748                            << " -> " << it.value().toString();
0749                     (*record)[i] = it.value();
0750                 }
0751             }
0752         }
0753     }
0754 
0755     d->pRecordEditBuffer->clear();
0756 
0757     if (repaint)
0758         emit recordRepaintRequested(record);
0759     return true;
0760 }
0761 
0762 bool KDbTableViewData::saveRecordChanges(KDbRecordData *record, bool repaint)
0763 {
0764     d->result.clear();
0765     emit aboutToUpdateRecord(record, d->pRecordEditBuffer, &d->result);
0766     if (!d->result.success)
0767         return false;
0768 
0769     if (saveRecord(record, false /*update*/, repaint)) {
0770         emit recordUpdated(record);
0771         return true;
0772     }
0773     return false;
0774 }
0775 
0776 bool KDbTableViewData::saveNewRecord(KDbRecordData *record, bool repaint)
0777 {
0778     d->result.clear();
0779     emit aboutToInsertRecord(record, &d->result, repaint);
0780     if (!d->result.success)
0781         return false;
0782 
0783     if (saveRecord(record, true /*insert*/, repaint)) {
0784         emit recordInserted(record, repaint);
0785         return true;
0786     }
0787     return false;
0788 }
0789 
0790 bool KDbTableViewData::deleteRecord(KDbRecordData *record, bool repaint)
0791 {
0792     d->result.clear();
0793     emit aboutToDeleteRecord(record, &d->result, repaint);
0794     if (!d->result.success)
0795         return false;
0796 
0797     if (d->cursor) {//db-aware
0798         d->result.success = false;
0799         if (!d->cursor->deleteRecord(static_cast<KDbRecordData*>(record), d->containsRecordIdInfo /*use ROWID*/)) {
0800             d->result.message = tr("Record deleting failed.");
0801             //! @todo use KDberrorMessage() for description as in KDbTableViewData::saveRecord() */
0802             KDb::getHTMLErrorMesage(*d->cursor, &d->result);
0803             d->result.success = false;
0804             return false;
0805         }
0806     }
0807 
0808     int index = indexOf(record);
0809     if (index == -1) {
0810         //aah - this shouldn't be!
0811         kdbWarning() << "!removeRef() - IMPL. ERROR?";
0812         d->result.success = false;
0813         return false;
0814     }
0815     removeAt(index);
0816     emit recordDeleted();
0817     return true;
0818 }
0819 
0820 void KDbTableViewData::deleteRecords(const QList<int> &recordsToDelete, bool repaint)
0821 {
0822     Q_UNUSED(repaint);
0823 
0824     if (recordsToDelete.isEmpty())
0825         return;
0826     int last_r = 0;
0827     KDbTableViewDataIterator it(begin());
0828     for (QList<int>::ConstIterator r_it = recordsToDelete.constBegin(); r_it != recordsToDelete.constEnd(); ++r_it) {
0829         for (; last_r < (*r_it); last_r++)
0830             ++it;
0831         it = erase(it);   /* this will delete *it */
0832         last_r++;
0833     }
0834 //DON'T CLEAR BECAUSE KexiTableViewPropertyBuffer will clear BUFFERS!
0835 //--> emit reloadRequested(); //! \todo more effective?
0836     emit recordsDeleted(recordsToDelete);
0837 }
0838 
0839 void KDbTableViewData::insertRecord(KDbRecordData *record, int index, bool repaint)
0840 {
0841     insert(index = qMin(index, count()), record);
0842     emit recordInserted(record, index, repaint);
0843 }
0844 
0845 void KDbTableViewData::clearInternal(bool processEvents)
0846 {
0847     clearRecordEditBuffer();
0848 //! @todo this is time consuming: find better data model
0849     const int c = count();
0850 #ifndef TABLEVIEW_NO_PROCESS_EVENTS
0851     const bool _processEvents = processEvents && !qApp->closingDown();
0852 #endif
0853     for (int i = 0; i < c; i++) {
0854         removeLast();
0855 #ifndef TABLEVIEW_NO_PROCESS_EVENTS
0856         if (_processEvents && i % 1000 == 0)
0857             qApp->processEvents(QEventLoop::AllEvents, 1);
0858 #endif
0859     }
0860 }
0861 
0862 bool KDbTableViewData::deleteAllRecords(bool repaint)
0863 {
0864     clearInternal();
0865 
0866     bool res = true;
0867     if (d->cursor) {
0868         //db-aware
0869         res = d->cursor->deleteAllRecords();
0870     }
0871 
0872     if (repaint)
0873         emit reloadRequested();
0874     return res;
0875 }
0876 
0877 int KDbTableViewData::autoIncrementedColumn() const
0878 {
0879     if (d->autoIncrementedColumn == -2) {
0880         //find such a column
0881         d->autoIncrementedColumn = -1;
0882         foreach(KDbTableViewColumn *col, d->columns) {
0883             d->autoIncrementedColumn++;
0884             if (col->field()->isAutoIncrement())
0885                 break;
0886         }
0887     }
0888     return d->autoIncrementedColumn;
0889 }
0890 
0891 bool KDbTableViewData::preloadAllRecords()
0892 {
0893     if (!d->cursor)
0894         return false;
0895     if (!d->cursor->moveFirst() && d->cursor->result().isError())
0896         return false;
0897 
0898 #ifndef TABLEVIEW_NO_PROCESS_EVENTS
0899     const bool processEvents = !qApp->closingDown();
0900 #endif
0901 
0902     for (int i = 0;!d->cursor->eof();i++) {
0903         KDbRecordData *record = d->cursor->storeCurrentRecord();
0904         if (!record) {
0905             clear();
0906             return false;
0907         }
0908 //  record->debug();
0909         append(record);
0910         if (!d->cursor->moveNext() && d->cursor->result().isError()) {
0911             clear();
0912             return false;
0913         }
0914 #ifndef TABLEVIEW_NO_PROCESS_EVENTS
0915         if (processEvents && (i % 1000) == 0)
0916             qApp->processEvents(QEventLoop::AllEvents, 1);
0917 #endif
0918     }
0919     return true;
0920 }
0921 
0922 bool KDbTableViewData::isReadOnly() const
0923 {
0924     return d->readOnly || (d->cursor && d->cursor->connection()->options()->isReadOnly());
0925 }
0926 
0927 // static
0928 QString KDbTableViewData::messageYouCanImproveData()
0929 {
0930     return tr("Please correct data in this record or use the \"Cancel record changes\" function.");
0931 }
0932 
0933 QDebug operator<<(QDebug dbg, const KDbTableViewData &data)
0934 {
0935     dbg.nospace() << "TableViewData(";
0936     dbg.space() << "sortColumn:" << data.sortColumn()
0937                 << "sortOrder:" << (data.sortOrder() == KDbOrderByColumn::SortOrder::Ascending
0938                                     ? "asc" : "desc")
0939                 << "isDBAware:" << data.isDBAware()
0940                 << "dbTableName:" << data.dbTableName()
0941                 << "cursor:" << (data.cursor() ? "yes" : "no")
0942                 << "columnCount:" << data.columnCount()
0943                 << "count:" << data.count()
0944                 << "autoIncrementedColumn:" << data.autoIncrementedColumn()
0945                 << "visibleColumnCount:" << data.visibleColumnCount()
0946                 << "isReadOnly:" << data.isReadOnly()
0947                 << "isInsertingEnabled:" << data.isInsertingEnabled()
0948                 << "containsRecordIdInfo:" << data.containsRecordIdInfo()
0949                 << "result:" << data.result();
0950     dbg.nospace() << ")";
0951     return dbg.space();
0952 }