The write set belongs to the underlying ORM template and is not scoped to this repository's entity type; + * it accepts entities of any type. This accessor is a convenience for repository methods, equivalent to + * {@code orm().writeSet()}.
+ * + * @return the write set operations of the underlying ORM template. + * @see WriteSet + * @since 1.13 + */ + default WriteSet writeSet() { + return orm().writeSet(); + } } diff --git a/storm-core/src/main/java/st/orm/core/repository/RepositoryLookup.java b/storm-core/src/main/java/st/orm/core/repository/RepositoryLookup.java index 7c10db496..aaa5b1550 100644 --- a/storm-core/src/main/java/st/orm/core/repository/RepositoryLookup.java +++ b/storm-core/src/main/java/st/orm/core/repository/RepositoryLookup.java @@ -18,6 +18,8 @@ import jakarta.annotation.Nonnull; import st.orm.Entity; import st.orm.Projection; +import st.orm.WriteSet; +import st.orm.core.repository.impl.WriteSetImpl; /** * Provides access to repositories. @@ -76,4 +78,19 @@ public interface RepositoryLookup { * @return a proxy for the repository of the given type. */A write set lifts the per-repository write verbs to collections spanning multiple entity types: entities + * are partitioned by type, ordered by their foreign key dependencies, and written with one batch statement per + * type per dependency level. Generated primary keys propagate to dependent entities within the set.
+ * + * @return the write set operations bound to this lookup. + * @see WriteSet + * @since 1.13 + */ + default WriteSet writeSet() { + return new WriteSetImpl(this); + } } diff --git a/storm-core/src/main/java/st/orm/core/repository/impl/WriteSetImpl.java b/storm-core/src/main/java/st/orm/core/repository/impl/WriteSetImpl.java new file mode 100644 index 000000000..ac83f024b --- /dev/null +++ b/storm-core/src/main/java/st/orm/core/repository/impl/WriteSetImpl.java @@ -0,0 +1,859 @@ +/* + * Copyright 2024 - 2026 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package st.orm.core.repository.impl; + +import static java.util.Objects.requireNonNull; + +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; +import java.lang.invoke.MethodType; +import java.lang.reflect.ParameterizedType; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import st.orm.Data; +import st.orm.Entity; +import st.orm.FK; +import st.orm.GenerationStrategy; +import st.orm.PK; +import st.orm.Persist; +import st.orm.PersistenceException; +import st.orm.Ref; +import st.orm.WriteSet; +import st.orm.core.repository.EntityRepository; +import st.orm.core.repository.RepositoryLookup; +import st.orm.core.spi.ORMReflection; +import st.orm.core.spi.Providers; +import st.orm.core.template.Column; +import st.orm.core.template.Model; +import st.orm.mapping.RecordField; +import st.orm.mapping.RecordType; + +/** + * Default implementation of {@link WriteSet}. + * + *Resolves the dependency graph of the passed entities, partitions it into topological levels, and delegates each + * level to the existing per-type repository batch operations. Generated primary keys are propagated to dependent + * records by rebuilding the immutable records with the keyed instances, correlated by instance identity. For + * non-insertable FK components whose column value is carried by a component of the primary key (the junction table + * pattern), the generated key is additionally written into the carrying key component.
+ * + *Instances are stateless per call (all resolution state is method-local; the per-type metadata cache is + * concurrent) and can safely be shared across threads.
+ * + * @since 1.13 + */ +public final class WriteSetImpl implements WriteSet { + + private static final ORMReflection REFLECTION = Providers.getORMReflection(); + + /** How the write set treats entities that are reachable but not part of the set. */ + private enum Verb { INSERT, UPSERT, UPDATE, REMOVE } + + private final RepositoryLookup lookup; + private final ConcurrentMap