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
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,12 @@ public List<ColumnExpression> getColumns(@Nonnull Class<? extends Data> table, @
return modelBuilder.build(type, true).getPrimaryKeyMetamodel().orElseThrow();
}
}
if (type == model.primaryKeyType()) {
// Entity-typed primary key: the object is the root's primary key value (e.g., a junction
// table keyed by an entity whose table also appears elsewhere in the join graph). Mirrors
// the primary-key fallback for scalar values below.
return model.getPrimaryKeyMetamodel().orElseThrow();
}
throw new SqlTemplateException("Cannot uniquely identify object in expression: multiple matches found for type %s. Ensure the expression resolves to a single, unambiguous metamodel path.".formatted(type.getSimpleName()));
}
if (isPrimaryKeyValue(object, model.primaryKeyType())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
Expand All @@ -30,8 +31,12 @@
import st.orm.PK;
import st.orm.PersistenceException;
import st.orm.Ref;
import st.orm.core.model.Address;
import st.orm.core.model.City;
import st.orm.core.model.Owner;
import st.orm.core.model.OwnerPrimaryPet;
import st.orm.core.model.Pet;
import st.orm.core.model.PetType;
import st.orm.core.template.ORMTemplate;

/**
Expand Down Expand Up @@ -980,4 +985,30 @@ public void testUuidScalarResultStream() {
assertTrue(ids.contains(SECONDARY_KEY_ID));
}
}

// Entity-typed primary keys.

@Test
public void testFindAllByIdWithEntityTypedPkWhoseTableIsReachedTwice() {
var orm = ORMTemplate.of(dataSource);
var owner = orm.entity(Owner.class).insertAndFetch(Owner.builder()
.firstName("EntityPk")
.lastName("FindAllById")
.address(Address.builder().address("110 W. Liberty St.").city(City.builder().id(1).name("Sun Paririe").build()).build())
.telephone("6085551023")
.build());
var pet = orm.entity(Pet.class).insertAndFetch(Pet.builder()
.name("EntityPkFindPet")
.birthDate(LocalDate.of(2024, 1, 1))
.type(Ref.of(PetType.class, 1))
.owner(owner)
.build());
var repository = orm.entity(OwnerPrimaryPet.class);
repository.insert(new OwnerPrimaryPet(owner, pet));
// The junction's join graph reaches the owner table twice (owner and pet.owner); the id lookup must
// resolve against the primary-key path rather than by type.
var result = repository.findAllById(List.of(owner));
assertEquals(1, result.size());
assertEquals(owner.id(), result.getFirst().owner().id());
}
}
14 changes: 14 additions & 0 deletions storm-core/src/test/java/st/orm/core/WriteSetIntegrationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import st.orm.core.model.Address;
import st.orm.core.model.City;
import st.orm.core.model.Owner;
import st.orm.core.model.OwnerPrimaryPet;
import st.orm.core.model.Pet;
import st.orm.core.model.PetOwnerRef;
import st.orm.core.model.PetType;
Expand Down Expand Up @@ -287,6 +288,19 @@ public void testInsertAndFetchJunctionReturnsCompleteKey() {
assertEquals(3, fetched.id().specialtyId());
}

@Test
public void testInsertAndFetchJunctionWithEntityTypedPrimaryKey() {
var orm = orm();
var owner = newOwner("EntityPk", "Junction");
var pet = Pet.builder().name("EntityPkJunctionPet").birthDate(LocalDate.of(2024, 4, 4)).type(dogType()).owner(owner).build();
// The junction's primary key is the owner entity itself, and its join graph reaches the owner table
// twice (owner and pet.owner); the fetch-back must resolve the propagated ids against the primary-key
// path rather than by type.
var fetched = orm.writeSet().insertAndFetch(new OwnerPrimaryPet(owner, pet));
assertNotEquals(0, (int) fetched.owner().id());
assertEquals(fetched.owner().id(), fetched.pet().owner().id());
}

@DbTable("vet_badge")
public record VetBadge(
@PK Integer id,
Expand Down
23 changes: 23 additions & 0 deletions storm-core/src/test/java/st/orm/core/model/OwnerPrimaryPet.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package st.orm.core.model;

import static st.orm.GenerationStrategy.NONE;

import jakarta.annotation.Nonnull;
import lombok.Builder;
import st.orm.Entity;
import st.orm.FK;
import st.orm.PK;

/**
* Junction-style entity whose primary key is an entity-typed foreign key to {@link Owner}, while its second
* foreign key reaches the owner table again through {@code pet.owner}.
*
* <p>This models the pattern where the root's join graph contains the primary-key entity's table more than once
* (e.g., a user-to-primary-session junction where the session also references the user). Id-based operations on
* this entity must resolve against the primary-key path; type-based resolution is ambiguous.</p>
*/
@Builder(toBuilder = true)
public record OwnerPrimaryPet(
@Nonnull @PK(generation = NONE) @FK("owner_id") Owner owner,
@Nonnull @FK("pet_id") Pet pet
) implements Entity<Owner> {}
4 changes: 4 additions & 0 deletions storm-core/src/test/resources/data.sql
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
drop table if exists tenant CASCADE;
drop table if exists city CASCADE;
drop table if exists owner CASCADE;
drop table if exists owner_primary_pet CASCADE;
drop table if exists pet CASCADE;
drop table if exists pet_extension CASCADE;
drop table if exists pet_type CASCADE;
Expand All @@ -20,8 +21,11 @@ create table vet_badge (id integer auto_increment, label varchar(255), vet_id in
create table vet_specialty (vet_id integer, specialty_id integer not null, primary key (vet_id, specialty_id));
create table visit (id integer auto_increment, visit_date date, description varchar(255), vet_id integer null, specialty_id integer null, pet_id integer not null, "timestamp" timestamp default CURRENT_TIMESTAMP, primary key (id));
create table pet_extension (pet_id integer not null, notes varchar(255), primary key (pet_id));
create table owner_primary_pet (owner_id integer not null, pet_id integer not null, primary key (owner_id));
alter table owner add constraint owner_city_fk foreign key (city_id) references city (id);
alter table pet_extension add constraint pet_extension_pet_fk foreign key (pet_id) references pet (id);
alter table owner_primary_pet add constraint owner_primary_pet_owner_fk foreign key (owner_id) references owner (id);
alter table owner_primary_pet add constraint owner_primary_pet_pet_fk foreign key (pet_id) references pet (id);
alter table pet add constraint pet_owner_fk foreign key (owner_id) references owner (id);
alter table pet add constraint pet_pet_type_fk foreign key (type_id) references pet_type (id);
alter table vet_badge add constraint vet_badge_vet_fk foreign key (vet_id) references vet (id);
Expand Down
Loading