Lightweight 0.20261002.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 <optional>
11#include <string_view>
12#include <tuple>
13#include <vector>
14
15namespace Lightweight
16{
17
18class SqlStatement;
19
20namespace SqlSchema
21{
22
23 namespace detail
24 {
25 // NOLINTNEXTLINE(readability-identifier-naming)
26 constexpr std::string_view rtrim(std::string_view value) noexcept
27 {
28 // Cast to unsigned char before std::isspace — see SqlConnectInfo.cpp for rationale.
29 while (!value.empty() && (std::isspace(static_cast<unsigned char>(value.back())) || value.back() == '\0'))
30 value.remove_suffix(1);
31 return value;
32 }
33
34 /// The @c sys.columns size/precision metrics that qualify a Microsoft SQL Server
35 /// column type. Grouped into one struct so the three same-typed integers cannot be
36 /// transposed at a call site.
37 struct MssqlColumnMetrics
38 {
39 /// @c sys.columns.max_length (bytes; @c -1 means MAX/LOB).
40 int maxLength = 0;
41 /// @c sys.columns.precision (for numeric/decimal/money types).
42 int precision = 0;
43 /// @c sys.columns.scale (for numeric/decimal/money types).
44 int scale = 0;
45 };
46
47 /// Maps a Microsoft SQL Server system type name (from @c sys.types.name) plus the
48 /// column's @c max_length / @c precision / @c scale (from @c sys.columns) to the
49 /// canonical @c SqlColumnTypeDefinition variant.
50 ///
51 /// This reproduces, for the batched MSSQL schema reader, exactly what the legacy
52 /// per-table path (@c MakeColumnTypeFromNative plus the MSSQL fixups in
53 /// @c SqlSchema.cpp) yields for the same column, so backup metadata stays
54 /// byte-identical.
55 ///
56 /// @param sysTypeName The system type name, e.g. @c "int", @c "nvarchar", @c "decimal".
57 /// @param metrics The column's @c max_length / @c precision / @c scale from @c sys.columns.
58 /// @return The mapped column type variant; defaults to @c Varchar for unknown names.
59 [[nodiscard]] LIGHTWEIGHT_API SqlColumnTypeDefinition MakeColumnTypeFromMssqlSysType(std::string_view sysTypeName,
60 MssqlColumnMetrics metrics);
61 } // namespace detail
62
63 struct FullyQualifiedTableName
64 {
65 std::string catalog;
66 std::string schema;
67 std::string table;
68
69 bool operator==(FullyQualifiedTableName const& other) const noexcept
70 {
71 return catalog == other.catalog && schema == other.schema && table == other.table;
72 }
73
74 bool operator!=(FullyQualifiedTableName const& other) const noexcept
75 {
76 return !(*this == other);
77 }
78
79 bool operator<(FullyQualifiedTableName const& other) const noexcept
80 {
81 return std::tie(catalog, schema, table) < std::tie(other.catalog, other.schema, other.table);
82 }
83 };
84
85 /// Identifies a single column within a schema (catalog.schema.table.column).
86 ///
87 /// Schema-introspection layer only — distinct from the query-builder DSL type
88 /// `Lightweight::SqlQualifiedTableColumnName`.
90 {
91 /// Fully qualified table the column belongs to.
92 FullyQualifiedTableName table;
93 /// Column name within the table.
94 std::string column;
95
96 /// Equality compares both the owning table and the column name.
97 bool operator==(ColumnIdentifier const& other) const noexcept
98 {
99 return table == other.table && column == other.column;
100 }
101
102 /// Inequality is the negation of equality.
103 bool operator!=(ColumnIdentifier const& other) const noexcept
104 {
105 return !(*this == other);
106 }
107
108 /// Lexicographic ordering by (table, column) so ColumnIdentifier can be used as a key in ordered containers.
109 bool operator<(ColumnIdentifier const& other) const noexcept
110 {
111 return std::tie(table, column) < std::tie(other.table, other.column);
112 }
113 };
114
115 /// Identifies an ordered set of columns within a single table (for composite keys / indexes).
117 {
118 /// Fully qualified table the column sequence belongs to.
119 FullyQualifiedTableName table;
120 /// Ordered list of column names (order is significant for composite keys / indexes).
121 std::vector<std::string> columns;
122 };
123
124 inline bool operator<(ColumnIdentifierSequence const& a, ColumnIdentifierSequence const& b) noexcept
125 {
126 return std::tie(a.table, a.columns) < std::tie(b.table, b.columns);
127 }
128
129 struct ForeignKeyConstraint
130 {
131 ColumnIdentifierSequence foreignKey;
132 ColumnIdentifierSequence primaryKey;
133 };
134
135 inline bool operator<(ForeignKeyConstraint const& a, ForeignKeyConstraint const& b) noexcept
136 {
137 return std::tie(a.foreignKey, a.primaryKey) < std::tie(b.foreignKey, b.primaryKey);
138 }
139
140 /// Represents an index definition on a table.
142 {
143 /// The name of the index.
144 std::string name;
145
146 /// The columns in the index (in order for composite indexes).
147 std::vector<std::string> columns;
148
149 /// Whether the index enforces uniqueness.
150 bool isUnique = false;
151 };
152
153 using KeyPair = std::pair<FullyQualifiedTableName /*fk table*/, FullyQualifiedTableName /*pk table*/>;
154
155 inline bool operator<(KeyPair const& a, KeyPair const& b)
156 {
157 return std::tie(a.first, a.second) < std::tie(b.first, b.second);
158 }
159
160 /// @ingroup CoreApi
161 /// Holds the definition of a column in a SQL table as read from the database schema.
162 struct Column
163 {
164 /// The name of the column.
165 std::string name = {};
166 /// The SQL column type definition.
167 SqlColumnTypeDefinition type = {};
168 /// The dialect-dependent type string.
170 /// Whether the column allows NULL values.
171 bool isNullable = true;
172 /// Whether the column has a UNIQUE constraint.
173 bool isUnique = false;
174 /// The size of the column (for character/binary types).
175 size_t size = 0;
176 /// The number of decimal digits (for numeric types).
177 unsigned short decimalDigits = 0;
178 /// Whether the column auto-increments.
179 bool isAutoIncrement = false;
180 /// Whether the column is a primary key.
181 bool isPrimaryKey = false;
182 /// Whether the column is a foreign key.
183 bool isForeignKey = false;
184 /// The foreign key constraint, if any.
185 std::optional<ForeignKeyConstraint> foreignKeyConstraint {};
186 /// The default value of the column.
187 std::string defaultValue = {};
188 };
189
190 /// Callback interface for handling events while reading a database schema.
192 {
193 public:
194 /// Default constructor.
195 EventHandler() = default;
196 /// Default move constructor.
198 /// Default copy constructor.
199 EventHandler(EventHandler const&) = default;
200 /// Default move assignment operator.
202 /// Default copy assignment operator.
204 virtual ~EventHandler() = default;
205
206 /// Called when the names of all tables are read.
207 virtual void OnTables(std::vector<std::string> const& tables) = 0;
208
209 /// Called for each table. Returns true to process this table, false to skip it.
210 /// @param schema The schema the table belongs to.
211 /// @param table The name of the table.
212 virtual bool OnTable(std::string_view schema, std::string_view table) = 0;
213 /// Called when the primary keys of a table are read.
214 virtual void OnPrimaryKeys(std::string_view table, std::vector<std::string> const& columns) = 0;
215 /// Called when a foreign key constraint is read.
216 virtual void OnForeignKey(ForeignKeyConstraint const& foreignKeyConstraint) = 0;
217 /// Called for each column in a table.
218 virtual void OnColumn(Column const& column) = 0;
219 /// Called when an external foreign key referencing this table is read.
220 virtual void OnExternalForeignKey(ForeignKeyConstraint const& foreignKeyConstraint) = 0;
221 /// Called when the indexes of a table are read.
222 virtual void OnIndexes(std::vector<IndexDefinition> const& indexes) = 0;
223 /// Called when a table's schema reading is complete.
224 virtual void OnTableEnd() = 0;
225 };
226
227 /// Reads all tables in the given database and schema and calls the event handler for each table.
228 ///
229 /// @param stmt The SQL statement to use for reading the database schema.
230 /// @param database The name of the database to read the schema from.
231 /// @param schema The name of the schema to read the schema from.
232 /// @param eventHandler The SAX-style event handler to call for each table.
233 ///
234 /// @note The event handler is called for each table in the database and schema.
235 LIGHTWEIGHT_API void ReadAllTables(SqlStatement& stmt,
236 std::string_view database,
237 std::string_view schema,
238 EventHandler& eventHandler);
239
240 /// @ingroup CoreApi
241 /// Holds the definition of a table in a SQL database as read from the database schema.
242 struct Table
243 {
244 // FullyQualifiedTableName name;
245
246 /// The schema the table belongs to.
247 std::string schema;
248
249 /// The name of the table.
250 std::string name;
251
252 /// The columns of the table.
253 std::vector<Column> columns {};
254
255 /// The foreign keys of the table.
256 std::vector<ForeignKeyConstraint> foreignKeys {};
257
258 /// The foreign keys of other tables that reference this table.
259 std::vector<ForeignKeyConstraint> externalForeignKeys {};
260
261 /// The primary keys of the table.
262 std::vector<std::string> primaryKeys {};
263
264 /// The indexes on the table (excluding primary key index).
265 std::vector<IndexDefinition> indexes {};
266 };
267
268 /// A list of tables.
269 using TableList = std::vector<Table>;
270
271 using ReadAllTablesCallback = std::function<void(std::string_view /*tableName*/, size_t /*current*/, size_t /*total*/)>;
272
273 /// Callback invoked when a table's schema is fully read.
274 ///
275 /// This callback is called for each table as soon as its schema (columns, keys, constraints)
276 /// is complete. Useful for streaming tables to consumers without waiting for all tables.
277 using TableReadyCallback = std::function<void(Table&&)>;
278
279 /// Predicate to filter tables before reading their full schema.
280 ///
281 /// @param schema The schema name.
282 /// @param tableName The table name.
283 /// @return true to include the table (read its full schema), false to skip it.
284 ///
285 /// When provided, tables that don't match the predicate will have their detailed
286 /// schema (columns, keys, constraints) skipped, improving performance when only
287 /// a subset of tables is needed.
288 using TableFilterPredicate = std::function<bool(std::string_view /*schema*/, std::string_view /*tableName*/)>;
289
290 /// Retrieves all tables in the given @p database and @p schema.
291 ///
292 /// @param stmt The SQL statement to use for reading.
293 /// @param database The database name.
294 /// @param schema The schema name (optional).
295 /// @param callback Progress callback invoked for each table during scanning.
296 /// @param tableReadyCallback Callback invoked when each table's schema is complete.
297 /// @param tableFilter Optional predicate to filter tables before reading their full schema.
298 /// If provided, only tables where the predicate returns true will have
299 /// their columns, keys, and constraints read.
300 LIGHTWEIGHT_API TableList ReadAllTables(SqlStatement& stmt,
301 std::string_view database,
302 std::string_view schema = {},
303 ReadAllTablesCallback callback = {},
304 TableReadyCallback tableReadyCallback = {},
305 TableFilterPredicate tableFilter = {});
306
307 /// Reads the schema of a single table.
308 ///
309 /// Use this instead of @ref ReadAllTables when only one table is of interest: it *describes* only
310 /// that table, rather than describing every table in the database. The table list is still
311 /// enumerated on each call — that is how existence is settled, and where the name casing the
312 /// foreign-key fixup needs comes from — but the per-table column, key and index queries, which
313 /// dominate the cost, run once instead of once per table.
314 ///
315 /// A missing table is reported as @c std::nullopt rather than an error, because asking whether a
316 /// table exists is a normal thing to do. Everything that genuinely went wrong — a broken
317 /// connection, a catalog call the driver rejected — throws @ref SqlException, as everywhere else
318 /// in @ref SqlSchema. So an empty result always means "absent", never "could not tell".
319 ///
320 /// @param stmt The SQL statement to use for reading.
321 /// @param table The fully qualified name of the table to describe. An empty catalog or schema
322 /// means "the connection's default", exactly as in @ref ReadAllTables.
323 /// @retval std::nullopt No table of that name exists in the given catalog and schema.
324 /// @throws SqlException The catalog could not be read. Absence is not an error and is reported
325 /// as @c std::nullopt; anything arriving as an exception is a genuine SQL failure.
326 [[nodiscard]] LIGHTWEIGHT_API std::optional<Table> ReadTable(SqlStatement& stmt, FullyQualifiedTableName const& table);
327
328 /// Retrieves all tables in the given database and schema that have a foreign key to the given table.
329 LIGHTWEIGHT_API std::vector<ForeignKeyConstraint> AllForeignKeysTo(SqlStatement& stmt,
330 FullyQualifiedTableName const& table);
331
332 /// Retrieves all tables in the given database and schema that have a foreign key from the given table.
333 LIGHTWEIGHT_API std::vector<ForeignKeyConstraint> AllForeignKeysFrom(SqlStatement& stmt,
334 FullyQualifiedTableName const& table);
335
336 /// Creats an SQL CREATE TABLE plan for the given table description.
337 ///
338 /// @param tableDescription The description of the table to create the plan for.
339 ///
340 /// @return An SQL CREATE TABLE plan for the given table description.
341 LIGHTWEIGHT_API SqlCreateTablePlan MakeCreateTablePlan(Table const& tableDescription);
342
343 /// Creates an SQL CREATE TABLE plan for all the given table descriptions.
344 ///
345 /// @param tableDescriptions The descriptions of the tables to create the plan for.
346 ///
347 /// @return An SQL CREATE TABLE plan for all the given table descriptions.
348 LIGHTWEIGHT_API std::vector<SqlCreateTablePlan> MakeCreateTablePlan(TableList const& tableDescriptions);
349
350} // namespace SqlSchema
351
352} // namespace Lightweight
353
354template <>
355struct std::formatter<Lightweight::SqlSchema::FullyQualifiedTableName>: std::formatter<std::string>
356{
357 auto format(Lightweight::SqlSchema::FullyQualifiedTableName const& value, format_context& ctx) const
358 -> format_context::iterator
359 {
360 string output = std::string(Lightweight::SqlSchema::detail::rtrim(value.schema));
361 if (!output.empty())
362 output += '.';
363 auto const trimmedSchema = Lightweight::SqlSchema::detail::rtrim(value.catalog);
364 output += trimmedSchema;
365 if (!output.empty() && !trimmedSchema.empty())
366 output += '.';
367 output += Lightweight::SqlSchema::detail::rtrim(value.table);
368 return formatter<string>::format(output, ctx);
369 }
370};
371
372template <>
373struct std::formatter<Lightweight::SqlSchema::ColumnIdentifier>: std::formatter<std::string>
374{
375 auto format(Lightweight::SqlSchema::ColumnIdentifier const& value, format_context& ctx) const -> format_context::iterator
376 {
377 auto const table = std::format("{}", value.table);
378 if (table.empty())
379 return formatter<string>::format(std::format("{}", value.column), ctx);
380 else
381 return formatter<string>::format(std::format("{}.{}", value.table, value.column), ctx);
382 }
383};
384
385template <>
386struct std::formatter<Lightweight::SqlSchema::ColumnIdentifierSequence>: std::formatter<std::string>
387{
388 auto format(Lightweight::SqlSchema::ColumnIdentifierSequence const& value, format_context& ctx) const
389 -> format_context::iterator
390 {
391 auto const resolvedTableName = std::format("{}", value.table);
392 string output;
393 output += resolvedTableName;
394 output += '(';
395
396#if !defined(__cpp_lib_ranges_enumerate)
397 int i { -1 };
398 for (auto const& column: value.columns)
399 {
400 ++i;
401#else
402 for (auto const [i, column]: value.columns | std::views::enumerate)
403 {
404#endif
405 if (i != 0)
406 output += ", ";
407 output += column;
408 }
409 output += ')';
410
411 return formatter<string>::format(output, ctx);
412 }
413};
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.
Identifies an ordered set of columns within a single table (for composite keys / indexes).
std::vector< std::string > columns
Ordered list of column names (order is significant for composite keys / indexes).
FullyQualifiedTableName table
Fully qualified table the column sequence belongs to.
std::string column
Column name within the table.
Definition SqlSchema.hpp:94
bool operator==(ColumnIdentifier const &other) const noexcept
Equality compares both the owning table and the column name.
Definition SqlSchema.hpp:97
bool operator!=(ColumnIdentifier const &other) const noexcept
Inequality is the negation of equality.
bool operator<(ColumnIdentifier const &other) const noexcept
Lexicographic ordering by (table, column) so ColumnIdentifier can be used as a key in ordered contain...
FullyQualifiedTableName table
Fully qualified table the column belongs to.
Definition SqlSchema.hpp:92
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.
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).