Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 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
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
31 changes: 19 additions & 12 deletions dev/constraints.h
Original file line number Diff line number Diff line change
Expand Up @@ -502,19 +502,26 @@ namespace sqlite_orm {
template<class T>
struct is_generated_always : polyfill::bool_constant<is_generated_always_v<T>> {};

/**
* PRIMARY KEY INSERTABLE traits.
/**
* 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.
*
* Note that the restrictions on an alias for the "rowid" key are actually more narrow:
* it must be of 64-bit signed integer type (or be able to represent this type),
* however due to sqlite_orm's current type mapping this is not enforced here.
*/
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<std::is_base_of<integer_printer, type_printer<field_type_t<Column>>>>,
check_if_has_template<default_t>>,
constraints_type_t<Column>> {

// internal programming error: column primary key required
static_assert(tuple_has<constraints_type_t<Column>, is_primary_key>::value);
};

template<class T>
Expand Down Expand Up @@ -595,7 +602,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.
*/
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 an 64-bit signed integer
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 arithmetic 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