1 /* This file is part of the KDE project
2    Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at>
3    Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org>
4    Copyright (C) 2003-2017 Jarosław Staniek <staniek@kde.org>
5    Copyright (C) 2014 Michał Poteralski <michalpoteralskikde@gmail.com>
6 
7    This program is free software; you can redistribute it and/or
8    modify it under the terms of the GNU Library General Public
9    License as published by the Free Software Foundation; either
10    version 2 of the License, or (at your option) any later version.
11 
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15    Library General Public License for more details.
16 
17    You should have received a copy of the GNU Library General Public License
18    along with this program; see the file COPYING.  If not, write to
19    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21 
22    Original Author:  Till Busch <till@bux.at>
23    Original Project: buX (www.bux.at)
24 */
25 
26 #include "KDbTableViewData.h"
27 #include "KDbConnection.h"
28 #include "KDbConnectionOptions.h"
29 #include "KDbCursor.h"
30 #include "KDbError.h"
31 #include "KDb.h"
32 #include "KDbOrderByColumn.h"
33 #include "KDbQuerySchema.h"
34 #include "KDbRecordEditBuffer.h"
35 #include "KDbTableViewColumn.h"
36 #include "kdb_debug.h"
37 
38 #include <QApplication>
39 
40 #include <unicode/coll.h>
41 
42 // #define TABLEVIEW_NO_PROCESS_EVENTS
43 
44 static unsigned short charTable[] = {
45 #include "chartable.txt"
46 };
47 
48 //-------------------------------
49 
50 //! @internal Unicode-aware collator for comparing strings
51 class CollatorInstance
52 {
53 public:
CollatorInstance()54     CollatorInstance() {
55         UErrorCode status = U_ZERO_ERROR;
56         m_collator = icu::Collator::createInstance(status);
57         if (U_FAILURE(status)) {
58             kdbWarning() << "Could not create instance of collator:" << status;
59             m_collator = nullptr;
60         } else {
61             // enable normalization by default
62             m_collator->setAttribute(UCOL_NORMALIZATION_MODE, UCOL_ON, status);
63             if (U_FAILURE(status)) {
64                 kdbWarning() << "Could not set collator attribute:" << status;
65             }
66         }
67     }
68 
getCollator()69     icu::Collator* getCollator() {
70         return m_collator;
71     }
72 
~CollatorInstance()73     ~CollatorInstance() {
74         delete m_collator;
75     }
76 
77 private:
78     icu::Collator *m_collator;
79 };
80 
81 Q_GLOBAL_STATIC(CollatorInstance, KDb_collator)
82 
83 //! @internal A functor used in qSort() in order to sort by a given column
84 class LessThanFunctor
85 {
86 private:
87     KDbOrderByColumn::SortOrder m_order;
88     QVariant m_leftTmp, m_rightTmp;
89     int m_sortColumn;
90 
91     bool (*m_lessThanFunction)(const QVariant&, const QVariant&);
92 
93 #define CAST_AND_COMPARE(casting) \
94     return left.casting() < right.casting()
95 
cmpInt(const QVariant & left,const QVariant & right)96     static bool cmpInt(const QVariant& left, const QVariant& right) {
97         CAST_AND_COMPARE(toInt);
98     }
99 
cmpUInt(const QVariant & left,const QVariant & right)100     static bool cmpUInt(const QVariant& left, const QVariant& right) {
101         CAST_AND_COMPARE(toUInt);
102     }
103 
cmpLongLong(const QVariant & left,const QVariant & right)104     static bool cmpLongLong(const QVariant& left, const QVariant& right) {
105         CAST_AND_COMPARE(toLongLong);
106     }
107 
cmpULongLong(const QVariant & left,const QVariant & right)108     static bool cmpULongLong(const QVariant& left, const QVariant& right) {
109         CAST_AND_COMPARE(toULongLong);
110     }
111 
cmpDouble(const QVariant & left,const QVariant & right)112     static bool cmpDouble(const QVariant& left, const QVariant& right) {
113         CAST_AND_COMPARE(toDouble);
114     }
115 
cmpDate(const QVariant & left,const QVariant & right)116     static bool cmpDate(const QVariant& left, const QVariant& right) {
117         CAST_AND_COMPARE(toDate);
118     }
119 
cmpDateTime(const QVariant & left,const QVariant & right)120     static bool cmpDateTime(const QVariant& left, const QVariant& right) {
121         CAST_AND_COMPARE(toDateTime);
122     }
123 
cmpTime(const QVariant & left,const QVariant & right)124     static bool cmpTime(const QVariant& left, const QVariant& right) {
125         CAST_AND_COMPARE(toDate);
126     }
127 
cmpString(const QVariant & left,const QVariant & right)128     static bool cmpString(const QVariant& left, const QVariant& right) {
129         const QString &as = left.toString();
130         const QString &bs = right.toString();
131 
132         const QChar *a = as.isEmpty() ? nullptr : as.unicode();
133         const QChar *b = bs.isEmpty() ? nullptr : bs.unicode();
134 
135         if (a == nullptr) {
136             return b != nullptr;
137         }
138         if (a == b || b == nullptr) {
139             return false;
140         }
141 
142         int len = qMin(as.length(), bs.length());
143         forever {
144             unsigned short au = a->unicode();
145             unsigned short bu = b->unicode();
146             au = (au <= 0x17e ? charTable[au] : 0xffff);
147             bu = (bu <= 0x17e ? charTable[bu] : 0xffff);
148 
149             if (len <= 0)
150                 return false;
151             len--;
152 
153             if (au != bu)
154                 return au < bu;
155             a++;
156             b++;
157         }
158         return false;
159     }
160 
cmpStringWithCollator(const QVariant & left,const QVariant & right)161     static bool cmpStringWithCollator(const QVariant& left, const QVariant& right) {
162         const QString &as = left.toString();
163         const QString &bs = right.toString();
164         return icu::Collator::LESS == KDb_collator->getCollator()->compare(
165                                         (const UChar *)as.constData(), as.size(),
166                                         (const UChar *)bs.constData(), bs.size());
167     }
168 
169     //! Compare function for BLOB data (QByteArray). Uses size as the weight.
cmpBLOB(const QVariant & left,const QVariant & right)170     static bool cmpBLOB(const QVariant& left, const QVariant& right) {
171         return left.toByteArray().size() < right.toByteArray().size();
172     }
173 
174 public:
LessThanFunctor()175     LessThanFunctor()
176             : m_order(KDbOrderByColumn::SortOrder::Ascending)
177             , m_sortColumn(-1)
178             , m_lessThanFunction(nullptr)
179     {
180     }
181 
setColumnType(const KDbField & field)182     void setColumnType(const KDbField& field) {
183         const KDbField::Type t = field.type();
184         if (field.isTextType())
185             m_lessThanFunction = &cmpString;
186         if (KDbField::isFPNumericType(t))
187             m_lessThanFunction = &cmpDouble;
188         else if (t == KDbField::Integer && field.isUnsigned())
189             m_lessThanFunction = &cmpUInt;
190         else if (t == KDbField::Boolean || KDbField::isNumericType(t))
191             m_lessThanFunction = &cmpInt; //other integers
192         else if (t == KDbField::BigInteger) {
193             if (field.isUnsigned())
194                 m_lessThanFunction = &cmpULongLong;
195             else
196                 m_lessThanFunction = &cmpLongLong;
197         } else if (t == KDbField::Date)
198             m_lessThanFunction = &cmpDate;
199         else if (t == KDbField::Time)
200             m_lessThanFunction = &cmpTime;
201         else if (t == KDbField::DateTime)
202             m_lessThanFunction = &cmpDateTime;
203         else if (t == KDbField::BLOB)
204             //! @todo allow users to define BLOB sorting function?
205             m_lessThanFunction = &cmpBLOB;
206         else { // anything else
207             // check if CollatorInstance is not destroyed and has valid collator
208             if (!KDb_collator.isDestroyed() && KDb_collator->getCollator()) {
209                 m_lessThanFunction = &cmpStringWithCollator;
210             }
211             else {
212                 m_lessThanFunction = &cmpString;
213             }
214         }
215     }
216 
setSortOrder(KDbOrderByColumn::SortOrder order)217     void setSortOrder(KDbOrderByColumn::SortOrder order) {
218         m_order = order;
219     }
220 
setSortColumn(int column)221     void setSortColumn(int column) {
222         m_sortColumn = column;
223     }
224 
225 #define _IIF(a,b) ((a) ? (b) : !(b))
226 
227     //! Main comparison operator that takes column number, type and order into account
operator ()(KDbRecordData * record1,KDbRecordData * record2)228     bool operator()(KDbRecordData* record1, KDbRecordData* record2) {
229         // compare NULLs : NULL is smaller than everything
230         if ((m_leftTmp = record1->at(m_sortColumn)).isNull())
231             return _IIF(m_order == KDbOrderByColumn::SortOrder::Ascending,
232                         !record2->at(m_sortColumn).isNull());
233         if ((m_rightTmp = record2->at(m_sortColumn)).isNull())
234             return m_order == KDbOrderByColumn::SortOrder::Descending;
235 
236         return _IIF(m_order == KDbOrderByColumn::SortOrder::Ascending,
237                     m_lessThanFunction(m_leftTmp, m_rightTmp));
238     }
239 };
240 #undef _IIF
241 #undef CAST_AND_COMPARE
242 
243 //! @internal
244 class Q_DECL_HIDDEN KDbTableViewData::Private
245 {
246 public:
Private()247     Private()
248             : sortColumn(0)
249             , realSortColumn(0)
250             , sortOrder(KDbOrderByColumn::SortOrder::Ascending)
251             , type(1)
252             , pRecordEditBuffer(nullptr)
253             , readOnly(false)
254             , insertingEnabled(true)
255             , containsRecordIdInfo(false)
256             , autoIncrementedColumn(-2) {
257     }
258 
~Private()259     ~Private() {
260         delete pRecordEditBuffer;
261     }
262 
263     //! Number of physical columns
264     int realColumnCount;
265 
266     /*! Columns information */
267     QList<KDbTableViewColumn*> columns;
268 
269     /*! Visible columns information */
270     QList<KDbTableViewColumn*> visibleColumns;
271 
272     //! (Logical) sorted column number, set by setSorting().
273     //! Can differ from realSortColumn if there's lookup column used.
274     int sortColumn;
275 
276     //! Real sorted column number, set by setSorting(), used by cmp*() methods
277     int realSortColumn;
278 
279     //! Specifies sorting order
280     KDbOrderByColumn::SortOrder sortOrder;
281 
282     LessThanFunctor lessThanFunctor;
283 
284     short type;
285 
286     KDbRecordEditBuffer *pRecordEditBuffer;
287 
288     KDbCursor *cursor;
289 
290     KDbResultInfo result;
291 
292     QList<int> visibleColumnIDs;
293     QList<int> globalColumnIDs;
294 
295     bool readOnly;
296 
297     bool insertingEnabled;
298 
299     //! @see KDbTableViewData::containsRecordIdInfo()
300     bool containsRecordIdInfo;
301 
302     mutable int autoIncrementedColumn;
303 };
304 
305 //-------------------------------
306 
KDbTableViewData()307 KDbTableViewData::KDbTableViewData()
308         : QObject()
309         , KDbTableViewDataBase()
310         , d(new Private)
311 {
312     d->realColumnCount = 0;
313     d->cursor = nullptr;
314 }
315 
316 // db-aware ctor
KDbTableViewData(KDbCursor * c)317 KDbTableViewData::KDbTableViewData(KDbCursor *c)
318         : QObject()
319         , KDbTableViewDataBase()
320         , d(new Private)
321 {
322     d->cursor = c;
323     d->containsRecordIdInfo = d->cursor->containsRecordIdInfo();
324     if (d->cursor && d->cursor->query()) {
325         const KDbQuerySchema::FieldsExpandedMode fieldsExpandedMode
326         = d->containsRecordIdInfo ? KDbQuerySchema::FieldsExpandedMode::WithInternalFieldsAndRecordId
327                                   : KDbQuerySchema::FieldsExpandedMode::WithInternalFields;
328         d->realColumnCount = d->cursor->query()->fieldsExpanded(
329                     d->cursor->connection(), fieldsExpandedMode).count();
330     } else {
331         d->realColumnCount = d->columns.count() + (d->containsRecordIdInfo ? 1 : 0);
332     }
333 
334     // Allocate KDbTableViewColumn objects for each visible query column
335     const KDbQueryColumnInfo::Vector fields
336             = d->cursor->query()->fieldsExpanded(d->cursor->connection());
337     const int fieldCount = fields.count();
338     for (int i = 0;i < fieldCount;i++) {
339         KDbQueryColumnInfo *ci = fields[i];
340         if (ci->isVisible()) {
341             KDbQueryColumnInfo *visibleLookupColumnInfo = nullptr;
342             if (ci->indexForVisibleLookupValue() != -1) {
343                 // Lookup field is defined
344                 visibleLookupColumnInfo = d->cursor->query()->expandedOrInternalField(
345                     d->cursor->connection(), ci->indexForVisibleLookupValue());
346             }
347             KDbTableViewColumn* col = new KDbTableViewColumn(*d->cursor->query(), ci, visibleLookupColumnInfo);
348             addColumn(col);
349         }
350     }
351 }
352 
KDbTableViewData(const QList<QVariant> & keys,const QList<QVariant> & values,KDbField::Type keyType,KDbField::Type valueType)353 KDbTableViewData::KDbTableViewData(const QList<QVariant> &keys, const QList<QVariant> &values,
354                                    KDbField::Type keyType, KDbField::Type valueType)
355     : KDbTableViewData()
356 {
357     KDbField *keyField = new KDbField(QLatin1String("key"), keyType);
358     keyField->setPrimaryKey(true);
359     KDbTableViewColumn *keyColumn
360         = new KDbTableViewColumn(keyField, KDbTableViewColumn::FieldIsOwned::Yes);
361     keyColumn->setVisible(false);
362     addColumn(keyColumn);
363 
364     KDbField *valueField = new KDbField(QLatin1String("value"), valueType);
365     KDbTableViewColumn *valueColumn
366         = new KDbTableViewColumn(valueField, KDbTableViewColumn::FieldIsOwned::Yes);
367     addColumn(valueColumn);
368 
369     int cnt = qMin(keys.count(), values.count());
370     QList<QVariant>::ConstIterator it_keys = keys.constBegin();
371     QList<QVariant>::ConstIterator it_values = values.constBegin();
372     for (;cnt > 0;++it_keys, ++it_values, cnt--) {
373         KDbRecordData *record = new KDbRecordData(2);
374         (*record)[0] = (*it_keys);
375         (*record)[1] = (*it_values);
376         append(record);
377     }
378 }
379 
KDbTableViewData(KDbField::Type keyType,KDbField::Type valueType)380 KDbTableViewData::KDbTableViewData(KDbField::Type keyType, KDbField::Type valueType)
381     : KDbTableViewData(QList<QVariant>(), QList<QVariant>(), keyType, valueType)
382 {
383 }
384 
~KDbTableViewData()385 KDbTableViewData::~KDbTableViewData()
386 {
387     emit destroying();
388     clearInternal(false /* !processEvents */);
389     qDeleteAll(d->columns);
390     delete d;
391 }
392 
deleteLater()393 void KDbTableViewData::deleteLater()
394 {
395     d->cursor = nullptr;
396     QObject::deleteLater();
397 }
398 
addColumn(KDbTableViewColumn * col)399 void KDbTableViewData::addColumn(KDbTableViewColumn* col)
400 {
401     d->columns.append(col);
402     col->setData(this);
403     if (col->isVisible()) {
404         d->visibleColumns.append(col);
405         d->visibleColumnIDs.append(d->visibleColumns.count() - 1);
406         d->globalColumnIDs.append(d->columns.count() - 1);
407     } else {
408         d->visibleColumnIDs.append(-1);
409     }
410     d->autoIncrementedColumn = -2; //clear cache;
411     if (!d->cursor || !d->cursor->query()) {
412         d->realColumnCount = d->columns.count() + (d->containsRecordIdInfo ? 1 : 0);
413     }
414 }
415 
columnVisibilityChanged(const KDbTableViewColumn & column)416 void KDbTableViewData::columnVisibilityChanged(const KDbTableViewColumn &column)
417 {
418     if (column.isVisible()) { // column made visible
419         int indexInGlobal = d->columns.indexOf(const_cast<KDbTableViewColumn*>(&column));
420         // find previous column that is visible
421         int prevIndexInGlobal = indexInGlobal - 1;
422         while (prevIndexInGlobal >= 0 && d->visibleColumnIDs[prevIndexInGlobal] == -1) {
423             prevIndexInGlobal--;
424         }
425         int indexInVisible = prevIndexInGlobal + 1;
426         // update
427         d->visibleColumns.insert(indexInVisible, const_cast<KDbTableViewColumn*>(&column));
428         d->visibleColumnIDs[indexInGlobal] = indexInVisible;
429         d->globalColumnIDs.insert(indexInVisible, indexInGlobal);
430         for (int i = indexInGlobal + 1; i < d->columns.count(); i++) { // increment ids of the rest
431             if (d->visibleColumnIDs[i] >= 0) {
432                 d->visibleColumnIDs[i]++;
433             }
434         }
435     }
436     else { // column made invisible
437         int indexInVisible = d->visibleColumns.indexOf(const_cast<KDbTableViewColumn*>(&column));
438         d->visibleColumns.removeAt(indexInVisible);
439         int indexInGlobal = globalIndexOfVisibleColumn(indexInVisible);
440         d->visibleColumnIDs[indexInGlobal] = -1;
441         d->globalColumnIDs.removeAt(indexInVisible);
442     }
443 }
444 
globalIndexOfVisibleColumn(int visibleIndex) const445 int KDbTableViewData::globalIndexOfVisibleColumn(int visibleIndex) const
446 {
447     return d->globalColumnIDs.value(visibleIndex, -1);
448 }
449 
visibleColumnIndex(int globalIndex) const450 int KDbTableViewData::visibleColumnIndex(int globalIndex) const
451 {
452     return d->visibleColumnIDs.value(globalIndex, -1);
453 }
454 
columnCount() const455 int KDbTableViewData::columnCount() const
456 {
457     return d->columns.count();
458 }
459 
visibleColumnCount() const460 int KDbTableViewData::visibleColumnCount() const
461 {
462     return d->visibleColumns.count();
463 }
464 
columns()465 QList<KDbTableViewColumn*>* KDbTableViewData::columns()
466 {
467     return &d->columns;
468 }
469 
visibleColumns()470 QList<KDbTableViewColumn*>* KDbTableViewData::visibleColumns()
471 {
472     return &d->visibleColumns;
473 }
474 
column(int index)475 KDbTableViewColumn* KDbTableViewData::column(int index)
476 {
477     return d->columns.value(index);
478 }
479 
visibleColumn(int index)480 KDbTableViewColumn* KDbTableViewData::visibleColumn(int index)
481 {
482     return d->visibleColumns.value(index);
483 }
484 
isDBAware() const485 bool KDbTableViewData::isDBAware() const
486 {
487     return d->cursor != nullptr;
488 }
489 
cursor() const490 KDbCursor* KDbTableViewData::cursor() const
491 {
492     return d->cursor;
493 }
494 
isInsertingEnabled() const495 bool KDbTableViewData::isInsertingEnabled() const
496 {
497     return d->insertingEnabled;
498 }
499 
recordEditBuffer() const500 KDbRecordEditBuffer* KDbTableViewData::recordEditBuffer() const
501 {
502     return d->pRecordEditBuffer;
503 }
504 
result() const505 const KDbResultInfo& KDbTableViewData::result() const
506 {
507     return d->result;
508 }
509 
containsRecordIdInfo() const510 bool KDbTableViewData::containsRecordIdInfo() const
511 {
512     return d->containsRecordIdInfo;
513 }
514 
createItem() const515 KDbRecordData* KDbTableViewData::createItem() const
516 {
517     return new KDbRecordData(d->realColumnCount);
518 }
519 
dbTableName() const520 QString KDbTableViewData::dbTableName() const
521 {
522     if (d->cursor && d->cursor->query() && d->cursor->query()->masterTable())
523         return d->cursor->query()->masterTable()->name();
524     return QString();
525 }
526 
setSorting(int column,KDbOrderByColumn::SortOrder order)527 void KDbTableViewData::setSorting(int column, KDbOrderByColumn::SortOrder order)
528 {
529     d->sortOrder = order;
530     if (column < 0 || column >= d->columns.count()) {
531         d->sortColumn = -1;
532         d->realSortColumn = -1;
533         return;
534     }
535     // find proper column information for sorting (lookup column points to alternate column with visible data)
536     const KDbTableViewColumn *tvcol = d->columns.at(column);
537     const KDbQueryColumnInfo* visibleLookupColumnInfo = tvcol->visibleLookupColumnInfo();
538     const KDbField *field = visibleLookupColumnInfo ? visibleLookupColumnInfo->field() : tvcol->field();
539     d->sortColumn = column;
540     d->realSortColumn = tvcol->columnInfo()->indexForVisibleLookupValue() != -1
541                           ? tvcol->columnInfo()->indexForVisibleLookupValue() : d->sortColumn;
542 
543     // setup compare functor
544     d->lessThanFunctor.setColumnType(*field);
545     d->lessThanFunctor.setSortOrder(d->sortOrder);
546     d->lessThanFunctor.setSortColumn(column);
547 }
548 
sortColumn() const549 int KDbTableViewData::sortColumn() const
550 {
551     return d->sortColumn;
552 }
553 
sortOrder() const554 KDbOrderByColumn::SortOrder KDbTableViewData::sortOrder() const
555 {
556     return d->sortOrder;
557 }
558 
sort()559 void KDbTableViewData::sort()
560 {
561     if (d->sortColumn < 0 || d->sortColumn >= d->columns.count()) {
562         return;
563     }
564     qSort(begin(), end(), d->lessThanFunctor);
565 }
566 
setReadOnly(bool set)567 void KDbTableViewData::setReadOnly(bool set)
568 {
569     if (d->readOnly == set)
570         return;
571     d->readOnly = set;
572     if (d->readOnly)
573         setInsertingEnabled(false);
574 }
575 
setInsertingEnabled(bool set)576 void KDbTableViewData::setInsertingEnabled(bool set)
577 {
578     if (d->insertingEnabled == set)
579         return;
580     d->insertingEnabled = set;
581     if (d->insertingEnabled)
582         setReadOnly(false);
583 }
584 
clearRecordEditBuffer()585 void KDbTableViewData::clearRecordEditBuffer()
586 {
587     //init record edit buffer
588     if (!d->pRecordEditBuffer)
589         d->pRecordEditBuffer = new KDbRecordEditBuffer(isDBAware());
590     else
591         d->pRecordEditBuffer->clear();
592 }
593 
updateRecordEditBufferRef(KDbRecordData * record,int colnum,KDbTableViewColumn * col,QVariant * newval,bool allowSignals,QVariant * visibleValueForLookupField)594 bool KDbTableViewData::updateRecordEditBufferRef(KDbRecordData *record,
595         int colnum, KDbTableViewColumn* col, QVariant* newval, bool allowSignals,
596         QVariant *visibleValueForLookupField)
597 {
598     if (!record || !newval) {
599         return false;
600     }
601     d->result.clear();
602     if (allowSignals)
603         emit aboutToChangeCell(record, colnum, newval, &d->result);
604     if (!d->result.success)
605         return false;
606 
607     //kdbDebug() << "column #" << colnum << " = " << newval.toString();
608     if (!col) {
609         kdbWarning() << "column #" << colnum << "not found! col==0";
610         return false;
611     }
612     if (!d->pRecordEditBuffer)
613         d->pRecordEditBuffer = new KDbRecordEditBuffer(isDBAware());
614     if (d->pRecordEditBuffer->isDBAware()) {
615         if (!(col->columnInfo())) {
616             kdbWarning() << "column #" << colnum << " not found!";
617             return false;
618         }
619         d->pRecordEditBuffer->insert(col->columnInfo(), *newval);
620 
621         if (col->visibleLookupColumnInfo() && visibleValueForLookupField) {
622             //this is value for lookup table: update visible value as well
623             d->pRecordEditBuffer->insert(col->visibleLookupColumnInfo(), *visibleValueForLookupField);
624         }
625         return true;
626     }
627     if (!(col->field())) {
628         kdbWarning() << "column #" << colnum << "not found!";
629         return false;
630     }
631     //not db-aware:
632     const QString colname = col->field()->name();
633     if (colname.isEmpty()) {
634         kdbWarning() << "column #" << colnum << "not found!";
635         return false;
636     }
637     d->pRecordEditBuffer->insert(colname, *newval);
638     return true;
639 }
640 
updateRecordEditBuffer(KDbRecordData * record,int colnum,KDbTableViewColumn * col,const QVariant & newval,bool allowSignals)641 bool KDbTableViewData::updateRecordEditBuffer(KDbRecordData *record, int colnum,
642                                               KDbTableViewColumn* col,
643                                               const QVariant &newval, bool allowSignals)
644 {
645     QVariant newv(newval);
646     return updateRecordEditBufferRef(record, colnum, col, &newv, allowSignals);
647 }
648 
updateRecordEditBuffer(KDbRecordData * record,int colnum,const QVariant & newval,bool allowSignals)649 bool KDbTableViewData::updateRecordEditBuffer(KDbRecordData *record, int colnum,
650                                               const QVariant &newval, bool allowSignals)
651 {
652     KDbTableViewColumn* col = d->columns.value(colnum);
653     QVariant newv(newval);
654     return col ? updateRecordEditBufferRef(record, colnum, col, &newv, allowSignals) : false;
655 }
656 
657 //! Get a new value (if present in the buffer), or the old one (taken here for optimization)
saveRecordGetValue(const QVariant ** pval,KDbCursor * cursor,KDbRecordEditBuffer * pRecordEditBuffer,QList<KDbTableViewColumn * >::ConstIterator * it_f,KDbRecordData * record,KDbField * f,QVariant * val,int col)658 static inline void saveRecordGetValue(const QVariant **pval, KDbCursor *cursor,
659                                       KDbRecordEditBuffer *pRecordEditBuffer,
660                                       QList<KDbTableViewColumn*>::ConstIterator* it_f,
661                                       KDbRecordData *record, KDbField *f, QVariant* val, int col)
662 {
663     if (!*pval) {
664         *pval = cursor
665                 ? pRecordEditBuffer->at( (**it_f)->columnInfo(),
666                                          record->at(col).isNull() /* useDefaultValueIfPossible */ )
667                 : pRecordEditBuffer->at( *f );
668         *val = *pval ? **pval : record->at(col); /* get old value */
669         //kdbDebug() << col << *(**it_f)->columnInfo() << "val:" << *val;
670     }
671 }
672 
673 //! @todo if there're multiple views for this data, we need multiple buffers!
saveRecord(KDbRecordData * record,bool insert,bool repaint)674 bool KDbTableViewData::saveRecord(KDbRecordData *record, bool insert, bool repaint)
675 {
676     if (!d->pRecordEditBuffer)
677         return true; //nothing to do
678 
679     //check constraints:
680     //-check if every NOT NULL and NOT EMPTY field is filled
681     QList<KDbTableViewColumn*>::ConstIterator it_f(d->columns.constBegin());
682     int col = 0;
683     const QVariant *pval = nullptr;
684     QVariant val;
685     for (;it_f != d->columns.constEnd() && col < record->count();++it_f, ++col) {
686         KDbField *f = (*it_f)->field();
687         if (f->isNotNull()) {
688             saveRecordGetValue(&pval, d->cursor, d->pRecordEditBuffer, &it_f, record, f, &val, col);
689             //check it
690             if (val.isNull() && !f->isAutoIncrement()) {
691                 //NOT NULL violated
692                 d->result.message = tr("\"%1\" column requires a value to be entered.").arg(f->captionOrName())
693                                 + QLatin1String("\n\n") + KDbTableViewData::messageYouCanImproveData();
694                 d->result.description = tr("The column's constraint is declared as NOT NULL.");
695                 d->result.column = col;
696                 return false;
697             }
698         }
699         if (f->isNotEmpty()) {
700             saveRecordGetValue(&pval, d->cursor, d->pRecordEditBuffer, &it_f, record, f, &val, col);
701             if (!f->isAutoIncrement() && (val.isNull() || KDb::isEmptyValue(f->type(), val))) {
702                 //NOT EMPTY violated
703                 d->result.message = tr("\"%1\" column requires a value to be entered.").arg(f->captionOrName())
704                                 + QLatin1String("\n\n") + KDbTableViewData::messageYouCanImproveData();
705                 d->result.description = tr("The column's constraint is declared as NOT EMPTY.");
706                 d->result.column = col;
707                 return false;
708             }
709         }
710     }
711 
712     if (d->cursor) {//db-aware
713         if (insert) {
714             if (!d->cursor->insertRecord(record, d->pRecordEditBuffer,
715                                          d->containsRecordIdInfo /*also retrieve ROWID*/))
716             {
717                 d->result.message = tr("Record inserting failed.") + QLatin1String("\n\n")
718                                 + KDbTableViewData::messageYouCanImproveData();
719                 KDb::getHTMLErrorMesage(*d->cursor, &d->result);
720 
721                 /*   if (desc)
722                       *desc = */
723                 /*! @todo use KexiMainWindow::showErrorMessage(const QString &title, KDbObject *obj)
724                   after it will be moved somewhere to KDb (this will require moving other
725                     showErrorMessage() methods from KexiMainWindow to libkexiutils....)
726                   then: just call: *desc = KDb::errorMessage(d->cursor); */
727                 return false;
728             }
729         } else { // record updating
730             if (!d->cursor->updateRecord(static_cast<KDbRecordData*>(record), d->pRecordEditBuffer,
731                                          d->containsRecordIdInfo /*use ROWID*/))
732             {
733                 d->result.message = tr("Record changing failed.") + QLatin1String("\n\n")
734                                 + KDbTableViewData::messageYouCanImproveData();
735 //! @todo set d->result.column if possible
736                 KDb::getHTMLErrorMesage(*d->cursor, &d->result.description);
737                 return false;
738             }
739         }
740     } else {//not db-aware version
741         KDbRecordEditBuffer::SimpleMap b = d->pRecordEditBuffer->simpleBuffer();
742         for (KDbRecordEditBuffer::SimpleMap::ConstIterator it = b.constBegin();it != b.constEnd();++it) {
743             int i = -1;
744             foreach(KDbTableViewColumn *col, d->columns) {
745                 i++;
746                 if (col->field()->name() == it.key()) {
747                     kdbDebug() << col->field()->name() << ": " << record->at(i).toString()
748                            << " -> " << it.value().toString();
749                     (*record)[i] = it.value();
750                 }
751             }
752         }
753     }
754 
755     d->pRecordEditBuffer->clear();
756 
757     if (repaint)
758         emit recordRepaintRequested(record);
759     return true;
760 }
761 
saveRecordChanges(KDbRecordData * record,bool repaint)762 bool KDbTableViewData::saveRecordChanges(KDbRecordData *record, bool repaint)
763 {
764     d->result.clear();
765     emit aboutToUpdateRecord(record, d->pRecordEditBuffer, &d->result);
766     if (!d->result.success)
767         return false;
768 
769     if (saveRecord(record, false /*update*/, repaint)) {
770         emit recordUpdated(record);
771         return true;
772     }
773     return false;
774 }
775 
saveNewRecord(KDbRecordData * record,bool repaint)776 bool KDbTableViewData::saveNewRecord(KDbRecordData *record, bool repaint)
777 {
778     d->result.clear();
779     emit aboutToInsertRecord(record, &d->result, repaint);
780     if (!d->result.success)
781         return false;
782 
783     if (saveRecord(record, true /*insert*/, repaint)) {
784         emit recordInserted(record, repaint);
785         return true;
786     }
787     return false;
788 }
789 
deleteRecord(KDbRecordData * record,bool repaint)790 bool KDbTableViewData::deleteRecord(KDbRecordData *record, bool repaint)
791 {
792     d->result.clear();
793     emit aboutToDeleteRecord(record, &d->result, repaint);
794     if (!d->result.success)
795         return false;
796 
797     if (d->cursor) {//db-aware
798         d->result.success = false;
799         if (!d->cursor->deleteRecord(static_cast<KDbRecordData*>(record), d->containsRecordIdInfo /*use ROWID*/)) {
800             d->result.message = tr("Record deleting failed.");
801             //! @todo use KDberrorMessage() for description as in KDbTableViewData::saveRecord() */
802             KDb::getHTMLErrorMesage(*d->cursor, &d->result);
803             d->result.success = false;
804             return false;
805         }
806     }
807 
808     int index = indexOf(record);
809     if (index == -1) {
810         //aah - this shouldn't be!
811         kdbWarning() << "!removeRef() - IMPL. ERROR?";
812         d->result.success = false;
813         return false;
814     }
815     removeAt(index);
816     emit recordDeleted();
817     return true;
818 }
819 
deleteRecords(const QList<int> & recordsToDelete,bool repaint)820 void KDbTableViewData::deleteRecords(const QList<int> &recordsToDelete, bool repaint)
821 {
822     Q_UNUSED(repaint);
823 
824     if (recordsToDelete.isEmpty())
825         return;
826     int last_r = 0;
827     KDbTableViewDataIterator it(begin());
828     for (QList<int>::ConstIterator r_it = recordsToDelete.constBegin(); r_it != recordsToDelete.constEnd(); ++r_it) {
829         for (; last_r < (*r_it); last_r++)
830             ++it;
831         it = erase(it);   /* this will delete *it */
832         last_r++;
833     }
834 //DON'T CLEAR BECAUSE KexiTableViewPropertyBuffer will clear BUFFERS!
835 //--> emit reloadRequested(); //! \todo more effective?
836     emit recordsDeleted(recordsToDelete);
837 }
838 
insertRecord(KDbRecordData * record,int index,bool repaint)839 void KDbTableViewData::insertRecord(KDbRecordData *record, int index, bool repaint)
840 {
841     insert(index = qMin(index, count()), record);
842     emit recordInserted(record, index, repaint);
843 }
844 
clearInternal(bool processEvents)845 void KDbTableViewData::clearInternal(bool processEvents)
846 {
847     clearRecordEditBuffer();
848 //! @todo this is time consuming: find better data model
849     const int c = count();
850 #ifndef TABLEVIEW_NO_PROCESS_EVENTS
851     const bool _processEvents = processEvents && !qApp->closingDown();
852 #endif
853     for (int i = 0; i < c; i++) {
854         removeLast();
855 #ifndef TABLEVIEW_NO_PROCESS_EVENTS
856         if (_processEvents && i % 1000 == 0)
857             qApp->processEvents(QEventLoop::AllEvents, 1);
858 #endif
859     }
860 }
861 
deleteAllRecords(bool repaint)862 bool KDbTableViewData::deleteAllRecords(bool repaint)
863 {
864     clearInternal();
865 
866     bool res = true;
867     if (d->cursor) {
868         //db-aware
869         res = d->cursor->deleteAllRecords();
870     }
871 
872     if (repaint)
873         emit reloadRequested();
874     return res;
875 }
876 
autoIncrementedColumn() const877 int KDbTableViewData::autoIncrementedColumn() const
878 {
879     if (d->autoIncrementedColumn == -2) {
880         //find such a column
881         d->autoIncrementedColumn = -1;
882         foreach(KDbTableViewColumn *col, d->columns) {
883             d->autoIncrementedColumn++;
884             if (col->field()->isAutoIncrement())
885                 break;
886         }
887     }
888     return d->autoIncrementedColumn;
889 }
890 
preloadAllRecords()891 bool KDbTableViewData::preloadAllRecords()
892 {
893     if (!d->cursor)
894         return false;
895     if (!d->cursor->moveFirst() && d->cursor->result().isError())
896         return false;
897 
898 #ifndef TABLEVIEW_NO_PROCESS_EVENTS
899     const bool processEvents = !qApp->closingDown();
900 #endif
901 
902     for (int i = 0;!d->cursor->eof();i++) {
903         KDbRecordData *record = d->cursor->storeCurrentRecord();
904         if (!record) {
905             clear();
906             return false;
907         }
908 //  record->debug();
909         append(record);
910         if (!d->cursor->moveNext() && d->cursor->result().isError()) {
911             clear();
912             return false;
913         }
914 #ifndef TABLEVIEW_NO_PROCESS_EVENTS
915         if (processEvents && (i % 1000) == 0)
916             qApp->processEvents(QEventLoop::AllEvents, 1);
917 #endif
918     }
919     return true;
920 }
921 
isReadOnly() const922 bool KDbTableViewData::isReadOnly() const
923 {
924     return d->readOnly || (d->cursor && d->cursor->connection()->options()->isReadOnly());
925 }
926 
927 // static
messageYouCanImproveData()928 QString KDbTableViewData::messageYouCanImproveData()
929 {
930     return tr("Please correct data in this record or use the \"Cancel record changes\" function.");
931 }
932 
operator <<(QDebug dbg,const KDbTableViewData & data)933 QDebug operator<<(QDebug dbg, const KDbTableViewData &data)
934 {
935     dbg.nospace() << "TableViewData(";
936     dbg.space() << "sortColumn:" << data.sortColumn()
937                 << "sortOrder:" << (data.sortOrder() == KDbOrderByColumn::SortOrder::Ascending
938                                     ? "asc" : "desc")
939                 << "isDBAware:" << data.isDBAware()
940                 << "dbTableName:" << data.dbTableName()
941                 << "cursor:" << (data.cursor() ? "yes" : "no")
942                 << "columnCount:" << data.columnCount()
943                 << "count:" << data.count()
944                 << "autoIncrementedColumn:" << data.autoIncrementedColumn()
945                 << "visibleColumnCount:" << data.visibleColumnCount()
946                 << "isReadOnly:" << data.isReadOnly()
947                 << "isInsertingEnabled:" << data.isInsertingEnabled()
948                 << "containsRecordIdInfo:" << data.containsRecordIdInfo()
949                 << "result:" << data.result();
950     dbg.nospace() << ")";
951     return dbg.space();
952 }
953