Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 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
c6db2ec
Addressed compilation error with msvc 141
trueqbit Dec 8, 2025
4eb9d57
More [[maybe_unused]] arguments
trueqbit Dec 8, 2025
c1a1545
Corrected typos in comments
trueqbit Dec 8, 2025
f9f7066
Allows non-64-bit integral types as rowid alias again
trueqbit Dec 8, 2025
8321ffc
Reverts `insert` return type to `int`
trueqbit Dec 8, 2025
0b6b3d5
Made documentation just a bit clearer
trueqbit Dec 8, 2025
98c379c
Simplified expression to exclude columns at compile-time
trueqbit Dec 8, 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
60 changes: 47 additions & 13 deletions dev/constraints.h
Original file line number Diff line number Diff line change
Expand Up @@ -502,20 +502,54 @@ namespace sqlite_orm {
template<class T>
struct is_generated_always : polyfill::bool_constant<is_generated_always_v<T>> {};

/**
* PRIMARY KEY INSERTABLE traits.
// Custom type:
// It is the programmer's responsibility to ensure data integrity in the value range of the custom type
// and in purview of SQLite using 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 64-bit signed integer type: capable
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;

// Design decision for integral types other than 64-bit signed integer:
// It is the programmer's responsibility to ensure data integrity in the value range of the integral type
// and in purview of SQLite using a 64-bit signed integer.
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;

// For non-integer types: unsuitable
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 +629,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
}
9 changes: 5 additions & 4 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 Expand Up @@ -158,8 +158,9 @@ namespace sqlite_orm {
};

template<typename Ctx, typename E, typename ExplicitColRefs, satisfies_is_specialization_of<E, select_t> = true>
std::vector<std::string>
collect_cte_column_names(const E& sel, const ExplicitColRefs& explicitColRefs, const Ctx& context) {
std::vector<std::string> collect_cte_column_names(const E& sel,
[[maybe_unused]] const ExplicitColRefs& explicitColRefs,
const Ctx& context) {
// 1. determine column names from subselect
std::vector<std::string> columnNames = get_cte_column_names(sel.col, context);

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
Loading