KDb

KDbCursor.cpp
1/* This file is part of the KDE project
2 Copyright (C) 2003-2016 Jarosław Staniek <staniek@kde.org>
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License as published by the Free Software Foundation; either
7 version 2 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Library General Public License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this program; see the file COPYING. If not, write to
16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18*/
19
20#include "KDbCursor.h"
21#include "KDbConnection.h"
22#include "KDbDriver.h"
23#include "KDbDriverBehavior.h"
24#include "KDbError.h"
25#include "KDb.h"
26#include "KDbNativeStatementBuilder.h"
27#include "KDbQuerySchema.h"
28#include "KDbRecordData.h"
29#include "KDbRecordEditBuffer.h"
30#include "kdb_debug.h"
31
32class Q_DECL_HIDDEN KDbCursor::Private
33{
34public:
35 Private()
36 : opened(false)
37 , atLast(false)
38 , readAhead(false)
39 , validRecord(false)
40 , atBuffer(false)
41 {
42 }
43
44 ~Private() {
45 }
46
47 bool containsRecordIdInfo; //!< true if result contains extra column for record id;
48 //!< used only for PostgreSQL now
49 //! @todo IMPORTANT: use something like QPointer<KDbConnection> conn;
50 KDbConnection *conn;
51 KDbEscapedString rawSql;
52 bool opened;
53 bool atLast;
54 bool readAhead;
55 bool validRecord; //!< true if valid record is currently retrieved @ current position
56
57 //! Used by setOrderByColumnList()
58 KDbQueryColumnInfo::Vector orderByColumnList;
59 QList<QVariant> queryParameters;
60
61 //<members related to buffering>
62 bool atBuffer; //!< true if we already point to the buffer with curr_coldata
63 //</members related to buffering>
64};
65
67 : m_query(nullptr)
68 , m_options(options)
69 , d(new Private)
70{
71#ifdef KDB_DEBUG_GUI
72 KDb::debugGUI(QLatin1String("Create cursor for raw SQL: ") + sql.toString());
73#endif
74 init(conn);
75 d->rawSql = sql;
76}
77
79 : m_query(query)
80 , m_options(options)
81 , d(new Private)
82{
83#ifdef KDB_DEBUG_GUI
84 KDb::debugGUI(QString::fromLatin1("Create cursor for query \"%1\":\n")
85 .arg(KDb::iifNotEmpty(query->name(), QString::fromLatin1("<unnamed>")))
86 + KDbUtils::debugString(query));
87#endif
88 init(conn);
89}
90
91void KDbCursor::init(KDbConnection* conn)
92{
93 Q_ASSERT(conn);
94 d->conn = conn;
95 d->conn->addCursor(this);
96 m_afterLast = false;
97 m_at = 0;
101
102 d->containsRecordIdInfo = (m_query && m_query->masterTable())
104
105 if (m_query) {
106 //get list of all fields
110 : KDbQuerySchema::FieldsExpandedMode::WithInternalFields);
112 - m_query->internalFields(conn).count() - (d->containsRecordIdInfo ? 1 : 0);
115 } else {
116 m_visibleFieldsExpanded = nullptr;
118 m_fieldCount = 0;
120 }
121}
122
123KDbCursor::~KDbCursor()
124{
125#ifdef KDB_DEBUG_GUI
126#if 0 // too many details
127 if (m_query)
128 KDb::debugGUI(QLatin1String("~ Delete cursor for query"));
129 else
130 KDb::debugGUI(QLatin1String("~ Delete cursor: ") + m_rawSql.toString());
131#endif
132#endif
133 /* if (!m_query)
134 kdbDebug() << "KDbCursor::~KDbCursor() '" << m_rawSql.toLatin1() << "'";
135 else
136 kdbDebug() << "KDbCursor::~KDbCursor() ";*/
137
138 d->conn->takeCursor(this);
140 delete d;
141}
142
143bool KDbCursor::readAhead() const
144{
145 return d->readAhead;
146}
147
149{
150 return d->conn;
151}
152
154{
155 return d->conn;
156}
157
159{
160 return m_query;
161}
162
164{
165 return d->rawSql;
166}
167
169{
170 return m_options;
171}
172
174{
175 return d->opened;
176}
177
179{
180 return d->containsRecordIdInfo;
181}
182
184{
186 if (!drv_storeCurrentRecord(data)) {
187 delete data;
188 return nullptr;
189 }
190 return data;
191}
192
194{
195 if (!data) {
196 return false;
197 }
199 return drv_storeCurrentRecord(data);
200}
201
203{
204 if (d->opened) {
205 if (!close())
206 return false;
207 }
208 if (!d->rawSql.isEmpty()) {
209 m_result.setSql(d->rawSql);
210 }
211 else {
212 if (!m_query) {
213 kdbDebug() << "no query statement (or schema) defined!";
214 m_result = KDbResult(ERR_SQL_EXECUTION_ERROR,
215 tr("No query statement or schema defined."));
216 return false;
217 }
219 options.setAlsoRetrieveRecordId(d->containsRecordIdInfo); /*get record Id if needed*/
222 if (!builder.generateSelectStatement(&sql, m_query, options, d->queryParameters)
223 || sql.isEmpty())
224 {
225 kdbDebug() << "no statement generated!";
226 m_result = KDbResult(ERR_SQL_EXECUTION_ERROR,
227 tr("Could not generate query statement."));
228 return false;
229 }
230 m_result.setSql(sql);
231#ifdef KDB_DEBUG_GUI
232 KDb::debugGUI(QString::fromLatin1("SQL for query \"%1\": ")
233 .arg(KDb::iifNotEmpty(m_query->name(), QString::fromLatin1("<unnamed>")))
234 + m_result.sql().toString());
235#endif
236 }
237 d->opened = drv_open(m_result.sql());
238 m_afterLast = false; //we are not @ the end
239 m_at = 0; //we are before 1st rec
240 if (!d->opened) {
241 m_result.setCode(ERR_SQL_EXECUTION_ERROR);
242 m_result.setMessage(tr("Error opening database cursor."));
243 return false;
244 }
245 d->validRecord = false;
246
248// kdbDebug() << "READ AHEAD:";
249 d->readAhead = getNextRecord(); //true if any record in this query
250// kdbDebug() << "READ AHEAD = " << d->readAhead;
251 }
252 m_at = 0; //we are still before 1st rec
253 return !m_result.isError();
254}
255
257{
258 if (!d->opened) {
259 return true;
260 }
261 bool ret = drv_close();
262
263 clearBuffer();
264
265 d->opened = false;
266 m_afterLast = false;
267 d->readAhead = false;
268 m_fieldCount = 0;
271 m_at = -1;
272
273// kdbDebug() << ret;
274 return ret;
275}
276
278{
279 if (!d->opened) {
280 return open();
281 }
282 return close() && open();
283}
284
286{
287 if (!d->opened) {
288 return false;
289 }
290 if (!d->readAhead) {
291 if (m_options & KDbCursor::Option::Buffered) {
293 //eof and bof should now return true:
294 m_afterLast = true;
295 m_at = 0;
296 return false; //buffering completed and there is no records!
297 }
298 if (m_records_in_buf > 0) {
299 //set state as we would be before first rec:
300 d->atBuffer = false;
301 m_at = 0;
302 //..and move to next, i.e. 1st record
303 m_afterLast = !getNextRecord();
304 return !m_afterLast;
305 }
307 // not buffered
308 m_at = 0;
309 m_afterLast = !getNextRecord();
310 return !m_afterLast;
311 }
312
313 if (m_afterLast && m_at == 0) //failure if already no records
314 return false;
315 if (!reopen()) //try reopen
316 return false;
317 if (m_afterLast) //eof
318 return false;
319 } else {
320 //we have a record already read-ahead: we now point @ that:
321 m_at = 1;
322 }
323 //get first record
324 m_afterLast = false;
325 d->readAhead = false; //1st record had been read
326 return d->validRecord;
327}
328
330{
331 if (!d->opened) {
332 return false;
333 }
334 if (m_afterLast || d->atLast) {
335 return d->validRecord; //we already have valid last record retrieved
336 }
337 if (!getNextRecord()) { //at least next record must be retrieved
338 m_afterLast = true;
339 d->validRecord = false;
340 d->atLast = false;
341 return false; //no records
342 }
343 while (getNextRecord()) //move after last rec.
344 ;
345 m_afterLast = false;
346 //cursor shows last record data
347 d->atLast = true;
348 return true;
349}
350
352{
353 if (!d->opened || m_afterLast) {
354 return false;
355 }
356 if (getNextRecord()) {
357 return true;
358 }
359 return false;
360}
361
363{
364 if (!d->opened /*|| m_beforeFirst*/ || !(m_options & KDbCursor::Option::Buffered)) {
365 return false;
366 }
367 //we're after last record and there are records in the buffer
368 //--let's move to last record
369 if (m_afterLast && (m_records_in_buf > 0)) {
371 m_at = m_records_in_buf;
372 d->atBuffer = true; //now current record is stored in the buffer
373 d->validRecord = true;
374 m_afterLast = false;
375 return true;
376 }
377 //we're at first record: go BOF
378 if ((m_at <= 1) || (m_records_in_buf <= 1/*sanity*/)) {
379 m_at = 0;
380 d->atBuffer = false;
381 d->validRecord = false;
382 return false;
383 }
384
385 m_at--;
386 if (d->atBuffer) {//we already have got a pointer to buffer
387 drv_bufferMovePointerPrev(); //just move to prev record in the buffer
388 } else {//we have no pointer
389 //compute a place in the buffer that contain next record's data
390 drv_bufferMovePointerTo(m_at - 1);
391 d->atBuffer = true; //now current record is stored in the buffer
392 }
393 d->validRecord = true;
394 m_afterLast = false;
395 return true;
396}
397
399{
400 return m_options & KDbCursor::Option::Buffered;
401}
402
403void KDbCursor::setBuffered(bool buffered)
404{
405 if (!d->opened) {
406 return;
407 }
408 if (isBuffered() == buffered)
409 return;
410 m_options ^= KDbCursor::Option::Buffered;
411}
412
414{
415 if (!isBuffered() || m_fieldCount == 0)
416 return;
417
419
421 d->atBuffer = false;
422}
423
425{
426 m_fetchResult = FetchResult::Invalid; //by default: invalid result of record fetching
427
428 if (m_options & KDbCursor::Option::Buffered) {//this cursor is buffered:
429// kdbDebug() << "m_at < m_records_in_buf :: " << (long)m_at << " < " << m_records_in_buf;
430 if (m_at < m_records_in_buf) {//we have next record already buffered:
431 if (d->atBuffer) {//we already have got a pointer to buffer
432 drv_bufferMovePointerNext(); //just move to next record in the buffer
433 } else {//we have no pointer
434 //compute a place in the buffer that contain next record's data
435 drv_bufferMovePointerTo(m_at - 1 + 1);
436 d->atBuffer = true; //now current record is stored in the buffer
437 }
438 } else {//we are after last retrieved record: we need to physically fetch next record:
439 if (!d->readAhead) {//we have no record that was read ahead
441 //retrieve record only if we are not after
442 //the last buffer's item (i.e. when buffer is not fully filled):
443// kdbDebug()<<"==== buffering: drv_getNextRecord() ====";
444 drv_getNextRecord();
445 }
446 if (m_fetchResult != FetchResult::Ok) {//there is no record
447 m_buffering_completed = true; //no more records for buffer
448// kdbDebug()<<"m_fetchResult != FetchResult::Ok ********";
449 d->validRecord = false;
450 m_afterLast = true;
451 m_at = -1; //position is invalid now and will not be used
453 m_result = KDbResult(ERR_CURSOR_RECORD_FETCHING,
454 tr("Could not fetch next record."));
455 return false;
456 }
457 return false; // in case of m_fetchResult = FetchResult::End or m_fetchResult = FetchInvalid
458 }
459 //we have a record: store this record's values in the buffer
462 } else //we have a record that was read ahead: eat this
463 d->readAhead = false;
464 }
465 } else {//we are after last retrieved record: we need to physically fetch next record:
466 if (!d->readAhead) {//we have no record that was read ahead
467// kdbDebug()<<"==== no prefetched record ====";
468 drv_getNextRecord();
469 if (m_fetchResult != FetchResult::Ok) {//there is no record
470// kdbDebug()<<"m_fetchResult != FetchResult::Ok ********";
471 d->validRecord = false;
472 m_afterLast = true;
473 m_at = -1;
475 return false;
476 }
477 m_result = KDbResult(ERR_CURSOR_RECORD_FETCHING,
478 tr("Could not fetch next record."));
479 return false;
480 }
481 } else { //we have a record that was read ahead: eat this
482 d->readAhead = false;
483 }
484 }
485
486 m_at++;
487
488// if (m_data->curr_colname && m_data->curr_coldata)
489// for (int i=0;i<m_data->curr_cols;i++) {
490// kdbDebug()<<i<<": "<< m_data->curr_colname[i]<<" == "<< m_data->curr_coldata[i];
491// }
492// kdbDebug()<<"m_at == "<<(long)m_at;
493
494 d->validRecord = true;
495 return true;
496}
497
499{
500//! @todo doesn't update cursor's buffer YET!
501 clearResult();
502 if (!m_query)
503 return false;
504 return d->conn->updateRecord(m_query, data, buf, useRecordId);
505}
506
508{
509//! @todo doesn't update cursor's buffer YET!
510 if (!m_query) {
511 clearResult();
512 return false;
513 }
514 return d->conn->insertRecord(m_query, data, buf, useRecordId);
515}
516
517bool KDbCursor::deleteRecord(KDbRecordData* data, bool useRecordId)
518{
519//! @todo doesn't update cursor's buffer YET!
520 clearResult();
521 if (!m_query)
522 return false;
523 return d->conn->deleteRecord(m_query, data, useRecordId);
524}
525
527{
528//! @todo doesn't update cursor's buffer YET!
529 clearResult();
530 if (!m_query)
531 return false;
532 return d->conn->deleteAllRecords(m_query);
533}
534
535QDebug debug(QDebug dbg, KDbCursor& cursor, bool buildSql)
536{
537 dbg.nospace() << "CURSOR(";
538 if (!cursor.query()) {
539 dbg.nospace() << "RAW SQL STATEMENT:" << cursor.rawSql().toString()
540 << "\n";
541 }
542 else if (buildSql) {
545 QString sqlString;
546 if (builder.generateSelectStatement(&sql, cursor.query())) {
547 sqlString = sql.toString();
548 }
549 else {
550 sqlString = QLatin1String("<CANNOT GENERATE!>");
551 }
552 dbg.nospace() << "KDbQuerySchema:" << sqlString << "\n";
553 }
554 if (cursor.isOpened()) {
555 dbg.space() << "OPENED";
556 }
557 else {
558 dbg.space() << "NOT_OPENED";
559 }
560 if (cursor.isBuffered()) {
561 dbg.space() << "BUFFERED";
562 }
563 else {
564 dbg.space() << "NOT_BUFFERED";
565 }
566 dbg.nospace() << "AT=" << cursor.at() << ")";
567 return dbg.space();
568}
569
570QDebug operator<<(QDebug dbg, KDbCursor &cursor)
571{
572 return debug(dbg, cursor, true /*buildSql*/);
573}
574
575QDebug operator<<(QDebug dbg, const KDbCursor &cursor)
576{
577 return debug(dbg, const_cast<KDbCursor&>(cursor), false /* !buildSql*/);
578}
579
581{
582 Q_UNUSED(columnNames);
583//! @todo implement this: all field names should be found, exit otherwise
584
585 // OK
586//! @todo if (!d->orderByColumnList)
587}
588
589/*! Convenience method, similar to setOrderBy(const QStringList&). */
590void KDbCursor::setOrderByColumnList(const QString& column1, const QString& column2,
591 const QString& column3, const QString& column4, const QString& column5)
592{
593 Q_UNUSED(column1);
594 Q_UNUSED(column2);
595 Q_UNUSED(column3);
596 Q_UNUSED(column4);
597 Q_UNUSED(column5);
598//! @todo implement this, like above
599//! @todo add ORDER BY info to debugString()
600}
601
603{
604 return d->orderByColumnList;
605}
606
608{
609 return d->queryParameters;
610}
611
613{
614 d->queryParameters = params;
615}
616
617//! @todo extraMessages
618#if 0
619static const char *extraMessages[] = {
620 QT_TRANSLATE_NOOP("KDbCursor", "No connection for cursor open operation specified.")
621};
622#endif
Provides database connection, allowing queries and data modification.
bool updateRecord(KDbQuerySchema *query, KDbRecordData *data, KDbRecordEditBuffer *buf, bool useRecordId=false)
void takeCursor(KDbCursor *cursor)
Used by KDbCursor class.
void addCursor(KDbCursor *cursor)
Used by KDbCursor class.
bool deleteAllRecords(KDbQuerySchema *query)
KDbDriver * driver() const
bool deleteRecord(KDbQuerySchema *query, KDbRecordData *data, bool useRecordId=false)
Provides database cursor functionality.
Definition KDbCursor.h:69
bool deleteRecord(KDbRecordData *data, bool useRecordId=false)
virtual bool moveNext()
bool updateRecord(KDbRecordData *data, KDbRecordEditBuffer *buf, bool useRecordId=false)
bool open()
KDbRecordData * storeCurrentRecord() const
bool isBuffered() const
virtual bool drv_storeCurrentRecord(KDbRecordData *data) const =0
@ Invalid
used before starting the fetching, result is not known yet
@ End
at the end of data
@ Error
error of fetching
@ Ok
the data is fetched
int m_fieldsToStoreInRecord
Used by storeCurrentRecord(), reimplement if needed.
Definition KDbCursor.h:302
virtual void drv_clearBuffer()
Definition KDbCursor.h:285
bool insertRecord(KDbRecordData *data, KDbRecordEditBuffer *buf, bool getRecrordId=false)
virtual void drv_bufferMovePointerTo(qint64 at)=0
QList< QVariant > queryParameters() const
KDbQueryColumnInfo::Vector orderByColumnList() const
void clearBuffer()
bool moveFirst()
bool reopen()
virtual bool moveLast()
KDbQuerySchema * query() const
int m_fieldCount
cached field count information
Definition KDbCursor.h:301
KDbCursor::Options m_options
cursor options that describes its behavior
Definition KDbCursor.h:306
virtual void drv_bufferMovePointerPrev()=0
bool isOpened() const
KDbCursor(KDbConnection *conn, const KDbEscapedString &sql, Options options=KDbCursor::Option::None)
Definition KDbCursor.cpp:66
int m_records_in_buf
number of records currently stored in the buffer
Definition KDbCursor.h:319
bool containsRecordIdInfo() const
bool m_buffering_completed
true if we already have all records stored in the buffer
Definition KDbCursor.h:320
virtual void drv_bufferMovePointerNext()=0
bool deleteAllRecords()
bool getNextRecord()
int m_logicalFieldCount
logical field count, i.e. without internal values like Record Id or lookup
Definition KDbCursor.h:305
void setQueryParameters(const QList< QVariant > &params)
Sets query parameters params for this cursor.
virtual void drv_appendCurrentRecordToBuffer()=0
Options options() const
void setOrderByColumnList(const QStringList &columnNames)
KDbQueryColumnInfo::Vector * m_visibleFieldsExpanded
Useful e.g. for value(int) method to obtain access to schema definition.
Definition KDbCursor.h:324
qint64 at() const
Definition KDbCursor.h:161
virtual bool drv_open(const KDbEscapedString &sql)=0
FetchResult m_fetchResult
result of a record fetching
Definition KDbCursor.h:316
KDbConnection * connection()
void setBuffered(bool buffered)
virtual bool movePrev()
virtual bool close()
KDbEscapedString rawSql() const
bool ROW_ID_FIELD_RETURNS_LAST_AUTOINCREMENTED_VALUE
bool _1ST_ROW_READ_AHEAD_REQUIRED_TO_KNOW_IF_THE_RESULT_IS_EMPTY
KDbDriverBehavior * behavior()
Returns structure that provides detailed information about driver's default behavior.
Definition KDbDriver.cpp:74
Specialized string for escaping.
A builder for generating various types of native SQL statements.
bool generateSelectStatement(KDbEscapedString *target, KDbQuerySchema *querySchema, const KDbSelectStatementOptions &options, const QList< QVariant > &parameters=QList< QVariant >()) const
KDbQuerySchema provides information about database query.
@ WithInternalFieldsAndRecordId
Like WithInternalFields but record ID (big int type) field is appended after internal fields.
KDbQueryColumnInfo::Vector visibleFieldsExpanded(KDbConnection *conn, FieldsExpandedMode options=FieldsExpandedMode::Default) const
KDbTableSchema * masterTable() const
KDbQueryColumnInfo::Vector internalFields(KDbConnection *conn) const
Structure for storing single record with type information.
void resize(int numCols)
provides data for single edited database record
bool isError() const
Definition KDbResult.cpp:64
Options used in KDbNativeStatementBuilder::generateSelectStatement()
T iifNotEmpty(const T &string, const T &stringIfEmpty)
Definition KDb.h:820
@ DriverEscaping
Identifiers are escaped by driver.
Definition KDbGlobal.h:145
QDebug operator<<(QDebug dbg, const PerceptualColor::LchaDouble &value)
QDebug & nospace()
QDebug & space()
qsizetype count() const const
QString fromLatin1(QByteArrayView str)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:20:59 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.