Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.

Bug 348876

Summary: SQL generation for subquery built using Criteria API causes "the expression builder is missing" exception
Product: z_Archived Reporter: Mikhail Skotnikov <Mikhail.Skotnikov>
Component: EclipselinkAssignee: Project Inbox <eclipselink.orm-inbox>
Status: NEW --- QA Contact:
Severity: normal    
Priority: P2 CC: Mikhail.Skotnikov, tom.ware
Version: unspecified   
Target Milestone: ---   
Hardware: PC   
OS: Windows 7   
Whiteboard:

Description Mikhail Skotnikov CLA 2011-06-09 07:59:46 EDT
Build Identifier: 2.0.0.v20091127-r5931 (the same problem for 2.2.0)

The exception generated is:

Exception [EclipseLink-6121] (Eclipse Persistence Services - 2.0.0.v20091127-r5931): org.eclipse.persistence.exceptions.QueryException
Exception Description: The query has not been defined correctly, the expression builder is missing.  For sub and parallel queries ensure the queries builder is always on the left.

The problem seems to be related to a bug reported before:
https://bugs.eclipse.org/bugs/show_bug.cgi?id=297331

Which was not actually fixed, so I put extra information:

The subquery is built using the following code:

		AbstractQuery<?> cq = queryAccessor.getIntermediateQuery();
		CriteriaBuilder cb = queryAccessor.getCriteriaBuilder();
		Join<?, Message> message = queryAccessor.getJoin();
		
		Subquery<String> sq = cq.subquery( String.class );
		Join<?, Message> sqMessage = sq.correlate( message );
		MapJoin<Message, Locale, LocalizedMessage> locMsg = sqMessage.join( Message_.localizations );
		sq.where( cb.equal( locMsg.get( LocalizedMessage_.locale ).get( Locale_.id ), cb.parameter( Long.class, localeParam ) ) );
		parametersCreated = true;

		return sq.select( locMsg.get( LocalizedMessage_.text ) );

The whole SQL that gets generated before the exception is thrown:

SELECT * FROM (SELECT /*+ FIRST_ROWS */ a.*, ROWNUM rnum  FROM (
SELECT t2.ITEM_ID AS ITEM_ID1, t2.ARMOR_SLOT_MSG AS ARMOR_SLOT_MSG2, t2.ARMOR_SPEC_MSG AS ARMOR_SPEC_MSG3, t2.DELETE_DATE AS DELETE_DATE4, t2.CREATE_DATE AS CREATE_DATE5, t2.UPDATE_DATE AS UPDATE_DATE6
FROM MESSAGE t0, ITEM_ARMOR t2, ITEM t1
WHERE ((t1.ITEM_ID = t2.ITEM_ID) AND (t0.msg_id (+) = t1.NAME_MSG))
ORDER BY CASE WHEN EXISTS (SELECT t3.TEXT

The tables involved are the following:


CREATE TABLE message 
  ( 
     msg_id      NUMBER(10) NOT NULL, 
     NAME        VARCHAR2(255), 

    //...

     primary key (msg_id) 
  ); 

CREATE TABLE localized_message 
  ( 
     msg_id      NUMBER(10) NOT NULL, 
     locale_id   NUMBER(10) NOT NULL, 
     text        VARCHAR2(2000) NOT NULL, 

     //...

     primary key (msg_id, locale_id) 
  ); 

CREATE TABLE locale 
  ( 
     locale_id   NUMBER(10) NOT NULL, 
     NAME        VARCHAR2(255) NOT NULL, 

     //...

     primary key (locale_id) 
  ); 

CREATE UNIQUE INDEX LOCALE_NAME ON LOCALE (NAME);

ALTER TABLE LOCALIZED_MESSAGE ADD CONSTRAINT FKLOCALIZED_794781 FOREIGN KEY (MSG_ID) REFERENCES MESSAGE (MSG_ID);
ALTER TABLE LOCALIZED_MESSAGE ADD CONSTRAINT FKLOCALIZED_936165 FOREIGN KEY (LOCALE_ID) REFERENCES LOCALE (LOCALE_ID);

The code generating the exception:

        // If there are no table aliases it means the query was malformed,
        // most likely the wrong builder was used, or wrong builder on the left in a sub-query.
        if (getTableAliases().isEmpty()) {
            throw QueryException.invalidBuilderInQuery(null);// Query is set in execute.


The field has been correctly prefixed with the alias (t3), but the table list of the statement is not correct (ITEM_ARMOR instead of LOCALIZED_MESSAGE correlated with the MESSAGE of the outer query):

this	SQLSelectStatement  (id=7629)	
	builder	ExpressionBuilder  (id=7631)	
	connectByExpression	null	
	currentAlias	null	
	currentAliasNumber	8	
	descriptorsForMultitableInheritanceOnly	null	
	distinctState	2	
	fieldCounter	0	
	fields	NonSynchronizedVector  (id=7632)	
	forUpdateClause	ForUpdateClause  (id=7006)	
	groupByExpressions	null	
	havingExpression	null	
	hintString	null	
	isAggregateSelect	false	
	lastTable	null	
	maximumAliasLength	null	
	nonSelectFields	ArrayList<E>  (id=7634)	
	orderByExpressions	null	
	orderSiblingsByExpressions	null	
	outerJoinedAdditionalJoinCriteria	null	
	outerJoinedExpressions	null	
	outerJoinedMappingCriteria	null	
	parentStatement	SQLSelectStatement  (id=7615)	
	query	ReportQuery  (id=7635)	
	requiresAliases	true	
	startWithExpression	null	
	tableAliases	Hashtable<K,V>  (id=7636)	
		count	0	
		entrySet	null	
		keySet	null	
		loadFactor	0.75	
		modCount	0	
		table	Hashtable$Entry<K,V>[5]  (id=7848)	
		threshold	3	
		values	null	
	tables	NonSynchronizedVector  (id=7637)	
		capacityIncrement	0	
		elementCount	1	
		elementData	Object[1]  (id=7839)	
			[0]	DatabaseTable  (id=7840)	
				name	"ITEM_ARMOR" (id=7842)	
				qualifiedName	"ITEM_ARMOR" (id=7842)	
				tableQualifier	"" (id=642)	
				uniqueConstraints	HashMap<K,V>  (id=7844)	
				useDelimiters	false	
		modCount	0	
	translationRow	null	
	useUniqueFieldAliases	false	
	whereClause	null	



INFO: [EL Warning]: 2011-06-09 15:13:12.917--UnitOfWork(23831545)--Thread(Thread[httpSSLWorkerThread-8080-0,10,Grizzly])--Local Exception Stack: 
Exception [EclipseLink-6121] (Eclipse Persistence Services - 2.0.0.v20091127-r5931): org.eclipse.persistence.exceptions.QueryException
Exception Description: The query has not been defined correctly, the expression builder is missing.  For sub and parallel queries ensure the queries builder is always on the left.
Query: ReadAllQuery(referenceClass=Armor )
	at org.eclipse.persistence.exceptions.QueryException.invalidBuilderInQuery(QueryException.java:630)
	at org.eclipse.persistence.internal.expressions.SQLSelectStatement.appendFromClauseToWriter(SQLSelectStatement.java:510)
	at org.eclipse.persistence.internal.expressions.SQLSelectStatement.printSQL(SQLSelectStatement.java:1444)
	at org.eclipse.persistence.internal.expressions.SubSelectExpression.printSQL(SubSelectExpression.java:193)
	at org.eclipse.persistence.expressions.Expression.printSQLWithoutConversion(Expression.java:3574)
	at org.eclipse.persistence.expressions.ExpressionOperator.printCollection(ExpressionOperator.java:1894)
	at org.eclipse.persistence.internal.expressions.FunctionExpression.printSQL(FunctionExpression.java:425)
	at org.eclipse.persistence.expressions.Expression.printSQLWithoutConversion(Expression.java:3574)
	at org.eclipse.persistence.expressions.ExpressionOperator.printCollection(ExpressionOperator.java:1894)
	at org.eclipse.persistence.internal.expressions.FunctionExpression.printSQL(FunctionExpression.java:425)
	at org.eclipse.persistence.expressions.ExpressionOperator.printCollection(ExpressionOperator.java:1896)
	at org.eclipse.persistence.internal.expressions.FunctionExpression.printSQL(FunctionExpression.java:425)
	at org.eclipse.persistence.internal.expressions.SQLSelectStatement.appendOrderClauseToWriter(SQLSelectStatement.java:699)
	at org.eclipse.persistence.internal.expressions.SQLSelectStatement.printSQL(SQLSelectStatement.java:1465)
	at org.eclipse.persistence.platform.database.OraclePlatform.printSQLSelectStatement(OraclePlatform.java:710)
	at org.eclipse.persistence.internal.expressions.SQLSelectStatement.buildCall(SQLSelectStatement.java:752)
	at org.eclipse.persistence.internal.expressions.SQLSelectStatement.buildCall(SQLSelectStatement.java:762)
	at org.eclipse.persistence.descriptors.ClassDescriptor.buildCallFromStatement(ClassDescriptor.java:658)
	at org.eclipse.persistence.internal.queries.StatementQueryMechanism.setCallFromStatement(StatementQueryMechanism.java:386)
	at org.eclipse.persistence.internal.queries.StatementQueryMechanism.prepareSelectAllRows(StatementQueryMechanism.java:312)
	at org.eclipse.persistence.internal.queries.ExpressionQueryMechanism.prepareSelectAllRows(ExpressionQueryMechanism.java:1554)
	at org.eclipse.persistence.queries.ReadAllQuery.prepareSelectAllRows(ReadAllQuery.java:793)
	at org.eclipse.persistence.queries.ReadAllQuery.prepare(ReadAllQuery.java:734)
	at org.eclipse.persistence.queries.DatabaseQuery.checkPrepare(DatabaseQuery.java:464)
	at org.eclipse.persistence.queries.ObjectLevelReadQuery.checkPrepare(ObjectLevelReadQuery.java:732)
	at org.eclipse.persistence.queries.DatabaseQuery.checkPrepare(DatabaseQuery.java:430)
	at org.eclipse.persistence.queries.DatabaseQuery.execute(DatabaseQuery.java:646)
	at org.eclipse.persistence.queries.ObjectLevelReadQuery.execute(ObjectLevelReadQuery.java:958)
	at org.eclipse.persistence.queries.ReadAllQuery.execute(ReadAllQuery.java:432)
	at org.eclipse.persistence.queries.ObjectLevelReadQuery.executeInUnitOfWork(ObjectLevelReadQuery.java:1021)
	at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.internalExecuteQuery(UnitOfWorkImpl.java:2863)
	at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1225)
	at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1207)
	at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1181)
	at org.eclipse.persistence.internal.jpa.EJBQueryImpl.executeReadQuery(EJBQueryImpl.java:453)
	at org.eclipse.persistence.internal.jpa.EJBQueryImpl.getResultList(EJBQueryImpl.java:669)

@Entity
public class Message implements StateTrackable
{
	/** The identifier should be auto-generated. **/
	@Id
	@Column(name = "msg_id")
	private Long id;
	
	/** Allows to distinguish messages. Optional. */
	private String name;
	
	/** Locale to Localized message mapping. */
	@OneToMany(mappedBy = "message")
	@MapKey(name = "locale")
	private Map<Locale, LocalizedMessage> localizations;


@Entity
@Table(name = "LOCALIZED_MESSAGE")
public class LocalizedMessage implements StateTrackable
{
	/** The id. */
	@EmbeddedId
	private LocalizedMessageId id;
	
	/** The foreign key reference to the message. */
	@MapsId("messageId")
	@ManyToOne
	@JoinColumn(name = "MSG_ID")
	private Message message;

	/** The foreign key reference to the locale. */
	@MapsId("localeId")
	@ManyToOne
	@JoinColumn(name = "LOCALE_ID")
	private Locale locale;
	
	/** The localized text. */
	private String text;



@Embeddable
public class LocalizedMessageId implements Serializable
{
	/** The Constant serialVersionUID. */
	private static final long serialVersionUID = 1L;

	/** The Constant HASH_CODE_PRIME. */
	private static final int HASH_CODE_PRIME = 31;
	
	/** The Constant HASH_CODE_INIT. */
	private static final int HASH_CODE_INIT = 17;
	
	/** The message id. */
	private Long messageId;
	
	/** The locale id. */
	private Long localeId;


@Entity
public class Locale
{
	/** "Find by Name" JPQL query name. */
	public static final String GET_ALL_QUERY = "Locale.getAll";
	
	/** The id. */
	@Id
	@Column(name = "locale_id")
	private Long id;
	
	/** The name. */
	private String name;



Reproducible: Always

Steps to Reproduce:
1. Use the entities defined in the description
2. Use text based or criteria based JPQL query
3. Execute the query
Comment 1 Mikhail Skotnikov CLA 2011-06-09 08:03:06 EDT
The same issue was reproduced in the version 2.2.0
Comment 2 Tom Ware CLA 2011-06-16 14:29:02 EDT
It is not clear to me what query you are running.  We have a number of tests that use subqueries with criteria API that pass.  The code you have supplied to create the query is incomplete - I cannot tell what the outer query is.(e.g. what does queryAccessor.getIntermediateQuery(); return?)  Please provide either a complete set of code to build a query that will fail with the noted exception or complete JPQL for a query that will fail.
Comment 3 Tom Ware CLA 2011-06-23 10:17:13 EDT
Setting target and priority.  See the following page for the meanings of these fields:

http://wiki.eclipse.org/EclipseLink/Development/Bugs/Guidelines

Note: You still have not provided complete code that allows us to build the query causing the problem.  That may hinder our attempts to recreate and fix this problem.

Community: Please vote for this bug if it is important to you.  Votes are one of the main criteria we use to determine which bugs to fix next.
Comment 4 Mikhail Skotnikov CLA 2011-06-23 14:03:38 EDT
The complete source code generating the query using Criteria API is rather broad and not easy to understand, so I put JPQL equivalent of what it tried to build:

SELECT a
FROM Armor a JOIN a.item i LEFT JOIN i.nameMsg m
ORDER BY (CASE WHEN EXISTS (SELECT lm.text
                            FROM m.localizations lm1
                            WHERE lm1.locale.id = :userLocale)
               THEN (SELECT lm.text
                            FROM m.localizations lm2
                            WHERE lm2.locale.id = :userLocale)
               WHEN EXISTS (SELECT lm.text
                            FROM m.localizations lm3
                            WHERE lm3.locale.id = :defLocale)
               THEN (SELECT lm.text
                            FROM m.localizations lm4
                            WHERE lm4.locale.id = :defLocale)
               ELSE m.name
           END)

I also noted a problem when a literal expression is used in any case expression part => it causes the infinite loop.

Again, all that was experienced when queries were built using Criteria API.

This particular query is not blocking, as it was re-written to call a PLSQL function encapsulating the subqueries, but the problem experienced may be encountered in another scenario if it is not analyzed.
Comment 5 Eclipse Webmaster CLA 2022-06-09 10:06:26 EDT
The Eclipselink project has moved to Github: https://github.com/eclipse-ee4j/eclipselink