Skip to content

Commit

Permalink
Bug: 'ORDER BY' HQL clauses do not support embedded or joined properties
Browse files Browse the repository at this point in the history
According to the HQL syntax:
> The implicit form does not use the join keyword. Instead, the associations are "dereferenced" using dot-notation. implicit joins can appear in any of the HQL clauses. implicit join result in inner joins in the resulting SQL statement.
>
>   from Cat as cat where cat.mate.name like '%s%'

See: https://docs.jboss.org/hibernate/orm/3.3/reference/en/html/queryhql.html#queryhql-joins-forms

This means that implicit joins such as `cat.mate.name` can appear in a `where` clause, as shown
above, but they can also appear in an `order by` clause as well. Currently this is not supported in
HibernateD. Unfortunately, this means that ordering by a column that is in an `@Embeddable` class
results in a syntax error.

To support this capability, the same logic used to perform implicit joins for `where` clauses was
used during an `order by` clause as well.

Change Summary:
- embeddedtest.d: Added additional tests for HQL queries using `order by`.
- query.d:
  - Modify `parseOrderByClauseItem` to first call `convertFields` to combine tokens of the
    form `[Alias.]Ident[.Ident]...` into a single `Field` token.
  - Remove now obviated logic and checks from `parseOrderByClauseItem`.
  - Modify `convertFields` error messages so that they are no longer specific to `where` clauses.
  - Add comments to `convertFields` to make the algorithm easier to understand.

The whitespace in query.d is still a mess, but that was not cleaned up in this PR.
  • Loading branch information
vnayar authored and SingingBush committed Apr 19, 2024
1 parent 9ce43e6 commit d9bafb8
Show file tree
Hide file tree
Showing 2 changed files with 184 additions and 136 deletions.
219 changes: 123 additions & 96 deletions hdtest/source/embeddedtest.d
Original file line number Diff line number Diff line change
Expand Up @@ -7,110 +7,137 @@ import hibernatetest : HibernateTest;

@Embeddable
class Address {
string street;
string city;
string street;
string city;
}

class Customer {
@Id @Generated
long cid;
@Id @Generated
long cid;

string name;
string name;

Address shippingAddress;
Address shippingAddress;

@Embedded("billing")
Address billingAddress;
@Embedded("billing")
Address billingAddress;
}

class EmbeddedTest : HibernateTest {
override
EntityMetaData buildSchema() {
return new SchemaInfoImpl!(Customer, Address);
}

@Test("embedded.creation")
void creationTest() {
Session sess = sessionFactory.openSession();
scope(exit) sess.close();

Customer c1 = new Customer();
c1.name = "Kickflip McOllie";
c1.shippingAddress = new Address();
c1.shippingAddress.street = "1337 Rad Street";
c1.shippingAddress.city = "Awesomeville";
c1.billingAddress = new Address();
c1.billingAddress.street = "101001 Robotface";
c1.billingAddress.city = "Lametown";

long c1Id = sess.save(c1).get!long;
assert(c1Id > 0);
}

@Test("embedded.read")
void readTest() {
Session sess = sessionFactory.openSession();
scope(exit) sess.close();

auto r1 = sess.createQuery("FROM Customer WHERE shippingAddress.city = :City")
.setParameter("City", "Awesomeville");
Customer c1 = r1.uniqueResult!Customer();
assert(c1 !is null);
assert(c1.shippingAddress.street == "1337 Rad Street");

auto r2 = sess.createQuery("FROM Customer WHERE billingAddress.city = :City")
.setParameter("City", "Lametown");
Customer c2 = r2.uniqueResult!Customer();
assert(c2 !is null);
assert(c2.billingAddress.street == "101001 Robotface");
}

@Test("embedded.update")
void updateTest() {
Session sess = sessionFactory.openSession();

auto r1 = sess.createQuery("FROM Customer WHERE billingAddress.city = :City")
.setParameter("City", "Lametown");
Customer c1 = r1.uniqueResult!Customer();
assert(c1 !is null);

c1.billingAddress.street = "17 Neat Street";
sess.update(c1);

// Create a new session to prevent caching.
sess.close();
sess = sessionFactory.openSession();

r1 = sess.createQuery("FROM Customer WHERE billingAddress.city = :City")
.setParameter("City", "Lametown");
c1 = r1.uniqueResult!Customer();
assert(c1 !is null);
assert(c1.billingAddress.street == "17 Neat Street");

sess.close();
}

@Test("embedded.delete")
void deleteTest() {
Session sess = sessionFactory.openSession();

auto r1 = sess.createQuery("FROM Customer WHERE billingAddress.city = :City")
.setParameter("City", "Lametown");
Customer c1 = r1.uniqueResult!Customer();
assert(c1 !is null);

sess.remove(c1);

// Create a new session to prevent caching.
sess.close();
sess = sessionFactory.openSession();

r1 = sess.createQuery("FROM Customer WHERE billingAddress.city = :City")
.setParameter("City", "Lametown");
c1 = r1.uniqueResult!Customer();
assert(c1 is null);

sess.close();
}
override
EntityMetaData buildSchema() {
return new SchemaInfoImpl!(Customer, Address);
}

@Test("embedded.creation")
void creationTest() {
Session sess = sessionFactory.openSession();
scope(exit) sess.close();

Customer c1 = new Customer();
c1.name = "Kickflip McOllie";
c1.shippingAddress = new Address();
c1.shippingAddress.street = "1337 Rad Street";
c1.shippingAddress.city = "Awesomeville";
c1.billingAddress = new Address();
c1.billingAddress.street = "101001 Robotface";
c1.billingAddress.city = "Lametown";

long c1Id = sess.save(c1).get!long;
assert(c1Id > 0);

Customer c2 = new Customer();
c2.name = "Jumpy Bunny";
c2.shippingAddress = new Address();
c2.shippingAddress.street = "21 Grassy Knoll";
c2.shippingAddress.city = "Warrenton";
c2.billingAddress = new Address();
c2.billingAddress.street = "327 Industrial Way";
c2.billingAddress.city = "Megatropolis";
long c2Id = sess.save(c2).get!long;
assert(c2Id > 0);
}

@Test("embedded.read")
void readTest() {
Session sess = sessionFactory.openSession();
scope(exit) sess.close();

auto r1 = sess.createQuery("FROM Customer WHERE shippingAddress.city = :City")
.setParameter("City", "Awesomeville");
Customer c1 = r1.uniqueResult!Customer();
assert(c1 !is null);
assert(c1.shippingAddress.street == "1337 Rad Street");

auto r2 = sess.createQuery("FROM Customer WHERE billingAddress.city = :City")
.setParameter("City", "Lametown");
Customer c2 = r2.uniqueResult!Customer();
assert(c2 !is null);
assert(c2.billingAddress.street == "101001 Robotface");
}

@Test("embedded.read.query-order-by")
void readQueryOrderByTest() {
Session sess = sessionFactory.openSession();
scope(exit) sess.close();

auto r1 = sess.createQuery("FROM Customer ORDER BY shippingAddress.street DESC");
Customer[] customers = r1.list!Customer();
assert(customers.length == 2);
assert(customers[0].shippingAddress.street == "21 Grassy Knoll");

auto r2 = sess.createQuery("FROM Customer c ORDER BY c.billingAddress.street ASC");
customers = r2.list!Customer();
assert(customers.length == 2);
assert(customers[0].billingAddress.street == "101001 Robotface");
}

@Test("embedded.update")
void updateTest() {
Session sess = sessionFactory.openSession();

auto r1 = sess.createQuery("FROM Customer WHERE billingAddress.city = :City")
.setParameter("City", "Lametown");
Customer c1 = r1.uniqueResult!Customer();
assert(c1 !is null);

c1.billingAddress.street = "17 Neat Street";
sess.update(c1);

// Create a new session to prevent caching.
sess.close();
sess = sessionFactory.openSession();

r1 = sess.createQuery("FROM Customer WHERE billingAddress.city = :City")
.setParameter("City", "Lametown");
c1 = r1.uniqueResult!Customer();
assert(c1 !is null);
assert(c1.billingAddress.street == "17 Neat Street");

sess.close();
}

@Test("embedded.delete")
void deleteTest() {
Session sess = sessionFactory.openSession();

auto r1 = sess.createQuery("FROM Customer WHERE billingAddress.city = :City")
.setParameter("City", "Lametown");
Customer c1 = r1.uniqueResult!Customer();
assert(c1 !is null);

sess.remove(c1);

// Create a new session to prevent caching.
sess.close();
sess = sessionFactory.openSession();

r1 = sess.createQuery("FROM Customer WHERE billingAddress.city = :City")
.setParameter("City", "Lametown");
c1 = r1.uniqueResult!Customer();
assert(c1 is null);

sess.close();
}

}
Loading

0 comments on commit d9bafb8

Please sign in to comment.