Skip to content

Commit 1900117

Browse files
author
Pierre-Luc Gagné
committed
Replace tag-based column_field with fixed_string NTTP design
1 parent 529cf5b commit 1900117

15 files changed

Lines changed: 304 additions & 357 deletions

README.md

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,34 +22,21 @@ DSMySQL provides a **header-only** library for building type-safe SQL queries an
2222

2323
using namespace ds_mysql;
2424

25-
// 1. Define your table as a C++ struct
25+
// 1. Define your table as a C++ struct.
26+
// Each field is a type alias for column_field<"name", ValueType>.
27+
// The string literal is the SQL column name — no tag structs needed.
2628
struct product {
27-
struct id_tag {};
28-
struct sku_tag {};
29-
struct name_tag {};
30-
struct price_tag {};
31-
32-
using id = column_field<id_tag, uint32_t>;
33-
using sku = column_field<sku_tag, varchar_field<64>>;
34-
using name = column_field<name_tag, varchar_field<255>>;
35-
using price = column_field<price_tag, double>;
29+
using id = column_field<"id", uint32_t>;
30+
using sku = column_field<"sku", varchar_field<64>>;
31+
using name = column_field<"name", varchar_field<255>>;
32+
using price = column_field<"price", double>;
3633

3734
id id_;
3835
sku sku_;
3936
name name_;
4037
price price_;
4138
};
4239

43-
// Alternatively, use the COLUMN_FIELD macro to reduce boilerplate:
44-
struct product_v2 {
45-
COLUMN_FIELD(id, uint32_t);
46-
COLUMN_FIELD(sku, varchar_field<64>);
47-
COLUMN_FIELD(name, varchar_field<255>);
48-
COLUMN_FIELD(price, double);
49-
};
50-
// Each COLUMN_FIELD(tag, type) expands to: a tag struct, a type alias, and a member variable.
51-
// The two definitions are equivalent and produce the same SQL schema.
52-
5340
// 2. Connect and query
5441
auto db = mysql_database::connect(mysql_config{
5542
host_name{"127.0.0.1"},

examples/basic_query.cpp

Lines changed: 16 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -35,36 +35,28 @@ constexpr unsigned int port = 3306;
3535
// ===================================================================
3636
// Define a typed table struct.
3737
//
38-
// Each field is a type alias for column_field<Tag, ValueType>.
39-
// The tag name drives the SQL column name: id_tag → "id".
38+
// Each field is a type alias for column_field<"name", ValueType>.
39+
// The string literal is the SQL column name.
4040
// Member variables use a trailing underscore to avoid shadowing the
4141
// column-descriptor aliases.
4242
// ===================================================================
4343

4444
struct product {
45-
struct id_tag {};
46-
struct sku_tag {};
47-
struct name_tag {};
48-
struct price_tag {};
49-
struct stock_tag {};
50-
struct description_tag {};
51-
struct created_at_tag {};
52-
53-
using id = ds_mysql::column_field<id_tag, uint32_t>;
54-
using sku = ds_mysql::column_field<sku_tag, ds_mysql::varchar_field<64>>;
55-
using name = ds_mysql::column_field<name_tag, ds_mysql::varchar_field<255>>;
56-
using price = ds_mysql::column_field<price_tag, double>;
57-
using stock = ds_mysql::column_field<stock_tag, uint32_t>;
58-
using description = ds_mysql::column_field<description_tag, std::optional<ds_mysql::varchar_field<512>>>;
59-
using created_at = ds_mysql::column_field<created_at_tag, ds_mysql::sql_datetime>;
60-
61-
id id_;
62-
sku sku_;
63-
name name_;
64-
price price_;
65-
stock stock_;
45+
using id = ds_mysql::column_field<"id", uint32_t>;
46+
using sku = ds_mysql::column_field<"sku", ds_mysql::varchar_field<64>>;
47+
using name = ds_mysql::column_field<"name", ds_mysql::varchar_field<255>>;
48+
using price = ds_mysql::column_field<"price", double>;
49+
using stock = ds_mysql::column_field<"stock", uint32_t>;
50+
using description = ds_mysql::column_field<"description", std::optional<ds_mysql::varchar_field<512>>>;
51+
using created_at = ds_mysql::column_field<"created_at", ds_mysql::sql_datetime>;
52+
53+
id id_;
54+
sku sku_;
55+
name name_;
56+
price price_;
57+
stock stock_;
6658
description description_;
67-
created_at created_at_;
59+
created_at created_at_;
6860
};
6961

7062
// ===================================================================

examples/schema_generation.cpp

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,11 @@
2121
// ===================================================================
2222

2323
struct user {
24-
struct id_tag {};
25-
struct username_tag {};
26-
struct email_tag {};
27-
struct is_active_tag {};
28-
struct created_at_tag {};
29-
30-
using id = ds_mysql::column_field<id_tag, uint32_t>;
31-
using username = ds_mysql::column_field<username_tag, ds_mysql::varchar_field<64>>;
32-
using email = ds_mysql::column_field<email_tag, ds_mysql::varchar_field<255>>;
33-
using is_active = ds_mysql::column_field<is_active_tag, bool>;
34-
using created_at = ds_mysql::column_field<created_at_tag, ds_mysql::sql_datetime>;
24+
using id = ds_mysql::column_field<"id", uint32_t>;
25+
using username = ds_mysql::column_field<"username", ds_mysql::varchar_field<64>>;
26+
using email = ds_mysql::column_field<"email", ds_mysql::varchar_field<255>>;
27+
using is_active = ds_mysql::column_field<"is_active", bool>;
28+
using created_at = ds_mysql::column_field<"created_at", ds_mysql::sql_datetime>;
3529

3630
id id_;
3731
username username_;
@@ -45,12 +39,12 @@ struct user {
4539
// ===================================================================
4640

4741
struct order_row {
48-
struct order_id_tag {}; using id = ds_mysql::column_field<order_id_tag, uint32_t>;
49-
struct user_id_tag {}; using user_id = ds_mysql::column_field<user_id_tag, uint32_t>;
50-
struct amount_tag {}; using amount = ds_mysql::column_field<amount_tag, double>;
51-
struct fee_tag {}; using fee = ds_mysql::column_field<fee_tag, std::optional<double>>;
52-
struct status_tag {}; using status = ds_mysql::column_field<status_tag, ds_mysql::varchar_field<32>>;
53-
struct order_created_at_tag {}; using created_at = ds_mysql::column_field<order_created_at_tag, ds_mysql::sql_datetime>;
42+
using id = ds_mysql::column_field<"order_id", uint32_t>;
43+
using user_id = ds_mysql::column_field<"user_id", uint32_t>;
44+
using amount = ds_mysql::column_field<"amount", double>;
45+
using fee = ds_mysql::column_field<"fee", std::optional<double>>;
46+
using status = ds_mysql::column_field<"status", ds_mysql::varchar_field<32>>;
47+
using created_at = ds_mysql::column_field<"order_created_at", ds_mysql::sql_datetime>;
5448

5549
id id_;
5650
user_id user_id_;

lib/include/ds_mysql/column_field.hpp

Lines changed: 15 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
#include <type_traits>
99
#include <utility>
1010

11-
#include "ds_mysql/name_reflection.hpp"
11+
#include "ds_mysql/fixed_string.hpp"
1212
#include "ds_mysql/sql_temporal.hpp"
1313
#include "ds_mysql/varchar_field.hpp"
1414

@@ -19,10 +19,10 @@ namespace ds_mysql {
1919
*/
2020
struct column_field_tag {};
2121

22-
// column_field<Tag, T> — the only user-facing form.
23-
// Tag determines the SQL column name (via tag_to_column_name); T is the stored value type.
24-
// using id = column_field<struct id_tag, uint32_t>;
25-
template <typename Tag, typename T>
22+
// column_field<Name, T> — the only user-facing form.
23+
// Name is the SQL column name as a string literal; T is the stored value type.
24+
// using id = column_field<"id", uint32_t>;
25+
template <fixed_string Name, typename T>
2626
struct column_field;
2727

2828
// ===================================================================
@@ -451,61 +451,36 @@ struct base<std::optional<sql_timestamp>> : column_field_tag {
451451
} // namespace column_field_detail
452452

453453
// ===================================================================
454-
// column_field<Tag, T> — the only user-facing form
454+
// column_field<Name, T> — the only user-facing form
455455
// ===================================================================
456456

457457
/**
458-
* column_field<Tag, T> — tagged column descriptor.
458+
* column_field<Name, T> — named column descriptor.
459459
*
460-
* Tag determines the SQL column name (stripped of any trailing "_tag" suffix).
460+
* Name is the SQL column name embedded directly as a string literal.
461461
* T is the stored value type. All constructors and operators are inherited
462462
* from the internal base type.
463463
*
464-
* using id = column_field<struct id_tag, uint32_t>;
465-
* using ticker = column_field<struct ticker_tag, varchar_field<32>>;
466-
* using sector = column_field<struct sector_tag, std::optional<varchar_field<64>>>;
464+
* using id = column_field<"id", uint32_t>;
465+
* using ticker = column_field<"ticker", varchar_field<32>>;
466+
* using sector = column_field<"sector", std::optional<varchar_field<64>>>;
467467
*
468468
* id id_;
469469
* ticker ticker_;
470470
* sector sector_;
471471
*
472472
* SQL column names: "id", "ticker", "sector"
473473
*/
474-
template <typename Tag, typename T>
474+
template <fixed_string Name, typename T>
475475
struct column_field : column_field_detail::base<T> {
476-
using tag_type = Tag;
477476
using column_field_detail::base<T>::base;
478477
using column_field_detail::base<T>::operator=;
479478

480479
[[nodiscard]] static constexpr std::string_view column_name() noexcept {
481-
return detail::tag_to_column_name<Tag>();
480+
return Name;
482481
}
483482
};
484483

485-
// ===================================================================
486-
// COLUMN_FIELD convenience macro
487-
// ===================================================================
488-
489-
/**
490-
* COLUMN_FIELD(tag, type) — declares a column field with a member variable.
491-
*
492-
* Expands to:
493-
* struct tag_tag {};
494-
* using tag = column_field<tag_tag, type>;
495-
* tag tag_;
496-
*
497-
* Example (inside a struct/class body):
498-
* COLUMN_FIELD(id, uint32_t)
499-
* COLUMN_FIELD(ticker, varchar_field<32>)
500-
* COLUMN_FIELD(sector, std::optional<varchar_field<64>>)
501-
*
502-
* SQL column names: "id", "ticker", "sector"
503-
*/
504-
#define COLUMN_FIELD(tag, type) \
505-
struct tag##_tag {}; \
506-
using tag = ::ds_mysql::column_field<tag##_tag, type>; \
507-
tag tag##_
508-
509484
// ===================================================================
510485
// ColumnFieldType concept
511486
// ===================================================================
@@ -514,8 +489,8 @@ struct column_field : column_field_detail::base<T> {
514489
* ColumnFieldType<T> — satisfied by any type that publicly derives from
515490
* column_field_tag and exposes a value_type alias.
516491
*
517-
* Only satisfied by the tagged form:
518-
* using id = column_field<struct id_tag, uint32_t>;
492+
* Only satisfied by the named form:
493+
* using id = column_field<"id", uint32_t>;
519494
*/
520495
template <typename T>
521496
concept ColumnFieldType = std::derived_from<T, column_field_tag> && requires { typename T::value_type; };
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#pragma once
2+
3+
#include <string_view>
4+
5+
namespace ds_mysql {
6+
7+
/**
8+
* fixed_string<N> — compile-time string literal for use as a non-type template parameter.
9+
*
10+
* Satisfies C++20 structural type requirements, enabling use as an NTTP:
11+
*
12+
* template <fixed_string Name, typename T>
13+
* struct column_field { ... };
14+
*
15+
* using id = column_field<"id", uint32_t>;
16+
*
17+
* The deduction guide allows the size to be inferred from a string literal:
18+
*
19+
* fixed_string s{"hello"}; // fixed_string<6>
20+
*/
21+
template <std::size_t N>
22+
struct fixed_string {
23+
char data[N]{};
24+
25+
// NOLINTNEXTLINE(google-explicit-constructor)
26+
consteval fixed_string(char const (&str)[N]) noexcept {
27+
for (std::size_t i = 0; i < N; ++i)
28+
data[i] = str[i];
29+
}
30+
31+
[[nodiscard]] constexpr operator std::string_view() const noexcept {
32+
return {data, N - 1}; // exclude null terminator
33+
}
34+
35+
[[nodiscard]] constexpr std::string_view view() const noexcept {
36+
return {data, N - 1};
37+
}
38+
39+
template <std::size_t M>
40+
[[nodiscard]] constexpr bool operator==(fixed_string<M> const& other) const noexcept {
41+
if constexpr (N != M)
42+
return false;
43+
else {
44+
for (std::size_t i = 0; i < N; ++i)
45+
if (data[i] != other.data[i])
46+
return false;
47+
return true;
48+
}
49+
}
50+
};
51+
52+
template <std::size_t N>
53+
fixed_string(char const (&)[N]) -> fixed_string<N>;
54+
55+
} // namespace ds_mysql

0 commit comments

Comments
 (0)