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