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