From 115cae40385288345e1e7155ef68625cc1abe545 Mon Sep 17 00:00:00 2001 From: maciejlach Date: Wed, 24 Sep 2014 09:54:30 +0200 Subject: [PATCH] Redesign lambdas and projections handling Fixes #5: validate lambda expression upon construction --- CHANGELOG.txt | 3 +- README.md | 2 +- doc/Type-conversion.md | 44 ++++++++-- .../java/com/exxeleron/qjava/QFunction.java | 50 +++++++++++ .../java/com/exxeleron/qjava/QLambda.java | 69 +++++++-------- .../java/com/exxeleron/qjava/QProjection.java | 85 +++++++++++++++++++ .../java/com/exxeleron/qjava/QReader.java | 71 +++++++++------- src/main/java/com/exxeleron/qjava/QType.java | 20 ++++- .../java/com/exxeleron/qjava/QWriter.java | 29 ++++--- .../com/exxeleron/qjava/QExpressions.java | 2 +- .../java/com/exxeleron/qjava/TestQReader.java | 77 ++++++++++++++++- src/test/resources/QExpressionsFunctions.out | 22 +++++ 12 files changed, 375 insertions(+), 99 deletions(-) create mode 100644 src/main/java/com/exxeleron/qjava/QFunction.java create mode 100644 src/main/java/com/exxeleron/qjava/QProjection.java create mode 100644 src/test/resources/QExpressionsFunctions.out diff --git a/CHANGELOG.txt b/CHANGELOG.txt index eea8b85..11abf19 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,7 +1,8 @@ ------------------------------------------------------------------------------ - qJava 2.1.0 [TBA] + qJava 2.1.0 [2014.10.01] ------------------------------------------------------------------------------ + - Redesigned lambdas and projections handling - Temporal classes are now Serializable ------------------------------------------------------------------------------ diff --git a/README.md b/README.md index eee53be..90bde25 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -qJava 2.0 +qJava 2.1 ========= The q/kdb+ interface is implemented as a set of Java classes and provides: diff --git a/doc/Type-conversion.md b/doc/Type-conversion.md index 8501f8f..5ad1d93 100644 --- a/doc/Type-conversion.md +++ b/doc/Type-conversion.md @@ -42,17 +42,25 @@ The `QType` class defines mapping between the q and corresponding Java types. | TIME | -19 | time | QTime | | TIME_LIST | 19 | time list | QTime[] | | GENERAL_LIST | 0 | general list | Object[] | -| LAMBDA | 100, 104 | function body | QLambda | | TABLE | 98 | table | QTable | | KEYED_TABLE | 99 | keyed table | QKeyedTable | | DICTIONARY | 99 | dictionary | QDictionary | +| LAMBDA | 100 | function body | QLambda | +| PROJECTION | 104 | function projection | QProjection | +| UNARY_PRIMITIVE_FUNC | 101 | function | QFunction | +| BINARY_PRIMITIVE_FUNC | 102 | function | QFunction | +| TERNARY_OPERATOR_FUNC | 103 | function | QFunction | +| COMPOSITION_FUNC | 105 | function | QFunction | +| ADVERB_FUNC | 106-111 | function | QFunction | ``` -Note that q list are represented as arrays of primitive type by the qJava library. -It is possible to send to q arrays of primitive type (e.g. `int[]`) as well as of boxed type (e.g. `Integer[]`). +Note that q list are represented as arrays of primitive type by the qJava +library. It is possible to send to q arrays of primitive type (e.g. `int[]`) as +well as of boxed type (e.g. `Integer[]`). ### Temporal types -q language provides multiple types for operating on temporal data. The qJava library provides a corresponding temporal class for each q temporal type. +q language provides multiple types for operating on temporal data. The qJava +library provides a corresponding temporal class for each q temporal type. Instance of each class can be created: * from the the underlying base type (e.g. `Long` in case of `QTimespan` and `QTimestamp`, `Double` in case of `QDateTime`), @@ -66,10 +74,34 @@ public Object getValue() // Returns internal q representation of the temp public DateTime toDateTime() // Represents q date/time with the instance of java.util.Date. ``` +### Functions, lambdas and projections + +IPC protocol type codes 100+ are used to represent functions, lambdas and +projections. These types are represented as instances of base class +`QFunction` or descendent classes: + +* `QLambda` - represents q lambda expression, note that expression is required + to be either: + * q expression enclosed in {}, e.g.: `{x + y}` + * k expression, e.g.: `k){x + y}` + +* `QProjection` - represents function projection with parameters, e.g.: + ```java + // { x + y}[3] + new QProjection(new Object[] {new QLambda("{x+y}"), 3L }); + ``` + +Note that only `QLambda` and `QProjection` are serializable. qJava doesn't +provide means to serialize other function types. + ### Null values -The `QType` enumeration exposes a utility static method `getQNull(QType type)` that returns corresponding q null value of given type. Keep in mind that null values are only defined and available for primitive q types. +The `QType` enumeration exposes a utility static method `getQNull(QType type)` +that returns corresponding q null value of given type. Keep in mind that null +values are only defined and available for primitive q types. -As null values in q are represented as arbitrary values, it is also possible to produce null value without explicitly calling the `getQNull` method. Q null values are mapped to Java according to the following table: +As null values in q are represented as arbitrary values, it is also possible to +produce null value without explicitly calling the `getQNull` method. Q null +values are mapped to Java according to the following table: ``` | q type | Java null | diff --git a/src/main/java/com/exxeleron/qjava/QFunction.java b/src/main/java/com/exxeleron/qjava/QFunction.java new file mode 100644 index 0000000..99f5abd --- /dev/null +++ b/src/main/java/com/exxeleron/qjava/QFunction.java @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2011-2014 Exxeleron GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.exxeleron.qjava; + +/** + * Represents q function. + * + * Note that the {@link QFunction} instances cannot be serialized to IPC protocol. + */ +public class QFunction { + + private final byte qTypeCode; + + /** + * Creates representation of q function with given q type code. + * + * @param qTypeCode + * q type code + */ + protected QFunction(final byte qTypeCode) { + this.qTypeCode = qTypeCode; + } + + /** + * Retrieve q type code connected with function. + * + * @return type code for function + */ + public byte getTypeCode() { + return qTypeCode; + } + + @Override + public String toString() { + return "QFunction#" + qTypeCode + "h"; + } +} \ No newline at end of file diff --git a/src/main/java/com/exxeleron/qjava/QLambda.java b/src/main/java/com/exxeleron/qjava/QLambda.java index a057099..099c6e5 100644 --- a/src/main/java/com/exxeleron/qjava/QLambda.java +++ b/src/main/java/com/exxeleron/qjava/QLambda.java @@ -1,12 +1,12 @@ /** * Copyright (c) 2011-2014 Exxeleron GmbH - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,16 +15,19 @@ */ package com.exxeleron.qjava; +import java.util.regex.Pattern; + /** * Represents a q lambda expression. */ -public final class QLambda { +public final class QLambda extends QFunction { + private static final Pattern LAMBDA_REGEX = Pattern.compile("\\s*(k\\))?\\s*\\{.*\\}"); + private final String expression; - private final Object[] parameters; /** * Gets body of a q lambda expression. - * + * * @return body of a q lambda expression */ public String getExpression() { @@ -32,56 +35,46 @@ public String getExpression() { } /** - * Gets parameters of a q lambda expression. - * - * @return array containing lambda expression parameters - */ - public Object[] getParameters() { - return parameters; - } - - /** - * Creates new {@link QLambda} instance with given body and parameters. - * + * Creates new {@link QLambda} instance with given body. Note that expression is trimmed and required to be enclosed + * in { and } brackets. + * * @param expression * body of a q lambda expression - * @param parameters - * array containing lambda expression parameters + * + * @throws IllegalArgumentException */ - public QLambda(final String expression, final Object[] parameters) { - if ( expression == null || expression.length() == 0 ) { + public QLambda(final String expression) { + super(QType.LAMBDA.getTypeCode()); + + if ( expression == null ) { throw new IllegalArgumentException("Lambda expression cannot be null or empty"); } - this.expression = expression; - this.parameters = parameters; - } + this.expression = expression.trim(); + if ( this.expression.length() == 0 ) { + throw new IllegalArgumentException("Lambda expression cannot be null or empty"); + } - /** - * Creates new {@link QLambda} instance with given body and no parameters. - * - * @param expression - * body of a q lambda expression - */ - public QLambda(final String expression) { - this(expression, null); + if ( !LAMBDA_REGEX.matcher(expression).matches() ) { + throw new IllegalArgumentException("Invalid lambda expression: " + expression); + } } /** * Returns a String that represents the current {@link QLambda}. - * + * * @return a String representation of the {@link QLambda} * @see java.lang.Object#toString() */ @Override public String toString() { - return "QLambda: " + expression + (parameters == null ? "" : Utils.arrayToString(parameters)); + return "QLambda: " + expression; } /** * Indicates whether some other object is "equal to" this lambda expression. {@link QLambda} objects are considered - * equal if the expression and parameters list are equal for both instances. - * + * equal if the expression is equal for both instances. + * * @return true if this object is the same as the obj argument, false otherwise. * @see java.lang.Object#equals(java.lang.Object) */ @@ -96,12 +89,12 @@ public boolean equals( final Object obj ) { } final QLambda l = (QLambda) obj; - return expression.equals(l.expression) && Utils.deepArraysEquals(parameters, l.parameters); + return expression.equals(l.expression); } /** * Returns a hash code value for this {@link QLambda}. - * + * * @return a hash code value for this object * @see java.lang.Object#hashCode() */ diff --git a/src/main/java/com/exxeleron/qjava/QProjection.java b/src/main/java/com/exxeleron/qjava/QProjection.java new file mode 100644 index 0000000..b31cd57 --- /dev/null +++ b/src/main/java/com/exxeleron/qjava/QProjection.java @@ -0,0 +1,85 @@ +/** + * Copyright (c) 2011-2014 Exxeleron GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.exxeleron.qjava; + +import java.util.Arrays; + +/** + * Represents a q projection. + */ +public final class QProjection extends QFunction{ + private final Object[] parameters; + + /** + * Gets parameters of a q projection. + * + * @return array containing projection parameters + */ + public Object[] getParameters() { + return parameters; + } + + /** + * Creates new {@link QProjection} instance with given parameters. + * + * @param parameters + * array containing projection parameters + * + * @throws IllegalArgumentException + */ + public QProjection(final Object[] parameters) { + super(QType.PROJECTION.getTypeCode()); + this.parameters = parameters; + } + + /** + * Returns a String that represents the current {@link QProjection}. + * + * @return a String representation of the {@link QProjection} + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "QProjection: " + (parameters == null ? "" : Utils.arrayToString(parameters)); + } + + /** + * Indicates whether some other object is "equal to" this projection. {@link QProjection} objects are considered + * equal if the parameters list are equal for both instances. + * + * @return true if this object is the same as the obj argument, false otherwise. + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals( final Object obj ) { + if ( this == obj ) { + return true; + } + + if ( !(obj instanceof QProjection) ) { + return false; + } + + final QProjection p = (QProjection) obj; + return Utils.deepArraysEquals(parameters, p.parameters); + } + + @Override + public int hashCode() { + return Arrays.hashCode(parameters); + } + +} diff --git a/src/main/java/com/exxeleron/qjava/QReader.java b/src/main/java/com/exxeleron/qjava/QReader.java index 2c46c06..f62a8f1 100644 --- a/src/main/java/com/exxeleron/qjava/QReader.java +++ b/src/main/java/com/exxeleron/qjava/QReader.java @@ -1,12 +1,12 @@ /** * Copyright (c) 2011-2014 Exxeleron GmbH - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -26,7 +26,7 @@ /** * Provides deserialization from q IPC protocol.
- * + * * Methods of {@link QReader} are not thread safe. */ public final class QReader { @@ -42,25 +42,25 @@ public final class QReader { /** * Initializes a new {@link QReader} instance. - * + * * @param inputStream * Input stream containing serialized messages * @param encoding * Encoding used for deserialization of string data */ public QReader(final DataInputStream inputStream, final String encoding) { - this.stream = inputStream; + stream = inputStream; this.encoding = encoding; - this.reader = new ByteInputStream(ByteOrder.nativeOrder()); + reader = new ByteInputStream(ByteOrder.nativeOrder()); } /** * Reads next message from the stream and returns a deserialized object. - * + * * @param raw * indicates whether reply should be parsed or return as raw data * @return {@link QMessage} instance encapsulating a deserialized message. - * + * * @throws IOException * @throws QException */ @@ -183,9 +183,6 @@ private Object readObject() throws QException, IOException { if ( qtype == QType.GENERAL_LIST ) { return readGeneralList(); - } else if ( qtype == QType.NULL_ITEM ) { - reader.get(); // ignore - return null; } else if ( qtype == QType.ERROR ) { throw readError(); } else if ( qtype == QType.DICTIONARY ) { @@ -196,11 +193,10 @@ private Object readObject() throws QException, IOException { return readAtom(qtype); } else if ( qtype.getTypeCode() >= QType.BOOL_LIST.getTypeCode() && qtype.getTypeCode() <= QType.TIME_LIST.getTypeCode() ) { return readList(qtype); - } else if ( qtype == QType.LAMBDA ) { - return readLambda(); - } else if ( qtype == QType.LAMBDA_PART ) { - return readLambdaPart(); + } else if ( qtype.getTypeCode() >= QType.LAMBDA.getTypeCode() ) { + return readFunction(qtype); } + throw new QReaderException("Unable to deserialize q type: " + qtype); } @@ -429,22 +425,35 @@ private QTable readTable() throws QException, IOException { return new QTable((String[]) readObject(), (Object[]) readObject()); } - private QLambda readLambda() throws QException, IOException { - reader.getSymbol(); - final char[] expression = (char[]) readObject(); - - return new QLambda(new String(expression)); - } - - private QLambda readLambdaPart() throws QException, IOException { - final int length = reader.getInt() - 1; - final QLambda lambda = readLambda(); - final Object[] parameters = new Object[length]; - for ( int i = 0; i < length; i++ ) { - parameters[i] = readObject(); + private QFunction readFunction( final QType qtype ) throws QException, IOException { + if ( qtype == QType.LAMBDA ) { + reader.getSymbol(); // ignore context + final String expression = new String((char[]) readObject()); + return new QLambda(expression); + } else if ( qtype == QType.PROJECTION ) { + final int length = reader.getInt(); + final Object[] parameters = new Object[length]; + for ( int i = 0; i < length; i++ ) { + parameters[i] = readObject(); + } + return new QProjection(parameters); + } else if ( qtype == QType.UNARY_PRIMITIVE_FUNC ) { + final byte code = reader.get(); + return code == 0 ? null : new QFunction(qtype.getTypeCode()); + } else if ( qtype.getTypeCode() < QType.PROJECTION.getTypeCode() ) { + reader.get(); // ignore function code + return new QFunction(qtype.getTypeCode()); + } else if ( qtype == QType.COMPOSITION_FUNC ) { + final int length = reader.getInt(); + final Object[] parameters = new Object[length]; + for ( int i = 0; i < length; i++ ) { + parameters[i] = readObject(); + } + return new QFunction(qtype.getTypeCode()); + } else { + readObject(); // ignore function object + return new QFunction(qtype.getTypeCode()); } - - return new QLambda(lambda.getExpression(), parameters); } private final class ByteInputStream { diff --git a/src/main/java/com/exxeleron/qjava/QType.java b/src/main/java/com/exxeleron/qjava/QType.java index 869b841..01d1082 100644 --- a/src/main/java/com/exxeleron/qjava/QType.java +++ b/src/main/java/com/exxeleron/qjava/QType.java @@ -64,11 +64,23 @@ public enum QType { SECOND_LIST(18), TIME(-19), TIME_LIST(19), - LAMBDA(100), - LAMBDA_PART(104), TABLE(98), KEYED_TABLE(99), - DICTIONARY(99); + DICTIONARY(99), + LAMBDA(100), + UNARY_PRIMITIVE_FUNC(101), + BINARY_PRIMITIVE_FUNC(102), + TERNARY_OPERATOR_FUNC(103), + COMPOSITION_FUNC(105), + ADVERB_FUNC_106(106), + ADVERB_FUNC_107(107), + ADVERB_FUNC_108(108), + ADVERB_FUNC_109(109), + ADVERB_FUNC_110(110), + ADVERB_FUNC_111(111), + @Deprecated + LAMBDA_PART(104), + PROJECTION(104); QType(final int code) { this.code = (byte) code; @@ -134,6 +146,7 @@ byte getTypeCode() { put(QTable.class, TABLE); put(QKeyedTable.class, KEYED_TABLE); put(QLambda.class, LAMBDA); + put(QProjection.class, PROJECTION); } }); @@ -184,7 +197,6 @@ byte getTypeCode() { put(TABLE, QTable.class); put(KEYED_TABLE, QKeyedTable.class); put(LAMBDA, QLambda.class); - put(LAMBDA_PART, QLambda.class); } }); diff --git a/src/main/java/com/exxeleron/qjava/QWriter.java b/src/main/java/com/exxeleron/qjava/QWriter.java index 498d916..ddaaa69 100644 --- a/src/main/java/com/exxeleron/qjava/QWriter.java +++ b/src/main/java/com/exxeleron/qjava/QWriter.java @@ -146,6 +146,8 @@ private void writeObject( final Object obj ) throws IOException, QException { writeList(obj, qtype); } else if ( qtype == QType.LAMBDA ) { writeLambda((QLambda) obj); + } else if ( qtype == QType.PROJECTION ) { + writeProjection((QProjection) obj); } else { throw new QWriterException("Unable to serialize q type: " + qtype); } @@ -486,20 +488,19 @@ private void writeKeyedTable( final QKeyedTable t ) throws IOException, QExcepti writeObject(t.getValues()); } - private void writeLambda( final QLambda l ) throws IOException, QException { - if ( l.getParameters() == null || l.getParameters().length == 0 ) { - writer.writeByte(QType.LAMBDA.getTypeCode()); - writer.writeByte((byte) 0); - writeString(l.getExpression().toCharArray()); - } else { - writer.writeByte(QType.LAMBDA_PART.getTypeCode()); - writer.writeInt(l.getParameters().length + 1); - writer.writeByte(QType.LAMBDA.getTypeCode()); - writer.writeByte((byte) 0); - writeString(l.getExpression().toCharArray()); - for ( final Object p : l.getParameters() ) { - writeObject(p); - } + private void writeLambda( final QLambda l ) throws IOException { + writer.writeByte(QType.LAMBDA.getTypeCode()); + writer.writeByte((byte) 0); + writeString(l.getExpression().toCharArray()); + } + + private void writeProjection( final QProjection p ) throws IOException, QException { + writer.writeByte(QType.PROJECTION.getTypeCode()); + final int length = p.getParameters().length; + writer.writeInt(length); + + for ( int i = 0; i < length; i++ ) { + writeObject(p.getParameters()[i]); } } diff --git a/src/test/java/com/exxeleron/qjava/QExpressions.java b/src/test/java/com/exxeleron/qjava/QExpressions.java index b03252d..ab90f75 100644 --- a/src/test/java/com/exxeleron/qjava/QExpressions.java +++ b/src/test/java/com/exxeleron/qjava/QExpressions.java @@ -140,7 +140,7 @@ private void initExpressions() throws QException { new Object[] { new long[] { 1001, 1002, 1003 } }), new QTable(new String[] { "pos", "dates" }, new Object[] { new String[] { "d1", "d2", "d3" }, new QDate[] { new QDate(366), new QDate(121), new QDate(Integer.MIN_VALUE) } }))); reference.put("{x+y}", new QLambda("{x+y}")); - reference.put("{x+y}[3]", new QLambda("{x+y}", new Object[] { 3L })); + reference.put("{x+y}[3]", new QProjection(new Object[] {new QLambda("{x+y}"), 3L })); reference.put("0Ng", new UUID(0, 0)); reference.put("\"G\"$\"8c680a01-5a49-5aab-5a65-d4bfddb6a661\"", UUID.fromString("8c680a01-5a49-5aab-5a65-d4bfddb6a661")); reference.put("(\"G\"$\"8c680a01-5a49-5aab-5a65-d4bfddb6a661\"; 0Ng)", diff --git a/src/test/java/com/exxeleron/qjava/TestQReader.java b/src/test/java/com/exxeleron/qjava/TestQReader.java index 28a2466..cd738d9 100644 --- a/src/test/java/com/exxeleron/qjava/TestQReader.java +++ b/src/test/java/com/exxeleron/qjava/TestQReader.java @@ -1,12 +1,12 @@ /** * Copyright (c) 2011-2014 Exxeleron GmbH - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -69,6 +69,77 @@ public void testDeserialization() throws IOException, QException { } } + private static class FunctionMock extends QFunction { + + FunctionMock() { + super((byte) 0); + } + + @Override + public boolean equals( final Object obj ) { + return obj instanceof QFunction; + } + + @Override + public int hashCode() { + return 0; + } + + } + + @Test + public void testFunctionsDeserialization() throws IOException, QException { + final QExpressions qe = new QExpressions("src/test/resources/QExpressionsFunctions.out"); + @SuppressWarnings("serial") + final Map ref = new HashMap() { + { + put("{x+y}[3]", new QProjection(new Object[] { new QLambda("{x+y}"), 3L })); + put("insert [1]", new QProjection(new Object[] { new FunctionMock(), 1L })); + put("xbar", new QLambda("k){x*y div x:$[16h=abs[@x];\"j\"$x;x]}")); + put("not", new FunctionMock()); + put("and", new FunctionMock()); + put("md5", new QProjection(new Object[] { new FunctionMock(), -15L })); + put("any", new FunctionMock()); + put("save", new FunctionMock()); + put("raze", new FunctionMock()); + put("sums", new FunctionMock()); + put("prev", new FunctionMock()); + } + }; + + for ( final String expr : qe.getExpressions() ) { + final QWriter.ByteOutputStream writer = new QWriter.ByteOutputStream(); + final byte[] binaryExpr = qe.getBinaryExpression(expr); + writer.writeByte((byte) 1); // little endian + writer.writeByte((byte) 0); + writer.writeByte((byte) 0); + writer.writeByte((byte) 0); + writer.writeInt(binaryExpr.length + 8); + writer.write(binaryExpr); + writer.flush(); + + final QReader reader = new QReader(new DataInputStream(new ByteArrayInputStream(writer.toByteArray())), "ISO-8859-1"); + + try { + final Object obj = reader.read(false).getData(); + + final Object refValue = ref.get(expr); + if ( refValue instanceof QProjection ) { + final QProjection pr = (QProjection) refValue; + final QProjection pa = (QProjection) obj; + final int length = pr.getParameters().length; + for ( int i = 0; i < length; i++ ) { + assertEquals("Deserialization failed for q expression: " + expr, pr.getParameters()[i], pa.getParameters()[i]); + } + } else { + assertEquals("Deserialization failed for q expression: " + expr, refValue, obj); + } + } finally { + writer.close(); + } + } + } + @Test public void testCompressedDeserialization() throws IOException, QException { final QExpressions qe = new QExpressions("src/test/resources/QCompressedExpressions.out"); diff --git a/src/test/resources/QExpressionsFunctions.out b/src/test/resources/QExpressionsFunctions.out new file mode 100644 index 0000000..5b82c63 --- /dev/null +++ b/src/test/resources/QExpressionsFunctions.out @@ -0,0 +1,22 @@ +{x+y}[3] +680200000064000A00050000007B782B797DF90300000000000000 +insert [1] +6802000000661cf90100000000000000 +xbar +6471000a00240000006b297b782a792064697620783a245b3136683d6162735b40785d3b226a2224783b785d7d +not +650f +and +6605 +md5 +68020000006610f9f1ffffffffffffff +any +6902000000651c6802000000660bf662 +save +6a6471000a003c0000006b297b245b313d23703a605c3a2a7c605c3a783a2d3121783b7365745b783b2e202a705d3b2020207820303a2e682e74785b7020315d402e2a705d7d +raze +6b660c +sums +6c6601 +prev +6d6600 \ No newline at end of file