Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
10ce490
Fixed handling composite primary key columns for tables without rowid
trueqbit Nov 27, 2025
0706a7b
Unit tests for insertions into tables without rowid and compound PKs
trueqbit Nov 28, 2025
64bfce4
Check up front whether type of range to insert or replace is convertible
trueqbit Nov 28, 2025
f1f81fa
Removed the period after static assert messages, according to the pre…
trueqbit Nov 28, 2025
871b134
Unit test for serializing for insert statements involving a primary key
trueqbit Nov 28, 2025
dac555b
Moved cpp guard that enables testing projected range insertions
trueqbit Nov 29, 2025
9d93093
Same compiler warning options for GNU as for Clang for unit tests
trueqbit Nov 29, 2025
df79495
Renamed functions dealing with the table primary key
trueqbit Nov 29, 2025
2534b1b
Omit only single primary key columns in insert statements
trueqbit Nov 29, 2025
d359eb4
Renamed auxiliary function testing whether a primary key contains a c…
trueqbit Nov 29, 2025
ebae237
Addressed a compiler error that occurred with msvc 141
trueqbit Nov 30, 2025
0de1671
Validate primary key requirements up front
trueqbit Nov 30, 2025
aeb72b5
Improved filter for excluding columns during serialization
trueqbit Dec 1, 2025
030d0e7
Returned the properly typed last inserted rowid
trueqbit Dec 1, 2025
dcc213f
Improved column primary key checks
trueqbit Dec 1, 2025
93a23fe
Deprecated types other than 64-bit signed integer for autoincrement PKs
trueqbit Dec 1, 2025
9e65e40
Ordinary insert statement skips primary key columns with a default value
trueqbit Dec 2, 2025
9eaf678
Fixed pre-C++23 if constexpr expression
trueqbit Dec 4, 2025
4791366
Corrected CPP guards for some SQLite features
trueqbit Dec 4, 2025
ae205d2
Taking into account that the rowid key might represented by a custom …
trueqbit Dec 5, 2025
0886bd5
Deprecated types other than 64-bit signed integer for single-column PKs
trueqbit Dec 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dev/column_expression.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ namespace sqlite_orm {
// No CTE for object expression.
template<class DBOs, class E>
struct column_expression_type<DBOs, object_t<E>, void> {
static_assert(polyfill::always_false_v<E>, "Selecting an object in a subselect is not allowed.");
static_assert(polyfill::always_false_v<E>, "Selecting an object in a subselect is not allowed");
};

/**
Expand Down
2 changes: 1 addition & 1 deletion dev/column_result.h
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ namespace sqlite_orm {
using colalias_index =
find_tuple_type<typename cte_mapper_type::final_colrefs_tuple, alias_holder<ColAlias>>;
static_assert(colalias_index::value < std::tuple_size_v<typename cte_mapper_type::final_colrefs_tuple>,
"No such column mapped into the CTE.");
"No such column mapped into the CTE");
using type = std::tuple_element_t<colalias_index::value, typename cte_mapper_type::fields_type>;
};
#endif
Expand Down
58 changes: 45 additions & 13 deletions dev/constraints.h
Original file line number Diff line number Diff line change
Expand Up @@ -502,20 +502,52 @@ namespace sqlite_orm {
template<class T>
struct is_generated_always : polyfill::bool_constant<is_generated_always_v<T>> {};

/**
* PRIMARY KEY INSERTABLE traits.
// Custom type: programmer's responsibility to garantee data integrity in the value range of a 64-bit signed integer
template<class F, class SFINAE = void>
inline constexpr bool is_rowid_alias_capable_v = std::is_base_of<integer_printer, type_printer<F>>::value;

// For integer types: further checks
template<class F>
inline constexpr bool is_rowid_alias_capable_v<
F,
std::enable_if_t<std::is_integral<F>::value &&
(sizeof(F) == sizeof(sqlite_int64) &&
std::is_signed<F>::value == std::is_signed<sqlite_int64>::value)>> = true;

// [Deprecation notice] For integral types other than 64-bit signed integer, AUTOINCREMENT is deprecated on PRIMARY KEY columns
// and will be turned into a static_assert failure in v1.11
template<class F>
[[deprecated(R"(Use a 64-bit signed integer for the "rowid" key alias)")]]
inline constexpr bool is_rowid_alias_capable_v<
F,
std::enable_if_t<std::is_integral<F>::value &&
(sizeof(F) != sizeof(sqlite_int64) ||
std::is_signed<F>::value != std::is_signed<sqlite_int64>::value)>> = true;

// For non-integer types: static_assert failure
template<class F>
inline constexpr bool
is_rowid_alias_capable_v<F, std::enable_if_t<!std::is_base_of<integer_printer, type_printer<F>>::value>> =
false;

/**
* COLUMN PRIMARY KEY INSERTABLE traits.
*
* A column primary key is considered implicitly insertable if:
* - it is an INTEGER PRIMARY KEY (and thus an alias for the "rowid" key),
* - or has a default value.
*
* In terms of C++ types, this means that the field type must be capable of representing a 64-bit signed integer,
* or the column is declared with a DEFAULT constraint.
*
* Implementation note: using a struct template in favor of a template alias so that the stack leading to a deprecation message is shorter.
*/
template<typename Column>
struct is_primary_key_insertable
: polyfill::disjunction<
mpl::invoke_t<mpl::disjunction<check_if_has_template<primary_key_with_autoincrement>,
check_if_has_template<default_t>>,
constraints_type_t<Column>>,
std::is_base_of<integer_printer, type_printer<field_type_t<Column>>>> {

static_assert(tuple_has<constraints_type_t<Column>, is_primary_key>::value,
"an unexpected type was passed");
};
struct is_pkcol_implicitly_insertable
: mpl::invoke_t<
mpl::disjunction<mpl::always<polyfill::bool_constant<is_rowid_alias_capable_v<field_type_t<Column>>>>,
check_if_has_template<default_t>>,
constraints_type_t<Column>> {};

template<class T>
using is_column_constraint = mpl::invoke_t<mpl::disjunction<check_if<std::is_base_of, primary_key_t<>>,
Expand Down Expand Up @@ -595,7 +627,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm {
return {{}};
}

#if SQLITE_VERSION_NUMBER >= 3009000
#if SQLITE_VERSION_NUMBER >= 3009000 || defined(SQLITE_ORM_ENABLE_FTS5)
/**
* UNINDEXED column constraint builder function. Used in FTS virtual tables.
*
Expand Down
2 changes: 2 additions & 0 deletions dev/core_functions.h
Original file line number Diff line number Diff line change
Expand Up @@ -2158,6 +2158,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm {
}
}

#if SQLITE_VERSION_NUMBER >= 3009000 || defined(SQLITE_ORM_ENABLE_FTS5)
struct fts5;

/**
Expand Down Expand Up @@ -2222,4 +2223,5 @@ SQLITE_ORM_EXPORT namespace sqlite_orm {
return {std::move(x), std::move(y), std::move(z)};
}
#endif
#endif
}
4 changes: 2 additions & 2 deletions dev/cte_column_names_collector.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,13 @@ namespace sqlite_orm {
// No CTE for object expressions.
template<class Object>
struct cte_column_names_collector<Object, match_specialization_of<Object, object_t>> {
static_assert(polyfill::always_false_v<Object>, "Selecting an object in a subselect is not allowed.");
static_assert(polyfill::always_false_v<Object>, "Selecting an object in a subselect is not allowed");
};

// No CTE for object expressions.
template<class Object>
struct cte_column_names_collector<Object, match_if<is_struct, Object>> {
static_assert(polyfill::always_false_v<Object>, "Repacking columns in a subselect is not allowed.");
static_assert(polyfill::always_false_v<Object>, "Repacking columns in a subselect is not allowed");
};

template<class Columns>
Expand Down
2 changes: 1 addition & 1 deletion dev/cte_storage.h
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ namespace sqlite_orm {
(!is_builtin_numeric_column_alias_v<
alias_holder_type_or_none_t<std::tuple_element_t<Idx, ExplicitColRefs>>> &&
...),
"Numeric column aliases are reserved for referencing columns locally within a single CTE.");
"Numeric column aliases are reserved for referencing columns locally within a single CTE");

return std::tuple{
determine_cte_colref(dbObjects, get<Idx>(subselectColRefs), get<Idx>(explicitColRefs))...};
Expand Down
3 changes: 0 additions & 3 deletions dev/error_code.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ SQLITE_ORM_EXPORT namespace sqlite_orm {
too_many_tables_specified,
incorrect_set_fields_specified,
column_not_found,
table_has_no_primary_key_column,
cannot_start_a_transaction_within_a_transaction,
no_active_transaction,
incorrect_journal_mode_string,
Expand Down Expand Up @@ -75,8 +74,6 @@ SQLITE_ORM_EXPORT namespace sqlite_orm {
return "Incorrect set fields specified";
case orm_error_code::column_not_found:
return "Column not found";
case orm_error_code::table_has_no_primary_key_column:
return "Table has no primary key column";
case orm_error_code::cannot_start_a_transaction_within_a_transaction:
return "Cannot start a transaction within a transaction";
case orm_error_code::no_active_transaction:
Expand Down
6 changes: 3 additions & 3 deletions dev/functional/mpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ namespace sqlite_orm {
template<class Pack, class ProjectQ>
struct invoke_this_fn {
static_assert(polyfill::always_false_v<Pack>,
"`finds` must be invoked with a type list as first argument.");
"`finds` must be invoked with a type list as first argument");
};

template<template<class...> class Pack, class... T, class ProjectQ>
Expand All @@ -384,7 +384,7 @@ namespace sqlite_orm {
template<class Pack, class ProjectQ>
struct invoke_this_fn {
static_assert(polyfill::always_false_v<Pack>,
"`counts` must be invoked with a type list as first argument.");
"`counts` must be invoked with a type list as first argument");
};

template<template<class...> class Pack, class... T, class ProjectQ>
Expand All @@ -411,7 +411,7 @@ namespace sqlite_orm {
template<class Pack, class ProjectQ>
struct invoke_this_fn {
static_assert(polyfill::always_false_v<Pack>,
"`contains` must be invoked with a type list as first argument.");
"`contains` must be invoked with a type list as first argument");
};

template<template<class...> class Pack, class... T, class ProjectQ>
Expand Down
8 changes: 4 additions & 4 deletions dev/implementations/table_definitions.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,17 @@ namespace sqlite_orm {
column.template is<is_primary_key>(),
column.template is<is_generated_always>());
});
auto compositeKeyColumnNames = this->composite_key_columns_names();
const auto tableKeyColumnNames = this->table_key_columns_names();
#if defined(SQLITE_ORM_INITSTMT_RANGE_BASED_FOR_SUPPORTED) && defined(SQLITE_ORM_CPP20_RANGES_SUPPORTED)
for (int n = 1; const std::string& columnName: compositeKeyColumnNames) {
for (int n = 1; const std::string& columnName: tableKeyColumnNames) {
if (auto it = std::ranges::find(res, columnName, &table_xinfo::name); it != res.end()) {
it->pk = n;
}
++n;
}
#else
for (size_t i = 0; i < compositeKeyColumnNames.size(); ++i) {
const std::string& columnName = compositeKeyColumnNames[i];
for (size_t i = 0; i < tableKeyColumnNames.size(); ++i) {
const std::string& columnName = tableKeyColumnNames[i];
auto it = std::find_if(res.begin(), res.end(), [&columnName](const table_xinfo& ti) {
return ti.name == columnName;
});
Expand Down
14 changes: 13 additions & 1 deletion dev/prepared_statement.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#ifndef SQLITE_ORM_IMPORT_STD_MODULE
#include <memory> // std::unique_ptr
#include <string> // std::string
#include <type_traits> // std::integral_constant, std::declval
#include <type_traits> // std::integral_constant, std::declval, std::is_convertible
#include <utility> // std::move, std::forward, std::exchange, std::pair
#include <tuple> // std::tuple
#endif
Expand Down Expand Up @@ -582,6 +582,12 @@ SQLITE_ORM_EXPORT namespace sqlite_orm {
*/
template<class O, class It, class Projection = polyfill::identity>
internal::replace_range_t<It, Projection, O> replace_range(It from, It to, Projection project = {}) {
// validate up front that projected type is convertible to mapped object type, avoiding hard to read error messages later;
// note: we use `is_convertible` instead of `is_invocable_r` because we do not create dangling references in `storage_t<>::execute()`
using projected_type = decltype(polyfill::invoke(std::declval<Projection>(), *std::declval<It>()));
static_assert(std::is_convertible<projected_type, const O&>::value,
"Projected type must be convertible to mapped object type");

return {{std::move(from), std::move(to)}, std::move(project)};
}

Expand Down Expand Up @@ -616,6 +622,12 @@ SQLITE_ORM_EXPORT namespace sqlite_orm {
*/
template<class O, class It, class Projection = polyfill::identity>
internal::insert_range_t<It, Projection, O> insert_range(It from, It to, Projection project = {}) {
// validate up front that projected type is convertible to mapped object type, avoiding hard to read error messages later;
// note: we use `is_convertible` instead of `is_invocable_r` because we do not create dangling references in `storage_t<>::execute()`
using projected_type = decltype(polyfill::invoke(std::declval<Projection>(), *std::declval<It>()));
static_assert(std::is_convertible<projected_type, const O&>::value,
"Projected type must be convertible to mapped object type");

return {{std::move(from), std::move(to)}, std::move(project)};
}

Expand Down
71 changes: 63 additions & 8 deletions dev/schema/column.h
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
#pragma once

#include <sqlite3.h> // sqlite_int64
#ifndef SQLITE_ORM_IMPORT_STD_MODULE
#include <tuple> // std::tuple
#include <string> // std::string
#include <memory> // std::unique_ptr
#include <type_traits> // std::is_same, std::is_member_object_pointer
#include <type_traits> // std::enable_if, std::is_same, std::is_member_object_pointer, std::is_signed
#include <utility> // std::move
#endif

#include "../functional/cxx_type_traits_polyfill.h"
#include "../tuple_helper/tuple_traits.h"
#include "../tuple_helper/tuple_filter.h"
#include "../type_traits.h"
#include "../member_traits/member_traits.h"
#include "../type_traits.h"
#include "../type_is_nullable.h"
#include "../constraints.h"

Expand Down Expand Up @@ -84,6 +85,14 @@ namespace sqlite_orm {
return tuple_has<constraints_type, Trait>::value;
}

/**
* Checks whether contraints contain specified class template.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: con[s]traints

*/
template<template<class...> class Primary>
constexpr static bool is_template() {
return tuple_has_template<constraints_type, Primary>::value;
}

/**
* Simplified interface for `DEFAULT` constraint
* @return string representation of default value if it exists otherwise nullptr
Expand Down Expand Up @@ -165,19 +174,65 @@ namespace sqlite_orm {
field_type_t,
filter_tuple_sequence_t<Elements, mpl::disjunction_fn<is_column, is_hidden_column>::template fn>>;

#if SQLITE_VERSION_NUMBER >= 3031000
// Custom type: programmer's responsibility to garantee data integrity in the value range of a 64-bit signed integer
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: g[u]arantee

template<class F, class SFINAE = void>
struct check_pkcol {
static constexpr void validate_column_primary_key_with_autoincrement() {}
};

// For integer types: further checks
template<class F>
struct check_pkcol<F, std::enable_if_t<std::is_integral<F>::value>> {
// For 64-bit signed integer type: valid
template<class X = F,
std::enable_if_t<sizeof(X) == sizeof(sqlite_int64) &&
std::is_signed<X>::value == std::is_signed<sqlite_int64>::value,
bool> = true>
static constexpr void validate_column_primary_key_with_autoincrement() {}

// [Deprecation notice] For integral types other than 64-bit signed integer, AUTOINCREMENT is deprecated on PRIMARY KEY columns
// and will be turned into a static_assert failure in v1.11
template<class X = F,
std::enable_if_t<sizeof(X) != sizeof(sqlite_int64) ||
std::is_signed<X>::value != std::is_signed<sqlite_int64>::value,
bool> = true>
[[deprecated(
R"(Use a 64-bit signed integer for AUTOINCREMENT on an INTEGER PRIMARY KEY as an alias for the "rowid" key)")]] static constexpr void
validate_column_primary_key_with_autoincrement() {}
};

// For non-integer types: static_assert failure
template<class F>
struct check_pkcol<F, std::enable_if_t<!std::is_base_of<integer_printer, type_printer<F>>::value>> {
static constexpr void validate_column_primary_key_with_autoincrement() {
static_assert(
polyfill::always_false_v<F>,
R"(AUTOINCREMENT is only allowed on an INTEGER PRIMARY KEY as an alias for the "rowid" key)");
}
};

template<class G, class... Op>
constexpr void validate_column_definition() {
using constraints_type = std::tuple<Op...>;

static_assert(polyfill::conjunction_v<is_column_constraint<Op>...>, "Incorrect column constraints");

if constexpr (tuple_has_template<constraints_type, primary_key_with_autoincrement>::value) {
check_pkcol<member_field_type_t<G>>::validate_column_primary_key_with_autoincrement();
}
}

/**
* Factory function for a column definition from a member object pointer for hidden virtual table columns.
*/
template<class M, class... Op, satisfies<std::is_member_object_pointer, M> = true>
hidden_column<M, empty_setter, Op...> make_hidden_column(std::string name, M memberPointer, Op... constraints) {
static_assert(polyfill::conjunction_v<is_column_constraint<Op>...>, "Incorrect constraints pack");
static_assert(polyfill::conjunction_v<is_column_constraint<Op>...>, "Incorrect column constraints");

// attention: do not use `std::make_tuple()` for constructing the tuple member `[[no_unique_address]] column_constraints::constraints`,
// as this will lead to UB with Clang on MinGW!
return {std::move(name), memberPointer, {}, std::tuple<Op...>{std::move(constraints)...}};
}
#endif
}
}

Expand All @@ -188,7 +243,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm {
template<class M, class... Op, internal::satisfies<std::is_member_object_pointer, M> = true>
internal::column_t<M, internal::empty_setter, Op...>
make_column(std::string name, M memberPointer, Op... constraints) {
static_assert(polyfill::conjunction_v<internal::is_column_constraint<Op>...>, "Incorrect constraints pack");
internal::validate_column_definition<M, Op...>();

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

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

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