Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
90 changes: 58 additions & 32 deletions src/Lightweight/DataMapper/DataMapper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -350,9 +350,17 @@ class DataMapper
template <typename Record>
bool IsModified(Record const& record) const noexcept;

/// Clears the modified state of the record.
template <typename Record>
void ClearModifiedState(Record& record) noexcept;
/// Enum to set the modified state of a record.
enum class ModifiedState : uint8_t
{
Modified,
NotModified
};

/// Sets the modified state of the record after receiving from the database.
/// This marks all fields as not modified.
template <ModifiedState state, typename Record>
void SetModifiedState(Record& record) noexcept;

/// Loads all direct relations to this record.
template <typename Record>
Expand Down Expand Up @@ -1323,15 +1331,15 @@ RecordPrimaryKeyType<Record> DataMapper::Create(Record& record)
if constexpr (FieldType::IsPrimaryKey)
if constexpr (FieldType::IsAutoAssignPrimaryKey)
{
if (!record.[:el:].IsModified())
using ValueType = typename FieldType::ValueType;
if constexpr (std::same_as<ValueType, SqlGuid>)
{
using ValueType = typename FieldType::ValueType;
if constexpr (std::same_as<ValueType, SqlGuid>)
{
if (!record.[:el:].Value())
record.[:el:] = SqlGuid::Create();
}
else if constexpr (requires { ValueType {} + 1; })
if (!record.[:el:].Value())
record.[:el:] = SqlGuid::Create();
}
else if constexpr (requires { ValueType {} + 1; })
{
if (record.[:el:].Value() == ValueType {})
{
auto maxId = SqlStatement { _connection }.ExecuteDirectScalar<ValueType>(std::format(
R"sql(SELECT MAX("{}") FROM "{}")sql", FieldNameOf<el>, RecordTableName<Record>));
Expand All @@ -1344,15 +1352,15 @@ RecordPrimaryKeyType<Record> DataMapper::Create(Record& record)
CallOnPrimaryKey(record, [&]<size_t PrimaryKeyIndex, typename PrimaryKeyType>(PrimaryKeyType& primaryKeyField) {
if constexpr (PrimaryKeyType::IsAutoAssignPrimaryKey)
{
if (!primaryKeyField.IsModified())
using ValueType = PrimaryKeyType::ValueType;
if constexpr (std::same_as<ValueType, SqlGuid>)
{
using ValueType = PrimaryKeyType::ValueType;
if constexpr (std::same_as<ValueType, SqlGuid>)
{
if (!primaryKeyField.Value())
primaryKeyField = SqlGuid::Create();
}
else if constexpr (requires { ValueType {} + 1; })
if (!primaryKeyField.Value())
primaryKeyField = SqlGuid::Create();
}
else if constexpr (requires { ValueType {} + 1; })
{
if (primaryKeyField.Value() == ValueType {})
{
auto maxId = SqlStatement { _connection }.ExecuteDirectScalar<ValueType>(
std::format(R"sql(SELECT MAX("{}") FROM "{}")sql",
Expand All @@ -1370,7 +1378,7 @@ RecordPrimaryKeyType<Record> DataMapper::Create(Record& record)
if constexpr (HasAutoIncrementPrimaryKey<Record>)
SetId(record, _stmt.LastInsertId(RecordTableName<Record>));

ClearModifiedState(record);
SetModifiedState<ModifiedState::NotModified>(record);

if constexpr (QueryOptions.loadRelations)
ConfigureRelationAutoLoading(record);
Expand Down Expand Up @@ -1474,7 +1482,7 @@ void DataMapper::Update(Record& record)

_stmt.Execute();

ClearModifiedState(record);
SetModifiedState<ModifiedState::NotModified>(record);
}

template <typename Record>
Expand Down Expand Up @@ -1550,6 +1558,9 @@ std::optional<Record> DataMapper::QuerySingleWithoutRelationAutoLoading(PrimaryK
if (!detail::ReadSingleResult(_stmt.Connection().ServerType(), reader, *resultRecord))
return std::nullopt;

if (resultRecord)
SetModifiedState<ModifiedState::NotModified>(resultRecord.value());

return resultRecord;
}

Expand All @@ -1558,7 +1569,9 @@ std::optional<Record> DataMapper::QuerySingle(PrimaryKeyTypes&&... primaryKeys)
{
auto record = QuerySingleWithoutRelationAutoLoading<Record>(std::forward<PrimaryKeyTypes>(primaryKeys)...);
if (record)
{
ConfigureRelationAutoLoading(*record);
}
return record;
}

Expand All @@ -1578,6 +1591,10 @@ std::optional<Record> DataMapper::QuerySingle(SqlSelectQueryBuilder selectQuery,
auto reader = _stmt.GetResultCursor();
if (!detail::ReadSingleResult(_stmt.Connection().ServerType(), reader, *resultRecord))
return std::nullopt;

if (resultRecord)
SetModifiedState<ModifiedState::NotModified>(resultRecord.value());

return resultRecord;
}

Expand Down Expand Up @@ -1638,7 +1655,10 @@ std::vector<Record> DataMapper::Query(std::string_view sqlQueryString, InputPara
result.pop_back();

for (auto& record: result)
{
SetModifiedState<ModifiedState::NotModified>(record);
ConfigureRelationAutoLoading(record);
}
}

return result;
Expand Down Expand Up @@ -1710,16 +1730,16 @@ std::vector<std::tuple<First, Second, Rest...>> DataMapper::Query(SqlSelectQuery
// Drop the last record, which we failed to fetch (End of result set).
result.pop_back();

if constexpr (QueryOptions.loadRelations)
for (auto& record: result)
{

for (auto& record: result)
{
Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<auto I>() {
auto& element = std::get<I>(record);
Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<auto I>() {
auto& element = std::get<I>(record);
SetModifiedState<ModifiedState::NotModified>(element);
if constexpr (QueryOptions.loadRelations)
{
ConfigureRelationAutoLoading(element);
});
}
}
});
}

return result;
Expand Down Expand Up @@ -1759,21 +1779,27 @@ std::vector<Record> DataMapper::Query(SqlSelectQueryBuilder::ComposedQuery const
records.pop_back();

for (auto& record: records)
{
SetModifiedState<ModifiedState::NotModified>(record);
ConfigureRelationAutoLoading(record);
}

return records;
}

template <typename Record>
void DataMapper::ClearModifiedState(Record& record) noexcept
template <DataMapper::ModifiedState state, typename Record>
void DataMapper::SetModifiedState(Record& record) noexcept
{
static_assert(!std::is_const_v<Record>);
static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");

Reflection::EnumerateMembers(record, []<size_t I, typename FieldType>(FieldType& field) {
if constexpr (requires { field.SetModified(false); })
{
field.SetModified(false);
if constexpr (state == ModifiedState::Modified)
field.SetModified(true);
else
field.SetModified(false);
}
});
}
Expand Down
26 changes: 19 additions & 7 deletions src/Lightweight/DataMapper/Field.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,20 +126,20 @@ struct Field
constexpr std::weak_ordering operator<=>(Field const& other) const noexcept;

/// Compares the field value with the given value for equality.
bool operator==(Field const& value) const noexcept = default;
constexpr bool operator==(Field const& value) const noexcept;

/// Compares the field value with the given value for inequality.
bool operator!=(Field const& value) const noexcept = default;
constexpr bool operator!=(Field const& value) const noexcept;

/// Compares the field value with the given value for equality.
template <typename S>
requires std::convertible_to<S, T>
bool operator==(S const& value) const noexcept;
constexpr bool operator==(S const& value) const noexcept;

/// Compares the field value with the given value for inequality.
template <typename S>
requires std::convertible_to<S, T>
bool operator!=(S const& value) const noexcept;
constexpr bool operator!=(S const& value) const noexcept;

/// Returns a string representation of the value, suitable for use in debugging and logging.
[[nodiscard]] std::string InspectValue() const;
Expand Down Expand Up @@ -167,7 +167,7 @@ struct Field

private:
ValueType _value {};
bool _modified { false };
bool _modified { true };
};

// clang-format off
Expand Down Expand Up @@ -237,18 +237,30 @@ constexpr std::weak_ordering LIGHTWEIGHT_FORCE_INLINE Field<T, P1, P2>::operator
return _value <=> other._value;
}

template <detail::FieldElementType T, auto P1, auto P2>
constexpr bool LIGHTWEIGHT_FORCE_INLINE Field<T, P1, P2>::operator==(Field const& other) const noexcept
{
return _value == other._value;
}

template <detail::FieldElementType T, auto P1, auto P2>
constexpr bool LIGHTWEIGHT_FORCE_INLINE Field<T, P1, P2>::operator!=(Field const& other) const noexcept
{
return _value != other._value;
}

template <detail::FieldElementType T, auto P1, auto P2>
template <typename S>
requires std::convertible_to<S, T>
inline LIGHTWEIGHT_FORCE_INLINE bool Field<T, P1, P2>::operator==(S const& value) const noexcept
constexpr bool LIGHTWEIGHT_FORCE_INLINE Field<T, P1, P2>::operator==(S const& value) const noexcept
{
return _value == value;
}

template <detail::FieldElementType T, auto P1, auto P2>
template <typename S>
requires std::convertible_to<S, T>
inline LIGHTWEIGHT_FORCE_INLINE bool Field<T, P1, P2>::operator!=(S const& value) const noexcept
constexpr bool LIGHTWEIGHT_FORCE_INLINE Field<T, P1, P2>::operator!=(S const& value) const noexcept
{
return _value != value;
}
Expand Down
18 changes: 18 additions & 0 deletions src/tests/DataMapper/CreateTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ TEST_CASE_METHOD(SqlTestFixture, "Table with multiple primary keys", "[DataMappe
CHECK(queriedRecords.size() == 1);
auto const& queriedRecord = queriedRecords.at(0);
INFO("Queried record: " << DataMapper::Inspect(queriedRecord));
INFO("record: " << DataMapper::Inspect(record));
CHECK(queriedRecord == record);
}

Expand All @@ -159,4 +160,21 @@ TEST_CASE_METHOD(SqlTestFixture, "Loading of the dependent records after create"
REQUIRE(!nullableFKUserNotSet.user.Record().has_value());
}

TEST_CASE_METHOD(SqlTestFixture, "Create with defined primary key", "[DataMapper]")
{
auto dm = DataMapper();

dm.CreateTables<EntryWithIntPrimaryKey>();

auto entry = EntryWithIntPrimaryKey { .id = 42, .comment = "The Answer" };
dm.Create(entry);

REQUIRE(entry.id.Value() == 42);

entry.comment = "Updated Comment";
dm.Update(entry);
REQUIRE(entry.id.Value() == 42);
REQUIRE(dm.QuerySingle<EntryWithIntPrimaryKey>(42).value().comment.Value() == "Updated Comment");
}

// NOLINTEND(bugprone-unchecked-optional-access)
6 changes: 6 additions & 0 deletions src/tests/DataMapper/Entities.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,9 @@ struct Appointment
return std::weak_ordering::equivalent;
}
};

struct EntryWithIntPrimaryKey
{
Light::Field<int, Light::PrimaryKey::AutoAssign> id;
Light::Field<Light::SqlAnsiString<30>> comment;
};
Loading