Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HHH-18809 @Formula @Generated #9201

Merged
merged 4 commits into from
Nov 4, 2024
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 @@ -32,6 +32,15 @@
* Character middleInitial;
* </pre>
* <p>
* By default, the fields of an entity are not updated with the results of evaluating
* the formula after an {@code insert} or {@code update}. The {@link Generated @Generated}
* annotation may be used to specify that this should happen:
* <pre>
* &#64;Generated // evaluate the formula after an insert
* &#64;Formula("sub_total * (1.0 + tax)")
* BigDecimal totalWithTax;
* </pre>
* <p>
* For an entity with {@linkplain jakarta.persistence.SecondaryTable secondary tables},
* a formula may involve columns of the primary table, or columns of any one of the
* secondary tables. But it may not involve columns of more than one table.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@
* <li>a mapped column has a default value defined in DDL, in which case
* {@code @Generated} is used in conjunction with {@link ColumnDefault},
* <li>a {@linkplain #sql() SQL expression} is used to compute the value of
* a mapped column, or
* <li>when a custom SQL {@link SQLInsert insert} or {@link SQLUpdate update}
* a mapped column,
* <li>a custom SQL {@link SQLInsert insert} or {@link SQLUpdate update}
* statement specified by an entity assigns a value to the annotated
* property of the entity, or {@linkplain #writable() transforms} the
* value currently assigned to the annotated property.
* property of the entity, or {@linkplain #writable transforms} the
* value currently assigned to the annotated property, or
* <li>there is no mapped column, and the value of the field is determined
* by evaluating a SQL {@link Formula}.
* </ul>
* <p>
* On the other hand:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.SelectableMapping;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.mutation.EntityTableMapping;
import org.hibernate.pretty.MessageHelper;
Expand Down Expand Up @@ -326,7 +327,11 @@ private static List<? extends ModelPart> getActualGeneratedModelParts(
public static GeneratedValuesMutationDelegate getGeneratedValuesDelegate(
EntityPersister persister,
EventType timing) {
final boolean hasGeneratedProperties = !persister.getGeneratedProperties( timing ).isEmpty();
final List<? extends ModelPart> generatedProperties = persister.getGeneratedProperties( timing );
final boolean hasGeneratedProperties = !generatedProperties.isEmpty();
final boolean hasFormula =
generatedProperties.stream()
.anyMatch( part -> part instanceof SelectableMapping selectable && selectable.isFormula() );
final boolean hasRowId = timing == EventType.INSERT && persister.getRowIdMapping() != null;
final Dialect dialect = persister.getFactory().getJdbcServices().getDialect();

Expand All @@ -341,7 +346,8 @@ && noCustomSql( persister, timing ) ) {
return null;
}

if ( dialect.supportsInsertReturningGeneratedKeys()
if ( !hasFormula
&& dialect.supportsInsertReturningGeneratedKeys()
&& persister.getFactory().getSessionFactoryOptions().isGetGeneratedKeysEnabled() ) {
return new GetGeneratedKeysDelegate( persister, false, timing );
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,10 @@ protected GeneratedValues executeAndExtractReturning(

@Override
public String prepareIdentifierGeneratingInsert(String insertSQL) {
return dialect().getIdentityColumnSupport().appendIdentitySelectToInsert(
( (BasicEntityIdentifierMapping) persister.getRootEntityDescriptor().getIdentifierMapping() ).getSelectionExpression(),
insertSQL
);
final BasicEntityIdentifierMapping identifierMapping =
(BasicEntityIdentifierMapping) persister.getRootEntityDescriptor().getIdentifierMapping();
return dialect().getIdentityColumnSupport()
.appendIdentitySelectToInsert( identifierMapping.getSelectionExpression(), insertSQL );
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ public SybaseJConnGetGeneratedKeysDelegate(EntityPersister persister) {

@Override
public String prepareIdentifierGeneratingInsert(String insertSQL) {
return dialect().getIdentityColumnSupport().appendIdentitySelectToInsert(
( (BasicEntityIdentifierMapping) persister.getRootEntityDescriptor().getIdentifierMapping() ).getSelectionExpression(),
insertSQL
);
final BasicEntityIdentifierMapping identifierMapping =
(BasicEntityIdentifierMapping) persister.getRootEntityDescriptor().getIdentifierMapping();
return dialect().getIdentityColumnSupport()
.appendIdentitySelectToInsert( identifierMapping.getSelectionExpression(), insertSQL );
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.boot.registry.classloading.spi.ClassLoadingException;
import org.hibernate.boot.spi.InFlightMetadataCollector;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.Size;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import java.util.Objects;
import java.util.function.Consumer;

import org.hibernate.internal.util.StringHelper;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.SelectableMapping;
import org.hibernate.metamodel.mapping.SelectablePath;
Expand All @@ -22,6 +21,7 @@

import org.checkerframework.checker.nullness.qual.Nullable;

import static org.hibernate.internal.util.StringHelper.nullIfEmpty;
import static org.hibernate.internal.util.StringHelper.replace;
import static org.hibernate.sql.Template.TEMPLATE;

Expand Down Expand Up @@ -116,21 +116,20 @@ public ColumnReference(
boolean isFormula,
String customReadExpression,
JdbcMapping jdbcMapping) {
this.qualifier = StringHelper.nullIfEmpty( qualifier );
this.qualifier = nullIfEmpty( qualifier );

if ( isFormula ) {
assert qualifier != null;
this.columnExpression = replace( columnExpression, TEMPLATE, qualifier );
this.columnExpression = qualifier == null
? replace( columnExpression, TEMPLATE + '.', "" )
: replace( columnExpression, TEMPLATE, qualifier );
}
else {
this.columnExpression = columnExpression;
}
if ( selectablePath == null ) {
this.selectablePath = new SelectablePath( this.columnExpression );
}
else {
this.selectablePath = selectablePath;
}

this.selectablePath = selectablePath == null
? new SelectablePath( this.columnExpression )
: selectablePath;

this.isFormula = isFormula;
this.readExpression = customReadExpression;
Expand Down Expand Up @@ -181,12 +180,9 @@ public void appendReadExpression(String qualifier, Consumer<String> appender) {
appender.accept( columnExpression );
}
else if ( readExpression != null ) {
if ( qualifier == null ) {
appender.accept( replace( readExpression, TEMPLATE + ".", "" ) );
}
else {
appender.accept( replace( readExpression, TEMPLATE, qualifier ) );
}
appender.accept( qualifier == null
? replace( readExpression, TEMPLATE + '.', "" )
: replace( readExpression, TEMPLATE, qualifier ) );
}
else {
if ( qualifier != null ) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.orm.test.mapping.generated.formula;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import org.hibernate.annotations.Formula;
import org.hibernate.annotations.Generated;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;

import java.math.BigDecimal;

import static org.junit.Assert.assertEquals;

/**
* @author Gavin King
*/
@SuppressWarnings("JUnitMalformedDeclaration")
@DomainModel(annotatedClasses = FormulaGeneratedTest.OrderLine.class)
@SessionFactory
//@ServiceRegistry(settings = @Setting(name = USE_GET_GENERATED_KEYS, value = "false"))
public class FormulaGeneratedTest {

@Test
public void test(SessionFactoryScope scope) {
BigDecimal unitPrice = new BigDecimal("12.99");
scope.inTransaction( session -> {
OrderLine entity = new OrderLine( unitPrice, 5 );
session.persist(entity);
session.flush();
assertEquals( "new", entity.status );
assertEquals( unitPrice, entity.unitPrice );
assertEquals( 5, entity.quantity );
} );
scope.inTransaction( session -> {
OrderLine entity = session.createQuery("from WithDefault", OrderLine.class ).getSingleResult();
assertEquals( unitPrice, entity.unitPrice );
assertEquals( 5, entity.quantity );
assertEquals( "new", entity.status );
entity.status = "old"; //should be ignored when fetch=true
} );
scope.inTransaction( session -> {
OrderLine entity = session.createQuery("from WithDefault", OrderLine.class ).getSingleResult();
assertEquals( unitPrice, entity.unitPrice );
assertEquals( 5, entity.quantity );
assertEquals( "new", entity.status );
} );
}

@AfterEach
public void dropTestData(SessionFactoryScope scope) {
scope.inTransaction( session -> session.createQuery( "delete WithDefault" ).executeUpdate() );
}

@Entity(name="WithDefault")
public static class OrderLine {
@Id
private BigDecimal unitPrice;
@Id
private int quantity = 1;
@Generated
@Formula(value = "'new'")
private String status;

public OrderLine() {}
public OrderLine(BigDecimal unitPrice, int quantity) {
this.unitPrice = unitPrice;
this.quantity = quantity;
}
}
}
Loading