diff --git a/src/main/java/org/cojen/tupl/table/expr/StandardFunctionFinder.java b/src/main/java/org/cojen/tupl/table/expr/StandardFunctionFinder.java index 22984b7d3..f147311d7 100644 --- a/src/main/java/org/cojen/tupl/table/expr/StandardFunctionFinder.java +++ b/src/main/java/org/cojen/tupl/table/expr/StandardFunctionFinder.java @@ -342,7 +342,7 @@ public FunctionApplier validate(List args, Map namedArgs, return null; } - Type originalType = args.get(0).type(); + Type originalType = args.isEmpty() ? BasicType.BOOLEAN : args.get(0).type(); Type valueType = originalType; return new StandardWindowFunctions.count(resultType, valueType, originalType, frame); diff --git a/src/main/java/org/cojen/tupl/table/expr/StandardWindowFunctions.java b/src/main/java/org/cojen/tupl/table/expr/StandardWindowFunctions.java index fcddb8a77..53b0c3c26 100644 --- a/src/main/java/org/cojen/tupl/table/expr/StandardWindowFunctions.java +++ b/src/main/java/org/cojen/tupl/table/expr/StandardWindowFunctions.java @@ -115,6 +115,18 @@ static final class count extends WindowFunction { super(resultType, valueType, originalType, frame); } + @Override + protected Variable evalArg(GroupContext context) { + if (context.args().isEmpty()) { + // TODO: Filling a buffer with "true" values isn't terribly efficient. Instead, + // use a simple counter value. It might be possible to implement a WindowBuffer + // which does this. + return context.methodMaker().var(boolean.class).set(true); + } else { + return super.evalArg(context); + } + } + @Override protected Variable compute(Variable bufferVar, Object frameStart, Object frameEnd) { return bufferVar.invoke("frameCount", frameStart, frameEnd); diff --git a/src/main/java/org/cojen/tupl/table/expr/ValueBuffer.java b/src/main/java/org/cojen/tupl/table/expr/ValueBuffer.java index b3eb31f70..28d92bab0 100644 --- a/src/main/java/org/cojen/tupl/table/expr/ValueBuffer.java +++ b/src/main/java/org/cojen/tupl/table/expr/ValueBuffer.java @@ -220,6 +220,13 @@ private static Class generateClass(Type type) { cm.public_(); + boolean addBridges = false; + + if (!type.isNumber() && !type.isPrimitive()) { + cm.extend(ValueBuffer.class); + addBridges = true; + } + cm.addField(arrayClass, "values").private_(); cm.addField(int.class, "first").private_(); cm.addField(int.class, "size").private_(); @@ -286,6 +293,11 @@ private static Class generateClass(Type type) { var valuesVar = mm.field("values").get(); var firstField = mm.field("first"); mm.return_(valuesVar.aget(ixVar(valuesVar, firstField, mm.param(0)))); + + if (addBridges) { + mm = cm.addMethod(Object.class, "get", int.class).public_().final_().bridge(); + mm.return_(mm.this_().invoke(clazz, "get", null, mm.param(0))); + } } { @@ -306,6 +318,11 @@ private static Class generateClass(Type type) { firstField.set(ixVar(valuesVar, firstVar, 1)); mm.field("size").inc(-1); mm.return_(valuesVar.aget(ixVar(valuesVar, firstVar, null))); + + if (addBridges) { + mm = cm.addMethod(Object.class, "removeFirst").public_().final_().bridge(); + mm.return_(mm.this_().invoke(clazz, "removeFirst", null)); + } } { @@ -324,8 +341,6 @@ private static Class generateClass(Type type) { if (type.isNumber()) { addNumericalMethods(cm, type); - } else if (!type.isPrimitive()) { - cm.extend(ValueBuffer.class); } return cm.finish(); diff --git a/src/main/java/org/cojen/tupl/table/expr/WindowFunction.java b/src/main/java/org/cojen/tupl/table/expr/WindowFunction.java index 95ebb834a..ab3f9c642 100644 --- a/src/main/java/org/cojen/tupl/table/expr/WindowFunction.java +++ b/src/main/java/org/cojen/tupl/table/expr/WindowFunction.java @@ -219,7 +219,7 @@ public void accumulate(GroupContext context) { mm.field(mRemainingFieldName).inc(1); } - private Variable evalArg(GroupContext context) { + protected Variable evalArg(GroupContext context) { var valueVar = context.args().get(0).eval(true); if (!mValueType.equals(mOriginalType)) { diff --git a/src/test/java/org/cojen/tupl/table/expr/FunctionTest.java b/src/test/java/org/cojen/tupl/table/expr/FunctionTest.java index 4b85b71a1..89797cf73 100644 --- a/src/test/java/org/cojen/tupl/table/expr/FunctionTest.java +++ b/src/test/java/org/cojen/tupl/table/expr/FunctionTest.java @@ -157,6 +157,13 @@ public void random() throws Exception { assertTrue(e.getMessage().contains("must be a number")); } + try { + Parser.parse(IdentityTable.THE, "{v = random(99999999999999999999999999999)}"); + fail(); + } catch (QueryException e) { + assertTrue(e.getMessage().contains("unsupported argument type")); + } + CompiledQuery q = parse ("{a = random(), b = random(), " + "c = random(100), d = random(1000L), e = random(10f), f = random(-100, 10d) " + @@ -186,6 +193,54 @@ public void random() throws Exception { double f = row.get_double("f"); assertTrue(rowStr, -100d <= f && f < 10d); } + + q = parse("{a = random(? + 0) - 100}").makeCompiledQuery(); + + try (Scanner s = q.newScanner(null, 100)) { + var row = (Row) s.row(); + var rowStr = row.toString(); + int a = row.get_int("a"); + assertTrue(rowStr, -100 <= a && a < 0); + } + } + + @Test + public void count() throws Exception { + try { + Parser.parse(IdentityTable.THE, "{v = count(1, 2)}"); + fail(); + } catch (QueryException e) { + assertTrue(e.getMessage().contains("at most 1 argument")); + } + + try { + Parser.parse(IdentityTable.THE, "{v = count()}"); + fail(); + } catch (QueryException e) { + assertTrue(e.getMessage().contains("requires grouping")); + } + + verify("{; v = count()}", "{v=1}"); + + Table table = fill(4); + + verify(table, "{; v = count()}", "{v=4}"); + verify(table, "{; v = count(name)}", "{v=4}"); + verify(table, "{; v = count(value)}", "{v=2}"); + verify(table, "{value; v = count()}", + "{value=value-1, v=1}", "{value=value-3, v=1}", "{value=null, v=2}"); + verify(table, "{value; +v = count(value)}", + "{value=null, v=0}", "{value=value-1, v=1}", "{value=value-3, v=1}"); + + verify(table, "{value; v = count(value, rows:..)}", + "{value=value-1, v=1}", "{value=value-3, v=1}", + "{value=null, v=0}", "{value=null, v=0}"); + + verify(table, "{id = (id + 1) / 2; v = count(coalesce(value, 'hello'), rows:..0)}", + "{id=1, v=1}", "{id=1, v=2}", "{id=2, v=1}", "{id=2, v=2}"); + + verify(table, "{id = (id + 1) / 2; v = count(rows:0..)}", + "{id=1, v=2}", "{id=1, v=1}", "{id=2, v=2}", "{id=2, v=1}"); } private static RelationExpr parse(String query) {