Repository: phoenix Updated Branches: refs/heads/json 3cf22a7de -> 67e3e3bfa
PHOENIX-628 Support native JSON data type Project: http://git-wip-us.apache.org/repos/asf/phoenix/repo Commit: http://git-wip-us.apache.org/repos/asf/phoenix/commit/67e3e3bf Tree: http://git-wip-us.apache.org/repos/asf/phoenix/tree/67e3e3bf Diff: http://git-wip-us.apache.org/repos/asf/phoenix/diff/67e3e3bf Branch: refs/heads/json Commit: 67e3e3bfa3fa3ab34b5f7b9acdff8f623a010507 Parents: 3cf22a7 Author: Aakash <[email protected]> Authored: Mon Apr 13 18:32:10 2015 -0700 Committer: Thomas D'Silva <[email protected]> Committed: Wed Jun 24 13:52:17 2015 -0700 ---------------------------------------------------------------------- .../apache/phoenix/end2end/PhoenixJsonIT.java | 453 +++++++++++++++++++ .../phoenix/compile/ExpressionCompiler.java | 26 ++ .../phoenix/exception/SQLExceptionCode.java | 3 +- .../phoenix/expression/ExpressionType.java | 4 +- .../DistinctCountAggregateFunction.java | 7 +- .../org/apache/phoenix/parse/ColumnDef.java | 7 + .../schema/EqualityNotSupportedException.java | 29 ++ .../apache/phoenix/schema/json/PhoenixJson.java | 234 ++++++++++ .../apache/phoenix/schema/types/PDataType.java | 37 +- .../phoenix/schema/types/PDataTypeFactory.java | 1 + .../org/apache/phoenix/schema/types/PJson.java | 213 +++++++++ .../apache/phoenix/schema/types/PVarchar.java | 2 +- .../expression/CoerceExpressionTest.java | 3 +- .../phoenix/schema/json/PhoenixJsonTest.java | 194 ++++++++ .../phoenix/schema/types/PDataTypeTest.java | 3 +- .../apache/phoenix/schema/types/PJsonTest.java | 187 ++++++++ .../org/apache/phoenix/util/IndexUtilTest.java | 2 + .../apache/phoenix/util/PhoenixRuntimeTest.java | 13 +- 18 files changed, 1400 insertions(+), 18 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/phoenix/blob/67e3e3bf/phoenix-core/src/it/java/org/apache/phoenix/end2end/PhoenixJsonIT.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/PhoenixJsonIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/PhoenixJsonIT.java new file mode 100644 index 0000000..dfb0b40 --- /dev/null +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/PhoenixJsonIT.java @@ -0,0 +1,453 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.phoenix.end2end; + +import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map.Entry; +import java.util.Properties; +import java.util.Set; + +import org.apache.phoenix.exception.SQLExceptionCode; +import org.apache.phoenix.schema.EqualityNotSupportedException; +import org.apache.phoenix.schema.json.PhoenixJson; +import org.apache.phoenix.schema.types.PJson; +import org.apache.phoenix.util.PropertiesUtil; +import org.junit.Test; + +/** + * End to end test for JSON data type for {@link PJson} and {@link PhoenixJson}. + */ +public class PhoenixJsonIT extends BaseHBaseManagedTimeIT { + + @Test + public void testJsonUpsertForJsonHavingChineseAndControlAndQuoteChars() throws Exception { + String json = "{\"k1\":\"\\n \\\"jumps \\r'æ®æ´¾'\",\"k2\":true, \"k3\":2}"; + String selectQuery = "SELECT col1 FROM testJson WHERE pk = 'valueOne'"; + String pk = "valueOne"; + Connection conn = getConnection(); + try { + + createTableAndUpsertRecord(json, pk, conn); + + PreparedStatement stmt = conn.prepareStatement(selectQuery); + ResultSet rs = stmt.executeQuery(); + assertTrue(rs.next()); + assertEquals("Json data read from DB is not as expected for query: <" + selectQuery + + ">", json, rs.getString(1)); + assertFalse(rs.next()); + + } finally { + conn.close(); + } + } + + @Test + public void testJsonUpsertValue() throws Exception { + String json = "{\"k1\":\"val\",\"k2\":true, \"k3\":2}"; + String selectQuery = "SELECT col1 FROM testJson WHERE pk = 'valueOne'"; + String pk = "valueOne"; + Connection conn = getConnection(); + try { + + createTableAndUpsertRecord(json, pk, conn); + + PreparedStatement stmt = conn.prepareStatement(selectQuery); + ResultSet rs = stmt.executeQuery(); + assertTrue(rs.next()); + assertEquals("Json data read from DB is not as expected for query: <" + selectQuery + + ">", json, rs.getString(1)); + assertFalse(rs.next()); + + } finally { + conn.close(); + } + } + + @Test + public void testJsonArrayUpsertValue() throws Exception { + Connection conn = getConnection(); + try { + String ddl = + "CREATE TABLE testJson" + " (pk VARCHAR NOT NULL PRIMARY KEY, " + "col1 json)"; + createTestTable(getUrl(), ddl); + + HashMap<String, String> jsonDataMap = new HashMap<String, String>(); + + jsonDataMap.put("justIntegerArray", "[1,2,3]"); + jsonDataMap.put("justBooleanArray", "[true,false]"); + jsonDataMap.put("justStringArray", "[\"One\",\"Two\"]"); + jsonDataMap.put("mixedArray", "[\"One\",2, true, null]"); + jsonDataMap.put("arrayInsideAKey", "{\"k1\":{\"k2\":[1,2,3]}}"); + + Set<Entry<String, String>> entrySet = jsonDataMap.entrySet(); + for (Entry<String, String> entry : entrySet) { + createTableAndUpsertRecord(entry.getValue(), entry.getKey(), conn); + + String selectQuery = + "SELECT col1 FROM testJson WHERE pk = '" + entry.getKey() + "'"; + PreparedStatement stmt = conn.prepareStatement(selectQuery); + ResultSet rs = stmt.executeQuery(); + assertTrue(rs.next()); + assertEquals( + "Json array data read from DB is not as expected for " + entry.getKey(), + entry.getValue(), rs.getString(1)); + assertFalse(rs.next()); + } + } finally { + conn.close(); + } + } + + @Test + public void testInvalidJsonUpsertValue() throws Exception { + String json = "{\"k1\"}"; + Connection conn = getConnection(); + try { + String ddl = + "CREATE TABLE testJson" + " (pk VARCHAR NOT NULL PRIMARY KEY, " + "col1 json)"; + createTestTable(getUrl(), ddl); + + String query = "UPSERT INTO testJson(pk, col1) VALUES(?,?)"; + PreparedStatement stmt = conn.prepareStatement(query); + stmt.setString(1, "valueOne"); + stmt.setString(2, json); + try { + stmt.execute(); + } catch (SQLException sqe) { + assertEquals("SQL error code is not as expected when Json is invalid.", + SQLExceptionCode.INVALID_JSON_DATA.getErrorCode(), sqe.getErrorCode()); + assertEquals("SQL state is not expected when Json is invalid.", "22000", + sqe.getSQLState()); + } + conn.commit(); + + } finally { + conn.close(); + } + } + + @Test + public void testInvalidJsonStringCastAsJson() throws Exception { + String json = "{\"k1\":\"val\",\"k2\":true, \"k3\":2}"; + String pk = "valueOne"; + String selectQuery = "SELECT cast(pk as json) FROM testJson WHERE pk = 'valueOne'"; + Connection conn = getConnection(); + try { + createTableAndUpsertRecord(json, pk, conn); + + PreparedStatement stmt = conn.prepareStatement(selectQuery); + ResultSet rs = stmt.executeQuery(); + assertTrue(rs.next()); + assertEquals(PhoenixJson.class.getName(), rs.getMetaData().getColumnClassName(1)); + try { + rs.getString(1); + fail("casting invalid json string to json should fail."); + } catch (SQLException sqe) { + assertEquals(SQLExceptionCode.INVALID_JSON_DATA.getErrorCode(), sqe.getErrorCode()); + } + + } finally { + conn.close(); + } + } + + private Connection getConnection() throws SQLException { + Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); + Connection conn = DriverManager.getConnection(getUrl(), props); + conn.setAutoCommit(false); + return conn; + } + + @Test + public void testValidJsonStringCastAsJson() throws Exception { + Connection conn = getConnection(); + String json = "{\"k1\":\"val\",\"k2\":true, \"k3\":2}"; + try { + createTableAndUpsertRecord(json, json, conn); + + String selectQuery = "SELECT cast(pk as json) FROM testJson"; + PreparedStatement stmt = conn.prepareStatement(selectQuery); + ResultSet rs = stmt.executeQuery(); + assertTrue(rs.next()); + assertEquals(PhoenixJson.class.getName(), rs.getMetaData().getColumnClassName(1)); + String stringCastToJson = null; + try { + stringCastToJson = rs.getString(1); + } catch (SQLException sqe) { + fail("casting valid json string to json should not fail."); + } + + assertEquals(json, stringCastToJson); + rs.close(); + } finally { + conn.close(); + } + } + + @Test + public void testJsonCastAsString() throws Exception { + Connection conn = getConnection(); + String json = "{\"k1\":\"val\",\"k2\":true, \"k3\":2}"; + String pk = "valueOne"; + String selectQuery = "SELECT cast(col1 as varchar) FROM testJson WHERE pk = 'valueOne'"; + try { + createTableAndUpsertRecord(json, pk, conn); + + PreparedStatement stmt = conn.prepareStatement(selectQuery); + ResultSet rs = stmt.executeQuery(); + assertTrue(rs.next()); + assertNotEquals(PhoenixJson.class.getName(), rs.getMetaData().getColumnClassName(1)); + assertEquals(String.class.getName(), rs.getMetaData().getColumnClassName(1)); + assertEquals("Json data read from DB is not as expected for query: <" + selectQuery + + ">", json, rs.getString(1)); + assertFalse(rs.next()); + + } finally { + conn.close(); + } + } + + @Test + public void testJsonAsNull() throws Exception { + Connection conn = getConnection(); + String json = null; + String pk = "valueOne"; + try { + createTableAndUpsertRecord(json, pk, conn); + + /*test is null*/ + String selectQuery = "SELECT col1 FROM testJson WHERE pk = 'valueOne' and col1 is NULL"; + PreparedStatement stmt = conn.prepareStatement(selectQuery); + ResultSet rs = stmt.executeQuery(); + assertTrue(rs.next()); + assertEquals(PhoenixJson.class.getName(), rs.getMetaData().getColumnClassName(1)); + assertEquals("Json data read from DB is not as expected for query: <" + selectQuery + + ">", json, rs.getString(1)); + + assertEquals("Json data read from DB is not as expected for query: <" + selectQuery + + ">", PhoenixJson.getInstance(json), rs.getObject(1, PhoenixJson.class)); + assertFalse(rs.next()); + + /*test is not null*/ + json = "[1,2,3]"; + pk = "valueTwo"; + upsertRecord(json, pk, conn); + selectQuery = "SELECT col1 FROM testJson WHERE col1 is not NULL"; + stmt = conn.prepareStatement(selectQuery); + rs = stmt.executeQuery(); + assertTrue(rs.next()); + assertEquals(PhoenixJson.class.getName(), rs.getMetaData().getColumnClassName(1)); + assertEquals("Json data read from DB is not as expected for query: <" + selectQuery + + ">", json, rs.getString(1)); + assertFalse(rs.next()); + + } finally { + conn.close(); + } + } + + @Test + public void testCountDistinct() throws Exception { + final int countDistinct = 11; + Connection conn = getConnection(); + String json = null; + String pk = "valueOne"; + String selectQuery = "SELECT DISTINCT_COUNT(col1) FROM testJson"; + try { + createTableAndUpsertRecord(json, pk, conn); + for (int i = 0; i < countDistinct; i++) { + upsertRecord("[" + i + "]", String.valueOf(i), conn); + } + + PreparedStatement stmt = conn.prepareStatement(selectQuery); + stmt.executeQuery(); + } catch (SQLException sqe) { + assertEquals(SQLExceptionCode.NON_EQUALITY_COMPARISON.getErrorCode(), + sqe.getErrorCode()); + + } finally { + conn.close(); + } + } + + @Test + public void testDistinct() throws Exception { + final int countDistinct = 11; + Connection conn = getConnection(); + String json = null; + String pk = "valueOne"; + String selectQuery = "SELECT DISTINCT(col1) FROM testJson"; + try { + createTableAndUpsertRecord(json, pk, conn); + for (int i = 0; i < countDistinct; i++) { + upsertRecord("[" + i + "]", String.valueOf(i), conn); + } + + PreparedStatement stmt = conn.prepareStatement(selectQuery); + stmt.executeQuery(); + } catch (SQLException sqe) { + assertEquals(SQLExceptionCode.NON_EQUALITY_COMPARISON.getErrorCode(), + sqe.getErrorCode()); + + } finally { + conn.close(); + } + } + + + @Test + public void testJsonColumnInWhereClause() throws Exception { + Connection conn = getConnection(); + String json = "[1]"; + String pk = "valueOne"; + String selectQuery = "SELECT col1 FROM testJson WHERE pk = 'valueOne' and col1 = '[1]'"; + try { + createTableAndUpsertRecord(json, pk, conn); + + PreparedStatement stmt = conn.prepareStatement(selectQuery); + stmt.executeQuery(); + fail("'=' operator should not be allowed with "); + } catch (SQLException sqe) { + assertEquals(SQLExceptionCode.NON_EQUALITY_COMPARISON.getErrorCode(), + sqe.getErrorCode()); + + } finally { + conn.close(); + } + } + + @Test + public void testJsonGroupByColumn() throws Exception { + Connection conn = getConnection(); + String json = "[1]"; + String pk = "valueOne"; + String selectQuery = "SELECT col1 FROM testJson group by col1"; + try { + createTableAndUpsertRecord(json, pk, conn); + + PreparedStatement stmt = conn.prepareStatement(selectQuery); + stmt.executeQuery(); + } catch (SQLException sqe) { + assertEquals(SQLExceptionCode.NON_EQUALITY_COMPARISON.getErrorCode(), + sqe.getErrorCode()); + + } finally { + conn.close(); + } + } + + @Test + public void testJsonColumnInWhereClauseOfSubQuery() throws Exception { + Connection conn = getConnection(); + String json = "[1]"; + String pk = "valueOne"; + String selectQuery = + "SELECT col1 FROM testJson WHERE col1 in (select col1 from testJson where col1='[1]')"; + try { + createTableAndUpsertRecord(json, pk, conn); + + PreparedStatement stmt = conn.prepareStatement(selectQuery); + stmt.executeQuery(); + } catch (SQLException sqe) { + assertEquals(SQLExceptionCode.NON_EQUALITY_COMPARISON.getErrorCode(), + sqe.getErrorCode()); + + } finally { + conn.close(); + } + } + + + @Test + public void testSetObject() throws SQLException { + + Connection conn = getConnection(); + try { + String json = "{\"k1\":\"val\",\"k2\":true, \"k3\":2}"; + String pk = "valueOne"; + String ddl = + "CREATE TABLE testJson" + " (pk VARCHAR NOT NULL PRIMARY KEY, " + "col1 json)"; + createTestTable(getUrl(), ddl); + + String query = "UPSERT INTO testJson(pk, col1) VALUES(?,?)"; + PreparedStatement stmt = conn.prepareStatement(query); + stmt.setString(1, pk); + stmt.setObject(2, PhoenixJson.getInstance(json), java.sql.Types.OTHER); + stmt.execute(); + + pk = "valueTwo"; + query = "UPSERT INTO testJson(pk, col1) VALUES(?,?)"; + stmt = conn.prepareStatement(query); + stmt.setString(1, pk); + stmt.setObject(2, json, java.sql.Types.OTHER); + stmt.execute(); + + conn.commit(); + + String selectQuery = "SELECT col1 FROM testJson WHERE pk = 'valueOne'"; + stmt = conn.prepareStatement(selectQuery); + ResultSet rs = stmt.executeQuery(); + assertTrue(rs.next()); + assertEquals("Json data read from DB is not as expected for query: <" + selectQuery + + ">", json, rs.getString(1)); + assertFalse(rs.next()); + + selectQuery = "SELECT col1 FROM testJson WHERE pk = 'valueTwo'"; + stmt = conn.prepareStatement(selectQuery); + rs = stmt.executeQuery(); + assertTrue(rs.next()); + assertEquals("Json data read from DB is not as expected for query: <" + selectQuery + + ">", json, rs.getString(1)); + assertFalse(rs.next()); + } finally { + conn.close(); + } + } + + private void createTableAndUpsertRecord(String json, String pk, Connection conn) throws SQLException { + String ddl = + "CREATE TABLE testJson" + " (pk VARCHAR NOT NULL PRIMARY KEY, " + "col1 json)"; + createTestTable(getUrl(), ddl); + + upsertRecord(json, pk, conn); + } + + private void upsertRecord(String json, String pk, Connection conn) throws SQLException { + String query = "UPSERT INTO testJson(pk, col1) VALUES(?,?)"; + PreparedStatement stmt = conn.prepareStatement(query); + stmt.setString(1, pk); + stmt.setString(2, json); + stmt.execute(); + conn.commit(); + } + +} http://git-wip-us.apache.org/repos/asf/phoenix/blob/67e3e3bf/phoenix-core/src/main/java/org/apache/phoenix/compile/ExpressionCompiler.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/ExpressionCompiler.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/ExpressionCompiler.java index 39baf7a..e2bd19d 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/compile/ExpressionCompiler.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/ExpressionCompiler.java @@ -199,6 +199,16 @@ public class ExpressionCompiler extends UnsupportedAllParseNodeVisitor<Expressio ParseNode rhsNode = node.getChildren().get(1); Expression lhsExpr = children.get(0); Expression rhsExpr = children.get(1); + PDataType dataTypeOfLHSExpr = lhsExpr.getDataType(); + if (dataTypeOfLHSExpr != null && !dataTypeOfLHSExpr.isEqualitySupported()) { + throw new SQLExceptionInfo.Builder(SQLExceptionCode.NON_EQUALITY_COMPARISON) + .setMessage(" for type " + dataTypeOfLHSExpr).build().buildException(); + } + PDataType dataTypeOfRHSExpr = rhsExpr.getDataType(); + if (dataTypeOfRHSExpr != null && !dataTypeOfRHSExpr.isEqualitySupported()) { + throw new SQLExceptionInfo.Builder(SQLExceptionCode.NON_EQUALITY_COMPARISON) + .setMessage(" for type " + dataTypeOfRHSExpr).build().buildException(); + } CompareOp op = node.getFilterOp(); if (lhsNode instanceof RowValueConstructorParseNode && rhsNode instanceof RowValueConstructorParseNode) { @@ -479,6 +489,16 @@ public class ExpressionCompiler extends UnsupportedAllParseNodeVisitor<Expressio !rhs.getDataType().isCoercibleTo(lhs.getDataType())) { throw TypeMismatchException.newException(lhs.getDataType(), rhs.getDataType(), node.toString()); } + if (!lhs.getDataType().isEqualitySupported()) { + throw new SQLExceptionInfo.Builder(SQLExceptionCode.NON_EQUALITY_COMPARISON) + .setMessage(" for type " + lhs.getDataType()).build().buildException(); + } + if (!rhs.getDataType().isEqualitySupported()) { + throw new SQLExceptionInfo.Builder(SQLExceptionCode.NON_EQUALITY_COMPARISON) + .setMessage(" for type " + rhs.getDataType()).build().buildException(); + } + + if (lhsNode instanceof BindParseNode) { context.getBindManager().addParamMetaData((BindParseNode)lhsNode, rhs); } @@ -617,6 +637,12 @@ public class ExpressionCompiler extends UnsupportedAllParseNodeVisitor<Expressio Expression firstChild = inChildren.get(0); ImmutableBytesWritable ptr = context.getTempPtr(); PDataType firstChildType = firstChild.getDataType(); + + if (firstChildType != null && !firstChildType.isEqualitySupported()) { + throw new SQLExceptionInfo.Builder(SQLExceptionCode.NON_EQUALITY_COMPARISON) + .setMessage(" for type " + firstChildType).build().buildException(); + } + ParseNode firstChildNode = node.getChildren().get(0); if (firstChildNode instanceof BindParseNode) { http://git-wip-us.apache.org/repos/asf/phoenix/blob/67e3e3bf/phoenix-core/src/main/java/org/apache/phoenix/exception/SQLExceptionCode.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/exception/SQLExceptionCode.java b/phoenix-core/src/main/java/org/apache/phoenix/exception/SQLExceptionCode.java index cf72384..6747661 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/exception/SQLExceptionCode.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/exception/SQLExceptionCode.java @@ -86,6 +86,7 @@ public enum SQLExceptionCode { SUBQUERY_RETURNS_DIFFERENT_NUMBER_OF_FIELDS(216, "22016", "Sub-query must return the same number of fields as the left-hand-side expression of 'IN'."), AMBIGUOUS_JOIN_CONDITION(217, "22017", "Amibiguous or non-equi join condition specified. Consider using table list with where clause."), CONSTRAINT_VIOLATION(218, "22018", "Constraint violatioin."), + INVALID_JSON_DATA(219, "22000", "Invalid json data."), /** * Constraint Violation (errorcode 03, sqlstate 23) @@ -147,7 +148,7 @@ public enum SQLExceptionCode { ORDER_BY_ARRAY_NOT_SUPPORTED(515, "42893", "ORDER BY of an array type is not allowed"), NON_EQUALITY_ARRAY_COMPARISON(516, "42894", "Array types may only be compared using = or !="), INVALID_NOT_NULL_CONSTRAINT(517, "42895", "Invalid not null constraint on non primary key column"), - + NON_EQUALITY_COMPARISON(523, "42900", "Could not identify an equality operator"), /** * Invalid Transaction State (errorcode 05, sqlstate 25) */ http://git-wip-us.apache.org/repos/asf/phoenix/blob/67e3e3bf/phoenix-core/src/main/java/org/apache/phoenix/expression/ExpressionType.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/expression/ExpressionType.java b/phoenix-core/src/main/java/org/apache/phoenix/expression/ExpressionType.java index 51f4089..e815817 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/expression/ExpressionType.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/expression/ExpressionType.java @@ -247,7 +247,9 @@ public enum ExpressionType { LogFunction(LogFunction.class), ExpFunction(ExpFunction.class), PowerFunction(PowerFunction.class), - ArrayConcatFunction(ArrayConcatFunction.class) + ArrayConcatFunction(ArrayConcatFunction.class), + JsonExtractPathFunction(JsonExtractPathFunction.class), + JsonExtractPathTextFunction(JsonExtractPathTextFunction.class) ; ExpressionType(Class<? extends Expression> clazz) { http://git-wip-us.apache.org/repos/asf/phoenix/blob/67e3e3bf/phoenix-core/src/main/java/org/apache/phoenix/expression/function/DistinctCountAggregateFunction.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/expression/function/DistinctCountAggregateFunction.java b/phoenix-core/src/main/java/org/apache/phoenix/expression/function/DistinctCountAggregateFunction.java index 6ce3c27..f014c95 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/expression/function/DistinctCountAggregateFunction.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/expression/function/DistinctCountAggregateFunction.java @@ -21,13 +21,13 @@ import java.util.List; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.io.ImmutableBytesWritable; - import org.apache.phoenix.expression.Expression; import org.apache.phoenix.expression.aggregator.Aggregator; import org.apache.phoenix.expression.aggregator.DistinctCountClientAggregator; import org.apache.phoenix.expression.aggregator.DistinctValueWithCountServerAggregator; import org.apache.phoenix.parse.FunctionParseNode.Argument; import org.apache.phoenix.parse.FunctionParseNode.BuiltInFunction; +import org.apache.phoenix.schema.EqualityNotSupportedException; import org.apache.phoenix.schema.types.PDataType; import org.apache.phoenix.schema.types.PLong; import org.apache.phoenix.schema.tuple.Tuple; @@ -100,6 +100,11 @@ public class DistinctCountAggregateFunction extends DelegateConstantToCountAggre @Override public boolean evaluate(Tuple tuple, ImmutableBytesWritable ptr) { + for (Expression child : getChildren()) { + if (!child.getDataType().isEqualitySupported()) { + throw new EqualityNotSupportedException(child.getDataType()); + } + } // TODO: optimize query plan of this to run scan serially for a limit of one row if (!super.evaluate(tuple, ptr)) { ptr.set(ZERO); // If evaluate returns false, then no rows were found, so result is 0 http://git-wip-us.apache.org/repos/asf/phoenix/blob/67e3e3bf/phoenix-core/src/main/java/org/apache/phoenix/parse/ColumnDef.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/parse/ColumnDef.java b/phoenix-core/src/main/java/org/apache/phoenix/parse/ColumnDef.java index cde3e9c..6402496 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/parse/ColumnDef.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/parse/ColumnDef.java @@ -134,6 +134,13 @@ public class ColumnDef { if(this.isArray) { this.dataType = localType; } + if (this.dataType != null && !this.dataType.canBePrimaryKey() && isPK) { + throw new SQLExceptionInfo.Builder(SQLExceptionCode.INVALID_PRIMARY_KEY_CONSTRAINT) + .setColumnName(columnDefName.getColumnName()) + .setMessage( + "," + this.dataType.toString() + " is not supported as primary key,") + .build().buildException(); + } this.expressionStr = expressionStr; } catch (SQLException e) { throw new ParseException(e); http://git-wip-us.apache.org/repos/asf/phoenix/blob/67e3e3bf/phoenix-core/src/main/java/org/apache/phoenix/schema/EqualityNotSupportedException.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/EqualityNotSupportedException.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/EqualityNotSupportedException.java new file mode 100644 index 0000000..a2a61c3 --- /dev/null +++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/EqualityNotSupportedException.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.phoenix.schema; + +import org.apache.phoenix.exception.SQLExceptionCode; +import org.apache.phoenix.exception.SQLExceptionInfo; +import org.apache.phoenix.schema.types.PDataType; + +public class EqualityNotSupportedException extends RuntimeException { + public EqualityNotSupportedException(PDataType<?> pDataType) { + super(new SQLExceptionInfo.Builder(SQLExceptionCode.NON_EQUALITY_COMPARISON) + .setMessage(" for type " + pDataType.toString()).build().buildException()); + } +} http://git-wip-us.apache.org/repos/asf/phoenix/blob/67e3e3bf/phoenix-core/src/main/java/org/apache/phoenix/schema/json/PhoenixJson.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/json/PhoenixJson.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/json/PhoenixJson.java new file mode 100644 index 0000000..bbd35fa --- /dev/null +++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/json/PhoenixJson.java @@ -0,0 +1,234 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.phoenix.schema.json; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.Arrays; + +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.phoenix.exception.SQLExceptionCode; +import org.apache.phoenix.exception.SQLExceptionInfo; +import org.apache.phoenix.schema.EqualityNotSupportedException; +import org.apache.phoenix.schema.types.PJson; +import org.codehaus.jackson.JsonFactory; +import org.codehaus.jackson.JsonNode; +import org.codehaus.jackson.JsonParser; +import org.codehaus.jackson.JsonParser.Feature; +import org.codehaus.jackson.JsonProcessingException; +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.node.ValueNode; + +import com.google.common.base.Preconditions; + +/** + * The {@link PhoenixJson} wraps json and uses Jackson library to parse and traverse the json. It + * should be used to represent the JSON data type and also should be used to parse Json data and + * read the value from it. It always conside the last value if same key exist more than once. + */ +public class PhoenixJson implements Comparable<PhoenixJson> { + private final JsonNode rootNode; + /* + * input data has been stored as it is, since some data is lost when json parser runs, for + * example if a JSON object within the value contains the same key more than once then only last + * one is stored rest all of them are ignored, which will defy the contract of PJsonDataType of + * keeping user data as it is. + */ + private final String jsonAsString; + + /** + * Static Factory method to get an {@link PhoenixJson} object. It also validates the json and + * throws {@link SQLException} if it is invalid with line number and character. + * @param jsonData Json data as {@link String}. + * @return {@link PhoenixJson}. + * @throws SQLException + */ + public static PhoenixJson getInstance(String jsonData) throws SQLException { + if (jsonData == null) { + return null; + } + try { + JsonFactory jsonFactory = new JsonFactory(); + JsonParser jsonParser = jsonFactory.createJsonParser(jsonData); + JsonNode jsonNode = getRootJsonNode(jsonParser); + return new PhoenixJson(jsonNode, jsonData); + } catch (IOException x) { + throw new SQLExceptionInfo.Builder(SQLExceptionCode.INVALID_JSON_DATA).setRootCause(x) + .setMessage(x.getMessage()).build().buildException(); + } + + } + + /** + * Returns the root of the resulting {@link JsonNode} tree. + */ + private static JsonNode getRootJsonNode(JsonParser jsonParser) throws IOException, + JsonProcessingException { + jsonParser.configure(Feature.ALLOW_COMMENTS, true); + ObjectMapper objectMapper = new ObjectMapper(); + try { + return objectMapper.readTree(jsonParser); + } finally { + jsonParser.close(); + } + } + + /* Default for unit testing */PhoenixJson(final JsonNode node, final String jsonData) { + Preconditions.checkNotNull(node, "root node cannot be null for json"); + this.rootNode = node; + this.jsonAsString = jsonData; + } + + /** + * Get {@link PhoenixJson} for a given json paths. For example : + * <p> + * <code> + * {"f2":{"f3":1},"f4":{"f5":99,"f6":{"f7":"2"}}}' + * </code> + * <p> + * for this source json, if we want to know the json at path {'f4','f6'} it will return + * {@link PhoenixJson} object for json {"f7":"2"}. It always returns the last key if same key + * exist more than once. + * <p> + * If the given path is unreachable then it throws {@link SQLException}. + * @param paths {@link String []} of path in the same order as they appear in json. + * @return {@link PhoenixJson} for the json against @paths. + * @throws SQLException + */ + public PhoenixJson getPhoenixJson(String[] paths) throws SQLException { + try { + PhoenixJson phoenixJson = getPhoenixJsonInternal(paths); + if (phoenixJson == null) { + throw new SQLException("path: " + Arrays.asList(paths) + " not found."); + } + return phoenixJson; + } catch (NumberFormatException nfe) { + throw new SQLException("path: " + Arrays.asList(paths) + " not found.", nfe); + } + } + + /** + * Get {@link PhoenixJson} for a given json paths. For example : + * <p> + * <code> + * {"f2":{"f3":1},"f4":{"f5":99,"f6":{"f7":"2"}}}' + * </code> + * <p> + * for this source json, if we want to know the json at path {'f4','f6'} it will return + * {@link PhoenixJson} object for json {"f7":"2"}. It always returns the last key if same key + * exist more than once. + * <p> + * If the given path is unreachable then it return null. + * @param paths {@link String []} of path in the same order as they appear in json. + * @return {@link PhoenixJson} for the json against @paths. + */ + public PhoenixJson getPhoenixJsonOrNull(String[] paths) { + try { + return getPhoenixJsonInternal(paths); + } catch (NumberFormatException nfe) { + // ignore + } + return null; + } + + /** + * Serialize the current {@link PhoenixJson} to String. Its required for + * json_extract_path_text(). If we just return node.toString() it will wrap String value in + * double quote which is not the expectation, hence avoiding calling toString() on + * {@link JsonNode} until PhoenixJson represent a Json Array or container for Json object. If + * PhoenixJson just represent a {@link ValueNode} then it should return value returned from + * objects toString(). + */ + public String serializeToString() { + if (this.rootNode == null || this.rootNode.isNull()) { + return null; + } else if (this.rootNode.isValueNode()) { + + if (this.rootNode.isNumber()) { + return this.rootNode.getNumberValue().toString(); + } else if (this.rootNode.isBoolean()) { + return String.valueOf(this.rootNode.getBooleanValue()); + } else if (this.rootNode.isTextual()) { + return this.rootNode.getTextValue(); + } else { + return this.jsonAsString; + } + } else if (this.rootNode.isArray()) { + return this.jsonAsString; + } else if (this.rootNode.isContainerNode()) { + return this.jsonAsString; + } + + return null; + + } + + @Override + public String toString() { + return this.jsonAsString; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + this.jsonAsString.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + throw new EqualityNotSupportedException(PJson.INSTANCE); + } + + /** + * @return length of the string represented by the current {@link PhoenixJson}. + */ + public int estimateByteSize() { + String jsonStr = toString(); + return jsonStr == null ? 1 : jsonStr.length(); + } + + public byte[] toBytes() { + return Bytes.toBytes(this.jsonAsString); + } + + @Override + public int compareTo(PhoenixJson o) { + throw new EqualityNotSupportedException(PJson.INSTANCE); + } + + private PhoenixJson getPhoenixJsonInternal(String[] paths) { + JsonNode node = this.rootNode; + for (String path : paths) { + JsonNode nodeTemp = null; + if (node.isArray()) { + int index = Integer.parseInt(path); + nodeTemp = node.path(index); + } else { + nodeTemp = node.path(path); + } + if (nodeTemp == null || nodeTemp.isMissingNode()) { + return null; + } + node = nodeTemp; + } + return new PhoenixJson(node, node.toString()); + } +} http://git-wip-us.apache.org/repos/asf/phoenix/blob/67e3e3bf/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PDataType.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PDataType.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PDataType.java index 60d2020..b24406e 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PDataType.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PDataType.java @@ -307,6 +307,21 @@ public abstract class PDataType<T> implements DataType<T>, Comparable<PDataType< return getClass() == o.getClass(); } + /** + * @return true if {@link PDataType} can be declared as primary key otherwise false. + */ + public boolean canBePrimaryKey() { + return true; + } + + /** + * @return true if {@link PDataType} supports equality operators (=,!=,<,>,<=,>=) otherwise + * false. + */ + public boolean isEqualitySupported() { + return true; + } + /** * @return true when {@code lhs} equals any of {@code rhs}. */ @@ -793,7 +808,7 @@ public abstract class PDataType<T> implements DataType<T>, Comparable<PDataType< public final boolean isNull(byte[] value) { return value == null || value.length == 0; } - + public byte[] toBytes(Object object, SortOrder sortOrder) { Preconditions.checkNotNull(sortOrder); byte[] bytes = toBytes(object); @@ -1152,17 +1167,17 @@ public abstract class PDataType<T> implements DataType<T>, Comparable<PDataType< return null; } for (PDataType type : PDataType.values()) { - if (type.isArrayType()) { - PhoenixArray arr = (PhoenixArray) value; - if ((type.getSqlType() == arr.baseType.sqlType + PDataType.ARRAY_TYPE_BASE) - && type.getJavaClass().isInstance(value)) { - return type; + if(type.getJavaClass().isInstance(value)){ + if (type.isArrayType()) { + PhoenixArray arr = (PhoenixArray) value; + if ((type.getSqlType() == arr.baseType.sqlType + + PDataType.ARRAY_TYPE_BASE)) { + return type; + } + } else { + return type; + } } - } else { - if (type.getJavaClass().isInstance(value)) { - return type; - } - } } throw new UnsupportedOperationException( "Unsupported literal value [" + value + "] of type " + value.getClass().getName()); http://git-wip-us.apache.org/repos/asf/phoenix/blob/67e3e3bf/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PDataTypeFactory.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PDataTypeFactory.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PDataTypeFactory.java index 45a9657..85ed169 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PDataTypeFactory.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PDataTypeFactory.java @@ -96,6 +96,7 @@ public class PDataTypeFactory { types.add(PVarbinaryArray.INSTANCE); types.add(PVarchar.INSTANCE); types.add(PVarcharArray.INSTANCE); + types.add(PJson.INSTANCE); classToInstance = new HashMap<>(types.size()); for (PDataType t : types) { http://git-wip-us.apache.org/repos/asf/phoenix/blob/67e3e3bf/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PJson.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PJson.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PJson.java new file mode 100644 index 0000000..0b556b2 --- /dev/null +++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PJson.java @@ -0,0 +1,213 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.phoenix.schema.types; + +import java.sql.SQLException; +import java.sql.Types; +import java.text.Format; + +import org.apache.hadoop.hbase.io.ImmutableBytesWritable; +import org.apache.phoenix.schema.EqualityNotSupportedException; +import org.apache.phoenix.schema.IllegalDataException; +import org.apache.phoenix.schema.SortOrder; +import org.apache.phoenix.schema.json.PhoenixJson; +import org.apache.phoenix.util.ByteUtil; +import org.apache.phoenix.util.StringUtil; + +import com.google.common.base.Preconditions; + +/** + * <p> + * A Phoenix data type to represent JSON. The json data type stores an exact copy of the input text, + * which processing functions must reparse on each execution. Because the json type stores an exact + * copy of the input text, it will preserve semantically-insignificant white space between tokens, + * as well as the order of keys within JSON objects. Also, if a JSON object within the value + * contains the same key more than once, all the key/value pairs are kept. It stores the data as + * string in single column of HBase and it has same data size limit as Phoenix's Varchar. + * <p> + * JSON data types are for storing JSON (JavaScript Object Notation) data, as specified in RFC 7159. + * Such data can also be stored as text, but the JSON data types have the advantage of enforcing + * that each stored value is valid according to the JSON rules. + */ +public class PJson extends PDataType<String> { + + public static final PJson INSTANCE = new PJson(); + + PJson() { + super("JSON", Types.OTHER, PhoenixJson.class, null, 48); + } + + @Override + public boolean canBePrimaryKey() { + return false; + } + + @Override + public boolean isEqualitySupported() { + return false; + } + + @Override + public int toBytes(Object object, byte[] bytes, int offset) { + + if (object == null) { + return 0; + } + byte[] b = toBytes(object); + System.arraycopy(b, 0, bytes, offset, b.length); + return b.length; + + } + + @Override + public byte[] toBytes(Object object) { + if (object == null) { + return ByteUtil.EMPTY_BYTE_ARRAY; + } + PhoenixJson phoenixJson = (PhoenixJson) object; + return phoenixJson.toBytes(); + } + + @Override + public Object toObject(byte[] bytes, int offset, int length, + @SuppressWarnings("rawtypes") PDataType actualType, SortOrder sortOrder, + Integer maxLength, Integer scale) { + + Object object = + PVarchar.INSTANCE.toObject(bytes, offset, length, actualType, sortOrder, maxLength, + scale); + /* + * avoiding the type casting of object to String by calling toString() since String's + * toString() returns itself. + */ + return object == null ? object : getPhoenixJson(object.toString()); + + } + + @Override + public Object toObject(Object object, @SuppressWarnings("rawtypes") PDataType actualType) { + if (object == null) { + return null; + } + if (equalsAny(actualType, PJson.INSTANCE)) { + return object; + } + if (equalsAny(actualType, PVarchar.INSTANCE)) { + return getPhoenixJson(object.toString()); + } + return throwConstraintViolationException(actualType, this); + } + + @Override + public boolean isCoercibleTo(@SuppressWarnings("rawtypes") PDataType targetType) { + return equalsAny(targetType, this, PVarchar.INSTANCE); + + } + + @Override + public boolean isSizeCompatible(ImmutableBytesWritable ptr, Object value, + @SuppressWarnings("rawtypes") PDataType srcType, Integer maxLength, Integer scale, + Integer desiredMaxLength, Integer desiredScale) { + return PVarchar.INSTANCE.isSizeCompatible(ptr, value, srcType, maxLength, scale, + desiredMaxLength, desiredScale); + } + + @Override + public boolean isFixedWidth() { + return false; + } + + @Override + public int estimateByteSize(Object o) { + PhoenixJson phoenixJson = (PhoenixJson) o; + return phoenixJson.estimateByteSize(); + } + + @Override + public Integer getByteSize() { + return null; + } + + @Override + public int compareTo(Object lhs, Object rhs, @SuppressWarnings("rawtypes") PDataType rhsType) { + if (PJson.INSTANCE != rhsType) { + throwConstraintViolationException(rhsType, this); + } + throw new EqualityNotSupportedException(PJson.INSTANCE); + } + + @Override + public Object toObject(String value) { + return getPhoenixJson(value); + } + + @Override + public boolean isBytesComparableWith(@SuppressWarnings("rawtypes") PDataType otherType) { + return otherType == PJson.INSTANCE || otherType == PVarchar.INSTANCE; + } + + @Override + public String toStringLiteral(Object o, Format formatter) { + if (o == null) { + return StringUtil.EMPTY_STRING; + } + PhoenixJson phoenixJson = (PhoenixJson) o; + return PVarchar.INSTANCE.toStringLiteral(phoenixJson.toString(), formatter); + } + + @Override + public Object getSampleValue(Integer maxLength, Integer arrayLength) { + Preconditions.checkArgument(maxLength == null || maxLength >= 0); + + char[] key = new char[4]; + char[] value = new char[4]; + int length = maxLength != null ? maxLength : 1; + if (length > (key.length + value.length)) { + key = new char[length + 2]; + value = new char[length - key.length]; + } + int j = 1; + key[0] = '"'; + key[j++] = 'k'; + for (int i = 2; i < key.length - 1; i++) { + key[j++] = (char) ('0' + RANDOM.get().nextInt(Byte.MAX_VALUE) % 10); + } + key[j] = '"'; + + int k = 1; + value[0] = '"'; + value[k++] = 'v'; + for (int i = 2; i < value.length - 1; i++) { + value[k++] = (char) ('0' + RANDOM.get().nextInt(Byte.MAX_VALUE) % 10); + } + value[k] = '"'; + StringBuilder sbr = new StringBuilder(); + sbr.append("{").append(key).append(":").append(value).append("}"); + + return getPhoenixJson(sbr.toString()); + } + + private Object getPhoenixJson(String jsonData) { + try { + return PhoenixJson.getInstance(jsonData); + } catch (SQLException sqe) { + throw new IllegalDataException(sqe); + } + } +} http://git-wip-us.apache.org/repos/asf/phoenix/blob/67e3e3bf/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PVarchar.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PVarchar.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PVarchar.java index fa3dbad..509e090 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PVarchar.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PVarchar.java @@ -83,7 +83,7 @@ public class PVarchar extends PDataType<String> { @Override public boolean isCoercibleTo(PDataType targetType) { - return equalsAny(targetType, this, PChar.INSTANCE, PVarbinary.INSTANCE, PBinary.INSTANCE); + return equalsAny(targetType, this, PChar.INSTANCE, PVarbinary.INSTANCE, PBinary.INSTANCE, PJsonDataType.INSTANCE); } @Override http://git-wip-us.apache.org/repos/asf/phoenix/blob/67e3e3bf/phoenix-core/src/test/java/org/apache/phoenix/expression/CoerceExpressionTest.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/test/java/org/apache/phoenix/expression/CoerceExpressionTest.java b/phoenix-core/src/test/java/org/apache/phoenix/expression/CoerceExpressionTest.java index b7baa97..0d49ab0 100644 --- a/phoenix-core/src/test/java/org/apache/phoenix/expression/CoerceExpressionTest.java +++ b/phoenix-core/src/test/java/org/apache/phoenix/expression/CoerceExpressionTest.java @@ -26,6 +26,7 @@ import java.sql.Timestamp; import java.util.HashMap; import org.apache.hadoop.hbase.io.ImmutableBytesWritable; +import org.apache.phoenix.schema.json.PhoenixJson; import org.apache.phoenix.schema.types.PBinary; import org.apache.phoenix.schema.types.PChar; import org.apache.phoenix.schema.types.PDecimal; @@ -34,7 +35,6 @@ import org.apache.phoenix.schema.types.PLong; import org.apache.phoenix.schema.types.PVarbinary; import org.apache.phoenix.schema.types.PVarchar; import org.junit.Test; - import org.apache.phoenix.schema.types.PDataType; /** @@ -49,6 +49,7 @@ public class CoerceExpressionTest { private static final HashMap<Class, Object> map = new HashMap<Class, Object>(); static { + map.put(PhoenixJson.class, "[1,2,3]"); map.put(String.class, "a"); map.put(Long.class, 1l); map.put(Integer.class, 1); http://git-wip-us.apache.org/repos/asf/phoenix/blob/67e3e3bf/phoenix-core/src/test/java/org/apache/phoenix/schema/json/PhoenixJsonTest.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/test/java/org/apache/phoenix/schema/json/PhoenixJsonTest.java b/phoenix-core/src/test/java/org/apache/phoenix/schema/json/PhoenixJsonTest.java new file mode 100644 index 0000000..4f9455d --- /dev/null +++ b/phoenix-core/src/test/java/org/apache/phoenix/schema/json/PhoenixJsonTest.java @@ -0,0 +1,194 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.phoenix.schema.json; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import java.sql.SQLException; +import java.util.Arrays; + +import org.apache.phoenix.exception.SQLExceptionCode; +import org.apache.phoenix.schema.ConstraintViolationException; +import org.apache.phoenix.schema.EqualityNotSupportedException; +import org.junit.Assert; +import org.junit.Test; + +/** + * Unit test for {@link PhoenixJson}. + */ +public class PhoenixJsonTest { + public static final String TEST_JSON_STR = + "{\"f2\":{\"f3\":\"value\"},\"f4\":{\"f5\":99,\"f6\":[1,true,\"foo\"]},\"f7\":true}"; + + @Test + public void testParsingForJsonHavingChineseChars() throws Exception { + + String jsonWithChineseChars = "[\"'æ®æ´¾'\"]"; + PhoenixJson phoenixJson = PhoenixJson.getInstance(jsonWithChineseChars); + assertNotNull(phoenixJson); + assertEquals(jsonWithChineseChars, phoenixJson.toString()); + + } + + @Test + public void testParsingForJsonHavingControlAndQuoteChars() throws Exception { + + String jsonWithControlChars = "[\"\\n \\\"jumps \\r'æ®æ´¾'\"]"; + PhoenixJson phoenixJson = PhoenixJson.getInstance(jsonWithControlChars); + assertNotNull(phoenixJson); + assertEquals(jsonWithControlChars, phoenixJson.toString()); + + } + + @Test + public void testEmptyJsonParsing() throws Exception { + + String emptyJson = "{}"; + PhoenixJson phoenixJson = PhoenixJson.getInstance(emptyJson); + assertNotNull(phoenixJson); + assertEquals(emptyJson, phoenixJson.toString()); + + } + + @Test + public void testZeroLengthJsonStringForParsing() throws Exception { + + String emptyJson = ""; + try { + PhoenixJson.getInstance(emptyJson); + } catch (SQLException expectedException) { + assertEquals("error code is not as expected.", + SQLExceptionCode.INVALID_JSON_DATA.getErrorCode(), expectedException.getErrorCode()); + assertEquals("sql state is not as expected.", + SQLExceptionCode.INVALID_JSON_DATA.getSQLState(), expectedException.getSQLState()); + } + + } + + @Test + public void testNullJsonStringForParsing() throws Exception { + + String nullJson = null; + try { + PhoenixJson.getInstance(nullJson); + } catch (SQLException expectedException) { + assertEquals("error code is not as expected.", + SQLExceptionCode.INVALID_JSON_DATA.getErrorCode(), expectedException.getErrorCode()); + assertEquals("sql state is not as expected.", + SQLExceptionCode.INVALID_JSON_DATA.getSQLState(), expectedException.getSQLState()); + } + + } + + @Test + public void testJsonArrayParsing() throws Exception { + + String jsonArrayString = "[1,2,3]"; + PhoenixJson phoenixJson = PhoenixJson.getInstance(jsonArrayString); + assertNotNull(phoenixJson); + assertEquals(jsonArrayString, phoenixJson.toString()); + } + + @Test + public void testVaidJsonParsing() throws Exception { + + PhoenixJson phoenixJson = PhoenixJson.getInstance(TEST_JSON_STR); + assertNotNull(phoenixJson); + assertEquals(TEST_JSON_STR, phoenixJson.toString()); + } + + @Test + public void getPhoenixJson() throws Exception { + PhoenixJson phoenixJson = PhoenixJson.getInstance(TEST_JSON_STR); + PhoenixJson phoenixJson2 = phoenixJson.getPhoenixJson(new String[] { "f2", "f3" }); + assertEquals("value", phoenixJson2.serializeToString()); + + String[] paths = new String[] { "f2", "f3", "f4" }; + try { + phoenixJson.getPhoenixJson(paths); + } catch (Exception e) { + SQLException jsonException = + new SQLException("path: " + Arrays.asList(paths) + " not found."); + assertEquals(jsonException.getMessage(), e.getMessage()); + assertEquals(jsonException.getClass(), e.getClass()); + } + } + + @Test + public void getNullablePhoenixJson() throws Exception { + PhoenixJson phoenixJson = PhoenixJson.getInstance(TEST_JSON_STR); + PhoenixJson phoenixJson2 = phoenixJson.getPhoenixJsonOrNull(new String[] { "f2", "f3" }); + assertEquals("value", phoenixJson2.serializeToString()); + + assertNull(phoenixJson.getPhoenixJsonOrNull(new String[] { "f2", "f3", "f4" })); + assertNotNull(phoenixJson.getPhoenixJsonOrNull(new String[] { "f4", "f6", "1" })); + assertNull(phoenixJson.getPhoenixJsonOrNull(new String[] { "f4", "f6", "3" })); + assertNull(phoenixJson.getPhoenixJsonOrNull(new String[] { "f4", "f6", "-1" })); + } + + @Test + public void serializeToString() throws Exception { + PhoenixJson phoenixJson = PhoenixJson.getInstance(TEST_JSON_STR); + PhoenixJson phoenixJson2 = phoenixJson.getPhoenixJson(new String[] { "f4", "f5" }); + assertEquals(new Integer(99).toString(), phoenixJson2.serializeToString()); + + PhoenixJson phoenixJson3 = phoenixJson.getPhoenixJson(new String[] { "f7" }); + assertEquals(Boolean.TRUE.toString(), phoenixJson3.serializeToString()); + + PhoenixJson phoenixJson4 = phoenixJson.getPhoenixJson(new String[] { "f2", "f3" }); + assertEquals("value", phoenixJson4.serializeToString()); + + PhoenixJson phoenixJson5 = phoenixJson.getPhoenixJson(new String[] { "f4", "f6" }); + + assertEquals("[1,true,\"foo\"]", phoenixJson5.serializeToString()); + } + + @Test + public void serializeToStringForJsonArray() throws Exception { + PhoenixJson phoenixJson = PhoenixJson.getInstance(TEST_JSON_STR); + PhoenixJson phoenixJson5 = + phoenixJson.getPhoenixJsonOrNull(new String[] { "f4", "f6", "0" }); + assertEquals(new Integer(1).toString(), phoenixJson5.serializeToString()); + phoenixJson5 = phoenixJson.getPhoenixJsonOrNull(new String[] { "f4", "f6", "1" }); + assertEquals(Boolean.TRUE.toString(), phoenixJson5.serializeToString()); + phoenixJson5 = phoenixJson.getPhoenixJsonOrNull(new String[] { "f4", "f6", "2" }); + assertEquals("foo", phoenixJson5.serializeToString()); + } + + @Test + public void testToString() throws Exception { + PhoenixJson phoenixJson = PhoenixJson.getInstance(TEST_JSON_STR); + assertEquals(TEST_JSON_STR, phoenixJson.toString()); + } + + @Test + public void compareTo() throws Exception { + PhoenixJson phoenixJson1 = PhoenixJson.getInstance(TEST_JSON_STR); + + try{ + phoenixJson1.compareTo(phoenixJson1); + }catch(EqualityNotSupportedException x){ + SQLException sqe =(SQLException)x.getCause(); + assertEquals(SQLExceptionCode.NON_EQUALITY_COMPARISON.getErrorCode(), sqe.getErrorCode()); + } + + } +} http://git-wip-us.apache.org/repos/asf/phoenix/blob/67e3e3bf/phoenix-core/src/test/java/org/apache/phoenix/schema/types/PDataTypeTest.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/test/java/org/apache/phoenix/schema/types/PDataTypeTest.java b/phoenix-core/src/test/java/org/apache/phoenix/schema/types/PDataTypeTest.java index 85f9436..8d445ec 100644 --- a/phoenix-core/src/test/java/org/apache/phoenix/schema/types/PDataTypeTest.java +++ b/phoenix-core/src/test/java/org/apache/phoenix/schema/types/PDataTypeTest.java @@ -1714,6 +1714,7 @@ public class PDataTypeTest { + "FLOAT ARRAY=[BINARY ARRAY, DECIMAL ARRAY, DOUBLE ARRAY, FLOAT ARRAY, VARBINARY ARRAY], " + "INTEGER=[BIGINT, BINARY, DECIMAL, DOUBLE, FLOAT, INTEGER, VARBINARY], " + "INTEGER ARRAY=[BIGINT ARRAY, BINARY ARRAY, DECIMAL ARRAY, DOUBLE ARRAY, FLOAT ARRAY, INTEGER ARRAY, VARBINARY ARRAY], " + + "JSON=[JSON, VARCHAR], " + "SMALLINT=[BIGINT, BINARY, DECIMAL, DOUBLE, FLOAT, INTEGER, SMALLINT, VARBINARY], " + "SMALLINT ARRAY=[BIGINT ARRAY, BINARY ARRAY, DECIMAL ARRAY, DOUBLE ARRAY, FLOAT ARRAY, INTEGER ARRAY, SMALLINT ARRAY, VARBINARY ARRAY], " + "TIME=[BINARY, DATE, TIME, TIMESTAMP, VARBINARY], " @@ -1742,7 +1743,7 @@ public class PDataTypeTest { + "UNSIGNED_TINYINT ARRAY=[BIGINT ARRAY, BINARY ARRAY, DECIMAL ARRAY, DOUBLE ARRAY, FLOAT ARRAY, INTEGER ARRAY, SMALLINT ARRAY, TINYINT ARRAY, UNSIGNED_DOUBLE ARRAY, UNSIGNED_FLOAT ARRAY, UNSIGNED_INT ARRAY, UNSIGNED_LONG ARRAY, UNSIGNED_SMALLINT ARRAY, UNSIGNED_TINYINT ARRAY, VARBINARY ARRAY], " + "VARBINARY=[BINARY, VARBINARY], " + "VARBINARY ARRAY=[BINARY ARRAY, VARBINARY ARRAY], " - + "VARCHAR=[BINARY, CHAR, VARBINARY, VARCHAR], " + + "VARCHAR=[BINARY, CHAR, JSON, VARBINARY, VARCHAR], " + "VARCHAR ARRAY=[BINARY ARRAY, CHAR ARRAY, VARBINARY ARRAY, VARCHAR ARRAY]}", coercibleToMap.toString()); } http://git-wip-us.apache.org/repos/asf/phoenix/blob/67e3e3bf/phoenix-core/src/test/java/org/apache/phoenix/schema/types/PJsonTest.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/test/java/org/apache/phoenix/schema/types/PJsonTest.java b/phoenix-core/src/test/java/org/apache/phoenix/schema/types/PJsonTest.java new file mode 100644 index 0000000..5939ef7 --- /dev/null +++ b/phoenix-core/src/test/java/org/apache/phoenix/schema/types/PJsonTest.java @@ -0,0 +1,187 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.phoenix.schema.types; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.sql.SQLException; + +import org.apache.hadoop.hbase.io.ImmutableBytesWritable; +import org.apache.phoenix.exception.SQLExceptionCode; +import org.apache.phoenix.schema.ConstraintViolationException; +import org.apache.phoenix.schema.EqualityNotSupportedException; +import org.apache.phoenix.schema.SortOrder; +import org.apache.phoenix.schema.TypeMismatchException; +import org.apache.phoenix.schema.json.PhoenixJson; +import org.apache.phoenix.schema.json.PhoenixJsonTest; +import org.apache.phoenix.util.ByteUtil; +import org.junit.Assert; +import org.junit.Test; + +/** + * Unit test for {@link PJson}. + */ +public class PJsonTest { + + final byte[] json = PhoenixJsonTest.TEST_JSON_STR.getBytes(); + + @Test + public void testToBytesWithOffset() throws Exception { + PhoenixJson phoenixJson = PhoenixJson.getInstance(PhoenixJsonTest.TEST_JSON_STR); + + byte[] bytes = new byte[json.length]; + + assertEquals(json.length, PJson.INSTANCE.toBytes(phoenixJson, bytes, 0)); + + assertArrayEquals(json, bytes); + } + + @Test + public void testToBytes() throws Exception { + PhoenixJson phoenixJson = PhoenixJson.getInstance(PhoenixJsonTest.TEST_JSON_STR); + + byte[] bytes = PJson.INSTANCE.toBytes(phoenixJson); + assertArrayEquals(json, bytes); + } + + @Test + public void testToBytesForNull() throws Exception { + assertEquals(ByteUtil.EMPTY_BYTE_ARRAY,PJson.INSTANCE.toBytes(null)); + assertEquals(ByteUtil.EMPTY_BYTE_ARRAY,PJson.INSTANCE.toBytes(null, SortOrder.ASC)); + assertEquals(ByteUtil.EMPTY_BYTE_ARRAY,PJson.INSTANCE.toBytes(null, SortOrder.DESC)); + } + + @Test + public void testToObjectWithSortOrder() { + Object object = + PJson.INSTANCE.toObject(json, 0, json.length, PVarchar.INSTANCE, SortOrder.ASC, + Integer.MAX_VALUE, Integer.MAX_VALUE); + PhoenixJson phoenixJson = (PhoenixJson) object; + assertEquals(PhoenixJsonTest.TEST_JSON_STR, phoenixJson.toString()); + } + + @Test + public void testToObject() { + Object object = + PJson.INSTANCE.toObject(json, 0, json.length, PVarchar.INSTANCE, SortOrder.ASC, + Integer.MAX_VALUE, Integer.MAX_VALUE); + PhoenixJson phoenixJson = (PhoenixJson) object; + assertEquals(PhoenixJsonTest.TEST_JSON_STR, phoenixJson.toString()); + + Object object2 = PJson.INSTANCE.toObject(phoenixJson, PJson.INSTANCE); + assertEquals(phoenixJson.toString(), object2.toString()); + + PJson.INSTANCE.toObject(PhoenixJsonTest.TEST_JSON_STR, PVarchar.INSTANCE); + assertEquals(phoenixJson.toString(), object2.toString()); + + try { + PJson.INSTANCE.toObject(PhoenixJsonTest.TEST_JSON_STR, PChar.INSTANCE); + } catch (ConstraintViolationException sqe) { + TypeMismatchException e = (TypeMismatchException) sqe.getCause(); + assertEquals(SQLExceptionCode.TYPE_MISMATCH.getErrorCode(), e.getErrorCode()); + } + + PhoenixJson object4 = (PhoenixJson) PJson.INSTANCE.toObject(PhoenixJsonTest.TEST_JSON_STR); + assertEquals(phoenixJson.toString(), object4.toString()); + + } + + @Test + public void testToObjectForNull(){ + String jsonStr= null; + assertNull(PJson.INSTANCE.toObject(jsonStr)); + + Object jsonObj = null; + assertNull(PJson.INSTANCE.toObject(jsonObj, PJson.INSTANCE)); + } + + @Test + public void isFixedWidth() { + assertFalse(PJson.INSTANCE.isFixedWidth()); + } + + public void getByteSize() { + assertNull(PJson.INSTANCE.getByteSize()); + } + + public void estimateByteSize() throws Exception { + PhoenixJson phoenixJson = PhoenixJson.getInstance(PhoenixJsonTest.TEST_JSON_STR); + assertEquals(PhoenixJsonTest.TEST_JSON_STR.length(), + PJson.INSTANCE.estimateByteSize(phoenixJson)); + } + + @Test + public void compareTo() throws Exception { + PhoenixJson phoenixJson1 = PhoenixJson.getInstance(PhoenixJsonTest.TEST_JSON_STR); + + PhoenixJson phoenixJson2 = PhoenixJson.getInstance(PhoenixJsonTest.TEST_JSON_STR); + + try{ + assertEquals(0, PJson.INSTANCE.compareTo(phoenixJson1, phoenixJson2, PJson.INSTANCE)); + Assert.fail("Comparision on PJson should have thrown ConstraintViolationException"); + }catch(EqualityNotSupportedException x){ + SQLException sqe = (SQLException) x.getCause(); + assertEquals(SQLExceptionCode.NON_EQUALITY_COMPARISON.getErrorCode(), + sqe.getErrorCode()); + } + + try { + PJson.INSTANCE + .compareTo(phoenixJson1, PhoenixJsonTest.TEST_JSON_STR, PVarchar.INSTANCE); + Assert.fail("Comparision on PJson should have thrown ConstraintViolationException"); + } catch (ConstraintViolationException x) { + TypeMismatchException e = (TypeMismatchException) x.getCause(); + assertEquals(SQLExceptionCode.TYPE_MISMATCH.getErrorCode(), e.getErrorCode()); + } + } + + @Test + public void isBytesComparableWith() { + assertTrue(PJson.INSTANCE.isBytesComparableWith(PJson.INSTANCE)); + assertTrue(PJson.INSTANCE.isBytesComparableWith(PVarchar.INSTANCE)); + assertFalse(PJson.INSTANCE.isBytesComparableWith(PChar.INSTANCE)); + } + + @Test + public void toStringLiteral() throws Exception { + PhoenixJson phoenixJson = PhoenixJson.getInstance(PhoenixJsonTest.TEST_JSON_STR); + String stringLiteral = PJson.INSTANCE.toStringLiteral(phoenixJson, null); + assertEquals(PVarchar.INSTANCE.toStringLiteral(PhoenixJsonTest.TEST_JSON_STR, null), + stringLiteral); + } + + @Test + public void coerceBytes() throws SQLException { + PhoenixJson phoenixJson = PhoenixJson.getInstance(PhoenixJsonTest.TEST_JSON_STR); + ImmutableBytesWritable ptr = new ImmutableBytesWritable(); + ptr.set(json, 0, 0); + PJson.INSTANCE.coerceBytes(ptr, phoenixJson, PJson.INSTANCE, json.length, new Integer(10), + SortOrder.ASC, json.length, new Integer(10), SortOrder.ASC); + assertEquals(0, ptr.getLength()); + + ptr.set(json); + PJson.INSTANCE.coerceBytes(ptr, phoenixJson, PVarchar.INSTANCE, json.length, + new Integer(10), SortOrder.ASC, json.length, new Integer(10), SortOrder.ASC); + assertArrayEquals(json, ptr.get()); + } + +} http://git-wip-us.apache.org/repos/asf/phoenix/blob/67e3e3bf/phoenix-core/src/test/java/org/apache/phoenix/util/IndexUtilTest.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/test/java/org/apache/phoenix/util/IndexUtilTest.java b/phoenix-core/src/test/java/org/apache/phoenix/util/IndexUtilTest.java index b7f0773..6ee7da5 100644 --- a/phoenix-core/src/test/java/org/apache/phoenix/util/IndexUtilTest.java +++ b/phoenix-core/src/test/java/org/apache/phoenix/util/IndexUtilTest.java @@ -50,6 +50,7 @@ public class IndexUtilTest { + "FLOAT ARRAY=FLOAT ARRAY, " + "INTEGER=INTEGER, " + "INTEGER ARRAY=INTEGER ARRAY, " + + "JSON=JSON, " + "SMALLINT=SMALLINT, " + "SMALLINT ARRAY=SMALLINT ARRAY, " + "TIME=TIME, " @@ -103,6 +104,7 @@ public class IndexUtilTest { + "FLOAT ARRAY=FLOAT ARRAY, " + "INTEGER=DECIMAL, " + "INTEGER ARRAY=INTEGER ARRAY, " + + "JSON=JSON, " + "SMALLINT=DECIMAL, " + "SMALLINT ARRAY=SMALLINT ARRAY, " + "TIME=DECIMAL, " http://git-wip-us.apache.org/repos/asf/phoenix/blob/67e3e3bf/phoenix-core/src/test/java/org/apache/phoenix/util/PhoenixRuntimeTest.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/test/java/org/apache/phoenix/util/PhoenixRuntimeTest.java b/phoenix-core/src/test/java/org/apache/phoenix/util/PhoenixRuntimeTest.java index b6465c3..920f78e 100644 --- a/phoenix-core/src/test/java/org/apache/phoenix/util/PhoenixRuntimeTest.java +++ b/phoenix-core/src/test/java/org/apache/phoenix/util/PhoenixRuntimeTest.java @@ -37,6 +37,7 @@ import java.util.Properties; import org.apache.commons.lang.exception.ExceptionUtils; import org.apache.hadoop.hbase.util.Pair; import org.apache.phoenix.compile.QueryPlan; +import org.apache.phoenix.exception.SQLExceptionCode; import org.apache.phoenix.expression.Expression; import org.apache.phoenix.jdbc.PhoenixDatabaseMetaData; import org.apache.phoenix.query.BaseConnectionlessQueryTest; @@ -132,7 +133,17 @@ public class PhoenixRuntimeTest extends BaseConnectionlessQueryTest { // create a table by using the type name as returned by PDataType sb.append("CREATE TABLE " + tableName + " ("); sb.append(columnName + " " + sqlTypeName + " NOT NULL PRIMARY KEY, V1 VARCHAR)"); - conn.createStatement().execute(sb.toString()); + if(!pType.canBePrimaryKey()) { + try{ + conn.createStatement().execute(sb.toString()); + fail("<"+pType.toString()+ "> should throw exception since primary key is not supported"); + }catch(SQLException sqe){ + assertEquals(SQLExceptionCode.INVALID_PRIMARY_KEY_CONSTRAINT.getErrorCode(), sqe.getErrorCode()); + } + continue; + }else { + conn.createStatement().execute(sb.toString()); + } // generate the optimized query plan by going through the pk of the table. PreparedStatement stmt = conn.prepareStatement("SELECT * FROM " + tableName + " WHERE " + columnName + " = ?");
