kundera icon indicating copy to clipboard operation
kundera copied to clipboard

Selecting only embeddedId property with JPQL fails

Open AntonYudin opened this issue 7 years ago • 26 comments

The following JPQL query:

SELECT e.id.identity, e.id.type, e.id.created FROM Event e gets translated into the following CQL:

SELECT "identity","type","created" FROM "events" LIMIT 100 But the returned List contains Event objects instead of arrays of Object List<Object[]> list = query.getResultList(); The list is returned as a list of Event objects.

Another JPQL query that tries to get the whole key as an Object: SELECT e.id FROM Event e gets translated into the following CQL: SELECT "id" FROM "events" LIMIT 100 and obviously fails because "id" is a compound/EmbeddedId field.

Here is the definition of the entity and the embedded class.

@Entity
@Table(name = "events")
@Inheritance(strategy = javax.persistence.InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "typed")
public class Event implements java.io.Serializable {

        @EmbeddedId
        @OrderBy("id.type ASC, id.created DESC")
        private EventId id;

        public EventId getId() {
                return id;
        }

        public void setId(final EventId value) {
                id = value;
        }
}
@Embeddable
public class EventId implements java.io.Serializable {


        @Column
        private java.util.UUID identity = null;

        public java.util.UUID getIdentity() {
                return identity;
        }

        public void setIdentity(final java.util.UUID value) {
                identity = value;
        }

        @Column
        private String type = null;

        public String getType() {
                return type;
        }

        public void setType(final String value) {
                type = value;
        }

        @Column
        private java.util.UUID created = null;

        public java.util.UUID getCreated() {
                return created;
        }

        public void setCreated(final java.util.UUID value) {
                created = value;
        }
}

Thanks.

AntonYudin avatar Jul 07 '17 00:07 AntonYudin

Hi @AntonYudin

Kundera returns only POJO objects loaded by data in case of select queries.

I could not replicate the issue with select e.id from Event e query. It is working fine at my end.

-Karthik

karthikprasad13 avatar Jul 07 '17 13:07 karthikprasad13

Are you using Cassandra as well?

AntonYudin avatar Jul 07 '17 13:07 AntonYudin

@AntonYudin

Yes.. which version are you using? Kundera and cassandra?

karthikprasad13 avatar Jul 07 '17 13:07 karthikprasad13

I'm using Kundera 3.8 and Cassandra 3.10. Thanks.

AntonYudin avatar Jul 07 '17 14:07 AntonYudin

@AntonYudin

Cool. I verified at my end with Kundera 3.9 and Cassandra 3.9 and its working. Can you try adding CQL3 version in EntityManagerFactory like below:

    Map<String, String> props = new HashMap<>();
    props.put(CassandraConstants.CQL_VERSION, CassandraConstants.CQL_VERSION_3_0);
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("test_pu", props);

karthikprasad13 avatar Jul 07 '17 14:07 karthikprasad13

I'm running this application under Wildfly 10.1.0. This is the way I'm accessing EntityManagerFactory:

@javax.persistence.PersistenceUnit(unitName = "default")
private javax.persistence.EntityManagerFactory entityManagerFactory;
final javax.persistence.EntityManager entityManager = entityManagerFactory.createEntityManager();
entityManager.setProperty("cql.version", "3.0.0");

Here is the persistence.xml:

<?xml version="1.0" encoding="UTF-8"?>

<persistence
	xmlns="http://java.sun.com/xml/ns/persistence"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
	version="2.0"
>
	<persistence-unit
		name="default"
		transaction-type="RESOURCE_LOCAL"
	>
		<provider>com.impetus.kundera.KunderaPersistence</provider>

		<class>com.antonyudin.configmanager.items.model.Event</class>
		<class>com.antonyudin.configmanager.items.model.EventId</class>
		<class>com.antonyudin.configmanager.items.model.ItemAdded</class>
		<class>com.antonyudin.configmanager.items.model.ItemRemoved</class>
		<class>com.antonyudin.configmanager.items.model.NameChanged</class>
		<class>com.antonyudin.configmanager.items.model.NameChangedIndex</class>
		<class>com.antonyudin.configmanager.items.model.NameChangedIndexId</class>
		<class>com.antonyudin.configmanager.authentication.model.Credentials</class>
		<exclude-unlisted-classes>true</exclude-unlisted-classes>
		<properties>			
			<property name="kundera.show.query" value="true"/>
			<property name="kundera.nodes" value="127.0.0.1"/>
			<property name="kundera.port" value="9042"/>
			<property name="kundera.keyspace" value="configmanager"/>
			<property name="kundera.dialect" value="cassandra"/>
			<property name="kundera.ddl.auto.prepare" value="create-drop" />
			<property
				name="kundera.client.lookup.class"
				value="com.impetus.kundera.client.cassandra.dsdriver.DSClientFactory"
			/>
		</properties>		
	</persistence-unit>
</persistence>

May be I need to specify the version of CQL somewhere else?

AntonYudin avatar Jul 07 '17 14:07 AntonYudin

@AntonYudin

I observed that you are using com.impetus.kundera.client.cassandra.dsdriver.DSClientFactory. Did you chose this for a particular reason?

If not, I suggest you to change it to com.impetus.client.cassandra.thrift.ThriftClientFactory and port to 9160

-Karthik

karthikprasad13 avatar Jul 07 '17 14:07 karthikprasad13

I was using information from the following document: https://github.com/impetus-opensource/Kundera/wiki/Datastax-java-driver-support-for-Cassandra-using-Kundera

It sounds like DataStax driver overcomes "limitations" of the Thrift client, but I'm not sure what is actually better - just judging by this document. I have also read in some other articles on the web that were suggesting a DataStax driver over Thrift client for java.

I'm about to try to run the same app using Thrift client library.

Thanks.

AntonYudin avatar Jul 07 '17 14:07 AntonYudin

It looks like Thrift is disabled by default in cassandra. I'm in the process of enabling it, but it looks as DataStax should be the preferred way? Is it?

AntonYudin avatar Jul 07 '17 14:07 AntonYudin

With Thrift Client I get a very similar error:

017-07-07 10:57:36,609 INFO  [com.antonyudin.configmanager.items.logic.ItemsImplementation] (default task-25) entityManager: [com.impetus.kundera.persistence.EntityManagerImpl@50896da9]
2017-07-07 10:57:36,610 INFO  [com.antonyudin.configmanager.items.logic.ItemsImplementation] (default task-25) query: [SELECT e.id FROM Event e]
2017-07-07 10:57:36,611 INFO  [stdout] (default task-25) SELECT "id" FROM "events" LIMIT 100

2017-07-07 10:57:36,613 ERROR [com.impetus.client.cassandra.CassandraClientBase] (default task-25) Error while executing query SELECT "id" FROM "events" LIMIT 100
2017-07-07 10:57:36,614 ERROR [com.impetus.client.cassandra.CassandraClientBase] (default task-25) Error during executing query SELECT "id" FROM "events" LIMIT 100, Caused by: com.impetus.kundera.KunderaException: InvalidRequestException(why:Undefined column name id) .
2017-07-07 10:57:36,614 ERROR [com.impetus.client.cassandra.CassandraClientBase] (default task-25) Error while executing native CQL query Caused by {}.: javax.persistence.PersistenceException: com.impetus.kundera.KunderaException: InvalidRequestException(why:Undefined column name id)
	at com.impetus.client.cassandra.CassandraClientBase.executeCQLQuery(CassandraClientBase.java:2071)

AntonYudin avatar Jul 07 '17 14:07 AntonYudin

These are my logs:

18:34:03.462 [main] INFO  c.i.k.q.QueryImpl - On getResultList() executing query: Select e.id from Event e
18:34:03.462 [main] DEBUG c.i.c.c.q.CassQuery - Populating entities for Cassandra query Select e.id from Event e.
18:34:03.463 [main] DEBUG c.i.c.c.CassandraClientBase - Executing cql query SELECT "identity","type","created" FROM "events" LIMIT 100.
18:34:03.463 [main] DEBUG c.i.c.c.CassandraClientBase - Executing query SELECT "identity","type","created" FROM "events" LIMIT 100.

Can you mail your sample project to [email protected] ? Meanwhile I will verify it on DataStax also.

-Karthik

karthikprasad13 avatar Jul 07 '17 15:07 karthikprasad13

@AntonYudin

I verified on DSClient (DataStax client) also and its working fine.

-Karthik

karthikprasad13 avatar Jul 07 '17 15:07 karthikprasad13

How do you create an entityManager? I'm using injection in a Stateless session bean under wildfly. Could that be an issue?

AntonYudin avatar Jul 07 '17 15:07 AntonYudin

AFAIK it shouldn't be the issue. I will try to replicate it if you can share your sample project.

-Karthik

karthikprasad13 avatar Jul 07 '17 15:07 karthikprasad13

Unfortunately I cannot share the whole project - it belong to the place where I work. I will try to isolate the problem into a separate project. But you would need a wildfly running.

AntonYudin avatar Jul 07 '17 15:07 AntonYudin

I will try to setup wildfly in my machine. Please send a basic sample of your project.

-Karthik

karthikprasad13 avatar Jul 07 '17 15:07 karthikprasad13

I think I just localized the problem. If I remove from the project any entity that extends "Event", everything works as expected. I do that by commenting out "<class>" in the persistence.xml. If I enable just one more entity that extends Event, I get the problem when Kundera treats id as a single column instead of a composite key.

Could you please try to add another entity that extends "Event"?

Thanks.

AntonYudin avatar Jul 07 '17 15:07 AntonYudin

I think I found it.

If the base class that uses @EmbeddedId and @Inheritance is listed first in the persistence.xml, kundera treats a composite key as a single column. If a child class that extends the base class is listed first and then the base class, there is no problem.

So it looks like when kundera generates meta data for the entities, it wants to see a child class listed first and then the base class in persistence.xml.

AntonYudin avatar Jul 07 '17 17:07 AntonYudin

SELECT e.id FROM Event e returns a List of Event objects instead of EventId.

AntonYudin avatar Jul 07 '17 17:07 AntonYudin

Another interesting thing. If I use createQuery("SELECT e.id FROM Event e", EventId.class) instead of a simple createQuery("SELECT e.id FROM Event e"), I get the following error message:

Mismatch in expected return type. Expected:class com.antonyudin.configmanager.items.model.EventId But actual class is:class com.antonyudin.configmanager.items.model.Event

AntonYudin avatar Jul 07 '17 17:07 AntonYudin

@AntonYudin

Kundera returns the Event objects only as it is the entity that is mapped to the table in Cassandra. In case of selecting specific columns, it returns the main object with selected fields populated.

-Karthik

karthikprasad13 avatar Jul 10 '17 07:07 karthikprasad13

According to the specification and my experience with other JPA implementations, the SELECT clause allows one to select not only entities, but separate fields and even use a constructor to build a custom object out of the selected fields. In my case I was trying to select an id field that is an embedded object. The query should return this embedded object and not the whole entity.

It looks that right now Kundera ignores the specified result list in the SELECT clause and simply returns an entity that was specified in the FROM clause.

Here is the spec:

4.8 SELECT Clause The SELECT clause denotes the query result. More than one value may be returned from the SELECT clause of a query. The SELECT clause can contain one or more of the following elements: an identification variable that ranges over an abstract schema type, a single-valued path expression, a scalar expression, an aggregate expression, a constructor expression. ... For example: SELECT c.id, c.status FROM Customer c JOIN c.orders o WHERE o.count > 100 In the following example, videoInventory is a Map from the entity Movie to the number of copies in stock: SELECT v.location.street, KEY(i).title, VALUE(i) FROM VideoStore v JOIN v.videoInventory i WHERE v.location.zipcode = '94301' AND VALUE(i) > 0

And here is the spec talking about the type of the result:

4.8.1 Result Type of the SELECT Clause The type of the query result specified by the SELECT clause of a query is an entity abstract schema type, a state field type, the result of a scalar expression, the result of an aggregate function, the result of a construction operation, or some sequence of these.

Thanks.

AntonYudin avatar Jul 10 '17 14:07 AntonYudin

@AntonYudin Correct. With Kundera scalar values are returned in form of Kundera PoJos having all other values as Null(the one not part of select clause). In this case other than embedded id object everything else would be null.

mevivs avatar Jul 26 '17 07:07 mevivs

Is there a plan to make Kundera follow the JPA spec on this and make it compatible with other JPA implementations?

AntonYudin avatar Jul 26 '17 14:07 AntonYudin

@AntonYudin

Since this is not a must have feature for most of the users, we do not have it in our roadmap as of now. Please let us know how important this is for you and we can discuss more on it.

Also, please check our contribution guidelines if you are interested in it.

-Karthik

karthikprasad13 avatar Jul 31 '17 10:07 karthikprasad13

This issue is mostly to be complaint with the spec and to be compatible with other JPA providers. It is very common to use JPQL or Criteria API to select only specific fields and not the whole entity.

Right now one could have a JPQL query: "SELECT e.id FROM Entity e" and it would be incompatible with other JPA providers. Kundera will return a List<Entity> and other providers will return a List<ID>.

A simple scenario where one would use a query like that: a table with groups (UUID id, String name, String description, List<String> permissions, UUID parent). Now there is a User dialog that allows one to select which groups the user belongs to. To populate a list of available groups one could select only ID and Name - "SELECT e.id, e.name FROM Groups e". That is more efficient than selecting whole entity with all the columns.

AntonYudin avatar Jul 31 '17 14:07 AntonYudin