Skip to content
Closed
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
35 changes: 29 additions & 6 deletions hibernate-core/src/main/java/org/hibernate/sql/Template.java
Original file line number Diff line number Diff line change
Expand Up @@ -194,22 +194,27 @@ else if ( LITERAL_PREFIXES.contains( lcToken ) ) {
continue;
}
else if ( nextToken != null && Character.isWhitespace( nextToken.charAt( 0 ) ) ) {
final StringTokenizer lookahead = lookahead( sqlWhereString, symbols, tokens);
String lookaheadToken = lookahead.hasMoreTokens() ? lookahead.nextToken() : null;
final StringBuilder additionalTokens = new StringBuilder();
TimeZoneTokens possibleNextToken = null;
do {
possibleNextToken = possibleNextToken == null
? TimeZoneTokens.getPossibleNextTokens( lcToken )
: possibleNextToken.nextToken();
do {
additionalTokens.append( nextToken );
hasMore = tokens.hasMoreTokens();
nextToken = tokens.nextToken();
} while ( nextToken != null && Character.isWhitespace( nextToken.charAt( 0 ) ) );
} while ( nextToken != null && possibleNextToken.isToken( nextToken ) );
if ( "'".equals( nextToken ) ) {
additionalTokens.append( lookaheadToken );
lookaheadToken = lookahead.hasMoreTokens() ? lookahead.nextToken() : null;
} while ( lookaheadToken != null && Character.isWhitespace( lookaheadToken.charAt( 0 ) ) );
} while ( lookaheadToken != null && possibleNextToken.isToken( lookaheadToken ) );
if ( "'".equals( lookaheadToken ) ) {
// Don't prefix a literal
result.append( token );
result.append( additionalTokens );
while (tokens.countTokens() > lookahead.countTokens()) {
hasMore = tokens.hasMoreTokens();
nextToken = hasMore ? tokens.nextToken() : null;
}
continue;
}
else {
Expand Down Expand Up @@ -401,6 +406,24 @@ else if ( inFromClause && ",".equals(lcToken) ) {
return result.toString();
}

/**
* Clone the given token stream, returning a token stream which begins
* from the next token.
*
* @param sql the full SQL we are scanning
* @param symbols the delimiter symbols
* @param tokens the current token stream
* @return a cloned token stream
*/
private static StringTokenizer lookahead(String sql, String symbols, StringTokenizer tokens) {
final StringTokenizer lookahead =
new StringTokenizer( sql, symbols, true );
while ( lookahead.countTokens() > tokens.countTokens() + 1) {
lookahead.nextToken();
}
return lookahead;
}

private enum TimeZoneTokens {
NONE,
WITH,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.orm.test.joinsubquery;

import jakarta.persistence.*;
import org.hibernate.annotations.JoinColumnOrFormula;
import org.hibernate.annotations.JoinFormula;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.JiraKey;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import java.io.Serializable;

import static org.junit.jupiter.api.Assertions.assertNotNull;

@DomainModel(annotatedClasses = {
JoinSubqueryTest.RecordItem.class,
JoinSubqueryTest.RecordType.class
})
@SessionFactory
@JiraKey("HHH-19052")
class JoinSubqueryTest {

@BeforeAll
static void setUp(SessionFactoryScope scope) throws Exception {
scope.inTransaction(session -> {
final var id = 1L;
final var typeId = 42L;
final var recordType = new RecordType(id, typeId);
session.persist(recordType);
final var item = new RecordItem(id, typeId, recordType);
session.persist(item);
});
}

@Test
void test(SessionFactoryScope scope) throws Exception {
scope.inSession(session -> {
final var item = session.get(RecordItem.class, 1L);
assertNotNull(item);
});
}

@Entity
@Table(name = "record_items")
public static class RecordItem implements Serializable {

@Id
protected Long id;

@Column(name = "type_id", insertable = false, updatable = false)
private Long typeId;

@ManyToOne(fetch = FetchType.EAGER)
@JoinColumnOrFormula(column = @JoinColumn(name = "type_id", referencedColumnName = "entity_id"))
@JoinColumnOrFormula(formula = @JoinFormula(value = "(SELECT x.id FROM record_types x WHERE x.entity_id = type_id)", referencedColumnName = "id"))
private RecordType type;

RecordItem() {
}

public RecordItem(Long id, Long typeId, RecordType type) {
this.id = id;
this.typeId = typeId;
this.type = type;
}

public void setId(Long id) {
this.id = id;
}

public Long getId() {
return this.id;
}

public Long getTypeId() {
return typeId;
}

public RecordType getType() {
return type;
}


}

@Entity
@Table(name = "record_types")
public static class RecordType implements Serializable {

@Id
protected Long id;

@Column(name = "entity_id")
private Long entityId;

RecordType() {
}

public RecordType(Long id, Long entityId) {
this.id = id;
this.entityId = entityId;
}

public void setId(Long id) {
this.id = id;
}

public Long getId() {
return this.id;
}

public Long getEntityId() {
return entityId;
}

public void setEntityId(Long entityId) {
this.entityId = entityId;
}

}
}
Loading