Lightweight 0.20260303.0
Loading...
Searching...
No Matches
SqlServerFormatter.hpp
1// SPDX-License-Identifier: Apache-2.0
2#pragma once
3
4#include "../SqlQueryFormatter.hpp"
5#include "SQLiteFormatter.hpp"
6
7#include <reflection-cpp/reflection.hpp>
8
9#include <cassert>
10#include <format>
11
12namespace Lightweight
13{
14
15class SqlServerQueryFormatter final: public SQLiteQueryFormatter
16{
17 protected:
18 [[nodiscard]] static std::string FormatFromTable(std::string_view table)
19 {
20 // If already quoted (starts with [ or "), return as-is
21 if (!table.empty() && (table.front() == '[' || table.front() == '"'))
22 return std::string(table);
23 // For backward compatibility, use double quotes for simple table names
24 // Square brackets are used for qualified names via QualifiedTableName
25 return std::format(R"("{}")", table);
26 }
27
28 public:
29 [[nodiscard]] bool RequiresTableRebuildForForeignKeyChange() const noexcept override
30 {
31 return false;
32 }
33
34 [[nodiscard]] StringList DropTable(std::string_view schemaName,
35 std::string_view const& tableName,
36 bool ifExists = false,
37 bool cascade = false) const override
38 {
39 StringList result;
40
41 if (cascade)
42 {
43 // Drop all FK constraints referencing this table first using dynamic SQL
44 std::string const schemaFilter = schemaName.empty() ? "dbo" : std::string(schemaName);
45
46 result.emplace_back(std::format(
47 R"(DECLARE @sql NVARCHAR(MAX) = N'';
48SELECT @sql = @sql + 'ALTER TABLE ' + QUOTENAME(OBJECT_SCHEMA_NAME(fk.parent_object_id)) + '.' + QUOTENAME(OBJECT_NAME(fk.parent_object_id)) + ' DROP CONSTRAINT ' + QUOTENAME(fk.name) + '; '
49FROM sys.foreign_keys fk
50WHERE OBJECT_NAME(fk.referenced_object_id) = '{}' AND OBJECT_SCHEMA_NAME(fk.referenced_object_id) = '{}';
51EXEC sp_executesql @sql;)",
52 tableName,
53 schemaFilter));
54 }
55
56 // Then drop the table
57 if (ifExists)
58 result.emplace_back(std::format("DROP TABLE IF EXISTS {};", FormatTableName(schemaName, tableName)));
59 else
60 result.emplace_back(std::format("DROP TABLE {};", FormatTableName(schemaName, tableName)));
61
62 return result;
63 }
64
65 [[nodiscard]] std::string BinaryLiteral(std::span<uint8_t const> data) const override
66 {
67 std::string result;
68 result.reserve((data.size() * 2) + 2);
69 result += "0x";
70 for (uint8_t byte: data)
71 result += std::format("{:02X}", byte);
72 return result;
73 }
74
75 [[nodiscard]] std::string QualifiedTableName(std::string_view schema, std::string_view table) const override
76 {
77 if (schema.empty())
78 return std::format("[{}]", table);
79 return std::format("[{}].[{}]", schema, table);
80 }
81
82 [[nodiscard]] std::string QueryLastInsertId(std::string_view /*tableName*/) const override
83 {
84 // TODO: Figure out how to get the last insert id in SQL Server for a given table.
85 return std::format("SELECT @@IDENTITY");
86 }
87
88 [[nodiscard]] std::string_view BooleanLiteral(bool literalValue) const noexcept override
89 {
90 return literalValue ? "1" : "0";
91 }
92
93 [[nodiscard]] std::string_view DateFunction() const noexcept override
94 {
95 return "GETDATE()";
96 }
97
98 [[nodiscard]] std::string SelectFirst(bool distinct,
99 // NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
100 std::string_view fields,
101 std::string_view fromTable,
102 std::string_view fromTableAlias,
103 std::string_view tableJoins,
104 std::string_view whereCondition,
105 std::string_view orderBy,
106 size_t count) const override
107 {
108 std::stringstream sqlQueryString;
109 sqlQueryString << "SELECT";
110 if (distinct)
111 sqlQueryString << " DISTINCT";
112 sqlQueryString << " TOP " << count;
113 sqlQueryString << ' ' << fields;
114 sqlQueryString << " FROM " << FormatFromTable(fromTable);
115 if (!fromTableAlias.empty())
116 sqlQueryString << " AS [" << fromTableAlias << ']';
117 sqlQueryString << tableJoins;
118 sqlQueryString << whereCondition;
119 sqlQueryString << orderBy;
120 return sqlQueryString.str();
121 }
122
123 [[nodiscard]] std::string SelectRange(bool distinct,
124 // NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
125 std::string_view fields,
126 std::string_view fromTable,
127 std::string_view fromTableAlias,
128 std::string_view tableJoins,
129 std::string_view whereCondition,
130 std::string_view orderBy,
131 std::string_view groupBy,
132 std::size_t offset,
133 std::size_t limit) const override
134 {
135 assert(!orderBy.empty());
136 std::stringstream sqlQueryString;
137 sqlQueryString << "SELECT " << fields;
138 if (distinct)
139 sqlQueryString << " DISTINCT";
140 sqlQueryString << " FROM " << FormatFromTable(fromTable);
141 if (!fromTableAlias.empty())
142 sqlQueryString << " AS [" << fromTableAlias << ']';
143 sqlQueryString << tableJoins;
144 sqlQueryString << whereCondition;
145 sqlQueryString << groupBy;
146 sqlQueryString << orderBy;
147 sqlQueryString << " OFFSET " << offset << " ROWS FETCH NEXT " << limit << " ROWS ONLY";
148 return sqlQueryString.str();
149 }
150
151 [[nodiscard]] std::string ColumnType(SqlColumnTypeDefinition const& type) const override
152 {
153 using namespace SqlColumnTypeDefinitions;
154 return std::visit(detail::overloaded {
155 [](Bigint const&) -> std::string { return "BIGINT"; },
156 [](Binary const& type) -> std::string {
157 if (type.size == 0 || type.size > 8000)
158 return "VARBINARY(MAX)";
159 else
160 return std::format("VARBINARY({})", type.size);
161 },
162 [](Bool const&) -> std::string { return "BIT"; },
163 [](Char const& type) -> std::string { return std::format("CHAR({})", type.size); },
164 [](Date const&) -> std::string { return "DATE"; },
165 [](DateTime const&) -> std::string { return "DATETIME"; },
166 [](Decimal const& type) -> std::string {
167 return std::format("DECIMAL({}, {})", type.precision, type.scale);
168 },
169 [](Guid const&) -> std::string { return "UNIQUEIDENTIFIER"; },
170 [](Integer const&) -> std::string { return "INTEGER"; },
171 [](NChar const& type) -> std::string { return std::format("NCHAR({})", type.size); },
172 [](NVarchar const& type) -> std::string {
173 if (type.size == 0 || type.size > SqlOptimalMaxColumnSize)
174 return "NVARCHAR(MAX)";
175 else
176 return std::format("NVARCHAR({})", type.size);
177 },
178 [](Real const&) -> std::string { return "REAL"; },
179 [](Smallint const&) -> std::string { return "SMALLINT"; },
180 [](Text const&) -> std::string { return "VARCHAR(MAX)"; },
181 [](Time const&) -> std::string { return "TIME"; },
182 [](Timestamp const&) -> std::string { return "TIMESTAMP"; },
183 [](Tinyint const&) -> std::string { return "TINYINT"; },
184 [](VarBinary const& type) -> std::string {
185 if (type.size == 0 || type.size > 8000)
186 return "VARBINARY(MAX)";
187 else
188 return std::format("VARBINARY({})", type.size);
189 },
190 [](Varchar const& type) -> std::string {
191 if (type.size == 0 || type.size > SqlOptimalMaxColumnSize)
192 return "VARCHAR(MAX)";
193 else
194 return std::format("VARCHAR({})", type.size);
195 },
196 },
197 type);
198 }
199
200 [[nodiscard]] std::string BuildColumnDefinition(SqlColumnDeclaration const& column) const override
201 {
202 std::stringstream sqlQueryString;
203 sqlQueryString << '"' << column.name << "\" " << ColumnType(column.type);
204
205 if (column.required)
206 sqlQueryString << " NOT NULL";
207
208 if (column.primaryKey == SqlPrimaryKeyType::AUTO_INCREMENT)
209 sqlQueryString << " IDENTITY(1,1) PRIMARY KEY";
210 else if (column.primaryKey == SqlPrimaryKeyType::NONE && !column.index && column.unique)
211 sqlQueryString << " UNIQUE";
212
213 if (!column.defaultValue.empty())
214 sqlQueryString << " DEFAULT " << column.defaultValue;
215
216 return sqlQueryString.str();
217 }
218
219 // NOLINTNEXTLINE(readability-function-cognitive-complexity)
220 [[nodiscard]] StringList CreateTable(std::string_view schema,
221 std::string_view tableName,
222 std::vector<SqlColumnDeclaration> const& columns,
223 std::vector<SqlCompositeForeignKeyConstraint> const& foreignKeys,
224 bool ifNotExists = false) const override
225 {
226 std::stringstream ss;
227
228 // SQL Server doesn't have CREATE TABLE IF NOT EXISTS, use conditional block
229 if (ifNotExists)
230 {
231 std::string schemaFilter = schema.empty() ? "dbo" : std::string(schema);
232 ss << std::format("IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = '{}' AND schema_id = SCHEMA_ID('{}'))\n",
233 tableName,
234 schemaFilter);
235 }
236
237 ss << std::format("CREATE TABLE {} (", FormatTableName(schema, tableName));
238
239 bool first = true;
240 for (auto const& column: columns)
241 {
242 if (!first)
243 ss << ",";
244 first = false;
245 ss << "\n " << BuildColumnDefinition(column);
246 }
247
248 auto const primaryKeys = [&]() -> std::vector<std::string> {
249 std::vector<std::pair<uint16_t, std::string>> indexedPrimaryKeys;
250 for (auto const& col: columns)
251 if (col.primaryKey != SqlPrimaryKeyType::NONE)
252 indexedPrimaryKeys.emplace_back(col.primaryKeyIndex, col.name);
253 std::ranges::sort(indexedPrimaryKeys, [](auto const& a, auto const& b) { return a.first < b.first; });
254
255 std::vector<std::string> primaryKeys;
256 primaryKeys.reserve(indexedPrimaryKeys.size());
257 for (auto const& [index, name]: indexedPrimaryKeys)
258 primaryKeys.push_back(name);
259 return primaryKeys;
260 }();
261
262 if (!primaryKeys.empty())
263 {
264 // If primary key is AUTO_INCREMENT, it's already defined inline in BuildColumnDefinition.
265 // Only add explicit PRIMARY KEY constraint if NOT AUTO_INCREMENT?
266 // SQLiteFormatter logic:
267 // if (!primaryKeys.empty()) ss << ", PRIMARY KEY (" << Join(primaryKeys, ", ") << ")";
268 // But BuildColumnDefinition adds "PRIMARY KEY" for AUTO_INCREMENT!
269 // Double primary key definition is invalid.
270
271 // Check if any column is AUTO_INCREMENT
272 bool hasIdentity = false;
273 for (auto const& col: columns)
274 if (col.primaryKey == SqlPrimaryKeyType::AUTO_INCREMENT)
275 hasIdentity = true;
276
277 if (!hasIdentity)
278 {
279 ss << ",\n PRIMARY KEY (";
280 bool firstPk = true;
281 for (auto const& pk: primaryKeys)
282 {
283 if (!firstPk)
284 ss << ", ";
285 firstPk = false;
286 ss << '"' << pk << '"';
287 }
288 ss << ")";
289 }
290 }
291
292 if (!foreignKeys.empty())
293 {
294 for (auto const& fk: foreignKeys)
295 {
296 ss << ",\n CONSTRAINT \"" << BuildForeignKeyConstraintName(tableName, fk.columns) << '"'
297 << " FOREIGN KEY (";
298
299 size_t i = 0;
300 for (auto const& col: fk.columns)
301 {
302 if (i++ > 0)
303 ss << ", ";
304 ss << '"' << col << '"';
305 }
306
307 ss << ") REFERENCES " << FormatTableName(schema, fk.referencedTableName) << " (";
308
309 i = 0;
310 for (auto const& col: fk.referencedColumns)
311 {
312 if (i++ > 0)
313 ss << ", ";
314 ss << '"' << col << '"';
315 }
316 ss << ")";
317 }
318 }
319
320 // Add single-column foreign keys that were defined inline in SQLite but need to be table-constraints here
321 // or just appended if we didn't add them in BuildColumnDefinition (which we didn't).
322 for (auto const& column: columns)
323 {
324 if (column.foreignKey)
325 {
326 ss << ",\n " << BuildForeignKeyConstraint(tableName, column.name, *column.foreignKey);
327 }
328 }
329
330 ss << "\n);";
331
332 StringList result;
333 result.emplace_back(ss.str());
334
335 // Create Indexes
336 for (SqlColumnDeclaration const& column: columns)
337 {
338 if (column.index && column.primaryKey == SqlPrimaryKeyType::NONE)
339 {
340 // primary keys are always indexed
341 if (column.unique)
342 {
343 if (schema.empty())
344 result.emplace_back(std::format(R"(CREATE UNIQUE INDEX "{}_{}_index" ON "{}" ("{}");)",
345 tableName,
346 column.name,
347 tableName,
348 column.name));
349 else
350 result.emplace_back(std::format(R"(CREATE UNIQUE INDEX "{}_{}_index" ON "{}"."{}" ("{}");)",
351 tableName,
352 column.name,
353 schema,
354 tableName,
355 column.name));
356 }
357 else
358 {
359 if (schema.empty())
360 result.emplace_back(std::format(R"(CREATE INDEX "{}_{}_index" ON "{}" ("{}");)",
361 tableName,
362 column.name,
363 tableName,
364 column.name));
365 else
366 result.emplace_back(std::format(R"(CREATE INDEX "{}_{}_index" ON "{}"."{}" ("{}");)",
367 tableName,
368 column.name,
369 schema,
370 tableName,
371 column.name));
372 }
373 }
374 }
375
376 return result;
377 }
378
379 // NOLINTNEXTLINE(readability-function-cognitive-complexity)
380 [[nodiscard]] StringList AlterTable(std::string_view schemaName,
381 std::string_view tableName,
382 std::vector<SqlAlterTableCommand> const& commands) const override
383 {
384 std::stringstream sqlQueryString;
385
386 int currentCommand = 0;
387 for (SqlAlterTableCommand const& command: commands)
388 {
389 if (currentCommand > 0)
390 sqlQueryString << '\n';
391 ++currentCommand;
392
393 using namespace SqlAlterTableCommands;
394 sqlQueryString << std::visit(
395 detail::overloaded {
396 [schemaName, tableName](RenameTable const& actualCommand) -> std::string {
397 return std::format(R"(ALTER TABLE {} RENAME TO "{}";)",
398 FormatTableName(schemaName, tableName),
399 actualCommand.newTableName);
400 },
401 [schemaName, tableName, this](AddColumn const& actualCommand) -> std::string {
402 return std::format(R"(ALTER TABLE {} ADD "{}" {} {};)",
403 FormatTableName(schemaName, tableName),
404 actualCommand.columnName,
405 ColumnType(actualCommand.columnType),
406 actualCommand.nullable == SqlNullable::NotNull ? "NOT NULL" : "NULL");
407 },
408 [schemaName, tableName, this](AlterColumn const& actualCommand) -> std::string {
409 return std::format(R"(ALTER TABLE {} ALTER COLUMN "{}" {} {};)",
410 FormatTableName(schemaName, tableName),
411 actualCommand.columnName,
412 ColumnType(actualCommand.columnType),
413 actualCommand.nullable == SqlNullable::NotNull ? "NOT NULL" : "NULL");
414 },
415 [schemaName, tableName](RenameColumn const& actualCommand) -> std::string {
416 return std::format(R"(ALTER TABLE {} RENAME COLUMN "{}" TO "{}";)",
417 FormatTableName(schemaName, tableName),
418 actualCommand.oldColumnName,
419 actualCommand.newColumnName);
420 },
421 [schemaName, tableName](DropColumn const& actualCommand) -> std::string {
422 return std::format(R"(ALTER TABLE {} DROP COLUMN "{}";)",
423 FormatTableName(schemaName, tableName),
424 actualCommand.columnName);
425 },
426 [schemaName, tableName](AddIndex const& actualCommand) -> std::string {
427 using namespace std::string_view_literals;
428 auto const uniqueStr = actualCommand.unique ? "UNIQUE "sv : ""sv;
429 if (schemaName.empty())
430 return std::format(R"(CREATE {2}INDEX "{0}_{1}_index" ON "{0}" ("{1}");)",
431 tableName,
432 actualCommand.columnName,
433 uniqueStr);
434 else
435 return std::format(R"(CREATE {3}INDEX "{0}_{1}_{2}_index" ON "{0}"."{1}" ("{2}");)",
436 schemaName,
437 tableName,
438 actualCommand.columnName,
439 uniqueStr);
440 },
441 [schemaName, tableName](DropIndex const& actualCommand) -> std::string {
442 if (schemaName.empty())
443 return std::format(R"(DROP INDEX "{0}_{1}_index";)", tableName, actualCommand.columnName);
444 else
445 return std::format(
446 R"(DROP INDEX "{0}_{1}_{2}_index";)", schemaName, tableName, actualCommand.columnName);
447 },
448 [schemaName, tableName](AddForeignKey const& actualCommand) -> std::string {
449 // Idempotent ADD CONSTRAINT — re-applying a migration must be a no-op.
450 // SQL Server has no `ADD CONSTRAINT IF NOT EXISTS`, so the guard is
451 // expressed as `IF NOT EXISTS (SELECT … FROM sys.foreign_keys …)`.
452 auto const fkName = BuildForeignKeyConstraintName(
453 tableName, std::array { std::string_view { actualCommand.columnName } });
454 return std::format(
455 R"(IF NOT EXISTS (SELECT 1 FROM sys.foreign_keys WHERE name = '{0}') ALTER TABLE {1} ADD {2};)",
456 fkName,
457 FormatTableName(schemaName, tableName),
458 BuildForeignKeyConstraint(tableName, actualCommand.columnName, actualCommand.referencedColumn));
459 },
460 [schemaName, tableName](DropForeignKey const& actualCommand) -> std::string {
461 return std::format(R"(ALTER TABLE {} DROP CONSTRAINT "{}";)",
462 FormatTableName(schemaName, tableName),
464 tableName, std::array { std::string_view { actualCommand.columnName } }));
465 },
466 [schemaName, tableName](AddCompositeForeignKey const& actualCommand) -> std::string {
467 std::stringstream ss;
468 ss << "ALTER TABLE " << FormatTableName(schemaName, tableName) << " ADD CONSTRAINT \""
469 << BuildForeignKeyConstraintName(tableName, actualCommand.columns) << "\" FOREIGN KEY (";
470
471 size_t i = 0;
472 for (auto const& col: actualCommand.columns)
473 {
474 if (i++ > 0)
475 ss << ", ";
476 ss << '"' << col << '"';
477 }
478 ss << ") REFERENCES " << FormatTableName(schemaName, actualCommand.referencedTableName) << " (";
479
480 i = 0;
481 for (auto const& col: actualCommand.referencedColumns)
482 {
483 if (i++ > 0)
484 ss << ", ";
485 ss << '"' << col << '"';
486 }
487 ss << ");";
488 return ss.str();
489 },
490 [schemaName, tableName, this](AddColumnIfNotExists const& actualCommand) -> std::string {
491 // SQL Server uses conditional IF NOT EXISTS
492 return std::format(
493 R"(IF NOT EXISTS (SELECT * FROM sys.columns WHERE object_id = OBJECT_ID('{}') AND name = '{}')
494ALTER TABLE {} ADD "{}" {} {};)",
495 FormatTableName(schemaName, tableName),
496 actualCommand.columnName,
497 FormatTableName(schemaName, tableName),
498 actualCommand.columnName,
499 ColumnType(actualCommand.columnType),
500 actualCommand.nullable == SqlNullable::NotNull ? "NOT NULL" : "NULL");
501 },
502 [schemaName, tableName](DropColumnIfExists const& actualCommand) -> std::string {
503 // SQL Server uses conditional IF EXISTS
504 return std::format(
505 R"(IF EXISTS (SELECT * FROM sys.columns WHERE object_id = OBJECT_ID('{}') AND name = '{}')
506ALTER TABLE {} DROP COLUMN "{}";)",
507 FormatTableName(schemaName, tableName),
508 actualCommand.columnName,
509 FormatTableName(schemaName, tableName),
510 actualCommand.columnName);
511 },
512 [schemaName, tableName](DropIndexIfExists const& actualCommand) -> std::string {
513 if (schemaName.empty())
514 return std::format(
515 R"(IF EXISTS (SELECT * FROM sys.indexes WHERE name = '{0}_{1}_index' AND object_id = OBJECT_ID('{0}'))
516DROP INDEX "{0}_{1}_index" ON "{0}";)",
517 tableName,
518 actualCommand.columnName);
519 else
520 return std::format(
521 R"(IF EXISTS (SELECT * FROM sys.indexes WHERE name = '{0}_{1}_{2}_index')
522DROP INDEX "{0}_{1}_{2}_index" ON "{0}"."{1}";)",
523 schemaName,
524 tableName,
525 actualCommand.columnName);
526 },
527 },
528 command);
529 }
530
531 return { sqlQueryString.str() };
532 }
533
534 [[nodiscard]] std::string QueryServerVersion() const override
535 {
536 return "SELECT @@VERSION";
537 }
538};
539
540} // namespace Lightweight
static std::string BuildForeignKeyConstraintName(std::string_view tableName, Range const &columns)
Builds the canonical foreign-key constraint name for a set of columns.
std::vector< std::string > StringList
Alias for a list of SQL statement strings.
static std::string FormatTableName(std::string_view schema, std::string_view table)
Formats a table name with optional schema prefix.
std::variant< SqlAlterTableCommands::RenameTable, SqlAlterTableCommands::AddColumn, SqlAlterTableCommands::AddColumnIfNotExists, SqlAlterTableCommands::AlterColumn, SqlAlterTableCommands::AddIndex, SqlAlterTableCommands::RenameColumn, SqlAlterTableCommands::DropColumn, SqlAlterTableCommands::DropColumnIfExists, SqlAlterTableCommands::DropIndex, SqlAlterTableCommands::DropIndexIfExists, SqlAlterTableCommands::AddForeignKey, SqlAlterTableCommands::AddCompositeForeignKey, SqlAlterTableCommands::DropForeignKey > SqlAlterTableCommand
Represents a single SQL ALTER TABLE command.
SqlPrimaryKeyType
Represents a primary key type.