Lightweight 0.20260303.0
Loading...
Searching...
No Matches
SqlSchema.hpp
1// SPDX-License-Identifier: Apache-2.0
2
3#pragma once
4
5#include "Api.hpp"
6#include "SqlQuery/MigrationPlan.hpp"
7
8#include <format>
9#include <functional>
10#include <string_view>
11#include <tuple>
12#include <vector>
13
14namespace Lightweight
15{
16
17class SqlStatement;
18
19namespace SqlSchema
20{
21
22 namespace detail
23 {
24 // NOLINTNEXTLINE(readability-identifier-naming)
25 constexpr std::string_view rtrim(std::string_view value) noexcept
26 {
27 // Cast to unsigned char before std::isspace — see SqlConnectInfo.cpp for rationale.
28 while (!value.empty() && (std::isspace(static_cast<unsigned char>(value.back())) || value.back() == '\0'))
29 value.remove_suffix(1);
30 return value;
31 }
32 } // namespace detail
33
34 struct FullyQualifiedTableName
35 {
36 std::string catalog;
37 std::string schema;
38 std::string table;
39
40 bool operator==(FullyQualifiedTableName const& other) const noexcept
41 {
42 return catalog == other.catalog && schema == other.schema && table == other.table;
43 }
44
45 bool operator!=(FullyQualifiedTableName const& other) const noexcept
46 {
47 return !(*this == other);
48 }
49
50 bool operator<(FullyQualifiedTableName const& other) const noexcept
51 {
52 return std::tie(catalog, schema, table) < std::tie(other.catalog, other.schema, other.table);
53 }
54 };
55
56 struct FullyQualifiedTableColumn
57 {
58 FullyQualifiedTableName table;
59 std::string column;
60
61 bool operator==(FullyQualifiedTableColumn const& other) const noexcept
62 {
63 return table == other.table && column == other.column;
64 }
65
66 bool operator!=(FullyQualifiedTableColumn const& other) const noexcept
67 {
68 return !(*this == other);
69 }
70
71 bool operator<(FullyQualifiedTableColumn const& other) const noexcept
72 {
73 return std::tie(table, column) < std::tie(other.table, other.column);
74 }
75 };
76
77 struct FullyQualifiedTableColumnSequence
78 {
79 FullyQualifiedTableName table;
80 std::vector<std::string> columns;
81 };
82
83 inline bool operator<(FullyQualifiedTableColumnSequence const& a, FullyQualifiedTableColumnSequence const& b) noexcept
84 {
85 return std::tie(a.table, a.columns) < std::tie(b.table, b.columns);
86 }
87
88 struct ForeignKeyConstraint
89 {
90 FullyQualifiedTableColumnSequence foreignKey;
91 FullyQualifiedTableColumnSequence primaryKey;
92 };
93
94 inline bool operator<(ForeignKeyConstraint const& a, ForeignKeyConstraint const& b) noexcept
95 {
96 return std::tie(a.foreignKey, a.primaryKey) < std::tie(b.foreignKey, b.primaryKey);
97 }
98
99 /// Represents an index definition on a table.
101 {
102 /// The name of the index.
103 std::string name;
104
105 /// The columns in the index (in order for composite indexes).
106 std::vector<std::string> columns;
107
108 /// Whether the index enforces uniqueness.
109 bool isUnique = false;
110 };
111
112 using KeyPair = std::pair<FullyQualifiedTableName /*fk table*/, FullyQualifiedTableName /*pk table*/>;
113
114 inline bool operator<(KeyPair const& a, KeyPair const& b)
115 {
116 return std::tie(a.first, a.second) < std::tie(b.first, b.second);
117 }
118
119 /// Holds the definition of a column in a SQL table as read from the database schema.
120 struct Column
121 {
122 /// The name of the column.
123 std::string name = {};
124 /// The SQL column type definition.
125 SqlColumnTypeDefinition type = {};
126 /// The dialect-dependent type string.
128 /// Whether the column allows NULL values.
129 bool isNullable = true;
130 /// Whether the column has a UNIQUE constraint.
131 bool isUnique = false;
132 /// The size of the column (for character/binary types).
133 size_t size = 0;
134 /// The number of decimal digits (for numeric types).
135 unsigned short decimalDigits = 0;
136 /// Whether the column auto-increments.
137 bool isAutoIncrement = false;
138 /// Whether the column is a primary key.
139 bool isPrimaryKey = false;
140 /// Whether the column is a foreign key.
141 bool isForeignKey = false;
142 /// The foreign key constraint, if any.
143 std::optional<ForeignKeyConstraint> foreignKeyConstraint {};
144 /// The default value of the column.
145 std::string defaultValue = {};
146 };
147
148 /// Callback interface for handling events while reading a database schema.
150 {
151 public:
152 /// Default constructor.
153 EventHandler() = default;
154 /// Default move constructor.
156 /// Default copy constructor.
157 EventHandler(EventHandler const&) = default;
158 /// Default move assignment operator.
160 /// Default copy assignment operator.
162 virtual ~EventHandler() = default;
163
164 /// Called when the names of all tables are read.
165 virtual void OnTables(std::vector<std::string> const& tables) = 0;
166
167 /// Called for each table. Returns true to process this table, false to skip it.
168 /// @param schema The schema the table belongs to.
169 /// @param table The name of the table.
170 virtual bool OnTable(std::string_view schema, std::string_view table) = 0;
171 /// Called when the primary keys of a table are read.
172 virtual void OnPrimaryKeys(std::string_view table, std::vector<std::string> const& columns) = 0;
173 /// Called when a foreign key constraint is read.
174 virtual void OnForeignKey(ForeignKeyConstraint const& foreignKeyConstraint) = 0;
175 /// Called for each column in a table.
176 virtual void OnColumn(Column const& column) = 0;
177 /// Called when an external foreign key referencing this table is read.
178 virtual void OnExternalForeignKey(ForeignKeyConstraint const& foreignKeyConstraint) = 0;
179 /// Called when the indexes of a table are read.
180 virtual void OnIndexes(std::vector<IndexDefinition> const& indexes) = 0;
181 /// Called when a table's schema reading is complete.
182 virtual void OnTableEnd() = 0;
183 };
184
185 /// Reads all tables in the given database and schema and calls the event handler for each table.
186 ///
187 /// @param stmt The SQL statement to use for reading the database schema.
188 /// @param database The name of the database to read the schema from.
189 /// @param schema The name of the schema to read the schema from.
190 /// @param eventHandler The SAX-style event handler to call for each table.
191 ///
192 /// @note The event handler is called for each table in the database and schema.
193 LIGHTWEIGHT_API void ReadAllTables(SqlStatement& stmt,
194 std::string_view database,
195 std::string_view schema,
196 EventHandler& eventHandler);
197
198 /// Holds the definition of a table in a SQL database as read from the database schema.
199 struct Table
200 {
201 // FullyQualifiedTableName name;
202
203 /// The schema the table belongs to.
204 std::string schema;
205
206 /// The name of the table.
207 std::string name;
208
209 /// The columns of the table.
210 std::vector<Column> columns {};
211
212 /// The foreign keys of the table.
213 std::vector<ForeignKeyConstraint> foreignKeys {};
214
215 /// The foreign keys of other tables that reference this table.
216 std::vector<ForeignKeyConstraint> externalForeignKeys {};
217
218 /// The primary keys of the table.
219 std::vector<std::string> primaryKeys {};
220
221 /// The indexes on the table (excluding primary key index).
222 std::vector<IndexDefinition> indexes {};
223 };
224
225 /// A list of tables.
226 using TableList = std::vector<Table>;
227
228 using ReadAllTablesCallback = std::function<void(std::string_view /*tableName*/, size_t /*current*/, size_t /*total*/)>;
229
230 /// Callback invoked when a table's schema is fully read.
231 ///
232 /// This callback is called for each table as soon as its schema (columns, keys, constraints)
233 /// is complete. Useful for streaming tables to consumers without waiting for all tables.
234 using TableReadyCallback = std::function<void(Table&&)>;
235
236 /// Predicate to filter tables before reading their full schema.
237 ///
238 /// @param schema The schema name.
239 /// @param tableName The table name.
240 /// @return true to include the table (read its full schema), false to skip it.
241 ///
242 /// When provided, tables that don't match the predicate will have their detailed
243 /// schema (columns, keys, constraints) skipped, improving performance when only
244 /// a subset of tables is needed.
245 using TableFilterPredicate = std::function<bool(std::string_view /*schema*/, std::string_view /*tableName*/)>;
246
247 /// Retrieves all tables in the given @p database and @p schema.
248 ///
249 /// @param stmt The SQL statement to use for reading.
250 /// @param database The database name.
251 /// @param schema The schema name (optional).
252 /// @param callback Progress callback invoked for each table during scanning.
253 /// @param tableReadyCallback Callback invoked when each table's schema is complete.
254 /// @param tableFilter Optional predicate to filter tables before reading their full schema.
255 /// If provided, only tables where the predicate returns true will have
256 /// their columns, keys, and constraints read.
257 LIGHTWEIGHT_API TableList ReadAllTables(SqlStatement& stmt,
258 std::string_view database,
259 std::string_view schema = {},
260 ReadAllTablesCallback callback = {},
261 TableReadyCallback tableReadyCallback = {},
262 TableFilterPredicate tableFilter = {});
263
264 /// Retrieves all tables in the given database and schema that have a foreign key to the given table.
265 LIGHTWEIGHT_API std::vector<ForeignKeyConstraint> AllForeignKeysTo(SqlStatement& stmt,
266 FullyQualifiedTableName const& table);
267
268 /// Retrieves all tables in the given database and schema that have a foreign key from the given table.
269 LIGHTWEIGHT_API std::vector<ForeignKeyConstraint> AllForeignKeysFrom(SqlStatement& stmt,
270 FullyQualifiedTableName const& table);
271
272 /// Creats an SQL CREATE TABLE plan for the given table description.
273 ///
274 /// @param tableDescription The description of the table to create the plan for.
275 ///
276 /// @return An SQL CREATE TABLE plan for the given table description.
277 LIGHTWEIGHT_API SqlCreateTablePlan MakeCreateTablePlan(Table const& tableDescription);
278
279 /// Creates an SQL CREATE TABLE plan for all the given table descriptions.
280 ///
281 /// @param tableDescriptions The descriptions of the tables to create the plan for.
282 ///
283 /// @return An SQL CREATE TABLE plan for all the given table descriptions.
284 LIGHTWEIGHT_API std::vector<SqlCreateTablePlan> MakeCreateTablePlan(TableList const& tableDescriptions);
285
286} // namespace SqlSchema
287
288} // namespace Lightweight
289
290template <>
291struct std::formatter<Lightweight::SqlSchema::FullyQualifiedTableName>: std::formatter<std::string>
292{
293 auto format(Lightweight::SqlSchema::FullyQualifiedTableName const& value, format_context& ctx) const
294 -> format_context::iterator
295 {
296 string output = std::string(Lightweight::SqlSchema::detail::rtrim(value.schema));
297 if (!output.empty())
298 output += '.';
299 auto const trimmedSchema = Lightweight::SqlSchema::detail::rtrim(value.catalog);
300 output += trimmedSchema;
301 if (!output.empty() && !trimmedSchema.empty())
302 output += '.';
303 output += Lightweight::SqlSchema::detail::rtrim(value.table);
304 return formatter<string>::format(output, ctx);
305 }
306};
307
308template <>
309struct std::formatter<Lightweight::SqlSchema::FullyQualifiedTableColumn>: std::formatter<std::string>
310{
311 auto format(Lightweight::SqlSchema::FullyQualifiedTableColumn const& value, format_context& ctx) const
312 -> format_context::iterator
313 {
314 auto const table = std::format("{}", value.table);
315 if (table.empty())
316 return formatter<string>::format(std::format("{}", value.column), ctx);
317 else
318 return formatter<string>::format(std::format("{}.{}", value.table, value.column), ctx);
319 }
320};
321
322template <>
323struct std::formatter<Lightweight::SqlSchema::FullyQualifiedTableColumnSequence>: std::formatter<std::string>
324{
325 auto format(Lightweight::SqlSchema::FullyQualifiedTableColumnSequence const& value, format_context& ctx) const
326 -> format_context::iterator
327 {
328 auto const resolvedTableName = std::format("{}", value.table);
329 string output;
330 output += resolvedTableName;
331 output += '(';
332
333#if !defined(__cpp_lib_ranges_enumerate)
334 int i { -1 };
335 for (auto const& column: value.columns)
336 {
337 ++i;
338#else
339 for (auto const [i, column]: value.columns | std::views::enumerate)
340 {
341#endif
342 if (i != 0)
343 output += ", ";
344 output += column;
345 }
346 output += ')';
347
348 return formatter<string>::format(output, ctx);
349 }
350};
Callback interface for handling events while reading a database schema.
virtual void OnColumn(Column const &column)=0
Called for each column in a table.
EventHandler(EventHandler &&)=default
Default move constructor.
virtual void OnIndexes(std::vector< IndexDefinition > const &indexes)=0
Called when the indexes of a table are read.
virtual void OnPrimaryKeys(std::string_view table, std::vector< std::string > const &columns)=0
Called when the primary keys of a table are read.
virtual void OnTables(std::vector< std::string > const &tables)=0
Called when the names of all tables are read.
EventHandler & operator=(EventHandler &&)=default
Default move assignment operator.
EventHandler & operator=(EventHandler const &)=default
Default copy assignment operator.
virtual void OnTableEnd()=0
Called when a table's schema reading is complete.
EventHandler()=default
Default constructor.
virtual bool OnTable(std::string_view schema, std::string_view table)=0
virtual void OnExternalForeignKey(ForeignKeyConstraint const &foreignKeyConstraint)=0
Called when an external foreign key referencing this table is read.
virtual void OnForeignKey(ForeignKeyConstraint const &foreignKeyConstraint)=0
Called when a foreign key constraint is read.
EventHandler(EventHandler const &)=default
Default copy constructor.
High level API for (prepared) raw SQL statements.
Holds the definition of a column in a SQL table as read from the database schema.
std::string name
The name of the column.
bool isNullable
Whether the column allows NULL values.
std::string defaultValue
The default value of the column.
bool isForeignKey
Whether the column is a foreign key.
SqlColumnTypeDefinition type
The SQL column type definition.
bool isPrimaryKey
Whether the column is a primary key.
std::string dialectDependantTypeString
The dialect-dependent type string.
std::optional< ForeignKeyConstraint > foreignKeyConstraint
The foreign key constraint, if any.
size_t size
The size of the column (for character/binary types).
bool isUnique
Whether the column has a UNIQUE constraint.
unsigned short decimalDigits
The number of decimal digits (for numeric types).
bool isAutoIncrement
Whether the column auto-increments.
Represents an index definition on a table.
std::string name
The name of the index.
std::vector< std::string > columns
The columns in the index (in order for composite indexes).
bool isUnique
Whether the index enforces uniqueness.
Holds the definition of a table in a SQL database as read from the database schema.
std::vector< ForeignKeyConstraint > foreignKeys
The foreign keys of the table.
std::vector< std::string > primaryKeys
The primary keys of the table.
std::string schema
The schema the table belongs to.
std::vector< ForeignKeyConstraint > externalForeignKeys
The foreign keys of other tables that reference this table.
std::string name
The name of the table.
std::vector< Column > columns
The columns of the table.
std::vector< IndexDefinition > indexes
The indexes on the table (excluding primary key index).