Skip to content

Commit fc11cb6

Browse files
author
Pierre-Luc Gagné
committed
Add SELECT lock clauses and transaction statement builders
1 parent 0bea3f2 commit fc11cb6

3 files changed

Lines changed: 129 additions & 12 deletions

File tree

lib/include/ds_mysql/sql.hpp

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4790,6 +4790,13 @@ struct alias_order_entry {
47904790

47914791
using order_by_item = std::variant<std::string, alias_order_entry>;
47924792

4793+
enum class select_lock_mode {
4794+
none,
4795+
for_update,
4796+
for_share,
4797+
lock_in_share_mode,
4798+
};
4799+
47934800
// ===================================================================
47944801
// select_query_builder — unified builder for all SELECT clauses
47954802
// ===================================================================
@@ -5072,6 +5079,36 @@ struct select_query_builder {
50725079
return std::move(*this);
50735080
}
50745081

5082+
[[nodiscard]] select_query_builder for_update() const& {
5083+
auto copy = *this;
5084+
copy.lock_mode_ = select_lock_mode::for_update;
5085+
return copy;
5086+
}
5087+
[[nodiscard]] select_query_builder for_update() && {
5088+
lock_mode_ = select_lock_mode::for_update;
5089+
return std::move(*this);
5090+
}
5091+
5092+
[[nodiscard]] select_query_builder for_share() const& {
5093+
auto copy = *this;
5094+
copy.lock_mode_ = select_lock_mode::for_share;
5095+
return copy;
5096+
}
5097+
[[nodiscard]] select_query_builder for_share() && {
5098+
lock_mode_ = select_lock_mode::for_share;
5099+
return std::move(*this);
5100+
}
5101+
5102+
[[nodiscard]] select_query_builder lock_in_share_mode() const& {
5103+
auto copy = *this;
5104+
copy.lock_mode_ = select_lock_mode::lock_in_share_mode;
5105+
return copy;
5106+
}
5107+
[[nodiscard]] select_query_builder lock_in_share_mode() && {
5108+
lock_mode_ = select_lock_mode::lock_in_share_mode;
5109+
return std::move(*this);
5110+
}
5111+
50755112
// ---- JOIN clauses ----
50765113
//
50775114
// Use col<Table, Index> descriptors for the join columns to produce
@@ -5266,6 +5303,13 @@ struct select_query_builder {
52665303
sql += " OFFSET ";
52675304
sql += std::to_string(*offset_);
52685305
}
5306+
if (lock_mode_ == select_lock_mode::for_update) {
5307+
sql += " FOR UPDATE";
5308+
} else if (lock_mode_ == select_lock_mode::for_share) {
5309+
sql += " FOR SHARE";
5310+
} else if (lock_mode_ == select_lock_mode::lock_in_share_mode) {
5311+
sql += " LOCK IN SHARE MODE";
5312+
}
52695313
return sql;
52705314
}
52715315

@@ -5318,6 +5362,7 @@ struct select_query_builder {
53185362
std::vector<order_by_item> order_by_clauses_;
53195363
std::optional<std::size_t> limit_;
53205364
std::optional<std::size_t> offset_;
5365+
select_lock_mode lock_mode_ = select_lock_mode::none;
53215366
bool distinct_ = false;
53225367
std::string joins_;
53235368
std::map<std::size_t, std::string> aliases_;
@@ -5380,6 +5425,19 @@ class union_query {
53805425
std::string sql_;
53815426
};
53825427

5428+
class statement_query {
5429+
public:
5430+
explicit statement_query(std::string sql) : sql_(std::move(sql)) {
5431+
}
5432+
5433+
[[nodiscard]] std::string build_sql() const {
5434+
return sql_;
5435+
}
5436+
5437+
private:
5438+
std::string sql_;
5439+
};
5440+
53835441
} // namespace detail
53845442

53855443
// ===================================================================
@@ -5418,6 +5476,22 @@ template <TypedSelectQuery Q1, TypedSelectQuery Q2>
54185476
")"};
54195477
}
54205478

5479+
[[nodiscard]] inline detail::statement_query start_transaction() {
5480+
return detail::statement_query{"START TRANSACTION"};
5481+
}
5482+
5483+
[[nodiscard]] inline detail::statement_query begin_transaction() {
5484+
return detail::statement_query{"BEGIN"};
5485+
}
5486+
5487+
[[nodiscard]] inline detail::statement_query commit_transaction() {
5488+
return detail::statement_query{"COMMIT"};
5489+
}
5490+
5491+
[[nodiscard]] inline detail::statement_query rollback_transaction() {
5492+
return detail::statement_query{"ROLLBACK"};
5493+
}
5494+
54215495
/**
54225496
* select<Projs...>() — begin a type-safe SELECT query.
54235497
*

tests/unit/test_dml.cpp

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@ using namespace std::string_literals;
1616

1717
namespace {
1818
struct asset {
19-
COLUMN_FIELD(id, uint32_t)
20-
COLUMN_FIELD(exchange_id, std::optional<uint32_t>)
21-
COLUMN_FIELD(ticker, varchar_field<32>)
22-
COLUMN_FIELD(instrument, varchar_field<64>)
23-
COLUMN_FIELD(name, std::optional<varchar_field<255>>)
24-
COLUMN_FIELD(sector, std::optional<varchar_field<255>>)
25-
COLUMN_FIELD(currency, std::optional<varchar_field<32>>)
26-
COLUMN_FIELD(created_date, sql_datetime)
19+
COLUMN_FIELD(id, uint32_t)
20+
COLUMN_FIELD(exchange_id, std::optional<uint32_t>)
21+
COLUMN_FIELD(ticker, varchar_field<32>)
22+
COLUMN_FIELD(instrument, varchar_field<64>)
23+
COLUMN_FIELD(name, std::optional<varchar_field<255>>)
24+
COLUMN_FIELD(sector, std::optional<varchar_field<255>>)
25+
COLUMN_FIELD(currency, std::optional<varchar_field<32>>)
26+
COLUMN_FIELD(created_date, sql_datetime)
2727
COLUMN_FIELD(last_updated_date, sql_datetime)
2828
};
2929
} // namespace
@@ -42,6 +42,26 @@ suite<"DML"> dml_suite = [] {
4242
expect(sql == "DESCRIBE asset"s) << sql;
4343
};
4444

45+
"start_transaction - generates correct SQL"_test = [] {
46+
auto const sql = start_transaction().build_sql();
47+
expect(sql == "START TRANSACTION"s) << sql;
48+
};
49+
50+
"begin_transaction - generates correct SQL"_test = [] {
51+
auto const sql = begin_transaction().build_sql();
52+
expect(sql == "BEGIN"s) << sql;
53+
};
54+
55+
"commit_transaction - generates correct SQL"_test = [] {
56+
auto const sql = commit_transaction().build_sql();
57+
expect(sql == "COMMIT"s) << sql;
58+
};
59+
60+
"rollback_transaction - generates correct SQL"_test = [] {
61+
auto const sql = rollback_transaction().build_sql();
62+
expect(sql == "ROLLBACK"s) << sql;
63+
};
64+
4565
// -------------------------------------------------------------------
4666
// insert_into<T>
4767
// -------------------------------------------------------------------
@@ -311,8 +331,10 @@ suite<"DML"> dml_suite = [] {
311331
row.exchange_id_ = 1u;
312332
row.ticker_ = "AAPL";
313333
row.instrument_ = "Stock";
314-
auto const sql =
315-
insert_into<asset>().values(row).on_duplicate_key_update(asset::ticker{"AAPL"}, asset::instrument{"Stock"}).build_sql();
334+
auto const sql = insert_into<asset>()
335+
.values(row)
336+
.on_duplicate_key_update(asset::ticker{"AAPL"}, asset::instrument{"Stock"})
337+
.build_sql();
316338
expect(sql ==
317339
"INSERT INTO asset (id, exchange_id, ticker, instrument, name, sector, currency, created_date, "
318340
"last_updated_date) "

tests/unit/test_dql.cpp

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,9 @@ suite<"DQL"> dql_suite = [] {
102102
.order_by<product::id>()
103103
.limit(3)
104104
.build_sql();
105-
expect(sql ==
106-
"SELECT id, category_id, sku, type, name, tag, unit, created_at, last_updated_at FROM product ORDER BY id ASC LIMIT 3"s)
105+
expect(
106+
sql ==
107+
"SELECT id, category_id, sku, type, name, tag, unit, created_at, last_updated_at FROM product ORDER BY id ASC LIMIT 3"s)
107108
<< sql;
108109
};
109110

@@ -330,6 +331,26 @@ suite<"DQL Extended Clauses"> dql_extended_clauses_suite = [] {
330331
<< sql;
331332
};
332333

334+
"for_update - appends FOR UPDATE lock clause"_test = [] {
335+
auto const sql = select<product::id>().from<product>().where(equal<product::id>(1u)).for_update().build_sql();
336+
expect(sql == "SELECT id FROM product WHERE id = 1 FOR UPDATE"s) << sql;
337+
};
338+
339+
"for_share - appends FOR SHARE lock clause"_test = [] {
340+
auto const sql = select<product::id>().from<product>().order_by<product::id>().for_share().build_sql();
341+
expect(sql == "SELECT id FROM product ORDER BY id ASC FOR SHARE"s) << sql;
342+
};
343+
344+
"lock_in_share_mode - appends LOCK IN SHARE MODE lock clause"_test = [] {
345+
auto const sql = select<product::id>().from<product>().limit(5).lock_in_share_mode().build_sql();
346+
expect(sql == "SELECT id FROM product LIMIT 5 LOCK IN SHARE MODE"s) << sql;
347+
};
348+
349+
"lock clause precedence - last lock method wins"_test = [] {
350+
auto const sql = select<product::id>().from<product>().for_update().for_share().build_sql();
351+
expect(sql == "SELECT id FROM product FOR SHARE"s) << sql;
352+
};
353+
333354
"union_ - combines two queries with UNION"_test = [] {
334355
auto const q1 = select<product::id>().from<product>().where(equal<product::type>("gadget"));
335356
auto const q2 = select<product::id>().from<product>().where(equal<product::type>("widget"));

0 commit comments

Comments
 (0)