File indexing completed on 2024-12-08 07:18:19
0001 /* This file is part of the KDE project 0002 Copyright (C) 2003-2016 Jarosław Staniek <staniek@kde.org> 0003 0004 This program is free software; you can redistribute it and/or 0005 modify it under the terms of the GNU Library General Public 0006 License as published by the Free Software Foundation; either 0007 version 2 of the License, or (at your option) any later version. 0008 0009 This program is distributed in the hope that it will be useful, 0010 but WITHOUT ANY WARRANTY; without even the implied warranty of 0011 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0012 Library General Public License for more details. 0013 0014 You should have received a copy of the GNU Library General Public License 0015 along with this program; see the file COPYING. If not, write to 0016 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0017 * Boston, MA 02110-1301, USA. 0018 */ 0019 0020 #include "KDbCursor.h" 0021 #include "KDbConnection.h" 0022 #include "KDbDriver.h" 0023 #include "KDbDriverBehavior.h" 0024 #include "KDbError.h" 0025 #include "KDb.h" 0026 #include "KDbNativeStatementBuilder.h" 0027 #include "KDbQuerySchema.h" 0028 #include "KDbRecordData.h" 0029 #include "KDbRecordEditBuffer.h" 0030 #include "kdb_debug.h" 0031 0032 class Q_DECL_HIDDEN KDbCursor::Private 0033 { 0034 public: 0035 Private() 0036 : opened(false) 0037 , atLast(false) 0038 , readAhead(false) 0039 , validRecord(false) 0040 , atBuffer(false) 0041 { 0042 } 0043 0044 ~Private() { 0045 } 0046 0047 bool containsRecordIdInfo; //!< true if result contains extra column for record id; 0048 //!< used only for PostgreSQL now 0049 //! @todo IMPORTANT: use something like QPointer<KDbConnection> conn; 0050 KDbConnection *conn; 0051 KDbEscapedString rawSql; 0052 bool opened; 0053 bool atLast; 0054 bool readAhead; 0055 bool validRecord; //!< true if valid record is currently retrieved @ current position 0056 0057 //! Used by setOrderByColumnList() 0058 KDbQueryColumnInfo::Vector orderByColumnList; 0059 QList<QVariant> queryParameters; 0060 0061 //<members related to buffering> 0062 bool atBuffer; //!< true if we already point to the buffer with curr_coldata 0063 //</members related to buffering> 0064 }; 0065 0066 KDbCursor::KDbCursor(KDbConnection* conn, const KDbEscapedString& sql, Options options) 0067 : m_query(nullptr) 0068 , m_options(options) 0069 , d(new Private) 0070 { 0071 #ifdef KDB_DEBUG_GUI 0072 KDb::debugGUI(QLatin1String("Create cursor for raw SQL: ") + sql.toString()); 0073 #endif 0074 init(conn); 0075 d->rawSql = sql; 0076 } 0077 0078 KDbCursor::KDbCursor(KDbConnection* conn, KDbQuerySchema* query, Options options) 0079 : m_query(query) 0080 , m_options(options) 0081 , d(new Private) 0082 { 0083 #ifdef KDB_DEBUG_GUI 0084 KDb::debugGUI(QString::fromLatin1("Create cursor for query \"%1\":\n") 0085 .arg(KDb::iifNotEmpty(query->name(), QString::fromLatin1("<unnamed>"))) 0086 + KDbUtils::debugString(query)); 0087 #endif 0088 init(conn); 0089 } 0090 0091 void KDbCursor::init(KDbConnection* conn) 0092 { 0093 Q_ASSERT(conn); 0094 d->conn = conn; 0095 d->conn->addCursor(this); 0096 m_afterLast = false; 0097 m_at = 0; 0098 m_records_in_buf = 0; 0099 m_buffering_completed = false; 0100 m_fetchResult = FetchResult::Invalid; 0101 0102 d->containsRecordIdInfo = (m_query && m_query->masterTable()) 0103 && d->conn->driver()->behavior()->ROW_ID_FIELD_RETURNS_LAST_AUTOINCREMENTED_VALUE == false; 0104 0105 if (m_query) { 0106 //get list of all fields 0107 m_visibleFieldsExpanded = new KDbQueryColumnInfo::Vector(); 0108 *m_visibleFieldsExpanded = m_query->visibleFieldsExpanded(conn, 0109 d->containsRecordIdInfo ? KDbQuerySchema::FieldsExpandedMode::WithInternalFieldsAndRecordId 0110 : KDbQuerySchema::FieldsExpandedMode::WithInternalFields); 0111 m_logicalFieldCount = m_visibleFieldsExpanded->count() 0112 - m_query->internalFields(conn).count() - (d->containsRecordIdInfo ? 1 : 0); 0113 m_fieldCount = m_visibleFieldsExpanded->count(); 0114 m_fieldsToStoreInRecord = m_fieldCount; 0115 } else { 0116 m_visibleFieldsExpanded = nullptr; 0117 m_logicalFieldCount = 0; 0118 m_fieldCount = 0; 0119 m_fieldsToStoreInRecord = 0; 0120 } 0121 } 0122 0123 KDbCursor::~KDbCursor() 0124 { 0125 #ifdef KDB_DEBUG_GUI 0126 #if 0 // too many details 0127 if (m_query) 0128 KDb::debugGUI(QLatin1String("~ Delete cursor for query")); 0129 else 0130 KDb::debugGUI(QLatin1String("~ Delete cursor: ") + m_rawSql.toString()); 0131 #endif 0132 #endif 0133 /* if (!m_query) 0134 kdbDebug() << "KDbCursor::~KDbCursor() '" << m_rawSql.toLatin1() << "'"; 0135 else 0136 kdbDebug() << "KDbCursor::~KDbCursor() ";*/ 0137 0138 d->conn->takeCursor(this); 0139 delete m_visibleFieldsExpanded; 0140 delete d; 0141 } 0142 0143 bool KDbCursor::readAhead() const 0144 { 0145 return d->readAhead; 0146 } 0147 0148 KDbConnection* KDbCursor::connection() 0149 { 0150 return d->conn; 0151 } 0152 0153 const KDbConnection* KDbCursor::connection() const 0154 { 0155 return d->conn; 0156 } 0157 0158 KDbQuerySchema *KDbCursor::query() const 0159 { 0160 return m_query; 0161 } 0162 0163 KDbEscapedString KDbCursor::rawSql() const 0164 { 0165 return d->rawSql; 0166 } 0167 0168 KDbCursor::Options KDbCursor::options() const 0169 { 0170 return m_options; 0171 } 0172 0173 bool KDbCursor::isOpened() const 0174 { 0175 return d->opened; 0176 } 0177 0178 bool KDbCursor::containsRecordIdInfo() const 0179 { 0180 return d->containsRecordIdInfo; 0181 } 0182 0183 KDbRecordData* KDbCursor::storeCurrentRecord() const 0184 { 0185 KDbRecordData* data = new KDbRecordData(m_fieldsToStoreInRecord); 0186 if (!drv_storeCurrentRecord(data)) { 0187 delete data; 0188 return nullptr; 0189 } 0190 return data; 0191 } 0192 0193 bool KDbCursor::storeCurrentRecord(KDbRecordData* data) const 0194 { 0195 if (!data) { 0196 return false; 0197 } 0198 data->resize(m_fieldsToStoreInRecord); 0199 return drv_storeCurrentRecord(data); 0200 } 0201 0202 bool KDbCursor::open() 0203 { 0204 if (d->opened) { 0205 if (!close()) 0206 return false; 0207 } 0208 if (!d->rawSql.isEmpty()) { 0209 m_result.setSql(d->rawSql); 0210 } 0211 else { 0212 if (!m_query) { 0213 kdbDebug() << "no query statement (or schema) defined!"; 0214 m_result = KDbResult(ERR_SQL_EXECUTION_ERROR, 0215 tr("No query statement or schema defined.")); 0216 return false; 0217 } 0218 KDbSelectStatementOptions options; 0219 options.setAlsoRetrieveRecordId(d->containsRecordIdInfo); /*get record Id if needed*/ 0220 KDbNativeStatementBuilder builder(d->conn, KDb::DriverEscaping); 0221 KDbEscapedString sql; 0222 if (!builder.generateSelectStatement(&sql, m_query, options, d->queryParameters) 0223 || sql.isEmpty()) 0224 { 0225 kdbDebug() << "no statement generated!"; 0226 m_result = KDbResult(ERR_SQL_EXECUTION_ERROR, 0227 tr("Could not generate query statement.")); 0228 return false; 0229 } 0230 m_result.setSql(sql); 0231 #ifdef KDB_DEBUG_GUI 0232 KDb::debugGUI(QString::fromLatin1("SQL for query \"%1\": ") 0233 .arg(KDb::iifNotEmpty(m_query->name(), QString::fromLatin1("<unnamed>"))) 0234 + m_result.sql().toString()); 0235 #endif 0236 } 0237 d->opened = drv_open(m_result.sql()); 0238 m_afterLast = false; //we are not @ the end 0239 m_at = 0; //we are before 1st rec 0240 if (!d->opened) { 0241 m_result.setCode(ERR_SQL_EXECUTION_ERROR); 0242 m_result.setMessage(tr("Error opening database cursor.")); 0243 return false; 0244 } 0245 d->validRecord = false; 0246 0247 if (d->conn->driver()->behavior()->_1ST_ROW_READ_AHEAD_REQUIRED_TO_KNOW_IF_THE_RESULT_IS_EMPTY) { 0248 // kdbDebug() << "READ AHEAD:"; 0249 d->readAhead = getNextRecord(); //true if any record in this query 0250 // kdbDebug() << "READ AHEAD = " << d->readAhead; 0251 } 0252 m_at = 0; //we are still before 1st rec 0253 return !m_result.isError(); 0254 } 0255 0256 bool KDbCursor::close() 0257 { 0258 if (!d->opened) { 0259 return true; 0260 } 0261 bool ret = drv_close(); 0262 0263 clearBuffer(); 0264 0265 d->opened = false; 0266 m_afterLast = false; 0267 d->readAhead = false; 0268 m_fieldCount = 0; 0269 m_fieldsToStoreInRecord = 0; 0270 m_logicalFieldCount = 0; 0271 m_at = -1; 0272 0273 // kdbDebug() << ret; 0274 return ret; 0275 } 0276 0277 bool KDbCursor::reopen() 0278 { 0279 if (!d->opened) { 0280 return open(); 0281 } 0282 return close() && open(); 0283 } 0284 0285 bool KDbCursor::moveFirst() 0286 { 0287 if (!d->opened) { 0288 return false; 0289 } 0290 if (!d->readAhead) { 0291 if (m_options & KDbCursor::Option::Buffered) { 0292 if (m_records_in_buf == 0 && m_buffering_completed) { 0293 //eof and bof should now return true: 0294 m_afterLast = true; 0295 m_at = 0; 0296 return false; //buffering completed and there is no records! 0297 } 0298 if (m_records_in_buf > 0) { 0299 //set state as we would be before first rec: 0300 d->atBuffer = false; 0301 m_at = 0; 0302 //..and move to next, i.e. 1st record 0303 m_afterLast = !getNextRecord(); 0304 return !m_afterLast; 0305 } 0306 } else if (!(d->conn->driver()->behavior()->_1ST_ROW_READ_AHEAD_REQUIRED_TO_KNOW_IF_THE_RESULT_IS_EMPTY)) { 0307 // not buffered 0308 m_at = 0; 0309 m_afterLast = !getNextRecord(); 0310 return !m_afterLast; 0311 } 0312 0313 if (m_afterLast && m_at == 0) //failure if already no records 0314 return false; 0315 if (!reopen()) //try reopen 0316 return false; 0317 if (m_afterLast) //eof 0318 return false; 0319 } else { 0320 //we have a record already read-ahead: we now point @ that: 0321 m_at = 1; 0322 } 0323 //get first record 0324 m_afterLast = false; 0325 d->readAhead = false; //1st record had been read 0326 return d->validRecord; 0327 } 0328 0329 bool KDbCursor::moveLast() 0330 { 0331 if (!d->opened) { 0332 return false; 0333 } 0334 if (m_afterLast || d->atLast) { 0335 return d->validRecord; //we already have valid last record retrieved 0336 } 0337 if (!getNextRecord()) { //at least next record must be retrieved 0338 m_afterLast = true; 0339 d->validRecord = false; 0340 d->atLast = false; 0341 return false; //no records 0342 } 0343 while (getNextRecord()) //move after last rec. 0344 ; 0345 m_afterLast = false; 0346 //cursor shows last record data 0347 d->atLast = true; 0348 return true; 0349 } 0350 0351 bool KDbCursor::moveNext() 0352 { 0353 if (!d->opened || m_afterLast) { 0354 return false; 0355 } 0356 if (getNextRecord()) { 0357 return true; 0358 } 0359 return false; 0360 } 0361 0362 bool KDbCursor::movePrev() 0363 { 0364 if (!d->opened /*|| m_beforeFirst*/ || !(m_options & KDbCursor::Option::Buffered)) { 0365 return false; 0366 } 0367 //we're after last record and there are records in the buffer 0368 //--let's move to last record 0369 if (m_afterLast && (m_records_in_buf > 0)) { 0370 drv_bufferMovePointerTo(m_records_in_buf - 1); 0371 m_at = m_records_in_buf; 0372 d->atBuffer = true; //now current record is stored in the buffer 0373 d->validRecord = true; 0374 m_afterLast = false; 0375 return true; 0376 } 0377 //we're at first record: go BOF 0378 if ((m_at <= 1) || (m_records_in_buf <= 1/*sanity*/)) { 0379 m_at = 0; 0380 d->atBuffer = false; 0381 d->validRecord = false; 0382 return false; 0383 } 0384 0385 m_at--; 0386 if (d->atBuffer) {//we already have got a pointer to buffer 0387 drv_bufferMovePointerPrev(); //just move to prev record in the buffer 0388 } else {//we have no pointer 0389 //compute a place in the buffer that contain next record's data 0390 drv_bufferMovePointerTo(m_at - 1); 0391 d->atBuffer = true; //now current record is stored in the buffer 0392 } 0393 d->validRecord = true; 0394 m_afterLast = false; 0395 return true; 0396 } 0397 0398 bool KDbCursor::isBuffered() const 0399 { 0400 return m_options & KDbCursor::Option::Buffered; 0401 } 0402 0403 void KDbCursor::setBuffered(bool buffered) 0404 { 0405 if (!d->opened) { 0406 return; 0407 } 0408 if (isBuffered() == buffered) 0409 return; 0410 m_options ^= KDbCursor::Option::Buffered; 0411 } 0412 0413 void KDbCursor::clearBuffer() 0414 { 0415 if (!isBuffered() || m_fieldCount == 0) 0416 return; 0417 0418 drv_clearBuffer(); 0419 0420 m_records_in_buf = 0; 0421 d->atBuffer = false; 0422 } 0423 0424 bool KDbCursor::getNextRecord() 0425 { 0426 m_fetchResult = FetchResult::Invalid; //by default: invalid result of record fetching 0427 0428 if (m_options & KDbCursor::Option::Buffered) {//this cursor is buffered: 0429 // kdbDebug() << "m_at < m_records_in_buf :: " << (long)m_at << " < " << m_records_in_buf; 0430 if (m_at < m_records_in_buf) {//we have next record already buffered: 0431 if (d->atBuffer) {//we already have got a pointer to buffer 0432 drv_bufferMovePointerNext(); //just move to next record in the buffer 0433 } else {//we have no pointer 0434 //compute a place in the buffer that contain next record's data 0435 drv_bufferMovePointerTo(m_at - 1 + 1); 0436 d->atBuffer = true; //now current record is stored in the buffer 0437 } 0438 } else {//we are after last retrieved record: we need to physically fetch next record: 0439 if (!d->readAhead) {//we have no record that was read ahead 0440 if (!m_buffering_completed) { 0441 //retrieve record only if we are not after 0442 //the last buffer's item (i.e. when buffer is not fully filled): 0443 // kdbDebug()<<"==== buffering: drv_getNextRecord() ===="; 0444 drv_getNextRecord(); 0445 } 0446 if (m_fetchResult != FetchResult::Ok) {//there is no record 0447 m_buffering_completed = true; //no more records for buffer 0448 // kdbDebug()<<"m_fetchResult != FetchResult::Ok ********"; 0449 d->validRecord = false; 0450 m_afterLast = true; 0451 m_at = -1; //position is invalid now and will not be used 0452 if (m_fetchResult == FetchResult::Error) { 0453 m_result = KDbResult(ERR_CURSOR_RECORD_FETCHING, 0454 tr("Could not fetch next record.")); 0455 return false; 0456 } 0457 return false; // in case of m_fetchResult = FetchResult::End or m_fetchResult = FetchInvalid 0458 } 0459 //we have a record: store this record's values in the buffer 0460 drv_appendCurrentRecordToBuffer(); 0461 m_records_in_buf++; 0462 } else //we have a record that was read ahead: eat this 0463 d->readAhead = false; 0464 } 0465 } else {//we are after last retrieved record: we need to physically fetch next record: 0466 if (!d->readAhead) {//we have no record that was read ahead 0467 // kdbDebug()<<"==== no prefetched record ===="; 0468 drv_getNextRecord(); 0469 if (m_fetchResult != FetchResult::Ok) {//there is no record 0470 // kdbDebug()<<"m_fetchResult != FetchResult::Ok ********"; 0471 d->validRecord = false; 0472 m_afterLast = true; 0473 m_at = -1; 0474 if (m_fetchResult == FetchResult::End) { 0475 return false; 0476 } 0477 m_result = KDbResult(ERR_CURSOR_RECORD_FETCHING, 0478 tr("Could not fetch next record.")); 0479 return false; 0480 } 0481 } else { //we have a record that was read ahead: eat this 0482 d->readAhead = false; 0483 } 0484 } 0485 0486 m_at++; 0487 0488 // if (m_data->curr_colname && m_data->curr_coldata) 0489 // for (int i=0;i<m_data->curr_cols;i++) { 0490 // kdbDebug()<<i<<": "<< m_data->curr_colname[i]<<" == "<< m_data->curr_coldata[i]; 0491 // } 0492 // kdbDebug()<<"m_at == "<<(long)m_at; 0493 0494 d->validRecord = true; 0495 return true; 0496 } 0497 0498 bool KDbCursor::updateRecord(KDbRecordData* data, KDbRecordEditBuffer* buf, bool useRecordId) 0499 { 0500 //! @todo doesn't update cursor's buffer YET! 0501 clearResult(); 0502 if (!m_query) 0503 return false; 0504 return d->conn->updateRecord(m_query, data, buf, useRecordId); 0505 } 0506 0507 bool KDbCursor::insertRecord(KDbRecordData* data, KDbRecordEditBuffer* buf, bool useRecordId) 0508 { 0509 //! @todo doesn't update cursor's buffer YET! 0510 if (!m_query) { 0511 clearResult(); 0512 return false; 0513 } 0514 return d->conn->insertRecord(m_query, data, buf, useRecordId); 0515 } 0516 0517 bool KDbCursor::deleteRecord(KDbRecordData* data, bool useRecordId) 0518 { 0519 //! @todo doesn't update cursor's buffer YET! 0520 clearResult(); 0521 if (!m_query) 0522 return false; 0523 return d->conn->deleteRecord(m_query, data, useRecordId); 0524 } 0525 0526 bool KDbCursor::deleteAllRecords() 0527 { 0528 //! @todo doesn't update cursor's buffer YET! 0529 clearResult(); 0530 if (!m_query) 0531 return false; 0532 return d->conn->deleteAllRecords(m_query); 0533 } 0534 0535 QDebug debug(QDebug dbg, KDbCursor& cursor, bool buildSql) 0536 { 0537 dbg.nospace() << "CURSOR("; 0538 if (!cursor.query()) { 0539 dbg.nospace() << "RAW SQL STATEMENT:" << cursor.rawSql().toString() 0540 << "\n"; 0541 } 0542 else if (buildSql) { 0543 KDbNativeStatementBuilder builder(cursor.connection(), KDb::DriverEscaping); 0544 KDbEscapedString sql; 0545 QString sqlString; 0546 if (builder.generateSelectStatement(&sql, cursor.query())) { 0547 sqlString = sql.toString(); 0548 } 0549 else { 0550 sqlString = QLatin1String("<CANNOT GENERATE!>"); 0551 } 0552 dbg.nospace() << "KDbQuerySchema:" << sqlString << "\n"; 0553 } 0554 if (cursor.isOpened()) { 0555 dbg.space() << "OPENED"; 0556 } 0557 else { 0558 dbg.space() << "NOT_OPENED"; 0559 } 0560 if (cursor.isBuffered()) { 0561 dbg.space() << "BUFFERED"; 0562 } 0563 else { 0564 dbg.space() << "NOT_BUFFERED"; 0565 } 0566 dbg.nospace() << "AT=" << cursor.at() << ")"; 0567 return dbg.space(); 0568 } 0569 0570 QDebug operator<<(QDebug dbg, KDbCursor &cursor) 0571 { 0572 return debug(dbg, cursor, true /*buildSql*/); 0573 } 0574 0575 QDebug operator<<(QDebug dbg, const KDbCursor &cursor) 0576 { 0577 return debug(dbg, const_cast<KDbCursor&>(cursor), false /* !buildSql*/); 0578 } 0579 0580 void KDbCursor::setOrderByColumnList(const QStringList& columnNames) 0581 { 0582 Q_UNUSED(columnNames); 0583 //! @todo implement this: all field names should be found, exit otherwise 0584 0585 // OK 0586 //! @todo if (!d->orderByColumnList) 0587 } 0588 0589 /*! Convenience method, similar to setOrderBy(const QStringList&). */ 0590 void KDbCursor::setOrderByColumnList(const QString& column1, const QString& column2, 0591 const QString& column3, const QString& column4, const QString& column5) 0592 { 0593 Q_UNUSED(column1); 0594 Q_UNUSED(column2); 0595 Q_UNUSED(column3); 0596 Q_UNUSED(column4); 0597 Q_UNUSED(column5); 0598 //! @todo implement this, like above 0599 //! @todo add ORDER BY info to debugString() 0600 } 0601 0602 KDbQueryColumnInfo::Vector KDbCursor::orderByColumnList() const 0603 { 0604 return d->orderByColumnList; 0605 } 0606 0607 QList<QVariant> KDbCursor::queryParameters() const 0608 { 0609 return d->queryParameters; 0610 } 0611 0612 void KDbCursor::setQueryParameters(const QList<QVariant>& params) 0613 { 0614 d->queryParameters = params; 0615 } 0616 0617 //! @todo extraMessages 0618 #if 0 0619 static const char *extraMessages[] = { 0620 QT_TRANSLATE_NOOP("KDbCursor", "No connection for cursor open operation specified.") 0621 }; 0622 #endif