Lightweight 0.20260617.0
Loading...
Searching...
No Matches
SQLiteFormatter.hpp
1// SPDX-License-Identifier: Apache-2.0
2#pragma once
3
4#include "../SqlAdvisoryLock.hpp"
5#include "../SqlQueryFormatter.hpp"
6
7#include <reflection-cpp/reflection.hpp>
8
9#include <format>
10
11namespace Lightweight
12{
13
14class SQLiteQueryFormatter: public SqlQueryFormatter
15{
16 protected:
17 /// Formats a table name for use in a FROM clause.
18 /// If the table name is already quoted (starts with " or [), returns it as-is.
19 /// Otherwise, wraps it in double quotes.
20 [[nodiscard]] static std::string FormatFromTable(std::string_view table)
21 {
22 if (!table.empty() && (table.front() == '"' || table.front() == '['))
23 return std::string(table); // Already quoted/qualified
24 return std::format(R"("{}")", table);
25 }
26
27 public:
28 /// SQLite has no native `ALTER TABLE … ADD/DROP CONSTRAINT`, so foreign-key
29 /// changes route through the migration executor's table-rebuild path. The
30 /// formatter signals that intent by emitting `-- LIGHTWEIGHT_SQLITE_GUARD:`
31 /// sentinels and overriding this hook so the executor takes the rebuild
32 /// branch instead of executing the (commented-out) sentinel script directly.
33 [[nodiscard]] bool RequiresTableRebuildForForeignKeyChange() const noexcept override
34 {
35 return true;
36 }
37
38 /// Builds the SQL query used to check whether a column exists on a SQLite table.
39 ///
40 /// The migration executor uses this to resolve the `-- LIGHTWEIGHT_SQLITE_GUARD:`
41 /// sentinels emitted by @ref AlterTable for `AddColumnIfNotExists` / `DropColumnIfExists`.
42 /// Keeping the pragma SQL here ensures the sentinel-emitting side and the
43 /// runtime-presence-check side share a single source of truth.
44 ///
45 /// @param tableName Name of the table to inspect.
46 /// @param columnName Name of the column whose existence to check.
47 /// @return SQL string returning a single integer column: non-zero iff the column exists.
48 [[nodiscard]] static std::string BuildColumnExistsQuery(std::string_view tableName, std::string_view columnName)
49 {
50 return std::format(R"(SELECT COUNT(*) FROM pragma_table_info('{}') WHERE name = '{}';)", tableName, columnName);
51 }
52
53 [[nodiscard]] std::string Insert(std::string_view intoTable,
54 std::string_view fields,
55 std::string_view values) const override
56 {
57 return std::format(R"(INSERT INTO "{}" ({}) VALUES ({}))", intoTable, fields, values);
58 }
59
60 [[nodiscard]] std::string Insert(std::string_view /*schema*/,
61 std::string_view intoTable,
62 std::string_view fields,
63 std::string_view values) const override
64 {
65 // SQLite doesn't support schemas - ignore schema parameter
66 return std::format(R"(INSERT INTO "{}" ({}) VALUES ({}))", intoTable, fields, values);
67 }
68
69 [[nodiscard]] std::string QueryLastInsertId(std::string_view /*tableName*/) const override
70 {
71 // This is SQLite syntax. We might want to provide aspecialized SQLite class instead.
72 return "SELECT LAST_INSERT_ROWID()";
73 }
74
75 [[nodiscard]] std::string_view BooleanLiteral(bool literalValue) const noexcept override
76 {
77 return literalValue ? "TRUE" : "FALSE";
78 }
79
80 [[nodiscard]] std::string_view DateFunction() const noexcept override
81 {
82 return "date()";
83 }
84
85 [[nodiscard]] std::string StringLiteral(std::string_view value) const noexcept override
86 {
87 if (value.empty())
88 return "''";
89
90 std::string escaped;
91 escaped.reserve(value.size() + 2);
92 escaped += '\'';
93 for (char const c: value)
94 {
95 if (c == '\'')
96 escaped += "''";
97 else
98 escaped += c;
99 }
100 escaped += '\'';
101 return escaped;
102 }
103
104 [[nodiscard]] std::string StringLiteral(char value) const noexcept override
105 {
106 if (value == '\'')
107 return "''''";
108 return std::format("'{}'", value);
109 }
110
111 [[nodiscard]] std::string BinaryLiteral(std::span<uint8_t const> data) const override
112 {
113 std::string result;
114 result.reserve((data.size() * 2) + 3);
115 result += "X'";
116 for (uint8_t byte: data)
117 result += std::format("{:02X}", byte);
118 result += "'";
119 return result;
120 }
121
122 [[nodiscard]] std::string QualifiedTableName(std::string_view schema, std::string_view table) const override
123 {
124 // SQLite doesn't use schemas in the same way - just return the quoted table name
125 if (schema.empty())
126 return std::format(R"("{}")", table);
127 // For SQLite attached databases, use database.table syntax
128 return std::format(R"("{}"."{}")", schema, table);
129 }
130
131 [[nodiscard]] std::string SelectCount(bool distinct,
132 std::string_view fromTable,
133 std::string_view fromTableAlias,
134 std::string_view tableJoins,
135 std::string_view whereCondition) const override
136 {
137 auto const formattedTable = FormatFromTable(fromTable);
138 if (fromTableAlias.empty())
139 return std::format(
140 "SELECT{} COUNT(*) FROM {}{}{}", distinct ? " DISTINCT" : "", formattedTable, tableJoins, whereCondition);
141 else
142 return std::format(R"(SELECT{} COUNT(*) FROM {} AS "{}"{}{})",
143 distinct ? " DISTINCT" : "",
144 formattedTable,
145 fromTableAlias,
146 tableJoins,
147 whereCondition);
148 }
149
150 [[nodiscard]] std::string SelectAll(bool distinct,
151 // NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
152 std::string_view fields,
153 std::string_view fromTable,
154 std::string_view fromTableAlias,
155 std::string_view tableJoins,
156 std::string_view whereCondition,
157 std::string_view orderBy,
158 std::string_view groupBy) const override
159 {
160 std::stringstream sqlQueryString;
161 sqlQueryString << "SELECT ";
162 if (distinct)
163 sqlQueryString << "DISTINCT ";
164 sqlQueryString << fields;
165 sqlQueryString << " FROM " << FormatFromTable(fromTable);
166 if (!fromTableAlias.empty())
167 sqlQueryString << " AS \"" << fromTableAlias << '"';
168 sqlQueryString << tableJoins;
169 sqlQueryString << whereCondition;
170 sqlQueryString << groupBy;
171 sqlQueryString << orderBy;
172
173 return sqlQueryString.str();
174 }
175
176 [[nodiscard]] std::string SelectFirst(bool distinct,
177 // NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
178 std::string_view fields,
179 std::string_view fromTable,
180 std::string_view fromTableAlias,
181 std::string_view tableJoins,
182 std::string_view whereCondition,
183 std::string_view orderBy,
184 size_t count) const override
185 {
186 std::stringstream sqlQueryString;
187 sqlQueryString << "SELECT " << fields;
188 if (distinct)
189 sqlQueryString << " DISTINCT";
190 sqlQueryString << " FROM " << FormatFromTable(fromTable);
191 if (!fromTableAlias.empty())
192 sqlQueryString << " AS \"" << fromTableAlias << "\"";
193 sqlQueryString << tableJoins;
194 sqlQueryString << whereCondition;
195 sqlQueryString << orderBy;
196 sqlQueryString << " LIMIT " << count;
197 return sqlQueryString.str();
198 }
199
200 [[nodiscard]] std::string SelectRange(bool distinct,
201 // NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
202 std::string_view fields,
203 std::string_view fromTable,
204 std::string_view fromTableAlias,
205 std::string_view tableJoins,
206 std::string_view whereCondition,
207 std::string_view orderBy,
208 std::string_view groupBy,
209 std::size_t offset,
210 std::size_t limit) const override
211 {
212 std::stringstream sqlQueryString;
213 sqlQueryString << "SELECT " << fields;
214 if (distinct)
215 sqlQueryString << " DISTINCT";
216 sqlQueryString << " FROM " << FormatFromTable(fromTable);
217 if (!fromTableAlias.empty())
218 sqlQueryString << " AS \"" << fromTableAlias << "\"";
219 sqlQueryString << tableJoins;
220 sqlQueryString << whereCondition;
221 sqlQueryString << groupBy;
222 sqlQueryString << orderBy;
223 sqlQueryString << " LIMIT " << limit << " OFFSET " << offset;
224 return sqlQueryString.str();
225 }
226
227 [[nodiscard]] std::string Update(std::string_view table,
228 std::string_view tableAlias,
229 std::string_view setFields,
230 std::string_view whereCondition) const override
231 {
232 auto const formattedTable = FormatFromTable(table);
233 if (tableAlias.empty())
234 return std::format("UPDATE {} SET {}{}", formattedTable, setFields, whereCondition);
235 else
236 return std::format(R"(UPDATE {} AS "{}" SET {}{})", formattedTable, tableAlias, setFields, whereCondition);
237 }
238
239 [[nodiscard]] std::string Delete(std::string_view fromTable,
240 std::string_view fromTableAlias,
241 std::string_view tableJoins,
242 std::string_view whereCondition) const override
243 {
244 auto const formattedTable = FormatFromTable(fromTable);
245 if (fromTableAlias.empty())
246 return std::format("DELETE FROM {}{}{}", formattedTable, tableJoins, whereCondition);
247 else
248 return std::format(R"(DELETE FROM {} AS "{}"{}{})", formattedTable, fromTableAlias, tableJoins, whereCondition);
249 }
250
251 [[nodiscard]] virtual std::string BuildColumnDefinition(SqlColumnDeclaration const& column) const
252 {
253 std::stringstream sqlQueryString;
254
255 sqlQueryString << '"' << column.name << "\" ";
256
257 if (column.primaryKey != SqlPrimaryKeyType::AUTO_INCREMENT)
258 sqlQueryString << ColumnType(column.type);
259 else
260 sqlQueryString << ColumnType(SqlColumnTypeDefinitions::Integer {});
261
262 if (column.required)
263 sqlQueryString << " NOT NULL";
264
265 if (column.primaryKey == SqlPrimaryKeyType::AUTO_INCREMENT)
266 sqlQueryString << " PRIMARY KEY AUTOINCREMENT";
267 else if (column.primaryKey == SqlPrimaryKeyType::NONE && !column.index && column.unique)
268 sqlQueryString << " UNIQUE";
269
270 if (!column.defaultValue.empty())
271 sqlQueryString << " DEFAULT " << column.defaultValue;
272
273 return sqlQueryString.str();
274 }
275
276 [[nodiscard]] static std::string BuildForeignKeyConstraint(std::string_view tableName,
277 std::string_view columnName,
278 SqlForeignKeyReferenceDefinition const& referencedColumn)
279 {
280 // Double-quote the constraint name so PostgreSQL preserves its case (otherwise
281 // PG would fold to lowercase, breaking the matching `DROP CONSTRAINT "FK_<…>"`
282 // emitted by `DropForeignKey`). The name is shared with the runtime rebuild
283 // path (`SqliteRebuildAddForeignKey`) via `BuildForeignKeyConstraintName` so
284 // CREATE-table and ALTER-table produce identical constraint names.
285 return std::format(R"(CONSTRAINT "{}" FOREIGN KEY ("{}") REFERENCES "{}"("{}"))",
286 BuildForeignKeyConstraintName(tableName, std::array { columnName }),
287 columnName,
288 referencedColumn.tableName,
289 referencedColumn.columnName);
290 }
291
292 // NOLINTNEXTLINE(readability-function-cognitive-complexity)
293 [[nodiscard]] StringList CreateTable(std::string_view schema,
294 std::string_view tableName,
295 std::vector<SqlColumnDeclaration> const& columns,
296 std::vector<SqlCompositeForeignKeyConstraint> const& foreignKeys,
297 bool ifNotExists = false) const override
298 {
299 auto sqlQueries = StringList {};
300
301 // NOLINTNEXTLINE(readability-function-cognitive-complexity)
302 sqlQueries.emplace_back([&]() {
303 std::stringstream sqlQueryString;
304 sqlQueryString << "CREATE TABLE ";
305 if (ifNotExists)
306 sqlQueryString << "IF NOT EXISTS ";
307 // SQLite doesn't support schemas - ignore schema parameter
308 (void) schema;
309 sqlQueryString << "\"" << tableName << "\" (";
310 std::vector<SqlColumnDeclaration const*> pks;
311 size_t currentColumn = 0;
312 std::string foreignKeyConstraints;
313 for (SqlColumnDeclaration const& column: columns)
314 {
315 if (currentColumn > 0)
316 sqlQueryString << ",";
317 ++currentColumn;
318 sqlQueryString << "\n ";
319 sqlQueryString << BuildColumnDefinition(column);
320
321 if (column.primaryKey != SqlPrimaryKeyType::NONE)
322 pks.push_back(&column);
323
324 if (column.foreignKey)
325 {
326 foreignKeyConstraints += ",\n ";
327 foreignKeyConstraints += BuildForeignKeyConstraint(tableName, column.name, *column.foreignKey);
328 }
329 }
330
331 for (SqlCompositeForeignKeyConstraint const& fk: foreignKeys)
332 {
333 // Emit a deterministic CONSTRAINT name (`FK_<table>_<col1>_<col2>...`) so that
334 // a later `DropForeignKey` / SQLite-rebuild can locate the constraint by name
335 // instead of relying on the backend's auto-generated identifier (PostgreSQL
336 // would otherwise pick `<table>_<col>_fkey`).
337 foreignKeyConstraints += ",\n CONSTRAINT \"";
338 foreignKeyConstraints += BuildForeignKeyConstraintName(tableName, fk.columns);
339 foreignKeyConstraints += "\" FOREIGN KEY (";
340 for (size_t i = 0; i < fk.columns.size(); ++i)
341 {
342 if (i > 0)
343 foreignKeyConstraints += ", ";
344 foreignKeyConstraints += '"' + fk.columns[i] + '"';
345 }
346 foreignKeyConstraints += ") REFERENCES \"";
347 foreignKeyConstraints += fk.referencedTableName;
348 foreignKeyConstraints += "\" (";
349 for (size_t i = 0; i < fk.referencedColumns.size(); ++i)
350 {
351 if (i > 0)
352 foreignKeyConstraints += ", ";
353 foreignKeyConstraints += '"' + fk.referencedColumns[i] + '"';
354 }
355 foreignKeyConstraints += ")";
356 }
357
358 // Filter out AUTO_INCREMENT from table-level PK constraint if it's the ONLY PK,
359 // because SQLite handles it inline. But for composite keys involving auto-inc (if that's even valid/used),
360 // or if we just want to be explicit.
361 // SQLite restriction: "INTEGER PRIMARY KEY" must be on the column definition for auto-increment.
362 // If we have AUTO_INCREMENT, it's already in BuildColumnDefinition.
363
364 std::erase_if(
365 pks, [](SqlColumnDeclaration const* col) { return col->primaryKey == SqlPrimaryKeyType::AUTO_INCREMENT; });
366
367 if (!pks.empty())
368 {
369 std::ranges::sort(pks, [](SqlColumnDeclaration const* a, SqlColumnDeclaration const* b) {
370 // If both have index, use it. If one has 0 (no index), treat it as "after" indexed ones?
371 // Or just rely on stable sort?
372 // Let's assume indices are properly set for composite keys.
373 // 1-based index vs 0. 0 means "unordered".
374 if (a->primaryKeyIndex != 0 && b->primaryKeyIndex != 0)
375 return a->primaryKeyIndex < b->primaryKeyIndex;
376 if (a->primaryKeyIndex != 0)
377 return true; // a comes first
378 if (b->primaryKeyIndex != 0)
379 return false; // b comes first
380 return false; // keep original order
381 });
382
383 sqlQueryString << ",\n PRIMARY KEY (";
384 for (size_t i = 0; i < pks.size(); ++i)
385 {
386 if (i > 0)
387 sqlQueryString << ", ";
388 sqlQueryString << '"' << pks[i]->name << '"';
389 }
390 sqlQueryString << ")";
391 }
392
393 sqlQueryString << foreignKeyConstraints;
394
395 sqlQueryString << "\n);";
396 return sqlQueryString.str();
397 }());
398
399 for (SqlColumnDeclaration const& column: columns)
400 {
401 if (column.index && column.primaryKey == SqlPrimaryKeyType::NONE)
402 {
403 // primary keys are always indexed
404 if (column.unique)
405 sqlQueries.emplace_back(std::format(R"(CREATE UNIQUE INDEX "{}_{}_index" ON "{}" ("{}");)",
406 tableName,
407 column.name,
408 tableName,
409 column.name));
410 else
411 sqlQueries.emplace_back(std::format(
412 R"(CREATE INDEX "{}_{}_index" ON "{}" ("{}");)", tableName, column.name, tableName, column.name));
413 }
414 }
415
416 return sqlQueries;
417 }
418
419 private:
420 [[nodiscard]] std::string FormatAlterTableCommand(std::string_view tableName, SqlAlterTableCommand const& command) const
421 {
422 auto const formatTable = [tableName]() {
423 return std::format(R"("{}")", tableName);
424 };
425
426 using namespace SqlAlterTableCommands;
427 return std::visit(
428 detail::overloaded {
429 [&formatTable](RenameTable const& actualCommand) -> std::string {
430 return std::format(R"(ALTER TABLE {} RENAME TO "{}";)", formatTable(), actualCommand.newTableName);
431 },
432 [&formatTable, this](AddColumn const& actualCommand) -> std::string {
433 return std::format(R"(ALTER TABLE {} ADD COLUMN "{}" {} {};)",
434 formatTable(),
435 actualCommand.columnName,
436 ColumnType(actualCommand.columnType),
437 actualCommand.nullable == SqlNullable::NotNull ? "NOT NULL" : "NULL");
438 },
439 [&formatTable, this](AlterColumn const& actualCommand) -> std::string {
440 return std::format(R"(ALTER TABLE {} ALTER COLUMN "{}" {} {};)",
441 formatTable(),
442 actualCommand.columnName,
443 ColumnType(actualCommand.columnType),
444 actualCommand.nullable == SqlNullable::NotNull ? "NOT NULL" : "NULL");
445 },
446 [&formatTable](RenameColumn const& actualCommand) -> std::string {
447 return std::format(R"(ALTER TABLE {} RENAME COLUMN "{}" TO "{}";)",
448 formatTable(),
449 actualCommand.oldColumnName,
450 actualCommand.newColumnName);
451 },
452 [&formatTable](DropColumn const& actualCommand) -> std::string {
453 return std::format(R"(ALTER TABLE {} DROP COLUMN "{}";)", formatTable(), actualCommand.columnName);
454 },
455 [tableName](AddIndex const& actualCommand) -> std::string {
456 using namespace std::string_view_literals;
457 auto const uniqueStr = actualCommand.unique ? "UNIQUE "sv : ""sv;
458 return std::format(R"(CREATE {2}INDEX "{0}_{1}_index" ON "{0}" ("{1}");)",
459 tableName,
460 actualCommand.columnName,
461 uniqueStr);
462 },
463 [tableName](DropIndex const& actualCommand) -> std::string {
464 return std::format(R"(DROP INDEX "{0}_{1}_index";)", tableName, actualCommand.columnName);
465 },
466 [tableName](AddForeignKey const& actualCommand) -> std::string {
467 // SQLite cannot `ALTER TABLE … ADD CONSTRAINT`. The runtime executor
468 // recognizes this sentinel and rebuilds the table from sqlite_schema.
469 // All metadata the executor needs fits in the sentinel itself. We keep
470 // a commented-out equivalent MSSQL-style ALTER below so dry-run output
471 // stays readable.
472 return std::format(
473 R"(-- LIGHTWEIGHT_SQLITE_GUARD: ADD_FOREIGN_KEY "{0}" "{1}" "{2}" "{3}"
474-- ALTER TABLE "{0}" ADD {4};)",
475 tableName,
476 actualCommand.columnName,
477 actualCommand.referencedColumn.tableName,
478 actualCommand.referencedColumn.columnName,
479 BuildForeignKeyConstraint(tableName, actualCommand.columnName, actualCommand.referencedColumn));
480 },
481 [tableName](DropForeignKey const& actualCommand) -> std::string {
482 // SQLite has no `DROP CONSTRAINT`. Executor rebuilds the table without
483 // the matching `CONSTRAINT FK_<tbl>_<col>` clause.
484 return std::format(
485 R"(-- LIGHTWEIGHT_SQLITE_GUARD: DROP_FOREIGN_KEY "{0}" "{1}"
486-- ALTER TABLE "{0}" DROP CONSTRAINT "{2}";)",
487 tableName,
488 actualCommand.columnName,
490 std::array { std::string_view { actualCommand.columnName } }));
491 },
492 [tableName](AddCompositeForeignKey const& actualCommand) -> std::string {
493 // SQLite cannot `ALTER TABLE … ADD CONSTRAINT`. Mirror the single-column
494 // AddForeignKey path: emit a sentinel the runtime executor recognises and
495 // translates into a table rebuild with the composite FK clause appended.
496 // Column tuples are encoded as comma-joined lists inside the sentinel's
497 // quoted fields; SQL identifiers in the target corpora do not contain
498 // commas so the split-on-',' parse on the runtime side is unambiguous.
499 auto const joinComma = [](std::vector<std::string> const& v) {
500 std::string out;
501 for (size_t i = 0; i < v.size(); ++i)
502 {
503 if (i != 0)
504 out += ',';
505 out += v[i];
506 }
507 return out;
508 };
509 auto const joinQuoted = [](std::vector<std::string> const& v) {
510 std::string out;
511 for (size_t i = 0; i < v.size(); ++i)
512 {
513 if (i != 0)
514 out += ", ";
515 out += '"';
516 out += v[i];
517 out += '"';
518 }
519 return out;
520 };
521 auto const fkName = BuildForeignKeyConstraintName(tableName, actualCommand.columns);
522 return std::format(
523 R"(-- LIGHTWEIGHT_SQLITE_GUARD: ADD_COMPOSITE_FOREIGN_KEY "{0}" "{1}" "{2}" "{3}"
524-- ALTER TABLE "{0}" ADD CONSTRAINT "{4}" FOREIGN KEY ({5}) REFERENCES "{2}"({6});)",
525 tableName,
526 joinComma(actualCommand.columns),
527 actualCommand.referencedTableName,
528 joinComma(actualCommand.referencedColumns),
529 fkName,
530 joinQuoted(actualCommand.columns),
531 joinQuoted(actualCommand.referencedColumns));
532 },
533 [&formatTable, tableName, this](AddColumnIfNotExists const& actualCommand) -> std::string {
534 // SQLite doesn't support IF NOT EXISTS for ADD COLUMN.
535 // Emit a sentinel comment so the migration executor can presence-check
536 // via pragma_table_info() before running the ALTER TABLE.
537 return std::format(
538 R"(-- LIGHTWEIGHT_SQLITE_GUARD: ADD_COLUMN_IF_NOT_EXISTS "{0}" "{1}"
539ALTER TABLE {2} ADD COLUMN "{1}" {3} {4};)",
540 tableName,
541 actualCommand.columnName,
542 formatTable(),
543 ColumnType(actualCommand.columnType),
544 actualCommand.nullable == SqlNullable::NotNull ? "NOT NULL" : "NULL");
545 },
546 [&formatTable, tableName](DropColumnIfExists const& actualCommand) -> std::string {
547 // SQLite doesn't support IF EXISTS for DROP COLUMN; guarded like above.
548 return std::format(
549 R"(-- LIGHTWEIGHT_SQLITE_GUARD: DROP_COLUMN_IF_EXISTS "{0}" "{1}"
550ALTER TABLE {2} DROP COLUMN "{1}";)",
551 tableName,
552 actualCommand.columnName,
553 formatTable());
554 },
555 [tableName](DropIndexIfExists const& actualCommand) -> std::string {
556 return std::format(R"(DROP INDEX IF EXISTS "{0}_{1}_index";)", tableName, actualCommand.columnName);
557 },
558 },
559 command);
560 }
561
562 public:
563 [[nodiscard]] StringList AlterTable(std::string_view /*schemaName*/,
564 std::string_view tableName,
565 std::vector<SqlAlterTableCommand> const& commands) const override
566 {
567 // SQLite has no native IF NOT EXISTS / IF EXISTS for columns. We emit each command
568 // as its own result entry; guarded commands are prefixed with a sentinel comment
569 // that the migration executor recognizes and uses to perform a pragma_table_info
570 // presence check at runtime.
571 StringList result;
572 for (SqlAlterTableCommand const& command: commands)
573 {
574 auto sql = FormatAlterTableCommand(tableName, command);
575 if (!sql.empty())
576 result.push_back(std::move(sql));
577 }
578 return result;
579 }
580
581 [[nodiscard]] std::string ColumnType(SqlColumnTypeDefinition const& type) const override
582 {
583 using namespace SqlColumnTypeDefinitions;
584 return std::visit(detail::overloaded {
585 [](Bigint const&) -> std::string { return "BIGINT"; },
586 [](Binary const&) -> std::string { return "BLOB"; },
587 [](Bool const&) -> std::string { return "BOOLEAN"; },
588 [](Char const& type) -> std::string { return std::format("CHAR({})", type.size); },
589 [](Date const&) -> std::string { return "DATE"; },
590 [](DateTime const&) -> std::string { return "DATETIME"; },
591 [](Decimal const& type) -> std::string {
592 return std::format("DECIMAL({}, {})", type.precision, type.scale);
593 },
594 [](Guid const&) -> std::string { return "GUID"; },
595 [](Integer const&) -> std::string { return "INTEGER"; },
596 [](NChar const& type) -> std::string { return std::format("NCHAR({})", type.size); },
597 [](NVarchar const& type) -> std::string { return std::format("NVARCHAR({})", type.size); },
598 [](Real const&) -> std::string { return "REAL"; },
599 [](Smallint const&) -> std::string { return "SMALLINT"; },
600 [](Text const&) -> std::string { return "TEXT"; },
601 [](Time const&) -> std::string { return "TIME"; },
602 [](Timestamp const&) -> std::string { return "TIMESTAMP"; },
603 [](Tinyint const&) -> std::string { return "TINYINT"; },
604 [](VarBinary const& type) -> std::string { return std::format("VARBINARY({})", type.size); },
605 [](Varchar const& type) -> std::string { return std::format("VARCHAR({})", type.size); },
606 },
607 type);
608 }
609
610 [[nodiscard]] StringList DropTable(std::string_view /*schemaName*/,
611 std::string_view const& tableName,
612 bool ifExists = false,
613 bool cascade = false) const override
614 {
615 // SQLite doesn't support CASCADE syntax, but if FK constraints are disabled
616 // (PRAGMA foreign_keys = OFF), dropping works. The cascade flag is ignored.
617 // SQLite doesn't support schemas - ignore schemaName parameter
618 (void) cascade;
619 if (ifExists)
620 return { std::format(R"(DROP TABLE IF EXISTS "{}";)", tableName) };
621 else
622 return { std::format(R"(DROP TABLE "{}";)", tableName) };
623 }
624
625 [[nodiscard]] std::string QueryServerVersion() const override
626 {
627 return "SELECT sqlite_version()";
628 }
629
630 /// SQLite has no native advisory-lock primitive, so the handler maintains
631 /// a `_lightweight_locks` table guarded by a unique constraint. The
632 /// override is intentionally inline-delegating: putting the body in
633 /// `SqlQueryFormatter.cpp` would have made it the class's *key function*
634 /// and forced the vtable into a single TU — fine on Windows, but on
635 /// Linux with `CXX_VISIBILITY_PRESET=hidden` + `LIGHTWEIGHT_BUILD_SHARED=ON`
636 /// the hidden vtable would fail to link from consumers of `Lightweight`.
637 /// Inline + free-function delegation keeps the vtable weak.
638 [[nodiscard]] SqlAdvisoryLockHandler const& AdvisoryLockOps() const override
639 {
640 return SqliteAdvisoryLockOps();
641 }
642};
643
644} // 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.
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.