Skip to content

Conversation

@trueqbit
Copy link
Collaborator

@trueqbit trueqbit commented Nov 27, 2025

Improvements:

  • Objects mapped to tables with composite primary keys are now insertable with an ordinary insert statement: they get inserted with the values of the specified object except for default values.
    • This improvement contains a bug fix for insert statements into tables without rowid where providing values for primary key columns is required (and expected).

Other improvements:

  • Validate base table definition when calling make_table().
    • Specifically this ensures that only one primary key definition is specified.
    • It also means that we can get rid of a runtime error when serializing a get query statement.
  • Validate column definition with respect to auto-increment specifier on a column primary key and the field type.
    • Deprecated types other than 64-bit signed integral types (except for custom integer types) - it is clearly stated in the SQLite documentation that auto-increment is only valid for 64-bit signed integer columns and if they are an alias for the implicit rowid column. With SQLite it is a hard error if you try to use a type for a column other than INTEGER.
  • Deprecated the ordinary 'insert' statement into tables with a single-column primary key whose integral types are not 64-bit signed integers (except for custom integer types). This is because they are actually not an alias for the rowid and cannot be implicitly inserted. While we could just take the values from the specified object an explicit alternative way is available with the 'replace' and 'insert with specified columns' statements.
  • For type safety reasons, a 64-bit integer is finally returned as the last inserted row ID.
    • The worst that can happen is a compiler warning or error depending on the compiler settings because of a narrowing conversion. Users should fix their code.

Fixes

  • Corrected some SQLite version test that enable version-dependent SQLite features.

The if-condition used for filtering primary key columns must be the same at run-time as at compile-time.
... to the mapped object type, avoiding hard to read error messages later.
Up until this commit, the functions were referred to as "composite PK functions", although this actually always meant "table PK functions", regardless of whether they were single or composite primary keys defined at the table level.
* Improved understanding through legibility.
For proper type safety `storage_t<>::insert()` needs to return an `sqlite3_int64` instead of an `int`.
This is the best way to correct the problem. Usually, compilers just accept narrowing type conversions, the nicest thing that can happen is a warning message, the worst is an error depending on compiler flags. The programmer has to handle the rest and it is the best thing to do anyway.
* 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`.
SQLite documentation clearly states that an auto-incrementable primary key column must be a 64-bit signed integer type.
@trueqbit trueqbit force-pushed the fix/insert-without-rowid branch from 337084c to 93a23fe Compare December 2, 2025 14:15
This approach keeps the previous way if all composite primary key columns have a default value, yet still enables inserting records if the table has composite primary key columns with only some or no default values.
* WITHOUT ROWID is only available since SQLite 3.8.2.
* FTS5: consider that it can be explicitly enabled with `SQLITE_ORM_ENABLE_FTS5`.
* Auxiliary virtual table columns are only available since SQLite 3.24.0.
* Hidden virtual table columns have no version attached.
@trueqbit trueqbit changed the title Fix insertion for tables without rowid Correcting ordinary insert statement for tables with composite primary keys Dec 4, 2025
@trueqbit trueqbit marked this pull request as ready for review December 4, 2025 19:33
@trueqbit trueqbit requested a review from fnc12 December 4, 2025 19:40
@trueqbit trueqbit force-pushed the fix/insert-without-rowid branch 2 times, most recently from 728b390 to 4007b1a Compare December 6, 2025 15:05
Only types that can represent a 64-bit signed integer - either integral or custom types - have the required properties to be an alias for the rowid and can cover the whole value range of it.
@trueqbit trueqbit force-pushed the fix/insert-without-rowid branch from 4007b1a to 0886bd5 Compare December 6, 2025 16:04
}

/**
* 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

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

constexpr size_t nTablePrimaryKeyColumns =
nested_tuple_size_for_t<columns_tuple_t, elements_type, pk_index_sequence>::value;

static_assert(nTablePrimaryKeyColumns > 0, "Tabel primary key definition must contain one column");
Copy link
Owner

Choose a reason for hiding this comment

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

typo: Table not Tabel


template<class O, class... Cols>
int insert(const O& o, columns_t<Cols...> cols) {
int64 insert(const O& o, columns_t<Cols...> cols) {
Copy link
Owner

Choose a reason for hiding this comment

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

on man, it will produce a lot of warnings. I'd leave it like this just like it is done in Java (where almost all integer operations are handles by simple ints) for simplicity and if somebody wants to get more precise int64 result one can use last_insert_rowid() instead or use a prepared statement of insert. I mean this function int insert is a real legacy and I'd leave its signature like this just like the below one's


struct DeptMaster {
int deptId = 0;
sqlite_orm::int64 deptId = 0;
Copy link
Owner

Choose a reason for hiding this comment

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

what happens to this example if we skip this change?

REQUIRE(get<1>(statement) == userPointers.end());
storage.execute(statement);
}
SECTION("references") {
Copy link
Owner

Choose a reason for hiding this comment

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

this type of sections is a really good shot

primary_key(&A::getAddress, &A::getType, &A::getIndex)));
storage.sync_schema();
storage.replace(A(1, 1, 0, std::make_shared<double>(55.5)));
storage.insert(A(1, 1, 0, std::make_shared<double>(55.5)));
Copy link
Owner

Choose a reason for hiding this comment

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

this is a really good bug fix

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

False NOT NULL constraint failure for table with true composite primary key

3 participants