1515
1616namespace ds_mysql {
1717
18+ // ===================================================================
19+ // default_value type compatibility
20+ // ===================================================================
21+
22+ namespace column_field_detail {
23+
24+ // Strip std::optional wrapper to get the base column type.
25+ template <typename T>
26+ struct unwrap_optional_for_default {
27+ using type = T;
28+ };
29+ template <typename T>
30+ struct unwrap_optional_for_default <std::optional<T>> {
31+ using type = T;
32+ };
33+ template <typename T>
34+ using unwrap_optional_for_default_t = typename unwrap_optional_for_default<T>::type;
35+
36+ // Check whether ValueType is a valid default / on_update value for ColumnType.
37+ template <typename ColumnType, typename ValueType>
38+ consteval bool is_compatible_column_value () {
39+ using CT = unwrap_optional_for_default_t <ColumnType>;
40+
41+ if constexpr (std::is_same_v<ValueType, current_timestamp_t >) {
42+ return std::is_same_v<CT , std::chrono::system_clock::time_point> || is_datetime_type_v<CT > ||
43+ is_timestamp_type_v<CT >;
44+ } else if constexpr (std::is_integral_v<CT >) {
45+ return std::is_integral_v<ValueType>;
46+ } else if constexpr (std::is_floating_point_v<CT >) {
47+ return std::is_arithmetic_v<ValueType>;
48+ } else if constexpr (requires { typename CT ::underlying_type; }) {
49+ // Formatted numeric types (int_type, decimal_type, etc.)
50+ return is_compatible_column_value<typename CT ::underlying_type, ValueType>();
51+ } else if constexpr (is_varchar_type_v<CT > || is_text_type_v<CT > || std::is_same_v<CT , std::string>) {
52+ return is_fixed_string_v<ValueType>;
53+ } else {
54+ return false ;
55+ }
56+ }
57+
58+ // Validate that all typed attrs in the pack are type-compatible with ColumnType.
59+ template <typename ColumnType, auto ... Attrs>
60+ struct attr_type_compat {
61+ static constexpr bool value = true ;
62+ };
63+
64+ template <typename ColumnType, auto First, auto ... Rest>
65+ struct attr_type_compat <ColumnType, First, Rest...> {
66+ static constexpr bool value = [] {
67+ if constexpr (is_default_value_attr_v<decltype (First)>) {
68+ return is_compatible_column_value<ColumnType, decltype (First.val )>() &&
69+ attr_type_compat<ColumnType, Rest...>::value;
70+ } else if constexpr (is_on_update_attr_v<decltype (First)>) {
71+ return is_compatible_column_value<ColumnType, decltype (First.val )>() &&
72+ attr_type_compat<ColumnType, Rest...>::value;
73+ } else {
74+ return attr_type_compat<ColumnType, Rest...>::value;
75+ }
76+ }();
77+ };
78+
79+ } // namespace column_field_detail
80+
1881// ===================================================================
1982// column_field<Name, T> — the only user-facing form
2083// ===================================================================
@@ -23,9 +86,8 @@ namespace ds_mysql {
2386 * column_field<Name, T, Attrs...> — named column descriptor.
2487 *
2588 * Name is the SQL column name embedded directly as a string literal.
26- * T is the stored value type. Attrs are optional typed DDL modifiers
27- * (column_attr::*). All constructors and operators are inherited from the
28- * internal base type (defined in the adapter headers).
89+ * T is the stored value type. Attrs are optional instance-based DDL
90+ * modifiers (column_attr::*, fk_attr::*) passed as NTTP values.
2991 *
3092 * using id = column_field<"id", uint32_t>;
3193 * using ticker = column_field<"ticker", varchar_type<32>>;
@@ -37,31 +99,44 @@ namespace ds_mysql {
3799 *
38100 * SQL column names: "id", "ticker", "sector"
39101 */
40- template <fixed_string Name, typename T, typename ... Attrs>
102+ template <fixed_string Name, typename T, auto ... Attrs>
41103struct column_field : column_field_detail::base<T> {
42104 using column_field_detail::base<T>::base;
43105 using column_field_detail::base<T>::operator =;
44106
45- static constexpr bool ddl_primary_key = (std::same_as<Attrs, column_attr::primary_key> || ...);
46- static constexpr bool ddl_auto_increment = (std::same_as<Attrs, column_attr::auto_increment> || ...);
47- static constexpr bool ddl_unique = (std::same_as<Attrs, column_attr::unique> || ...);
48- static constexpr bool ddl_default_current_timestamp =
49- (std::same_as<Attrs, column_attr::default_current_timestamp> || ...);
50- static constexpr bool ddl_on_update_current_timestamp =
51- (std::same_as<Attrs, column_attr::on_update_current_timestamp> || ...);
52- static constexpr std::string_view ddl_comment = column_field_detail::comment_attr_value<Attrs...>::value;
53- static constexpr std::string_view ddl_collate = column_field_detail::collate_attr_value<Attrs...>::value;
107+ static constexpr bool ddl_primary_key = (std::is_same_v<decltype (Attrs), column_attr::primary_key> || ...);
108+ static constexpr bool ddl_auto_increment = (std::is_same_v<decltype (Attrs), column_attr::auto_increment> || ...);
109+ static constexpr bool ddl_unique = (std::is_same_v<decltype (Attrs), column_attr::unique> || ...);
110+
111+ static constexpr bool ddl_has_default = (column_field_detail::is_default_value_attr_v<decltype (Attrs)> || ...);
112+ static constexpr bool ddl_has_on_update = (column_field_detail::is_on_update_attr_v<decltype (Attrs)> || ...);
113+
114+ static_assert (column_field_detail::attr_type_compat<T, Attrs...>::value,
115+ " default_value / on_update type must match column value type" );
116+
117+ [[nodiscard]] static std::string ddl_default_sql () {
118+ return column_field_detail::default_value_sql<Attrs...>::get ();
119+ }
120+
121+ [[nodiscard]] static std::string ddl_on_update_sql () {
122+ return column_field_detail::on_update_sql<Attrs...>::get ();
123+ }
124+
125+ static constexpr std::string_view ddl_comment =
126+ column_field_detail::string_attr_value<column_attr::comment, Attrs...>::value;
127+ static constexpr std::string_view ddl_collate =
128+ column_field_detail::string_attr_value<column_attr::collate, Attrs...>::value;
54129
55130 // Foreign key attributes — populated only when an fk_attr::references<> is present.
56- static constexpr bool ddl_has_fk = column_field_detail::fk_references_attr <Attrs...>::has_value;
57- using ddl_fk_ref_table = typename column_field_detail::fk_references_attr <Attrs...>::ref_table;
58- using ddl_fk_ref_column = typename column_field_detail::fk_references_attr <Attrs...>::ref_column;
59- static constexpr std::string_view ddl_fk_on_delete = column_field_detail::fk_on_delete_attr <Attrs...>::value;
60- static constexpr std::string_view ddl_fk_on_update = column_field_detail::fk_on_update_attr <Attrs...>::value;
131+ static constexpr bool ddl_has_fk = column_field_detail::find_fk_ref <Attrs...>::has_value;
132+ using ddl_fk_ref_table = typename column_field_detail::find_fk_ref <Attrs...>::ref_table;
133+ using ddl_fk_ref_column = typename column_field_detail::find_fk_ref <Attrs...>::ref_column;
134+ static constexpr std::string_view ddl_fk_on_delete = column_field_detail::fk_on_delete_value <Attrs...>::value;
135+ static constexpr std::string_view ddl_fk_on_update = column_field_detail::fk_on_update_value <Attrs...>::value;
61136
62137 template <typename Attr>
63138 [[nodiscard]] static consteval bool has_attribute () noexcept {
64- return (std::same_as<Attr, Attrs > || ...);
139+ return (std::is_same_v< decltype (Attrs), Attr > || ...);
65140 }
66141
67142 [[nodiscard]] static constexpr std::string_view column_name () noexcept {
@@ -79,7 +154,7 @@ struct column_field : column_field_detail::base<T> {
79154 *
80155 * Satisfied by named column descriptors (with or without typed attributes):
81156 * using id = column_field<"id", uint32_t>;
82- * using id = column_field<"id", uint32_t, column_attr::auto_increment>;
157+ * using id = column_field<"id", uint32_t, column_attr::auto_increment{} >;
83158 */
84159template <typename T>
85160concept ColumnFieldType = std::derived_from<T, column_field_tag> && requires { typename T::value_type; };
@@ -113,7 +188,7 @@ using unwrap_column_field_t = typename unwrap_column_field<T>::type;
113188// ===================================================================
114189
115190/* *
116- * tagged_column_field<Tag, T> — tag-struct based column descriptor.
191+ * tagged_column_field<Tag, T, Attrs... > — tag-struct based column descriptor.
117192 *
118193 * The SQL column name is derived at compile time from the tag type name by
119194 * stripping a trailing "_tag" suffix. Unlike a plain type alias, this is a
@@ -144,7 +219,7 @@ using unwrap_column_field_t = typename unwrap_column_field<T>::type;
144219 *
145220 * Both produce the SQL column name "id" (derived from the tag name).
146221 */
147- template <typename Tag, typename T, typename ... Attrs>
222+ template <typename Tag, typename T, auto ... Attrs>
148223struct tagged_column_field : column_field<detail::column_name_from_tag<Tag>(), T, Attrs...> {
149224 using tag_type = Tag;
150225
@@ -160,7 +235,7 @@ struct tagged_column_field : column_field<detail::column_name_from_tag<Tag>(), T
160235
161236/* *
162237 * COLUMN_FIELD(tag, type[, attrs...]) — one-liner macro to declare a tagged
163- * column field, optionally with typed DDL attribute tags .
238+ * column field, optionally with instance-based DDL attribute values .
164239 *
165240 * Generates a nested tag struct, a type alias, and a member variable:
166241 *
@@ -172,8 +247,8 @@ struct tagged_column_field : column_field<detail::column_name_from_tag<Tag>(), T
172247 *
173248 * COLUMN_FIELD(created_at,
174249 * ::ds_mysql::timestamp_type,
175- * ::ds_mysql::column_attr::default_current_timestamp ,
176- * ::ds_mysql::column_attr::on_update_current_timestamp )
250+ * ::ds_mysql::column_attr::default_value(::ds_mysql::current_timestamp) ,
251+ * ::ds_mysql::column_attr::on_update(::ds_mysql::current_timestamp) )
177252 *
178253 * The tag struct is nested inside the enclosing class, guaranteeing
179254 * per-table type uniqueness and satisfying the compile-time membership
0 commit comments