Lightweight 0.20261105.1
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 <cstdint>
9#include <format>
10#include <functional>
11#include <optional>
12#include <string>
13#include <string_view>
14#include <tuple>
15#include <vector>
16
17namespace Lightweight
18{
19
20class SqlStatement;
21
22namespace SqlSchema
23{
24
25 namespace detail
26 {
27 // NOLINTNEXTLINE(readability-identifier-naming)
28 constexpr std::string_view rtrim(std::string_view value) noexcept
29 {
30 // Cast to unsigned char before std::isspace — see SqlConnectInfo.cpp for rationale.
31 while (!value.empty() && (std::isspace(static_cast<unsigned char>(value.back())) || value.back() == '\0'))
32 value.remove_suffix(1);
33 return value;
34 }
35
36 /// The @c sys.columns size/precision metrics that qualify a Microsoft SQL Server
37 /// column type. Grouped into one struct so the three same-typed integers cannot be
38 /// transposed at a call site.
39 struct MssqlColumnMetrics
40 {
41 /// @c sys.columns.max_length (bytes; @c -1 means MAX/LOB).
42 int maxLength = 0;
43 /// @c sys.columns.precision (for numeric/decimal/money types).
44 int precision = 0;
45 /// @c sys.columns.scale (for numeric/decimal/money types).
46 int scale = 0;
47 };
48
49 /// Maps a Microsoft SQL Server system type name (from @c sys.types.name) plus the
50 /// column's @c max_length / @c precision / @c scale (from @c sys.columns) to the
51 /// canonical @c SqlColumnTypeDefinition variant.
52 ///
53 /// This reproduces, for the batched MSSQL schema reader, exactly what the legacy
54 /// per-table path (@c MakeColumnTypeFromNative plus the MSSQL fixups in
55 /// @c SqlSchema.cpp) yields for the same column, so backup metadata stays
56 /// byte-identical.
57 ///
58 /// @param sysTypeName The system type name, e.g. @c "int", @c "nvarchar", @c "decimal".
59 /// @param metrics The column's @c max_length / @c precision / @c scale from @c sys.columns.
60 /// @return The mapped column type variant; defaults to @c Varchar for unknown names.
61 [[nodiscard]] LIGHTWEIGHT_API SqlColumnTypeDefinition MakeColumnTypeFromMssqlSysType(std::string_view sysTypeName,
62 MssqlColumnMetrics metrics);
63 } // namespace detail
64
65 struct FullyQualifiedTableName
66 {
67 std::string catalog;
68 std::string schema;
69 std::string table;
70
71 bool operator==(FullyQualifiedTableName const& other) const noexcept
72 {
73 return catalog == other.catalog && schema == other.schema && table == other.table;
74 }
75
76 bool operator!=(FullyQualifiedTableName const& other) const noexcept
77 {
78 return !(*this == other);
79 }
80
81 bool operator<(FullyQualifiedTableName const& other) const noexcept
82 {
83 return std::tie(catalog, schema, table) < std::tie(other.catalog, other.schema, other.table);
84 }
85 };
86
87 namespace detail
88 {
89 /// A table as the catalog (@c SQLTables) spells it.
90 struct TableWithSchema
91 {
92 /// The schema the catalog reports the table in; empty where the DBMS has none.
93 std::string schema;
94 /// The table name in the catalog's own spelling.
95 std::string name;
96 };
97
98 /// Which step of @ref ResolveTable found the table.
99 enum class TableResolutionStep : std::uint8_t
100 {
101 /// A catalog lookup of the name exactly as requested.
102 Exact,
103 /// A catalog lookup of the name folded the way the DBMS folds unquoted identifiers.
104 Folded,
105 /// A case-insensitive scan of every table in the catalog and schema.
106 FullListing,
107 };
108
109 /// A table found by @ref ResolveTable.
110 struct ResolvedTable
111 {
112 /// The table in the catalog's own spelling.
113 TableWithSchema table;
114 /// The resolution step that found it.
115 TableResolutionStep step = TableResolutionStep::Exact;
116 };
117
118 /// Escapes the search-pattern wildcards @c _ and @c % (and @p escape itself) in @p name, so
119 /// that an ODBC catalog function taking a pattern argument matches @p name literally.
120 ///
121 /// @param name The literal name.
122 /// @param escape The driver's @c SQL_SEARCH_PATTERN_ESCAPE string.
123 /// @return The escaped pattern, or @c std::nullopt when @p escape is empty, i.e. the driver
124 /// cannot escape wildcards.
125 [[nodiscard]] LIGHTWEIGHT_API std::optional<std::string> EscapeSearchPattern(std::string_view name,
126 std::string_view escape);
127
128 /// Asks the catalog for the tables named @p name, rather than listing every table.
129 ///
130 /// The name is passed to @c SQLTables as a search pattern with its wildcards escaped (see
131 /// @ref EscapeSearchPattern), so @c _ and @c % match only themselves. The DBMS's own name
132 /// comparison applies: a case-insensitive collation also returns other spellings, while
133 /// PostgreSQL matches the spelling exactly.
134 ///
135 /// @param stmt The statement to run the catalog call on.
136 /// @param database The catalog; empty for the connection's default.
137 /// @param schema The schema; empty for any schema.
138 /// @param name The literal table name.
139 /// @return The matching tables, or @c std::nullopt when the driver reports no search-pattern
140 /// escape, so the name cannot be matched literally.
141 [[nodiscard]] LIGHTWEIGHT_API std::optional<std::vector<TableWithSchema>> FindTablesNamed(SqlStatement& stmt,
142 std::string_view database,
143 std::string_view schema,
144 std::string_view name);
145
146 /// Finds @p table in the catalog, matching its name case-insensitively.
147 ///
148 /// Tries, in order and stopping at the first hit: a catalog lookup of the name as given; one
149 /// of the name folded as the DBMS folds unquoted identifiers (when that differs); and a scan
150 /// of every table in the catalog and schema.
151 ///
152 /// @param stmt The statement to run the catalog calls on.
153 /// @param table The table to find. An empty catalog or schema means the connection's default.
154 /// @return The table as the catalog spells it, with the step that found it, or
155 /// @c std::nullopt when no such table exists.
156 [[nodiscard]] LIGHTWEIGHT_API std::optional<ResolvedTable> ResolveTable(SqlStatement& stmt,
157 FullyQualifiedTableName const& table);
158 } // namespace detail
159
160 /// Identifies a single column within a schema (catalog.schema.table.column).
161 ///
162 /// Schema-introspection layer only — distinct from the query-builder DSL type
163 /// `Lightweight::SqlQualifiedTableColumnName`.
165 {
166 /// Fully qualified table the column belongs to.
167 FullyQualifiedTableName table;
168 /// Column name within the table.
169 std::string column;
170
171 /// Equality compares both the owning table and the column name.
172 bool operator==(ColumnIdentifier const& other) const noexcept
173 {
174 return table == other.table && column == other.column;
175 }
176
177 /// Inequality is the negation of equality.
178 bool operator!=(ColumnIdentifier const& other) const noexcept
179 {
180 return !(*this == other);
181 }
182
183 /// Lexicographic ordering by (table, column) so ColumnIdentifier can be used as a key in ordered containers.
184 bool operator<(ColumnIdentifier const& other) const noexcept
185 {
186 return std::tie(table, column) < std::tie(other.table, other.column);
187 }
188 };
189
190 /// Identifies an ordered set of columns within a single table (for composite keys / indexes).
192 {
193 /// Fully qualified table the column sequence belongs to.
194 FullyQualifiedTableName table;
195 /// Ordered list of column names (order is significant for composite keys / indexes).
196 std::vector<std::string> columns;
197 };
198
199 inline bool operator<(ColumnIdentifierSequence const& a, ColumnIdentifierSequence const& b) noexcept
200 {
201 return std::tie(a.table, a.columns) < std::tie(b.table, b.columns);
202 }
203
204 struct ForeignKeyConstraint
205 {
206 ColumnIdentifierSequence foreignKey;
207 ColumnIdentifierSequence primaryKey;
208 };
209
210 inline bool operator<(ForeignKeyConstraint const& a, ForeignKeyConstraint const& b) noexcept
211 {
212 return std::tie(a.foreignKey, a.primaryKey) < std::tie(b.foreignKey, b.primaryKey);
213 }
214
215 /// Represents an index definition on a table.
217 {
218 /// The name of the index.
219 std::string name;
220
221 /// The columns in the index (in order for composite indexes).
222 std::vector<std::string> columns;
223
224 /// Whether the index enforces uniqueness.
225 bool isUnique = false;
226 };
227
228 using KeyPair = std::pair<FullyQualifiedTableName /*fk table*/, FullyQualifiedTableName /*pk table*/>;
229
230 inline bool operator<(KeyPair const& a, KeyPair const& b)
231 {
232 return std::tie(a.first, a.second) < std::tie(b.first, b.second);
233 }
234
235 /// @ingroup CoreApi
236 /// Holds the definition of a column in a SQL table as read from the database schema.
237 struct Column
238 {
239 /// The name of the column.
240 std::string name = {};
241 /// The SQL column type definition.
242 SqlColumnTypeDefinition type = {};
243 /// The dialect-dependent type string.
245 /// Whether the column allows NULL values.
246 bool isNullable = true;
247 /// Whether the column has a UNIQUE constraint.
248 bool isUnique = false;
249 /// The size of the column (for character/binary types).
250 size_t size = 0;
251 /// The number of decimal digits (for numeric types).
252 unsigned short decimalDigits = 0;
253 /// Whether the column auto-increments.
254 bool isAutoIncrement = false;
255 /// Whether the column is a primary key.
256 bool isPrimaryKey = false;
257 /// Whether the column is a foreign key.
258 bool isForeignKey = false;
259 /// The foreign key constraint, if any.
260 std::optional<ForeignKeyConstraint> foreignKeyConstraint {};
261 /// The default value of the column.
262 std::string defaultValue = {};
263 };
264
265 /// Callback interface for handling events while reading a database schema.
267 {
268 public:
269 /// Default constructor.
270 EventHandler() = default;
271 /// Default move constructor.
273 /// Default copy constructor.
274 EventHandler(EventHandler const&) = default;
275 /// Default move assignment operator.
277 /// Default copy assignment operator.
279 virtual ~EventHandler() = default;
280
281 /// Called when the names of all tables are read.
282 virtual void OnTables(std::vector<std::string> const& tables) = 0;
283
284 /// Called for each table. Returns true to process this table, false to skip it.
285 /// @param schema The schema the table belongs to.
286 /// @param table The name of the table.
287 virtual bool OnTable(std::string_view schema, std::string_view table) = 0;
288 /// Called when the primary keys of a table are read.
289 virtual void OnPrimaryKeys(std::string_view table, std::vector<std::string> const& columns) = 0;
290 /// Called when a foreign key constraint is read.
291 virtual void OnForeignKey(ForeignKeyConstraint const& foreignKeyConstraint) = 0;
292 /// Called for each column in a table.
293 virtual void OnColumn(Column const& column) = 0;
294 /// Called when an external foreign key referencing this table is read.
295 virtual void OnExternalForeignKey(ForeignKeyConstraint const& foreignKeyConstraint) = 0;
296 /// Called when the indexes of a table are read.
297 virtual void OnIndexes(std::vector<IndexDefinition> const& indexes) = 0;
298 /// Called when a table's schema reading is complete.
299 virtual void OnTableEnd() = 0;
300 };
301
302 /// Reads all tables in the given database and schema and calls the event handler for each table.
303 ///
304 /// @param stmt The SQL statement to use for reading the database schema.
305 /// @param database The name of the database to read the schema from.
306 /// @param schema The name of the schema to read the schema from.
307 /// @param eventHandler The SAX-style event handler to call for each table.
308 ///
309 /// @note The event handler is called for each table in the database and schema.
310 LIGHTWEIGHT_API void ReadAllTables(SqlStatement& stmt,
311 std::string_view database,
312 std::string_view schema,
313 EventHandler& eventHandler);
314
315 /// @ingroup CoreApi
316 /// Holds the definition of a table in a SQL database as read from the database schema.
317 struct Table
318 {
319 // FullyQualifiedTableName name;
320
321 /// The schema the table belongs to.
322 std::string schema;
323
324 /// The name of the table.
325 std::string name;
326
327 /// The columns of the table.
328 std::vector<Column> columns {};
329
330 /// The foreign keys of the table.
331 std::vector<ForeignKeyConstraint> foreignKeys {};
332
333 /// The foreign keys of other tables that reference this table.
334 std::vector<ForeignKeyConstraint> externalForeignKeys {};
335
336 /// The primary keys of the table.
337 std::vector<std::string> primaryKeys {};
338
339 /// The indexes on the table (excluding primary key index).
340 std::vector<IndexDefinition> indexes {};
341 };
342
343 /// A list of tables.
344 using TableList = std::vector<Table>;
345
346 using ReadAllTablesCallback = std::function<void(std::string_view /*tableName*/, size_t /*current*/, size_t /*total*/)>;
347
348 /// Callback invoked when a table's schema is fully read.
349 ///
350 /// This callback is called for each table as soon as its schema (columns, keys, constraints)
351 /// is complete. Useful for streaming tables to consumers without waiting for all tables.
352 using TableReadyCallback = std::function<void(Table&&)>;
353
354 /// Predicate to filter tables before reading their full schema.
355 ///
356 /// @param schema The schema name.
357 /// @param tableName The table name.
358 /// @return true to include the table (read its full schema), false to skip it.
359 ///
360 /// When provided, tables that don't match the predicate will have their detailed
361 /// schema (columns, keys, constraints) skipped, improving performance when only
362 /// a subset of tables is needed.
363 using TableFilterPredicate = std::function<bool(std::string_view /*schema*/, std::string_view /*tableName*/)>;
364
365 /// Retrieves all tables in the given @p database and @p schema.
366 ///
367 /// @param stmt The SQL statement to use for reading.
368 /// @param database The database name.
369 /// @param schema The schema name (optional).
370 /// @param callback Progress callback invoked for each table during scanning.
371 /// @param tableReadyCallback Callback invoked when each table's schema is complete.
372 /// @param tableFilter Optional predicate to filter tables before reading their full schema.
373 /// If provided, only tables where the predicate returns true will have
374 /// their columns, keys, and constraints read.
375 LIGHTWEIGHT_API TableList ReadAllTables(SqlStatement& stmt,
376 std::string_view database,
377 std::string_view schema = {},
378 ReadAllTablesCallback callback = {},
379 TableReadyCallback tableReadyCallback = {},
380 TableFilterPredicate tableFilter = {});
381
382 /// Lists the schemas that hold at least one user table, sorted by name.
383 ///
384 /// System schemas are left out: `sys`, `INFORMATION_SCHEMA`, `guest`, and those starting with
385 /// `db_` (SQL Server roles) or `pg_` (PostgreSQL catalogs). A dialect without schemas (SQLite)
386 /// yields one empty name when the database holds a table.
387 ///
388 /// @param stmt The SQL statement to use for reading.
389 /// @param database The database name.
390 /// @return The schema names.
391 [[nodiscard]] LIGHTWEIGHT_API std::vector<std::string> AllUserSchemas(SqlStatement& stmt, std::string_view database);
392
393 /// How much of a table's description @ref ReadTable gathers.
394 enum class TableReadScope : std::uint8_t
395 {
396 /// Columns, primary key, foreign keys in both directions, and indexes.
397 Full,
398 /// Columns (with their unique, identity and primary-key flags) and the primary key only.
399 /// Foreign keys, external foreign keys and indexes are left empty, and the foreign-key
400 /// target casing lookups are skipped with them. Because the foreign keys are not read, the
401 /// per-column @c Column::isForeignKey (false) and @c Column::foreignKeyConstraint (empty)
402 /// are not populated either.
403 ColumnsAndPrimaryKey,
404 };
405
406 /// Reads the schema of a single table.
407 ///
408 /// Use this instead of @ref ReadAllTables when only one table is of interest: it *describes* only
409 /// that table, rather than describing every table in the database. The table list is still
410 /// enumerated on each call — that is how existence is settled, and where the name casing the
411 /// foreign-key fixup needs comes from — but the per-table column, key and index queries, which
412 /// dominate the cost, run once instead of once per table.
413 ///
414 /// A missing table is reported as @c std::nullopt rather than an error, because asking whether a
415 /// table exists is a normal thing to do. Everything that genuinely went wrong — a broken
416 /// connection, a catalog call the driver rejected — throws @ref SqlException, as everywhere else
417 /// in @ref SqlSchema. So an empty result always means "absent", never "could not tell".
418 ///
419 /// @param stmt The SQL statement to use for reading.
420 /// @param table The fully qualified name of the table to describe. An empty catalog or schema
421 /// means "the connection's default", exactly as in @ref ReadAllTables.
422 /// @param scope How much to read. @ref TableReadScope::ColumnsAndPrimaryKey skips the foreign-key
423 /// and index queries for callers that only need columns and the primary key; in
424 /// that mode @c Column::isForeignKey and @c Column::foreignKeyConstraint are not
425 /// populated on any column.
426 /// @retval std::nullopt No table of that name exists in the given catalog and schema.
427 /// @throws SqlException The catalog could not be read. Absence is not an error and is reported
428 /// as @c std::nullopt; anything arriving as an exception is a genuine SQL failure.
429 [[nodiscard]] LIGHTWEIGHT_API std::optional<Table> ReadTable(SqlStatement& stmt,
430 FullyQualifiedTableName const& table,
431 TableReadScope scope = TableReadScope::Full);
432
433 /// Retrieves all tables in the given database and schema that have a foreign key to the given table.
434 LIGHTWEIGHT_API std::vector<ForeignKeyConstraint> AllForeignKeysTo(SqlStatement& stmt,
435 FullyQualifiedTableName const& table);
436
437 /// Retrieves all tables in the given database and schema that have a foreign key from the given table.
438 LIGHTWEIGHT_API std::vector<ForeignKeyConstraint> AllForeignKeysFrom(SqlStatement& stmt,
439 FullyQualifiedTableName const& table);
440
441 /// Creats an SQL CREATE TABLE plan for the given table description.
442 ///
443 /// @param tableDescription The description of the table to create the plan for.
444 ///
445 /// @return An SQL CREATE TABLE plan for the given table description.
446 LIGHTWEIGHT_API SqlCreateTablePlan MakeCreateTablePlan(Table const& tableDescription);
447
448 /// Creates an SQL CREATE TABLE plan for all the given table descriptions.
449 ///
450 /// @param tableDescriptions The descriptions of the tables to create the plan for.
451 ///
452 /// @return An SQL CREATE TABLE plan for all the given table descriptions.
453 LIGHTWEIGHT_API std::vector<SqlCreateTablePlan> MakeCreateTablePlan(TableList const& tableDescriptions);
454
455} // namespace SqlSchema
456
457} // namespace Lightweight
458
459template <>
460struct std::formatter<Lightweight::SqlSchema::FullyQualifiedTableName>: std::formatter<std::string>
461{
462 auto format(Lightweight::SqlSchema::FullyQualifiedTableName const& value, format_context& ctx) const
463 -> format_context::iterator
464 {
465 string output = std::string(Lightweight::SqlSchema::detail::rtrim(value.schema));
466 if (!output.empty())
467 output += '.';
468 auto const trimmedSchema = Lightweight::SqlSchema::detail::rtrim(value.catalog);
469 output += trimmedSchema;
470 if (!output.empty() && !trimmedSchema.empty())
471 output += '.';
472 output += Lightweight::SqlSchema::detail::rtrim(value.table);
473 return formatter<string>::format(output, ctx);
474 }
475};
476
477template <>
478struct std::formatter<Lightweight::SqlSchema::ColumnIdentifier>: std::formatter<std::string>
479{
480 auto format(Lightweight::SqlSchema::ColumnIdentifier const& value, format_context& ctx) const -> format_context::iterator
481 {
482 auto const table = std::format("{}", value.table);
483 if (table.empty())
484 return formatter<string>::format(std::format("{}", value.column), ctx);
485 else
486 return formatter<string>::format(std::format("{}.{}", value.table, value.column), ctx);
487 }
488};
489
490template <>
491struct std::formatter<Lightweight::SqlSchema::ColumnIdentifierSequence>: std::formatter<std::string>
492{
493 auto format(Lightweight::SqlSchema::ColumnIdentifierSequence const& value, format_context& ctx) const
494 -> format_context::iterator
495 {
496 auto const resolvedTableName = std::format("{}", value.table);
497 string output;
498 output += resolvedTableName;
499 output += '(';
500
501#if !defined(__cpp_lib_ranges_enumerate)
502 int i { -1 };
503 for (auto const& column: value.columns)
504 {
505 ++i;
506#else
507 for (auto const [i, column]: value.columns | std::views::enumerate)
508 {
509#endif
510 if (i != 0)
511 output += ", ";
512 output += column;
513 }
514 output += ')';
515
516 return formatter<string>::format(output, ctx);
517 }
518};
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.
bool operator==(ColumnIdentifier const &other) const noexcept
Equality compares both the owning table and the column name.
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.
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).