swift
datastoreobjectlist.h
Go to the documentation of this file.
1 // SPDX-FileCopyrightText: Copyright (C) 2015 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
5 
6 #ifndef SWIFT_MISC_DB_DATABASEOBJECTLIST_H
7 #define SWIFT_MISC_DB_DATABASEOBJECTLIST_H
8 
9 #include <QJsonArray>
10 #include <QMap>
11 #include <QSet>
12 #include <QString>
13 
14 #include "misc/db/datastore.h"
15 #include "misc/jsonexception.h"
16 #include "misc/mapbuilder.h"
17 #include "misc/setbuilder.h"
19 
20 namespace swift::misc::db
21 {
25  template <class OBJ, class CONTAINER, typename KEYTYPE>
26  class IDatastoreObjectList : public ITimestampObjectList<OBJ, CONTAINER>
27  {
28  static constexpr bool hasIntegerKey =
29  std::is_base_of_v<IDatastoreObjectWithIntegerKey, OBJ> && std::is_same_v<int, KEYTYPE>;
30  static constexpr bool hasStringKey =
31  std::is_base_of_v<IDatastoreObjectWithStringKey, OBJ> && std::is_base_of_v<QString, KEYTYPE>;
32  static_assert(hasIntegerKey || hasStringKey,
33  "ObjectType needs to implement IDatastoreObjectWithXXXXKey and have appropriate KeyType");
34 
35  public:
37  OBJ findByKey(KEYTYPE key, const OBJ &notFound = OBJ()) const
38  {
39  return this->container().findFirstByOrDefault(&OBJ::getDbKey, key, notFound);
40  }
41 
43  CONTAINER findByKeys(const QSet<KEYTYPE> &keys) const
44  {
45  CONTAINER objects;
46  if (keys.isEmpty()) { return objects; }
48  {
49  if (!keys.contains(obj.getDbKey())) { continue; }
50  objects.push_back(obj);
51  }
52  return objects;
53  }
54 
56  CONTAINER findObjectsWithDbKey() const
57  {
58  CONTAINER objects;
60  {
61  if (!obj.hasValidDbKey()) { continue; }
62  objects.push_back(obj);
63  }
64  return objects;
65  }
66 
68  CONTAINER findObjectsWithoutDbKey() const
69  {
70  CONTAINER objects;
72  {
73  if (obj.hasValidDbKey()) { continue; }
74  objects.push_back(obj);
75  }
76  return objects;
77  }
78 
80  OBJ maxKeyObject() const
81  {
82  if (this->container().isEmpty()) { return OBJ(); }
83  const OBJ max = *std::max_element(this->container().begin(), this->container().end(),
84  [](const OBJ &obj1, const OBJ &obj2) {
85  bool v1 = obj1.hasValidDbKey();
86  bool v2 = obj2.hasValidDbKey();
87  if (v1 && v2) { return obj1.getDbKey() < obj2.getDbKey(); }
88  return v2;
89  });
90  return max;
91  }
92 
94  void sortByKey() { this->container().sort(swift::misc::predicates::MemberLess(&OBJ::getDbKey)); }
95 
97  QSet<KEYTYPE> toDbKeySet() const
98  {
100  for (const OBJ &obj : ITimestampObjectList<OBJ, CONTAINER>::container())
101  {
102  if (!obj.hasValidDbKey()) { continue; }
103  keys.insert(obj.getDbKey());
104  }
105  return keys;
106  }
107 
110  {
112  for (const OBJ &obj : ITimestampObjectList<OBJ, CONTAINER>::container())
113  {
114  if (!obj.hasValidDbKey()) { continue; }
115  map.insert(obj.getDbKey(), obj);
116  }
117  return map;
118  }
119 
121  QSet<QString> toDbKeyStringSet() const
122  {
124  for (const OBJ &obj : ITimestampObjectList<OBJ, CONTAINER>::container())
125  {
126  if (!obj.hasValidDbKey()) { continue; }
127  keys.insert(obj.getDbKeyAsString());
128  }
129  return keys;
130  }
131 
133  QString dbKeysAsString(const QString &separator) const
134  {
135  if (ITimestampObjectList<OBJ, CONTAINER>::container().isEmpty()) { return {}; }
137  QString s;
138  for (const QString &k : keys)
139  {
140  if (s.isEmpty()) { s += k; }
141  else { s += separator + k; }
142  }
143  return s;
144  }
145 
147  KEYTYPE getMaxKey(bool *ok = nullptr) const
148  {
149  QSet<KEYTYPE> keys(this->toDbKeySet());
150  if (keys.isEmpty())
151  {
152  if (ok) { *ok = false; }
153  return KEYTYPE();
154  }
155  KEYTYPE max = *std::max_element(keys.begin(), keys.end());
156  if (ok) { *ok = true; }
157  return max;
158  }
159 
161  int removeObjectsWithKeys(const QSet<KEYTYPE> &keys)
162  {
163  if (keys.isEmpty()) { return 0; }
164  if (this->container().isEmpty()) { return 0; }
165  CONTAINER newValues;
166  for (const OBJ &obj : ITimestampObjectList<OBJ, CONTAINER>::container())
167  {
168  if (keys.contains(obj.getDbKey())) { continue; }
169  newValues.push_back(obj);
170  }
171  const int delta = this->container().size() - newValues.size();
172  if (delta > 0) { this->container() = newValues; }
173  return delta;
174  }
175 
178  {
179  if (this->container().isEmpty()) { return 0; }
180  CONTAINER newValues;
181  for (const OBJ &obj : ITimestampObjectList<OBJ, CONTAINER>::container())
182  {
183  if (!obj.hasValidDbKey()) { continue; }
184  newValues.push_back(obj);
185  }
186  int delta = this->container().size() - newValues.size();
187  this->container() = newValues;
188  return delta;
189  }
190 
192  int replaceOrAddObjectsByKey(const CONTAINER &container)
193  {
194  if (container.isEmpty()) { return 0; }
195  if (this->container().isEmpty())
196  {
197  this->container() = container;
198  return this->container().size();
199  }
200  CONTAINER newValues(this->container());
201  const QSet<KEYTYPE> keys(container.toDbKeySet());
202  newValues.removeObjectsWithKeys(keys);
203  int removeSize = newValues.size(); // size after removing data
204  newValues.push_back(container);
205  this->container() = newValues;
206  return newValues.size() - removeSize;
207  }
208 
210  QDateTime latestDbTimestamp() const
211  {
212  CONTAINER objs(this->container());
213  objs.removeObjectsWithoutDbKey();
214  if (objs.isEmpty()) { return QDateTime(); }
215  return objs.latestTimestamp();
216  }
217 
219  QDateTime oldestDbTimestamp() const
220  {
221  CONTAINER objs(this->container());
222  objs.removeObjectsWithoutDbKey();
223  if (objs.isEmpty()) { return QDateTime(); }
224  return objs.oldestDbTimestamp();
225  }
226 
228  int countWithValidDbKey(bool withKey) const
229  {
230  int count = 0;
231  for (const OBJ &obj : ITimestampObjectList<OBJ, CONTAINER>::container())
232  {
233  if (obj.hasValidDbKey() && withKey) { count++; }
234  }
235  return count;
236  }
237 
239  int countWithValidDbKey() const { return this->countWithValidDbKey(true); }
240 
243  {
244  for (const OBJ &obj : ITimestampObjectList<OBJ, CONTAINER>::container())
245  {
246  if (!obj.hasValidDbKey()) { return true; }
247  }
248  return false;
249  }
250 
252  bool containsDbKey(KEYTYPE key) const
253  {
254  for (const OBJ &obj : ITimestampObjectList<OBJ, CONTAINER>::container())
255  {
256  if (!obj.hasValidDbKey()) { continue; }
257  if (obj.getDbKey() == key) { return true; }
258  }
259  return false;
260  }
261 
264  static CONTAINER fromMultipleJsonFormats(const QJsonObject &jsonObject)
265  {
266  // also accept cache format
267  if (jsonObject.isEmpty())
268  {
269  const CONTAINER c;
270  return c;
271  }
272 
273  // cache or settings format?
274  if (json::looksLikeSwiftDataObjectJson(jsonObject))
275  {
276  const QJsonObject cacheObj = json::swiftDataObjectValue(jsonObject);
277  CONTAINER container;
278  private_ns::CValueObjectMetaInfoHelper::convertFromMemoizedJson(
279  cacheObj, container, true, 0); // handles both, memoized or "normal" convertFromJson
280  return container;
281  }
282 
283  // plain vanilla container, does not match memoized objects
284  if (json::looksLikeSwiftContainerJson(jsonObject))
285  {
286  CONTAINER container;
287  container.convertFromJson(jsonObject);
288  return container;
289  }
290 
291  // still as type/value pair
292  if (json::looksLikeSwiftTypeValuePairJson(jsonObject))
293  {
294  const QJsonObject valueObject = jsonObject.value("value").toObject();
295  CONTAINER container;
296  private_ns::CValueObjectMetaInfoHelper::convertFromMemoizedJson(
297  valueObject, container, true, 0); // handles both, memoized or "normal" convertFromJson
298  return container;
299  }
300 
301  // DB format, as array
302  if (jsonObject.contains("data"))
303  {
304  return IDatastoreObjectList::fromDatabaseJson(jsonObject.value("data").toArray());
305  }
306 
307  // no idea what this is
308  throw CJsonException("Unsupported JSON format");
309  }
310 
313  static CONTAINER fromMultipleJsonFormats(const QString &jsonString)
314  {
315  // also accept cache format
316  if (jsonString.isEmpty())
317  {
318  const CONTAINER c;
319  return c;
320  }
321 
322  const QJsonObject jo = json::jsonObjectFromString(jsonString, false);
324  }
325 
328  static CONTAINER fromDatabaseJson(const QJsonArray &array)
329  {
330  CONTAINER container;
331  for (const QJsonValue &value : array) { container.push_back(OBJ::fromDatabaseJson(value.toObject())); }
332  return container;
333  }
334 
337  static CONTAINER fromDatabaseJson(const QJsonArray &array, const QString &prefix)
338  {
339  CONTAINER container;
340  for (const QJsonValue &value : array)
341  {
342  container.push_back(OBJ::fromDatabaseJson(value.toObject(), prefix));
343  }
344  return container;
345  }
346 
347  protected:
349  IDatastoreObjectList() = default;
350  };
351 } // namespace swift::misc::db
352 
353 #endif // SWIFT_MISC_DB_DATABASEOBJECTLIST_H
Thrown when a convertFromJson method encounters an unrecoverable error in JSON data.
Definition: jsonexception.h:24
Build a QMap more efficiently when calling insert() in a for loop.
Definition: mapbuilder.h:26
void insert(Ts &&...keyAndValue)
Add an key/value pair to the map. Runs in amortized constant time.
Definition: mapbuilder.h:30
Build a QSet more efficiently when calling insert() in a for loop.
Definition: setbuilder.h:25
void insert(const T &value)
Add an element to the set. Runs in amortized constant time.
Definition: setbuilder.h:29
List of objects with timestamp. Such objects should implement.
const CONTAINER & container() const
Container.
List of objects read from database. Such objects should implement.
int replaceOrAddObjectsByKey(const CONTAINER &container)
Update or insert data (based on DB key)
QDateTime oldestDbTimestamp() const
Latest DB timestamp (means objects with DB key)
QSet< QString > toDbKeyStringSet() const
All keys as string set (also integer keys will be converted to string)
int removeObjectsWithKeys(const QSet< KEYTYPE > &keys)
Remove objects with keys.
int removeObjectsWithoutDbKey()
Remove objects without key.
OBJ findByKey(KEYTYPE key, const OBJ &notFound=OBJ()) const
Object with key, notFound otherwise.
static CONTAINER fromDatabaseJson(const QJsonArray &array, const QString &prefix)
From DB JSON.
OBJ maxKeyObject() const
Object with max.key.
KEYTYPE getMaxKey(bool *ok=nullptr) const
Max.key value (making sense with integer key)
QDateTime latestDbTimestamp() const
Latest DB timestamp (means objects with DB key)
QMap< KEYTYPE, OBJ > toDbKeyValueMap() const
As map with DB key/object.
static CONTAINER fromDatabaseJson(const QJsonArray &array)
From DB JSON with default prefixes.
QSet< KEYTYPE > toDbKeySet() const
All keys as set.
QString dbKeysAsString(const QString &separator) const
The DB keys as string.
IDatastoreObjectList()=default
Constructor.
static CONTAINER fromMultipleJsonFormats(const QJsonObject &jsonObject)
From multiple JSON formats.
int countWithValidDbKey() const
Number of entries with valid DB key.
CONTAINER findObjectsWithoutDbKey() const
Objects without DB key.
CONTAINER findByKeys(const QSet< KEYTYPE > &keys) const
Object with key, notFound otherwise.
static CONTAINER fromMultipleJsonFormats(const QString &jsonString)
From multiple JSON formats.
bool containsAnyObjectWithoutKey() const
Any object without key?
int countWithValidDbKey(bool withKey) const
Number of objects with/without key.
bool containsDbKey(KEYTYPE key) const
Contains object with key?
CONTAINER findObjectsWithDbKey() const
Objects with DB key.
QJsonObject jsonObjectFromString(const QString &json, bool acceptCacheFormat)
JSON Object from string.
Definition: json.cpp:413
auto MemberLess(Ts... vs)
Predicate which compares the return values of some member functions of two objects.
Definition: predicates.h:43
T::const_iterator begin(const LockFreeReader< T > &reader)
Non-member begin() and end() for so LockFree containers can be used in ranged for loops.
Definition: lockfree.h:332
T::const_iterator end(const LockFreeReader< T > &reader)
Non-member begin() and end() for so LockFree containers can be used in ranged for loops.
Definition: lockfree.h:338