Skip to content

Commit dcc213f

Browse files
committed
Improved column primary key checks
* Check column definition up front when making the column. * Renamed internal traits. * Removed unit test file `is_column_with_insertable_primary_key.cpp`. It became superfluous during the course of development in the past years. Column constraints are checked elsewhere already, and it is now only a duplicate of `is_primary_key_insertable.cpp`.
1 parent 030d0e7 commit dcc213f

File tree

7 files changed

+103
-103
lines changed

7 files changed

+103
-103
lines changed

dev/constraints.h

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -502,19 +502,26 @@ namespace sqlite_orm {
502502
template<class T>
503503
struct is_generated_always : polyfill::bool_constant<is_generated_always_v<T>> {};
504504

505-
/**
506-
* PRIMARY KEY INSERTABLE traits.
505+
/**
506+
* COLUMN PRIMARY KEY INSERTABLE traits.
507+
*
508+
* A column primary key is considered implicitly insertable if:
509+
* - it is an INTEGER PRIMARY KEY (and thus an alias for the "rowid" key),
510+
* - or has a default value.
511+
*
512+
* Note that the restrictions on an alias for the "rowid" key are actually more narrow:
513+
* it must be of 64-bit signed integer type (not any other integral arithmetic type),
514+
* however due to sqlite_orm's current type mapping this is not enforced here.
507515
*/
508516
template<typename Column>
509-
struct is_primary_key_insertable
510-
: polyfill::disjunction<
511-
mpl::invoke_t<mpl::disjunction<check_if_has_template<primary_key_with_autoincrement>,
512-
check_if_has_template<default_t>>,
513-
constraints_type_t<Column>>,
514-
std::is_base_of<integer_printer, type_printer<field_type_t<Column>>>> {
515-
516-
static_assert(tuple_has<constraints_type_t<Column>, is_primary_key>::value,
517-
"an unexpected type was passed");
517+
struct is_pkcol_implicitly_insertable
518+
: mpl::invoke_t<
519+
mpl::disjunction<mpl::always<std::is_base_of<integer_printer, type_printer<field_type_t<Column>>>>,
520+
check_if_has_template<default_t>>,
521+
constraints_type_t<Column>> {
522+
523+
// internal programming error: column primary key required
524+
static_assert(tuple_has<constraints_type_t<Column>, is_primary_key>::value);
518525
};
519526

520527
template<class T>

dev/schema/column.h

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
#include "../functional/cxx_type_traits_polyfill.h"
1212
#include "../tuple_helper/tuple_traits.h"
1313
#include "../tuple_helper/tuple_filter.h"
14-
#include "../type_traits.h"
1514
#include "../member_traits/member_traits.h"
15+
#include "../type_traits.h"
1616
#include "../type_is_nullable.h"
1717
#include "../constraints.h"
1818

@@ -165,13 +165,32 @@ namespace sqlite_orm {
165165
field_type_t,
166166
filter_tuple_sequence_t<Elements, mpl::disjunction_fn<is_column, is_hidden_column>::template fn>>;
167167

168+
template<class G, class... Op>
169+
constexpr void validate_column_definition() {
170+
using constraints_type = std::tuple<Op...>;
171+
172+
static_assert(polyfill::conjunction_v<is_column_constraint<Op>...>, "Incorrect column constraints");
173+
174+
if constexpr (tuple_has<constraints_type, is_primary_key>::value) {
175+
using field_type = member_field_type_t<G>;
176+
using is_pkcol_correct = mpl::invoke_t<
177+
mpl::disjunction<mpl::always<std::is_base_of<integer_printer, type_printer<field_type>>>,
178+
mpl::not_<check_if_has_template<primary_key_with_autoincrement>>>,
179+
constraints_type>;
180+
181+
static_assert(
182+
is_pkcol_correct::value,
183+
R"(AUTOINCREMENT is only allowed on an INTEGER PRIMARY KEY as an alias for the "rowid" key)");
184+
}
185+
}
186+
168187
#if SQLITE_VERSION_NUMBER >= 3031000
169188
/**
170189
* Factory function for a column definition from a member object pointer for hidden virtual table columns.
171190
*/
172191
template<class M, class... Op, satisfies<std::is_member_object_pointer, M> = true>
173192
hidden_column<M, empty_setter, Op...> make_hidden_column(std::string name, M memberPointer, Op... constraints) {
174-
static_assert(polyfill::conjunction_v<is_column_constraint<Op>...>, "Incorrect constraints pack");
193+
static_assert(polyfill::conjunction_v<is_column_constraint<Op>...>, "Incorrect column constraints");
175194

176195
// attention: do not use `std::make_tuple()` for constructing the tuple member `[[no_unique_address]] column_constraints::constraints`,
177196
// as this will lead to UB with Clang on MinGW!
@@ -188,7 +207,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm {
188207
template<class M, class... Op, internal::satisfies<std::is_member_object_pointer, M> = true>
189208
internal::column_t<M, internal::empty_setter, Op...>
190209
make_column(std::string name, M memberPointer, Op... constraints) {
191-
static_assert(polyfill::conjunction_v<internal::is_column_constraint<Op>...>, "Incorrect constraints pack");
210+
internal::validate_column_definition<M, Op...>();
192211

193212
// attention: do not use `std::make_tuple()` for constructing the tuple member `[[no_unique_address]] column_constraints::constraints`,
194213
// as this will lead to UB with Clang on MinGW!
@@ -206,7 +225,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm {
206225
internal::column_t<G, S, Op...> make_column(std::string name, S setter, G getter, Op... constraints) {
207226
static_assert(std::is_same<internal::setter_field_type_t<S>, internal::getter_field_type_t<G>>::value,
208227
"Getter and setter must get and set same data type");
209-
static_assert(polyfill::conjunction_v<internal::is_column_constraint<Op>...>, "Incorrect constraints pack");
228+
internal::validate_column_definition<G, Op...>();
210229

211230
// attention: do not use `std::make_tuple()` for constructing the tuple member `[[no_unique_address]] column_constraints::constraints`,
212231
// as this will lead to UB with Clang on MinGW!
@@ -224,7 +243,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm {
224243
internal::column_t<G, S, Op...> make_column(std::string name, G getter, S setter, Op... constraints) {
225244
static_assert(std::is_same<internal::setter_field_type_t<S>, internal::getter_field_type_t<G>>::value,
226245
"Getter and setter must get and set same data type");
227-
static_assert(polyfill::conjunction_v<internal::is_column_constraint<Op>...>, "Incorrect constraints pack");
246+
internal::validate_column_definition<G, Op...>();
228247

229248
// attention: do not use `std::make_tuple()` for constructing the tuple member `[[no_unique_address]] column_constraints::constraints`,
230249
// as this will lead to UB with Clang on MinGW!

dev/schema/table_base.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -210,9 +210,7 @@ namespace sqlite_orm::internal {
210210
definition.visit_table_primary_key([&column, &res](auto& primaryKey) {
211211
// note: use `decltype(primaryKey)` instead of `decltype(primaryKey.columns)` otherwise msvc 141 chokes on the `if constexpr` below
212212
using colrefs_tuple = columns_tuple_t<polyfill::remove_cvref_t<decltype(primaryKey)>>;
213-
if constexpr (std::tuple_size<colrefs_tuple>::value != 1) {
214-
return;
215-
} else {
213+
if constexpr (std::tuple_size<colrefs_tuple>::value == 1) {
216214
auto& memberPointer = std::get<0>(primaryKey.columns);
217215
if (compare_fields(memberPointer, column.member_pointer) ||
218216
compare_fields(memberPointer, column.setter)) {

dev/storage.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,12 +255,13 @@ namespace sqlite_orm {
255255
using elements_type = elements_type_t<Table>;
256256
using pkcol_index_sequence = col_index_sequence_with<elements_type, is_primary_key>;
257257
static_assert(
258-
count_filtered_tuple<elements_type, is_primary_key_insertable, pkcol_index_sequence>::value <= 1,
258+
count_filtered_tuple<elements_type, is_pkcol_implicitly_insertable, pkcol_index_sequence>::value <=
259+
1,
259260
"Attempting to execute 'insert' request into an noninsertable table was detected. "
260261
"Insertable table cannot contain > 1 primary keys. Please use 'replace' instead of "
261262
"'insert', or you can use 'insert' with explicit column listing.");
262263
static_assert(count_filtered_tuple<elements_type,
263-
check_if_not<is_primary_key_insertable>::template fn,
264+
check_if_not<is_pkcol_implicitly_insertable>::template fn,
264265
pkcol_index_sequence>::value == 0,
265266
"Attempting to execute 'insert' request into an noninsertable table was detected. "
266267
"Insertable table cannot contain non-standard primary keys. Please use 'replace' instead "

include/sqlite_orm/sqlite_orm.h

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3975,19 +3975,26 @@ namespace sqlite_orm {
39753975
template<class T>
39763976
struct is_generated_always : polyfill::bool_constant<is_generated_always_v<T>> {};
39773977

3978-
/**
3979-
* PRIMARY KEY INSERTABLE traits.
3978+
/**
3979+
* COLUMN PRIMARY KEY INSERTABLE traits.
3980+
*
3981+
* A column primary key is considered implicitly insertable if:
3982+
* - it is an INTEGER PRIMARY KEY (and thus an alias for the "rowid" key),
3983+
* - or has a default value.
3984+
*
3985+
* Note that the restrictions on an alias for the "rowid" key are actually more narrow:
3986+
* it must be of 64-bit signed integer type (not any other integral arithmetic type),
3987+
* however due to sqlite_orm's current type mapping this is not enforced here.
39803988
*/
39813989
template<typename Column>
3982-
struct is_primary_key_insertable
3983-
: polyfill::disjunction<
3984-
mpl::invoke_t<mpl::disjunction<check_if_has_template<primary_key_with_autoincrement>,
3985-
check_if_has_template<default_t>>,
3986-
constraints_type_t<Column>>,
3987-
std::is_base_of<integer_printer, type_printer<field_type_t<Column>>>> {
3990+
struct is_pkcol_implicitly_insertable
3991+
: mpl::invoke_t<
3992+
mpl::disjunction<mpl::always<std::is_base_of<integer_printer, type_printer<field_type_t<Column>>>>,
3993+
check_if_has_template<default_t>>,
3994+
constraints_type_t<Column>> {
39883995

3989-
static_assert(tuple_has<constraints_type_t<Column>, is_primary_key>::value,
3990-
"an unexpected type was passed");
3996+
// internal programming error: column primary key required
3997+
static_assert(tuple_has<constraints_type_t<Column>, is_primary_key>::value);
39913998
};
39923999

39934000
template<class T>
@@ -8924,10 +8931,10 @@ SQLITE_ORM_EXPORT namespace sqlite_orm {
89248931

89258932
// #include "../tuple_helper/tuple_filter.h"
89268933

8927-
// #include "../type_traits.h"
8928-
89298934
// #include "../member_traits/member_traits.h"
89308935

8936+
// #include "../type_traits.h"
8937+
89318938
// #include "../type_is_nullable.h"
89328939

89338940
#ifndef SQLITE_ORM_IMPORT_STD_MODULE
@@ -9122,13 +9129,32 @@ namespace sqlite_orm {
91229129
field_type_t,
91239130
filter_tuple_sequence_t<Elements, mpl::disjunction_fn<is_column, is_hidden_column>::template fn>>;
91249131

9132+
template<class G, class... Op>
9133+
constexpr void validate_column_definition() {
9134+
using constraints_type = std::tuple<Op...>;
9135+
9136+
static_assert(polyfill::conjunction_v<is_column_constraint<Op>...>, "Incorrect column constraints");
9137+
9138+
if constexpr (tuple_has<constraints_type, is_primary_key>::value) {
9139+
using field_type = member_field_type_t<G>;
9140+
using is_pkcol_correct = mpl::invoke_t<
9141+
mpl::disjunction<mpl::always<std::is_base_of<integer_printer, type_printer<field_type>>>,
9142+
mpl::not_<check_if_has_template<primary_key_with_autoincrement>>>,
9143+
constraints_type>;
9144+
9145+
static_assert(
9146+
is_pkcol_correct::value,
9147+
R"(AUTOINCREMENT is only allowed on an INTEGER PRIMARY KEY as an alias for the "rowid" key)");
9148+
}
9149+
}
9150+
91259151
#if SQLITE_VERSION_NUMBER >= 3031000
91269152
/**
91279153
* Factory function for a column definition from a member object pointer for hidden virtual table columns.
91289154
*/
91299155
template<class M, class... Op, satisfies<std::is_member_object_pointer, M> = true>
91309156
hidden_column<M, empty_setter, Op...> make_hidden_column(std::string name, M memberPointer, Op... constraints) {
9131-
static_assert(polyfill::conjunction_v<is_column_constraint<Op>...>, "Incorrect constraints pack");
9157+
static_assert(polyfill::conjunction_v<is_column_constraint<Op>...>, "Incorrect column constraints");
91329158

91339159
// attention: do not use `std::make_tuple()` for constructing the tuple member `[[no_unique_address]] column_constraints::constraints`,
91349160
// as this will lead to UB with Clang on MinGW!
@@ -9145,7 +9171,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm {
91459171
template<class M, class... Op, internal::satisfies<std::is_member_object_pointer, M> = true>
91469172
internal::column_t<M, internal::empty_setter, Op...>
91479173
make_column(std::string name, M memberPointer, Op... constraints) {
9148-
static_assert(polyfill::conjunction_v<internal::is_column_constraint<Op>...>, "Incorrect constraints pack");
9174+
internal::validate_column_definition<M, Op...>();
91499175

91509176
// attention: do not use `std::make_tuple()` for constructing the tuple member `[[no_unique_address]] column_constraints::constraints`,
91519177
// as this will lead to UB with Clang on MinGW!
@@ -9163,7 +9189,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm {
91639189
internal::column_t<G, S, Op...> make_column(std::string name, S setter, G getter, Op... constraints) {
91649190
static_assert(std::is_same<internal::setter_field_type_t<S>, internal::getter_field_type_t<G>>::value,
91659191
"Getter and setter must get and set same data type");
9166-
static_assert(polyfill::conjunction_v<internal::is_column_constraint<Op>...>, "Incorrect constraints pack");
9192+
internal::validate_column_definition<G, Op...>();
91679193

91689194
// attention: do not use `std::make_tuple()` for constructing the tuple member `[[no_unique_address]] column_constraints::constraints`,
91699195
// as this will lead to UB with Clang on MinGW!
@@ -9181,7 +9207,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm {
91819207
internal::column_t<G, S, Op...> make_column(std::string name, G getter, S setter, Op... constraints) {
91829208
static_assert(std::is_same<internal::setter_field_type_t<S>, internal::getter_field_type_t<G>>::value,
91839209
"Getter and setter must get and set same data type");
9184-
static_assert(polyfill::conjunction_v<internal::is_column_constraint<Op>...>, "Incorrect constraints pack");
9210+
internal::validate_column_definition<G, Op...>();
91859211

91869212
// attention: do not use `std::make_tuple()` for constructing the tuple member `[[no_unique_address]] column_constraints::constraints`,
91879213
// as this will lead to UB with Clang on MinGW!
@@ -12848,9 +12874,7 @@ namespace sqlite_orm::internal {
1284812874
definition.visit_table_primary_key([&column, &res](auto& primaryKey) {
1284912875
// note: use `decltype(primaryKey)` instead of `decltype(primaryKey.columns)` otherwise msvc 141 chokes on the `if constexpr` below
1285012876
using colrefs_tuple = columns_tuple_t<polyfill::remove_cvref_t<decltype(primaryKey)>>;
12851-
if constexpr (std::tuple_size<colrefs_tuple>::value != 1) {
12852-
return;
12853-
} else {
12877+
if constexpr (std::tuple_size<colrefs_tuple>::value == 1) {
1285412878
auto& memberPointer = std::get<0>(primaryKey.columns);
1285512879
if (compare_fields(memberPointer, column.member_pointer) ||
1285612880
compare_fields(memberPointer, column.setter)) {
@@ -24450,12 +24474,13 @@ namespace sqlite_orm {
2445024474
using elements_type = elements_type_t<Table>;
2445124475
using pkcol_index_sequence = col_index_sequence_with<elements_type, is_primary_key>;
2445224476
static_assert(
24453-
count_filtered_tuple<elements_type, is_primary_key_insertable, pkcol_index_sequence>::value <= 1,
24477+
count_filtered_tuple<elements_type, is_pkcol_implicitly_insertable, pkcol_index_sequence>::value <=
24478+
1,
2445424479
"Attempting to execute 'insert' request into an noninsertable table was detected. "
2445524480
"Insertable table cannot contain > 1 primary keys. Please use 'replace' instead of "
2445624481
"'insert', or you can use 'insert' with explicit column listing.");
2445724482
static_assert(count_filtered_tuple<elements_type,
24458-
check_if_not<is_primary_key_insertable>::template fn,
24483+
check_if_not<is_pkcol_implicitly_insertable>::template fn,
2445924484
pkcol_index_sequence>::value == 0,
2446024485
"Attempting to execute 'insert' request into an noninsertable table was detected. "
2446124486
"Insertable table cannot contain non-standard primary keys. Please use 'replace' instead "

tests/static_tests/is_column_with_insertable_primary_key.cpp

Lines changed: 0 additions & 44 deletions
This file was deleted.

0 commit comments

Comments
 (0)