|
19 | 19 | #include "ds_mysql/database_name.hpp" |
20 | 20 | #include "ds_mysql/host_name.hpp" |
21 | 21 | #include "ds_mysql/port_number.hpp" |
| 22 | +#include "ds_mysql/prepared_statement.hpp" |
22 | 23 | #include "ds_mysql/sql_ddl.hpp" |
23 | 24 | #include "ds_mysql/sql_dml.hpp" |
24 | 25 | #include "ds_mysql/sql_dql.hpp" |
@@ -392,6 +393,28 @@ class mysql_connection { |
392 | 393 | return out; |
393 | 394 | } |
394 | 395 |
|
| 396 | + // --------------------------------------------------------------- |
| 397 | + // Prepared statements |
| 398 | + // --------------------------------------------------------------- |
| 399 | + |
| 400 | + // Prepare a SQL string for repeated execution with bound parameters. |
| 401 | + [[nodiscard]] std::expected<prepared_statement, std::string> prepare(std::string_view sql) const { |
| 402 | + auto stmt = std::unique_ptr<MYSQL_STMT, prepared_statement::stmt_deleter>(mysql_stmt_init(connection_.get())); |
| 403 | + if (!stmt) { |
| 404 | + return std::unexpected(last_error()); |
| 405 | + } |
| 406 | + if (mysql_stmt_prepare(stmt.get(), sql.data(), static_cast<unsigned long>(sql.size())) != 0) { |
| 407 | + return std::unexpected(std::string(mysql_stmt_error(stmt.get()))); |
| 408 | + } |
| 409 | + return prepared_statement{std::move(stmt)}; |
| 410 | + } |
| 411 | + |
| 412 | + // Prepare a typed query builder for repeated execution. |
| 413 | + template <SqlBuilder Stmt> |
| 414 | + [[nodiscard]] std::expected<prepared_statement, std::string> prepare(Stmt const& stmt) const { |
| 415 | + return prepare(stmt.build_sql()); |
| 416 | + } |
| 417 | + |
395 | 418 | // Validate that the C++ table struct T matches the live schema in the database. |
396 | 419 | // |
397 | 420 | // Runs DESCRIBE <table> and checks: |
@@ -598,4 +621,97 @@ class mysql_connection { |
598 | 621 | std::unique_ptr<MYSQL, decltype(&mysql_close)> connection_; |
599 | 622 | }; |
600 | 623 |
|
| 624 | +// =================================================================== |
| 625 | +// transaction_guard — RAII scoped transaction helper. |
| 626 | +// |
| 627 | +// This is a C++ resource-management utility, not a MySQL semantic. |
| 628 | +// Disables autocommit on construction and automatically rolls back on |
| 629 | +// destruction unless commit() has been called. Move-only. |
| 630 | +// |
| 631 | +// { |
| 632 | +// auto guard = transaction_guard::begin(conn); |
| 633 | +// if (!guard) { /* handle error */ } |
| 634 | +// conn.execute(insert_into(t{}).values(row)); |
| 635 | +// auto result = guard->commit(); |
| 636 | +// if (!result) { /* handle commit error */ } |
| 637 | +// } |
| 638 | +// // If commit() was never called, destructor rolls back. |
| 639 | +// =================================================================== |
| 640 | + |
| 641 | +class transaction_guard { |
| 642 | +public: |
| 643 | + transaction_guard(transaction_guard const&) = delete; |
| 644 | + transaction_guard& operator=(transaction_guard const&) = delete; |
| 645 | + |
| 646 | + transaction_guard(transaction_guard&& other) noexcept : conn_(other.conn_), committed_(other.committed_) { |
| 647 | + other.conn_ = nullptr; |
| 648 | + } |
| 649 | + |
| 650 | + transaction_guard& operator=(transaction_guard&& other) noexcept { |
| 651 | + if (this != &other) { |
| 652 | + if (conn_ && !committed_) { |
| 653 | + (void)conn_->rollback(); |
| 654 | + } |
| 655 | + conn_ = other.conn_; |
| 656 | + committed_ = other.committed_; |
| 657 | + other.conn_ = nullptr; |
| 658 | + } |
| 659 | + return *this; |
| 660 | + } |
| 661 | + |
| 662 | + ~transaction_guard() { |
| 663 | + if (conn_ && !committed_) { |
| 664 | + (void)conn_->rollback(); |
| 665 | + } |
| 666 | + } |
| 667 | + |
| 668 | + // Factory: begin a transaction by disabling autocommit. |
| 669 | + [[nodiscard]] static std::expected<transaction_guard, std::string> begin(mysql_connection const& conn) { |
| 670 | + auto result = conn.autocommit(false); |
| 671 | + if (!result) { |
| 672 | + return std::unexpected(result.error()); |
| 673 | + } |
| 674 | + return transaction_guard{&conn}; |
| 675 | + } |
| 676 | + |
| 677 | + // Commit the transaction and re-enable autocommit. |
| 678 | + [[nodiscard]] std::expected<void, std::string> commit() { |
| 679 | + if (!conn_) { |
| 680 | + return std::unexpected("transaction_guard: no active connection"); |
| 681 | + } |
| 682 | + auto result = conn_->commit(); |
| 683 | + if (!result) { |
| 684 | + return std::unexpected(result.error()); |
| 685 | + } |
| 686 | + committed_ = true; |
| 687 | + // Re-enable autocommit so the connection returns to its default state. |
| 688 | + return conn_->autocommit(true); |
| 689 | + } |
| 690 | + |
| 691 | + // Explicitly roll back the transaction and re-enable autocommit. |
| 692 | + [[nodiscard]] std::expected<void, std::string> rollback() { |
| 693 | + if (!conn_) { |
| 694 | + return std::unexpected("transaction_guard: no active connection"); |
| 695 | + } |
| 696 | + auto result = conn_->rollback(); |
| 697 | + if (!result) { |
| 698 | + return std::unexpected(result.error()); |
| 699 | + } |
| 700 | + committed_ = true; // prevent double-rollback in destructor |
| 701 | + return conn_->autocommit(true); |
| 702 | + } |
| 703 | + |
| 704 | + // Check whether commit() has been called. |
| 705 | + [[nodiscard]] bool is_committed() const noexcept { |
| 706 | + return committed_; |
| 707 | + } |
| 708 | + |
| 709 | +private: |
| 710 | + explicit transaction_guard(mysql_connection const* conn) : conn_(conn) { |
| 711 | + } |
| 712 | + |
| 713 | + mysql_connection const* conn_ = nullptr; |
| 714 | + bool committed_ = false; |
| 715 | +}; |
| 716 | + |
601 | 717 | } // namespace ds_mysql |
0 commit comments