File indexing completed on 2024-10-13 04:14:11
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 }