1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "private/qsqlcachedresult_p.h"
5
6#include <qvariant.h>
7#include <QtSql/private/qsqldriver_p.h>
8
9QT_BEGIN_NAMESPACE
10
11/*
12 QSqlCachedResult is a convenience class for databases that only allow
13 forward only fetching. It will cache all the results so we can iterate
14 backwards over the results again.
15
16 All you need to do is to inherit from QSqlCachedResult and reimplement
17 gotoNext(). gotoNext() will have a reference to the internal cache and
18 will give you an index where you can start filling in your data. Special
19 case: If the user actually wants a forward-only query, idx will be -1
20 to indicate that we are not interested in the actual values.
21*/
22
23static constexpr qsizetype initial_cache_size = 128;
24
25void QSqlCachedResultPrivate::cleanup()
26{
27 cache.clear();
28 atEnd = false;
29 colCount = 0;
30 rowCacheEnd = 0;
31}
32
33void QSqlCachedResultPrivate::init(int count, bool fo)
34{
35 Q_ASSERT(count);
36 cleanup();
37 forwardOnly = fo;
38 colCount = count;
39 if (fo) {
40 cache.resize(size: count);
41 rowCacheEnd = count;
42 } else {
43 cache.resize(size: initial_cache_size * count);
44 }
45}
46
47int QSqlCachedResultPrivate::nextIndex()
48{
49 if (forwardOnly)
50 return 0;
51 int newIdx = rowCacheEnd;
52 if (newIdx + colCount > cache.size())
53 cache.resize(size: qMin(a: cache.size() * 2, b: cache.size() + 10000));
54 rowCacheEnd += colCount;
55
56 return newIdx;
57}
58
59bool QSqlCachedResultPrivate::canSeek(int i) const
60{
61 if (forwardOnly || i < 0)
62 return false;
63 return rowCacheEnd >= (i + 1) * colCount;
64}
65
66void QSqlCachedResultPrivate::revertLast()
67{
68 if (forwardOnly)
69 return;
70 rowCacheEnd -= colCount;
71}
72
73inline int QSqlCachedResultPrivate::cacheCount() const
74{
75 Q_ASSERT(!forwardOnly);
76 Q_ASSERT(colCount);
77 return rowCacheEnd / colCount;
78}
79
80//////////////
81
82QSqlCachedResult::QSqlCachedResult(QSqlCachedResultPrivate &d)
83 : QSqlResult(d)
84{
85}
86
87void QSqlCachedResult::init(int colCount)
88{
89 Q_D(QSqlCachedResult);
90 d->init(count: colCount, fo: isForwardOnly());
91}
92
93bool QSqlCachedResult::fetch(int i)
94{
95 Q_D(QSqlCachedResult);
96 if ((!isActive()) || (i < 0))
97 return false;
98 if (at() == i)
99 return true;
100 if (d->forwardOnly) {
101 // speed hack - do not copy values if not needed
102 if (at() > i || at() == QSql::AfterLastRow)
103 return false;
104 while(at() < i - 1) {
105 if (!gotoNext(values&: d->cache, index: -1))
106 return false;
107 setAt(at() + 1);
108 }
109 if (!gotoNext(values&: d->cache, index: 0))
110 return false;
111 setAt(at() + 1);
112 return true;
113 }
114 if (d->canSeek(i)) {
115 setAt(i);
116 return true;
117 }
118 if (d->rowCacheEnd > 0)
119 setAt(d->cacheCount());
120 while (at() < i + 1) {
121 if (!cacheNext()) {
122 if (d->canSeek(i))
123 break;
124 return false;
125 }
126 }
127 setAt(i);
128
129 return true;
130}
131
132bool QSqlCachedResult::fetchNext()
133{
134 Q_D(QSqlCachedResult);
135 if (d->canSeek(i: at() + 1)) {
136 setAt(at() + 1);
137 return true;
138 }
139 return cacheNext();
140}
141
142bool QSqlCachedResult::fetchPrevious()
143{
144 return fetch(i: at() - 1);
145}
146
147bool QSqlCachedResult::fetchFirst()
148{
149 Q_D(QSqlCachedResult);
150 if (d->forwardOnly && at() != QSql::BeforeFirstRow) {
151 return false;
152 }
153 if (d->canSeek(i: 0)) {
154 setAt(0);
155 return true;
156 }
157 return cacheNext();
158}
159
160bool QSqlCachedResult::fetchLast()
161{
162 Q_D(QSqlCachedResult);
163 if (d->atEnd) {
164 if (d->forwardOnly)
165 return false;
166 else
167 return fetch(i: d->cacheCount() - 1);
168 }
169
170 int i = at();
171 while (fetchNext())
172 ++i; /* brute force */
173 if (d->forwardOnly && at() == QSql::AfterLastRow) {
174 setAt(i);
175 return true;
176 } else {
177 return fetch(i);
178 }
179}
180
181QVariant QSqlCachedResult::data(int i)
182{
183 Q_D(const QSqlCachedResult);
184 int idx = d->forwardOnly ? i : at() * d->colCount + i;
185 if (i >= d->colCount || i < 0 || at() < 0 || idx >= d->rowCacheEnd)
186 return QVariant();
187
188 return d->cache.at(i: idx);
189}
190
191bool QSqlCachedResult::isNull(int i)
192{
193 Q_D(const QSqlCachedResult);
194 int idx = d->forwardOnly ? i : at() * d->colCount + i;
195 if (i >= d->colCount || i < 0 || at() < 0 || idx >= d->rowCacheEnd)
196 return true;
197
198 return d->cache.at(i: idx).isNull();
199}
200
201void QSqlCachedResult::cleanup()
202{
203 Q_D(QSqlCachedResult);
204 setAt(QSql::BeforeFirstRow);
205 setActive(false);
206 d->cleanup();
207}
208
209void QSqlCachedResult::clearValues()
210{
211 Q_D(QSqlCachedResult);
212 setAt(QSql::BeforeFirstRow);
213 d->rowCacheEnd = 0;
214 d->atEnd = false;
215}
216
217bool QSqlCachedResult::cacheNext()
218{
219 Q_D(QSqlCachedResult);
220 if (d->atEnd)
221 return false;
222
223 if (isForwardOnly()) {
224 d->cache.resize(size: d->colCount);
225 }
226
227 if (!gotoNext(values&: d->cache, index: d->nextIndex())) {
228 d->revertLast();
229 d->atEnd = true;
230 return false;
231 }
232 setAt(at() + 1);
233 return true;
234}
235
236int QSqlCachedResult::colCount() const
237{
238 Q_D(const QSqlCachedResult);
239 return d->colCount;
240}
241
242QSqlCachedResult::ValueCache &QSqlCachedResult::cache()
243{
244 Q_D(QSqlCachedResult);
245 return d->cache;
246}
247
248void QSqlCachedResult::virtual_hook(int id, void *data)
249{
250 QSqlResult::virtual_hook(id, data);
251}
252
253void QSqlCachedResult::detachFromResultSet()
254{
255 cleanup();
256}
257
258void QSqlCachedResult::setNumericalPrecisionPolicy(QSql::NumericalPrecisionPolicy policy)
259{
260 QSqlResult::setNumericalPrecisionPolicy(policy);
261 cleanup();
262}
263
264
265QT_END_NAMESPACE
266

source code of qtbase/src/sql/kernel/qsqlcachedresult.cpp