Revert "Remove support for @Query ORDER BY ASC / DESC"
This reverts commit cf066607cacb6f1e182812e422b9115735a46e1d.
Change-Id: I24668bc0580625529a5326cbcde219db675f7b30
diff --git a/src/main/antlr/com/google/gwtorm/schema/Query.g b/src/main/antlr/com/google/gwtorm/schema/Query.g
index f3c5e8f..105d351 100644
--- a/src/main/antlr/com/google/gwtorm/schema/Query.g
+++ b/src/main/antlr/com/google/gwtorm/schema/Query.g
@@ -31,6 +31,8 @@
ID;
PLACEHOLDER;
COMMA;
+ ASC;
+ DESC;
LIMIT;
CONSTANT_INTEGER;
CONSTANT_STRING;
@@ -147,7 +149,17 @@
;
orderBy
- : ORDER^ BY! field (COMMA! field)*
+ : ORDER^ BY! fieldSort (COMMA! fieldSort)*
+ ;
+
+fieldSort
+ : field sortDirection^
+ | field -> ^(ASC field)
+ ;
+
+sortDirection
+ : ASC
+ | DESC
;
limit
@@ -200,6 +212,8 @@
ORDER: 'ORDER' ;
BY: 'BY' ;
AND: 'AND' ;
+ASC: 'ASC' ;
+DESC: 'DESC' ;
LIMIT: 'LIMIT' ;
TRUE: 'true' ;
FALSE: 'false' ;
diff --git a/src/main/java/com/google/gwtorm/client/Query.java b/src/main/java/com/google/gwtorm/client/Query.java
index f2d99bc..4a0ca9e 100644
--- a/src/main/java/com/google/gwtorm/client/Query.java
+++ b/src/main/java/com/google/gwtorm/client/Query.java
@@ -35,7 +35,7 @@
*
* <pre>
* [WHERE <condition> [AND <condition> ...]]
- * [ORDER BY <property> [, <property> ...]]
+ * [ORDER BY <property> [ASC | DESC] [, <property> [ASC | DESC] ...]]
* [LIMIT { <count> | ? }]
*
* <condition> := <property> { < | <= | > | >= | = } <value>
diff --git a/src/main/java/com/google/gwtorm/nosql/AccessGen.java b/src/main/java/com/google/gwtorm/nosql/AccessGen.java
index 5344e1e..2049c39 100644
--- a/src/main/java/com/google/gwtorm/nosql/AccessGen.java
+++ b/src/main/java/com/google/gwtorm/nosql/AccessGen.java
@@ -306,7 +306,7 @@
final QueryCGS cgs =
new QueryCGS(mv, new Type[] {argType}, pCols, new int[] {2}, 1);
for (ColumnModel f : pCols) {
- IndexFunctionGen.encodeField(f, mv, cgs);
+ IndexFunctionGen.encodeField(new QueryModel.OrderBy(f, false), mv, cgs);
}
mv.visitInsn(RETURN);
@@ -508,7 +508,7 @@
private void encodeField(Tree node, MethodVisitor mv, QueryCGS cgs)
throws OrmException {
ColumnModel f = ((QueryParser.Column) node.getChild(0)).getField();
- IndexFunctionGen.encodeField(f, mv, cgs);
+ IndexFunctionGen.encodeField(new QueryModel.OrderBy(f, false), mv, cgs);
}
private List<Tree> compareOpsOnly(Tree node) throws OrmException {
diff --git a/src/main/java/com/google/gwtorm/nosql/IndexFunctionGen.java b/src/main/java/com/google/gwtorm/nosql/IndexFunctionGen.java
index 15dfae4..ce69b99 100644
--- a/src/main/java/com/google/gwtorm/nosql/IndexFunctionGen.java
+++ b/src/main/java/com/google/gwtorm/nosql/IndexFunctionGen.java
@@ -31,6 +31,7 @@
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@@ -45,7 +46,7 @@
private final GeneratedClassLoader classLoader;
private final QueryModel query;
- private final List<ColumnModel> myFields;
+ private final List<QueryModel.OrderBy> myFields;
private final Class<T> pojo;
private final Type pojoType;
@@ -59,15 +60,16 @@
classLoader = loader;
query = qm;
- myFields = new ArrayList<ColumnModel>();
+ myFields = new ArrayList<QueryModel.OrderBy>();
// Only add each parameter column once, but in the order used.
// This avoids a range test on the same column from duplicating
// the data in the index record.
//
for (ColumnModel m : leaves(query.getParameters())) {
- if (!myFields.contains(m)) {
- myFields.add(m);
+ QueryModel.OrderBy o = new QueryModel.OrderBy(m, false);
+ if (!myFields.contains(o)) {
+ myFields.add(o);
}
}
@@ -75,11 +77,12 @@
// add anything else onto the end.
//
int p = 0;
- Iterator<ColumnModel> orderby = leaves(query.getOrderBy()).iterator();
+ Iterator<QueryModel.OrderBy> orderby = orderByLeaves(query.getOrderBy()).iterator();
while (p < myFields.size() && orderby.hasNext()) {
- ColumnModel c = orderby.next();
- if (!myFields.get(p).equals(c)) {
- myFields.add(c);
+ QueryModel.OrderBy o = orderby.next();
+ ColumnModel c = o.column;
+ if (!myFields.get(p).equals(o)) {
+ myFields.add(o);
break;
}
p++;
@@ -92,7 +95,7 @@
pojoType = Type.getType(pojo);
}
- private List<ColumnModel> leaves(List<ColumnModel> in) {
+ private static List<ColumnModel> leaves(List<ColumnModel> in) {
ArrayList<ColumnModel> r = new ArrayList<ColumnModel>(in.size());
for (ColumnModel m : in) {
if (m.isNested()) {
@@ -104,6 +107,20 @@
return r;
}
+ private static List<QueryModel.OrderBy> orderByLeaves(List<QueryModel.OrderBy> in) {
+ ArrayList<QueryModel.OrderBy> r = new ArrayList<QueryModel.OrderBy>(in.size());
+ for (QueryModel.OrderBy m : in) {
+ if (m.column.isNested()) {
+ for (ColumnModel c : m.column.getAllLeafColumns()) {
+ r.add(new QueryModel.OrderBy(c, m.descending));
+ }
+ } else {
+ r.add(m);
+ }
+ }
+ return r;
+ }
+
IndexFunction<T> create() throws OrmException {
init();
implementConstructor();
@@ -183,7 +200,9 @@
mv.visitVarInsn(ASTORE, 1);
Set<ColumnModel> checked = new HashSet<ColumnModel>();
- checkNotNullFields(myFields, checked, mv, cgs);
+ for (QueryModel.OrderBy orderby : myFields) {
+ checkNotNullFields(Collections.singleton(orderby.column), checked, mv, cgs);
+ }
final Tree parseTree = query.getParseTree();
if (parseTree != null) {
@@ -362,11 +381,11 @@
mv.visitEnd();
}
- static void encodeFields(final Collection<ColumnModel> myFields,
+ static void encodeFields(Collection<QueryModel.OrderBy> myFields,
final MethodVisitor mv, final EncodeCGS cgs) throws OrmException {
- Iterator<ColumnModel> i = myFields.iterator();
+ Iterator<QueryModel.OrderBy> i = myFields.iterator();
while (i.hasNext()) {
- ColumnModel f = i.next();
+ QueryModel.OrderBy f = i.next();
encodeScalar(f, mv, cgs);
if (i.hasNext()) {
cgs.delimiter();
@@ -374,20 +393,22 @@
}
}
- static void encodeField(ColumnModel f, final MethodVisitor mv,
+ static void encodeField(QueryModel.OrderBy f, final MethodVisitor mv,
final EncodeCGS cgs) throws OrmException {
- if (f.isNested()) {
- encodeFields(f.getAllLeafColumns(), mv, cgs);
+ if (f.column.isNested()) {
+ encodeFields(orderByLeaves(Collections.singletonList(f)), mv, cgs);
} else {
encodeScalar(f, mv, cgs);
}
}
- private static void encodeScalar(final ColumnModel f, final MethodVisitor mv,
+ private static void encodeScalar(QueryModel.OrderBy f, final MethodVisitor mv,
final EncodeCGS cgs) throws OrmException {
- cgs.setFieldReference(f);
+ String method = f.descending ? "desc" : "add";
+ ColumnModel c = f.column;
+ cgs.setFieldReference(c);
- switch (Type.getType(f.getPrimitiveType()).getSort()) {
+ switch (Type.getType(c.getPrimitiveType()).getSort()) {
case Type.BOOLEAN:
case Type.BYTE:
case Type.SHORT:
@@ -397,7 +418,7 @@
cgs.pushFieldValue();
mv.visitInsn(I2L);
mv.visitMethodInsn(INVOKEVIRTUAL, indexKeyBuilder.getInternalName(),
- "add", Type.getMethodDescriptor(Type.VOID_TYPE,
+ method, Type.getMethodDescriptor(Type.VOID_TYPE,
new Type[] {Type.LONG_TYPE}));
break;
@@ -405,47 +426,47 @@
cgs.pushBuilder();
cgs.pushFieldValue();
mv.visitMethodInsn(INVOKEVIRTUAL, indexKeyBuilder.getInternalName(),
- "add", Type.getMethodDescriptor(Type.VOID_TYPE,
+ method, Type.getMethodDescriptor(Type.VOID_TYPE,
new Type[] {Type.LONG_TYPE}));
break;
case Type.ARRAY:
case Type.OBJECT: {
- if (f.getPrimitiveType() == byte[].class) {
+ if (c.getPrimitiveType() == byte[].class) {
cgs.pushBuilder();
cgs.pushFieldValue();
mv.visitMethodInsn(INVOKEVIRTUAL, indexKeyBuilder.getInternalName(),
- "add", Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {Type
+ method, Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {Type
.getType(byte[].class)}));
- } else if (f.getPrimitiveType() == String.class) {
+ } else if (c.getPrimitiveType() == String.class) {
cgs.pushBuilder();
cgs.pushFieldValue();
mv.visitMethodInsn(INVOKEVIRTUAL, indexKeyBuilder.getInternalName(),
- "add", Type.getMethodDescriptor(Type.VOID_TYPE,
+ method, Type.getMethodDescriptor(Type.VOID_TYPE,
new Type[] {string}));
- } else if (f.getPrimitiveType() == java.sql.Timestamp.class
- || f.getPrimitiveType() == java.util.Date.class
- || f.getPrimitiveType() == java.sql.Date.class) {
+ } else if (c.getPrimitiveType() == java.sql.Timestamp.class
+ || c.getPrimitiveType() == java.util.Date.class
+ || c.getPrimitiveType() == java.sql.Date.class) {
cgs.pushBuilder();
cgs.pushFieldValue();
- String tsType = Type.getType(f.getPrimitiveType()).getInternalName();
+ String tsType = Type.getType(c.getPrimitiveType()).getInternalName();
mv.visitMethodInsn(INVOKEVIRTUAL, tsType, "getTime", Type
.getMethodDescriptor(Type.LONG_TYPE, new Type[] {}));
mv.visitMethodInsn(INVOKEVIRTUAL, indexKeyBuilder.getInternalName(),
- "add", Type.getMethodDescriptor(Type.VOID_TYPE,
+ method, Type.getMethodDescriptor(Type.VOID_TYPE,
new Type[] {Type.LONG_TYPE}));
} else {
- throw new OrmException("Type " + f.getPrimitiveType()
- + " not supported for field " + f.getPathToFieldName());
+ throw new OrmException("Type " + c.getPrimitiveType()
+ + " not supported for field " + c.getPathToFieldName());
}
break;
}
default:
- throw new OrmException("Type " + f.getPrimitiveType()
- + " not supported for field " + f.getPathToFieldName());
+ throw new OrmException("Type " + c.getPrimitiveType()
+ + " not supported for field " + c.getPathToFieldName());
}
}
diff --git a/src/main/java/com/google/gwtorm/nosql/IndexKeyBuilder.java b/src/main/java/com/google/gwtorm/nosql/IndexKeyBuilder.java
index 8c6d9f4..2bed6ce 100644
--- a/src/main/java/com/google/gwtorm/nosql/IndexKeyBuilder.java
+++ b/src/main/java/com/google/gwtorm/nosql/IndexKeyBuilder.java
@@ -106,6 +106,23 @@
}
}
+ public void desc(byte[] bin, int pos, int cnt) {
+ while (0 < cnt--) {
+ int b = 0xff - (bin[pos++] & 0xff);
+ if (b == 0x00) {
+ buf.write(0x00);
+ buf.write(0xff);
+
+ } else if (b == 0xff) {
+ buf.write(0xff);
+ buf.write(0x00);
+
+ } else {
+ buf.write(b);
+ }
+ }
+ }
+
/**
* Add a raw sequence of bytes.
* <p>
@@ -118,6 +135,10 @@
add(bin, 0, bin.length);
}
+ public void desc(byte[] bin) {
+ desc(bin, 0, bin.length);
+ }
+
/**
* Encode a string into UTF-8 and append as a sequence of bytes.
*
@@ -131,6 +152,14 @@
}
}
+ public void desc(String str) {
+ try {
+ desc(str.getBytes("UTF-8"));
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException("JVM does not support UTF-8", e);
+ }
+ }
+
/**
* Add a single character as though it were part of a UTF-8 string.
*
@@ -149,6 +178,10 @@
}
}
+ public void desc(char ch) {
+ desc(Character.toString(ch));
+ }
+
/**
* Add an integer value as a big-endian variable length integer.
*
@@ -165,6 +198,10 @@
buf.write(t, i - 1, t.length - i + 1);
}
+ public void desc(long val) {
+ add(~val);
+ }
+
/**
* Add a byte array as-is, without escaping.
* <p>
diff --git a/src/main/java/com/google/gwtorm/schema/QueryModel.java b/src/main/java/com/google/gwtorm/schema/QueryModel.java
index f557a0f..5cec721 100644
--- a/src/main/java/com/google/gwtorm/schema/QueryModel.java
+++ b/src/main/java/com/google/gwtorm/schema/QueryModel.java
@@ -75,14 +75,41 @@
return r;
}
- public List<ColumnModel> getOrderBy() {
- final ArrayList<ColumnModel> r = new ArrayList<ColumnModel>();
+ public static class OrderBy {
+ public final ColumnModel column;
+ public final boolean descending;
+
+ public OrderBy(ColumnModel column, boolean desc) {
+ this.column = column;
+ this.descending = desc;
+ }
+
+ @Override
+ public int hashCode() {
+ return column.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof OrderBy) {
+ OrderBy o = (OrderBy) other;
+ return column.equals(o.column) && descending == o.descending;
+ }
+ return false;
+ }
+ }
+
+ public List<OrderBy> getOrderBy() {
+ ArrayList<OrderBy> r = new ArrayList<OrderBy>();
if (parsedQuery != null) {
Tree node = findOrderBy(parsedQuery);
if (node != null) {
for (int i = 0; i < node.getChildCount(); i++) {
- final Tree id = node.getChild(i);
- r.add(((QueryParser.Column) id).getField());
+ Tree sortOrder = node.getChild(i);
+ Tree id = sortOrder.getChild(0);
+ r.add(new OrderBy(
+ ((QueryParser.Column) id).getField(),
+ sortOrder.getType() == QueryParser.DESC));
}
}
}
@@ -279,7 +306,8 @@
case QueryParser.ORDER:
fmt.buf.append(" ORDER BY ");
for (int i = 0; i < node.getChildCount(); i++) {
- final Tree id = node.getChild(i);
+ final Tree sortOrder = node.getChild(i);
+ final Tree id = sortOrder.getChild(0);
if (i > 0) {
fmt.buf.append(',');
}
@@ -290,6 +318,9 @@
fmt.buf.append(fmt.tableAlias);
fmt.buf.append('.');
fmt.buf.append(cItr.next().getColumnName());
+ if (sortOrder.getType() == QueryParser.DESC) {
+ fmt.buf.append(" DESC");
+ }
if (cItr.hasNext()) {
fmt.buf.append(',');
}
@@ -298,6 +329,9 @@
fmt.buf.append(fmt.tableAlias);
fmt.buf.append('.');
fmt.buf.append(col.getColumnName());
+ if (sortOrder.getType() == QueryParser.DESC) {
+ fmt.buf.append(" DESC");
+ }
}
}
break;
diff --git a/src/test/java/com/google/gwtorm/data/PersonAccess.java b/src/test/java/com/google/gwtorm/data/PersonAccess.java
index 594d588..271250a 100644
--- a/src/test/java/com/google/gwtorm/data/PersonAccess.java
+++ b/src/test/java/com/google/gwtorm/data/PersonAccess.java
@@ -30,6 +30,10 @@
@Query("WHERE age > ? ORDER BY age")
ResultSet<TestPerson> olderThan(int age) throws OrmException;
+ @Query("WHERE age > ? ORDER BY name DESC")
+ ResultSet<TestPerson> olderThanDescByName(int age)
+ throws OrmException;
+
@Query("WHERE name = 'bob' LIMIT ?")
ResultSet<TestPerson> firstNBob(int n) throws OrmException;
diff --git a/src/test/java/com/google/gwtorm/schema/QueryParserTest.java b/src/test/java/com/google/gwtorm/schema/QueryParserTest.java
index 8eb9936..f9ef3c5 100644
--- a/src/test/java/com/google/gwtorm/schema/QueryParserTest.java
+++ b/src/test/java/com/google/gwtorm/schema/QueryParserTest.java
@@ -131,27 +131,33 @@
assertEquals(1, t.getChildCount());
final Tree a = t.getChild(0);
- assertEquals(QueryParser.ID, a.getType());
- assertTrue(a instanceof QueryParser.Column);
- assertEquals("a", a.getText());
+ assertEquals(QueryParser.ASC, a.getType());
+ assertEquals(1, a.getChildCount());
+ assertEquals(QueryParser.ID, a.getChild(0).getType());
+ assertTrue(a.getChild(0) instanceof QueryParser.Column);
+ assertEquals("a", a.getChild(0).getText());
}
public void testOrderByAB() throws QueryParseException {
- final Tree t = parse("ORDER BY a, b");
+ final Tree t = parse("ORDER BY a DESC, b ASC");
assertNotNull(t);
assertEquals(QueryParser.ORDER, t.getType());
assertEquals(2, t.getChildCount());
{
final Tree a = t.getChild(0);
- assertEquals(QueryParser.ID, a.getType());
- assertTrue(a instanceof QueryParser.Column);
- assertEquals("a", a.getText());
+ assertEquals(QueryParser.DESC, a.getType());
+ assertEquals(1, a.getChildCount());
+ assertEquals(QueryParser.ID, a.getChild(0).getType());
+ assertTrue(a.getChild(0) instanceof QueryParser.Column);
+ assertEquals("a", a.getChild(0).getText());
}
{
final Tree b = t.getChild(1);
- assertEquals(QueryParser.ID, b.getType());
- assertTrue(b instanceof QueryParser.Column);
- assertEquals("b", b.getText());
+ assertEquals(QueryParser.ASC, b.getType());
+ assertEquals(1, b.getChildCount());
+ assertEquals(QueryParser.ID, b.getChild(0).getType());
+ assertTrue(b.getChild(0) instanceof QueryParser.Column);
+ assertEquals("b", b.getChild(0).getText());
}
}
@@ -171,7 +177,10 @@
assertEquals(QueryParser.ORDER, o.getType());
assertEquals(1, o.getChildCount());
- final Tree aId = o.getChild(0);
+ final Tree a = o.getChild(0);
+ assertEquals(QueryParser.ASC, a.getType());
+ assertEquals(1, a.getChildCount());
+ final Tree aId = a.getChild(0);
assertEquals(QueryParser.ID, aId.getType());
assertTrue(aId instanceof QueryParser.Column);
assertEquals("a", aId.getText());
diff --git a/src/test/java/com/google/gwtorm/server/PhoneBookDbTestCase.java b/src/test/java/com/google/gwtorm/server/PhoneBookDbTestCase.java
index 55fd759..d87d54d 100644
--- a/src/test/java/com/google/gwtorm/server/PhoneBookDbTestCase.java
+++ b/src/test/java/com/google/gwtorm/server/PhoneBookDbTestCase.java
@@ -293,6 +293,21 @@
assertEquals(all.get(2).name(), r.get(1).name());
}
+ public void testFetchNotPerson() throws Exception {
+ final PhoneBookDb schema = openAndCreate();
+ final ArrayList<TestPerson> all = new ArrayList<TestPerson>();
+ all.add(new TestPerson(new TestPerson.Key("Bob"), 18));
+ all.add(new TestPerson(new TestPerson.Key("Mary"), 22));
+ all.add(new TestPerson(new TestPerson.Key("Zak"), 33));
+ schema.people().insert(all);
+
+ final List<TestPerson> r =
+ schema.people().olderThanDescByName(18).toList();
+ assertEquals(2, r.size());
+ assertEquals(all.get(2).name(), r.get(0).name());
+ assertEquals(all.get(1).name(), r.get(1).name());
+ }
+
public void testBooleanType() throws Exception {
final PhoneBookDb schema = openAndCreate();
final TestPerson bob = new TestPerson(new TestPerson.Key("Bob"), 18);