Qx v0.7
Qt Extensions Library
Loading...
Searching...
No Matches
qx-sqlschemareport.h
1#ifndef QX_SQLSCHEMAREPORT_H
2#define QX_SQLSCHEMAREPORT_H
3
4// Shared Lib Support
5#include "qx/sql/qx_sql_export.h"
6
7// Qt Includes
8#include <QSqlDatabase>
9#include <QSqlQuery>
10#include <QSqlRecord>
11#include <QSqlField>
12
13// Intra-component Includes
14#include "qx/sql/qx-sqlerror.h"
15#include "qx/sql/qx-sqlquery.h"
16
17// Extra-component Includes
19
20class QSqlError;
21
22namespace Qx
23{
24
25class SqlQuery;
26class SqlDatabase;
27
28class QX_SQL_EXPORT SqlSchemaReport final : public AbstractError<"Qx::SqlSchemaReport", 8>
29{
30 friend class SqlDatabase;
31//-Class Enums-------------------------------------------------------------
32public:
33 enum Defect
34 {
35 None = 0x0,
36 MissingTables = 0x1,
37 MissingFields = 0x2,
38 TypeMismatches = 0x4,
39 ExtraTables = 0x8,
40 ExtraFields = 0x10
41 };
42 Q_DECLARE_FLAGS(Defects, Defect);
43
44 // TODO: When implementing view check, these terms should account for that (make tables/views share values)
45 enum Strictness
46 {
47 Lenient = 0x0,
48 FieldStrict = 0x1,
49 TableStrict = 0x2,
50 TypeStrict = 0x4
51 };
52 Q_DECLARE_FLAGS(StrictnessFlags, Strictness);
53
54//-Class Aliases-------------------------------------------------------------
55private:
56 // HELPER
57 template<typename T>
58 struct unwrap_optional { using type = T; };
59
60 template<typename U>
61 struct unwrap_optional<std::optional<U>> { using type = U; };
62
63 template<typename T>
64 using unwrap_optional_t = typename unwrap_optional<T>::type;
65
66//-Class Structs-------------------------------------------------------------
67public:
68 struct FieldMismatch
69 {
70 QString name;
71 QLatin1String expected;
72 QLatin1String actual;
73 };
74
75 struct DefectiveTable
76 {
77 QString name;
78 Defects defects{};
79 QStringList missingFields{};
80 QStringList extraFields{};
81 QList<FieldMismatch> mismatchedFields{};
82 };
83
84//-Class Variables-------------------------------------------------------------
85private:
86 static inline const QString PRIMARY = u"SQL Error."_s;
87 static inline const QString SECONDARY = u"The database does not follow the expected schema."_s;
88
89//-Instance Variables-------------------------------------------------------------
90private:
91 QString mDatabase;
92 Defects mDefects;
93 QList<DefectiveTable> mDefTables;
94
95//-Constructor--------------------------------------------------------------------
96public:
97 SqlSchemaReport();
98
99//-Class Functions-------------------------------------------------------------
100private:
101 static void addDefect(SqlSchemaReport& rp, DefectiveTable& table, Defect defect);
102
103 template<QxSql::sql_struct... Structs>
104 static SqlSchemaReport generate(QSqlDatabase& db, StrictnessFlags strictness)
105 {
106 Q_ASSERT(db.isValid() && db.isOpen());
107 // TODO: This only checks tables. Check views as wells. They cannot share the same name so there is no risk of overlap
108 SqlSchemaReport rp;
109
110 QStringList allTables = db.tables(QSql::Tables);
111
112 // For each struct type in Structs (table)...
113 ([&] {
114 // Get table info
115 constexpr auto tableView = QxSqlPrivate::getStructId<Structs>().view();
116 constexpr auto tableViewQuoted = QxSqlPrivate::getStructIdQuoted<Structs>().view();
117 const QString table(tableView);
118 const QString tableQuoted(tableViewQuoted);
119 const QSqlRecord tableRecord = db.record(tableQuoted); // Docs suggest quoting here
120
121 // Error prep
122 DefectiveTable tableDefects{.name = table};
123
124 // Examine table
125 if(!tableRecord.isEmpty())
126 {
127 // Account for table
128 allTables.removeAll(table);
129
130 // Get field info for table
131 constexpr auto memberMetas = QxSqlPrivate::getMemberMeta<Structs>();
132 QStringList allFields;
133 for(auto i = 0; i < tableRecord.count(); ++i)
134 allFields.append(tableRecord.fieldName(i));
135
136 // For each member in the struct (field in the table)...
137 std::apply([&](auto&&... memberMeta) constexpr {
138 ([&]{
139 // Get field info
140 static constexpr auto memNameRaw = std::remove_reference_t<decltype(memberMeta)>::M_NAME;
141 constexpr QLatin1StringView memName(memNameRaw);
142 using memType = typename std::remove_reference_t<decltype(memberMeta)>::M_TYPE;
143 const QSqlField field = tableRecord.field(memName);
144
145 // Examine field
146 if(field.isValid())
147 {
148 // Account for field
149 allFields.removeAll(memName.toString());
150
151 // Check type
152 QMetaType expectedCppType = QMetaType::fromType<unwrap_optional_t<memType>>();
153 QMetaType actualCppType = field.metaType();
154
155 bool strict = strictness.testFlag(TypeStrict);
156 if((strict && (actualCppType != expectedCppType)) ||
157 (!strict && (!QMetaType::canConvert(actualCppType, expectedCppType))))
158 addDefect(rp, tableDefects, TypeMismatches);
159 }
160 else if constexpr(!QxSql::sql_optional<memType>)
161 {
162 addDefect(rp, tableDefects, MissingFields);
163 tableDefects.missingFields.append(memName);
164 }
165
166 // Account for extra fields
167 if(strictness.testFlag(FieldStrict) && !allFields.isEmpty())
168 {
169 addDefect(rp, tableDefects, ExtraFields);
170 tableDefects.extraFields.append(allFields);
171 }
172 }(), ...);
173 }, memberMetas);
174 }
175 else
176 addDefect(rp, tableDefects, MissingTables);
177
178 // Note defects
179 if(tableDefects.defects != None)
180 rp.mDefTables.append(tableDefects);
181 }(), ...);
182
183 // Account for extra tables
184 if(strictness.testFlag(TableStrict))
185 {
186 for(const QString& tb : std::as_const(allTables))
187 {
188 DefectiveTable dt{.name = tb};
189 addDefect(rp, dt, ExtraTables);
190 rp.mDefTables.append(dt);
191 }
192 }
193
194 return rp;
195 }
196
197//-Instance Functions-------------------------------------------------------------
198private:
199 quint32 deriveValue() const override;
200 QString derivePrimary() const override;
201 QString deriveSecondary() const override;
202 QString deriveDetails() const override;
203
204public:
205 bool hasDefects() const;
206 Defects defects() const;
207 QList<DefectiveTable> defectList() const;
208 QString database() const;
209};
210Q_DECLARE_OPERATORS_FOR_FLAGS(SqlSchemaReport::Defects);
211Q_DECLARE_OPERATORS_FOR_FLAGS(SqlSchemaReport::StrictnessFlags);
212
213}
214
215#endif // QX_SQLSCHEMAREPORT_H
The AbstractError template class completes the Error interface and acts as the base class from which ...
Definition qx-abstracterror.h:88
The SqlDatabase class provides straightforward access to an SQL database.
Definition qx-sqldatabase.h:36
SqlQuery is a base class from which all query types derive.
Definition qx-sqlquery.h:171
The Qx namespace is the main namespace through which all non-global functionality of the Qx library i...
Definition qx-abstracterror.cpp:13
The qx-abstracterror.h header file provides access to the base class from which custom error types sh...
The qx-sql header file offers a straightforward interface for querying an SQL database.