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 | |
9 | QT_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 | |
23 | static constexpr qsizetype initial_cache_size = 128; |
24 | |
25 | void QSqlCachedResultPrivate::cleanup() |
26 | { |
27 | cache.clear(); |
28 | atEnd = false; |
29 | colCount = 0; |
30 | rowCacheEnd = 0; |
31 | } |
32 | |
33 | void 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 | |
47 | int 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 | |
59 | bool QSqlCachedResultPrivate::canSeek(int i) const |
60 | { |
61 | if (forwardOnly || i < 0) |
62 | return false; |
63 | return rowCacheEnd >= (i + 1) * colCount; |
64 | } |
65 | |
66 | void QSqlCachedResultPrivate::revertLast() |
67 | { |
68 | if (forwardOnly) |
69 | return; |
70 | rowCacheEnd -= colCount; |
71 | } |
72 | |
73 | inline int QSqlCachedResultPrivate::cacheCount() const |
74 | { |
75 | Q_ASSERT(!forwardOnly); |
76 | Q_ASSERT(colCount); |
77 | return rowCacheEnd / colCount; |
78 | } |
79 | |
80 | ////////////// |
81 | |
82 | QSqlCachedResult::QSqlCachedResult(QSqlCachedResultPrivate &d) |
83 | : QSqlResult(d) |
84 | { |
85 | } |
86 | |
87 | void QSqlCachedResult::init(int colCount) |
88 | { |
89 | Q_D(QSqlCachedResult); |
90 | d->init(count: colCount, fo: isForwardOnly()); |
91 | } |
92 | |
93 | bool 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 | |
132 | bool 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 | |
142 | bool QSqlCachedResult::fetchPrevious() |
143 | { |
144 | return fetch(i: at() - 1); |
145 | } |
146 | |
147 | bool 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 | |
160 | bool 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 | |
181 | QVariant 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 | |
191 | bool 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 | |
201 | void QSqlCachedResult::cleanup() |
202 | { |
203 | Q_D(QSqlCachedResult); |
204 | setAt(QSql::BeforeFirstRow); |
205 | setActive(false); |
206 | d->cleanup(); |
207 | } |
208 | |
209 | void QSqlCachedResult::clearValues() |
210 | { |
211 | Q_D(QSqlCachedResult); |
212 | setAt(QSql::BeforeFirstRow); |
213 | d->rowCacheEnd = 0; |
214 | d->atEnd = false; |
215 | } |
216 | |
217 | bool 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 | |
236 | int QSqlCachedResult::colCount() const |
237 | { |
238 | Q_D(const QSqlCachedResult); |
239 | return d->colCount; |
240 | } |
241 | |
242 | QSqlCachedResult::ValueCache &QSqlCachedResult::cache() |
243 | { |
244 | Q_D(QSqlCachedResult); |
245 | return d->cache; |
246 | } |
247 | |
248 | void QSqlCachedResult::virtual_hook(int id, void *data) |
249 | { |
250 | QSqlResult::virtual_hook(id, data); |
251 | } |
252 | |
253 | void QSqlCachedResult::detachFromResultSet() |
254 | { |
255 | cleanup(); |
256 | } |
257 | |
258 | void QSqlCachedResult::setNumericalPrecisionPolicy(QSql::NumericalPrecisionPolicy policy) |
259 | { |
260 | QSqlResult::setNumericalPrecisionPolicy(policy); |
261 | cleanup(); |
262 | } |
263 | |
264 | |
265 | QT_END_NAMESPACE |
266 |