diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ArrayToJsonFunctionIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ArrayToJsonFunctionIT.java new file mode 100755 index 00000000000..3649c04e11f --- /dev/null +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ArrayToJsonFunctionIT.java @@ -0,0 +1,198 @@ +/* + * 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.ROW1; +import static org.apache.phoenix.util.TestUtil.TABLE_WITH_ARRAY; +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.assertTrue; + +import java.sql.*; +import java.util.Properties; +import org.apache.phoenix.expression.function.ArrayToJsonFunction; +import org.apache.phoenix.query.BaseTest; +import org.apache.phoenix.util.PhoenixRuntime; +import org.apache.phoenix.util.PropertiesUtil; +import org.junit.Test; + +/** + * End to end test for {@link org.apache.phoenix.expression.function.ArrayToJsonFunction}. + * + */ +public class ArrayToJsonFunctionIT extends BaseHBaseManagedTimeIT { + private static final String TABLE_WITH_ALL_ARRAY_TYPES = "TABLE_WITH_ALL_ARRAY_TYPES"; + + @Test + public void testArrayToJsonWithAllArrayTypes() throws Exception { + // create the table + createTableWithAllArrayTypes(getUrl()); + + Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); + Connection conn = DriverManager.getConnection(getUrl(), props); + conn.setAutoCommit(false); + + try{ + // populate the table with data + PreparedStatement stmt = + conn.prepareStatement("UPSERT INTO " + + TABLE_WITH_ALL_ARRAY_TYPES + + "(pk, BOOLEAN_ARRAY, BYTE_ARRAY, DOUBLE_ARRAY, FLOAT_ARRAY, INT_ARRAY, LONG_ARRAY, SHORT_ARRAY, STRING_ARRAY)\n" + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"); + + stmt.setString(1, "valueOne"); + + // boolean array + Array boolArray = conn.createArrayOf("BOOLEAN", new Boolean[] { true,false }); + int boolColumnIndex = 2; + stmt.setArray(boolColumnIndex , boolArray); + // byte array + Array byteArray = conn.createArrayOf("TINYINT", new Byte[] { 11, 22 }); + int byteColumnIndex = 3; + stmt.setArray(byteColumnIndex, byteArray); + // double array + Array doubleArray = conn.createArrayOf("DOUBLE", new Double[] { 67.78, 78.89 }); + int doubleColumnIndex = 4; + stmt.setArray(doubleColumnIndex, doubleArray); + // float array + Array floatArray = conn.createArrayOf("FLOAT", new Float[] { 12.23f, 45.56f }); + int floatColumnIndex = 5; + stmt.setArray(floatColumnIndex, floatArray); + // int array + Array intArray = conn.createArrayOf("INTEGER", new Integer[] { 5555, 6666 }); + int intColumnIndex = 6; + stmt.setArray(intColumnIndex, intArray); + // long array + Array longArray = conn.createArrayOf("BIGINT", new Long[] { 7777777L, 8888888L }); + int longColumnIndex = 7; + stmt.setArray(longColumnIndex, longArray); + // short array + Array shortArray = conn.createArrayOf("SMALLINT", new Short[] { 333, 444 }); + int shortColumnIndex = 8; + stmt.setArray(shortColumnIndex, shortArray); + // create character array + Array stringArray = conn.createArrayOf("VARCHAR", new String[] { "a", "b" }); + int stringColumnIndex = 9; + stmt.setArray(stringColumnIndex, stringArray); + stmt.execute(); + conn.commit(); + + stmt = + conn.prepareStatement("SELECT pk, " + + "array_to_json(boolean_array), " + + "array_to_json(byte_array), " + + "array_to_json(double_array), " + + "array_to_json(float_array), " + + "array_to_json(int_array), " + + "array_to_json(long_array), " + + "array_to_json(short_array)," + + "array_to_json(string_array) FROM " + + TABLE_WITH_ALL_ARRAY_TYPES); + + ResultSet rs = stmt.executeQuery(); + assertTrue(rs.next()); + + assertEquals("valueOne", rs.getString(1)); + assertArrayToJson(rs, boolColumnIndex , "[true,false]"); + assertArrayToJson(rs, byteColumnIndex, "[11,22]"); + assertArrayToJson(rs, doubleColumnIndex, "[67.78,78.89]"); + assertArrayToJson(rs, floatColumnIndex, "[12.23,45.56]"); + assertArrayToJson(rs, intColumnIndex, "[5555,6666]"); + assertArrayToJson(rs, longColumnIndex, "[7777777,8888888]"); + assertArrayToJson(rs, shortColumnIndex, "[333,444]"); + assertArrayToJson(rs, stringColumnIndex, "[\"a\",\"b\"]"); + + } finally { + conn.close(); + } + + } + + @Test + public void testArrayToJsonWithNullValueArray() throws Exception { + // create the table + String ddlStmt = "create table " + + "TABLE_NULL_VALUE_ARRAY" + + " (PK VARCHAR NOT NULL PRIMARY KEY,\n" + + " NULL_VALUE_ARRAY varchar(100) array[2]" + + ")"; + createTestTable(getUrl(), ddlStmt); + + Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); + Connection conn = DriverManager.getConnection(getUrl(), props); + conn.setAutoCommit(false); + + try{ + // populate the table with data + PreparedStatement stmt = + conn.prepareStatement("UPSERT INTO " + + "TABLE_NULL_VALUE_ARRAY" + + "(PK, NULL_VALUE_ARRAY)\n" + + "VALUES (?, ?)"); + + stmt.setString(1, "valueOne"); + + Array nullValueArray = conn.createArrayOf("VARCHAR", new String[] { null, null }); + int nullValueIndex = 2; + stmt.setArray(nullValueIndex, nullValueArray); + stmt.execute(); + conn.commit(); + + stmt = + conn.prepareStatement("SELECT PK, " + + "ARRAY_TO_JSON(NULL_VALUE_ARRAY) FROM " + + "TABLE_NULL_VALUE_ARRAY"); + + ResultSet rs = stmt.executeQuery(); + assertTrue(rs.next()); + + assertEquals("valueOne", rs.getString(1)); + assertArrayToJson(rs, nullValueIndex, "[null,null]"); + + } finally { + conn.close(); + } + + } + + + + private void assertArrayToJson(ResultSet rs, int arrayIndex, String expectedJson) + throws SQLException { + assertEquals("Json array data is not as expected.",expectedJson, rs.getString(arrayIndex)); + } + + + private static void createTableWithAllArrayTypes(String url) throws SQLException { + String ddlStmt = "create table " + + TABLE_WITH_ALL_ARRAY_TYPES + + " (pk VARCHAR NOT NULL PRIMARY KEY,\n" + + " boolean_array boolean array[2],\n" + + " byte_array tinyint[2],\n" + + " double_array double[2],\n" + + " float_array float[2],\n" + + " int_array integer[2],\n" + + " long_array bigint[5],\n" + + " short_array smallint[2],\n" + + " string_array varchar(100) array[2]" + + ")"; + createTestTable(url, ddlStmt); + } + +} diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/JsonArrayElementsFunctionIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/JsonArrayElementsFunctionIT.java new file mode 100755 index 00000000000..a91c4979566 --- /dev/null +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/JsonArrayElementsFunctionIT.java @@ -0,0 +1,176 @@ +/* + * 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 org.apache.phoenix.exception.SQLExceptionCode; +import org.apache.phoenix.schema.types.PhoenixArray; +import org.apache.phoenix.util.PropertiesUtil; +import org.junit.Test; + +import java.sql.*; +import java.util.Properties; + +import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; +import static org.junit.Assert.*; + +/** + * End to end test for {@link org.apache.phoenix.expression.function.JsonArrayElementsFunction}. + * + */ +public class JsonArrayElementsFunctionIT extends BaseHBaseManagedTimeIT { + + @Test + public void testJsonArrayElementsWithSameType() throws Exception { + Connection conn = getConnection(); + + try { + String json = "[25.343,36.763,37.56,386.63]"; + String pk = "valueOne"; + + populateJsonTable(conn, json, pk); + + String selectQuery = "SELECT json_array_elements(col1) FROM testJson WHERE pk = 'valueOne'"; + PreparedStatement stmt = conn.prepareStatement(selectQuery); + ResultSet rs = stmt.executeQuery(); + assertTrue(rs.next()); + String[] strArr = new String[4]; + strArr[0] = "25.343"; + strArr[1] = "36.763"; + strArr[2] = "37.56"; + strArr[3] = "386.63"; + Array array = conn.createArrayOf("VARCHAR", strArr); + PhoenixArray resultArray = (PhoenixArray) rs.getArray(1); + assertEquals("Json array elements is not as expected.", resultArray, + array); + assertFalse(rs.next()); + + } finally { + conn.close(); + } + } + @Test + public void testJsonArrayElementsWithDifferentDataTypes() throws Exception { + Connection conn = getConnection(); + + try { + String json = "[1,36.763,null,false,\"string\"]"; + String pk = "valueOne"; + + populateJsonTable(conn, json, pk); + + String selectQuery = "SELECT json_array_elements(col1) FROM testJson WHERE pk = 'valueOne'"; + PreparedStatement stmt = conn.prepareStatement(selectQuery); + ResultSet rs = stmt.executeQuery(); + assertTrue(rs.next()); + String[] strArr = new String[5]; + strArr[0] = "1"; + strArr[1] = "36.763"; + strArr[2] = "null"; + strArr[3] = "false"; + strArr[4] = "\"string\""; + + Array array = conn.createArrayOf("VARCHAR", strArr); + PhoenixArray resultArray = (PhoenixArray) rs.getArray(1); + + assertEquals("Json array elements is not as expected.", resultArray, + array); + assertFalse(rs.next()); + + } finally { + conn.close(); + } + } + @Test + public void testJsonArrayElementsWithNestJson() throws Exception { + Connection conn = getConnection(); + + try { + String json = "[1,[1,true,\"string\"]]"; + String pk = "valueOne"; + + populateJsonTable(conn, json, pk); + + String selectQuery = "SELECT json_array_elements(col1) FROM testJson WHERE pk = 'valueOne'"; + PreparedStatement stmt = conn.prepareStatement(selectQuery); + ResultSet rs = stmt.executeQuery(); + assertTrue(rs.next()); + String[] strArr = new String[2]; + strArr[0] = "1"; + strArr[1] = "[1,true,\"string\"]"; + Array array = conn.createArrayOf("VARCHAR", strArr); + PhoenixArray resultArray = (PhoenixArray) rs.getArray(1); + assertEquals("Json array elements is not as expected.", resultArray, + array); + + assertFalse(rs.next()); + + } finally { + conn.close(); + } + } + + @Test + public void testJsonArrayElementsWithInvalidJsonInput() throws Exception { + Connection conn = getConnection(); + String json = "{\"f1\":1,\"f2\":\"abc\"}"; + String pk = "valueOne"; + try { + populateJsonTable(conn, json, pk); + + String selectQuery = "SELECT json_array_elements(col1) FROM testJson WHERE pk = 'valueOne'"; + + try { + PreparedStatement stmt = conn.prepareStatement(selectQuery); + ResultSet rs = stmt.executeQuery(); + assertTrue(rs.next()); + rs.getArray(1); + fail("The Json Node should be an array!"); + } catch (SQLException sqe) { + assertEquals("SQL error code is not as expected.", + SQLExceptionCode.JSON_NODE_MISMATCH.getErrorCode(), sqe.getErrorCode()); + assertEquals("SQL state is not expected.", "22001", + sqe.getSQLState()); + } + + } finally { + conn.close(); + } + } + + + private void populateJsonTable(Connection conn, String json, String pk) + throws SQLException { + 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.setString(2, json); + stmt.execute(); + conn.commit(); + } + + private Connection getConnection() throws SQLException { + Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); + Connection conn = DriverManager.getConnection(getUrl(), props); + conn.setAutoCommit(false); + return conn; + } +} diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/JsonArrayLengthFunctionIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/JsonArrayLengthFunctionIT.java new file mode 100755 index 00000000000..37e452b1e48 --- /dev/null +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/JsonArrayLengthFunctionIT.java @@ -0,0 +1,176 @@ +/* + * 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.*; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Properties; + +import org.apache.phoenix.exception.SQLExceptionCode; +import org.apache.phoenix.expression.function.JsonArrayLengthFunction; +import org.apache.phoenix.schema.IllegalDataException; +import org.apache.phoenix.util.PropertiesUtil; +import org.junit.Test; + +/** + * End to end test for {@link JsonArrayLengthFunction}. + * + */ +public class JsonArrayLengthFunctionIT extends BaseHBaseManagedTimeIT { + + @Test + public void testJsonArrayLengthWithIntTypeInWhereClause() throws Exception { + Connection conn = getConnection(); + String json = "[1,2,3]"; + String pk = "valueOne"; + try { + populateJsonTable(conn, json, pk); + + String selectQuery = "SELECT col1 FROM testJson WHERE json_array_length(col1) = 3"; + PreparedStatement stmt = conn.prepareStatement(selectQuery); + ResultSet rs = stmt.executeQuery(); + assertTrue(rs.next()); + assertEquals("Json data is not as expected.", json, + rs.getString(1)); + assertFalse(rs.next()); + + } finally { + conn.close(); + } + } + + @Test + public void testJsonArrayLengthWithDoubleType() throws Exception { + Connection conn = getConnection(); + String json = "[1.23,2.34,3.56,54.3]"; + String pk = "valueOne"; + try { + populateJsonTable(conn, json, pk); + + String selectQuery = "SELECT json_array_length(col1) FROM testJson WHERE pk = 'valueOne'"; + PreparedStatement stmt = conn.prepareStatement(selectQuery); + ResultSet rs = stmt.executeQuery(); + assertTrue(rs.next()); + assertEquals("Json array length is not as expected.", 4, + rs.getInt(1)); + assertFalse(rs.next()); + + } finally { + conn.close(); + } + } + + @Test + public void testJsonArrayLengthWithDifferentDataTypes() + throws Exception { + Connection conn = getConnection(); + String json = "[1,2.3,null,true,\"f1\",[\"string\",3]]"; + String pk = "valueOne"; + try { + populateJsonTable(conn, json, pk); + + String selectQuery = "SELECT json_array_length(col1) FROM testJson WHERE pk = 'valueOne'"; + PreparedStatement stmt = conn.prepareStatement(selectQuery); + ResultSet rs = stmt.executeQuery(); + assertTrue(rs.next()); + assertEquals("Json array length is not as expected.", + 6, rs.getInt(1)); + + assertFalse(rs.next()); + + } finally { + conn.close(); + } + } + @Test + public void testJsonArrayLengthWithNestedJson() throws Exception { + Connection conn = getConnection(); + String json = "[1,\"string\",false,[1.23,[true,\"ok\"]]]"; + String pk = "valueOne"; + try { + populateJsonTable(conn, json, pk); + + String selectQuery = "SELECT col1 FROM testJson WHERE json_array_length(col1) = 4"; + PreparedStatement stmt = conn.prepareStatement(selectQuery); + ResultSet rs = stmt.executeQuery(); + assertTrue(rs.next()); + assertEquals("Json data read from DB is not as expected.", json, + rs.getString(1)); + assertFalse(rs.next()); + + } finally { + conn.close(); + } + } + @Test + public void testJsonArrayLengthWithInvalidJsonInput() throws Exception { + Connection conn = getConnection(); + String json = "{\"f1\":1,\"f2\":\"abc\"}"; + String pk = "valueOne"; + try { + populateJsonTable(conn, json, pk); + + String selectQuery = "SELECT json_array_length(col1) FROM testJson WHERE pk = 'valueOne'"; + + try { + PreparedStatement stmt = conn.prepareStatement(selectQuery); + ResultSet rs = stmt.executeQuery(); + assertTrue(rs.next()); + rs.getInt(1); + fail("The Json Node should be an array!"); + } catch (SQLException sqe) { + assertEquals("SQL error code is not as expected.", + SQLExceptionCode.JSON_NODE_MISMATCH.getErrorCode(), sqe.getErrorCode()); + assertEquals("SQL state is not expected.", "22001", + sqe.getSQLState()); + } + + } finally { + conn.close(); + } + } + + + + private void populateJsonTable(Connection conn, String json, String pk) + throws SQLException { + 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.setString(2, json); + stmt.execute(); + conn.commit(); + } + + private Connection getConnection() throws SQLException { + Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); + Connection conn = DriverManager.getConnection(getUrl(), props); + conn.setAutoCommit(false); + return conn; + } +} diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/JsonEachFunctionIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/JsonEachFunctionIT.java new file mode 100755 index 00000000000..c26a55704ce --- /dev/null +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/JsonEachFunctionIT.java @@ -0,0 +1,107 @@ +/* + * 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 org.apache.phoenix.schema.types.PhoenixArray; +import org.apache.phoenix.util.PropertiesUtil; +import org.junit.Test; + +import java.sql.*; +import java.util.Properties; + +import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; +import static org.junit.Assert.*; + +/** + * End to end test for {@link org.apache.phoenix.expression.function.JsonEachFunction}. + * + */ +public class JsonEachFunctionIT extends BaseHBaseManagedTimeIT { + + @Test + public void testJsonEach() throws Exception { + Connection conn = getConnection(); + + try{ + String json = + "{\"f2\":{\"f3\":\"value\"},\"f4\":{\"f5\":99,\"f6\":[1,true,\"foo\"]},\"f7\":true}"; + populateJsonTable(conn, json, "valueOne"); + + String selectQuery = "SELECT json_each(col1) FROM testJson WHERE pk = 'valueOne'"; + PreparedStatement stmt = conn.prepareStatement(selectQuery); + ResultSet rs = stmt.executeQuery(); + assertTrue(rs.next()); + String[] strArr = new String[]{ + "f2,{\"f3\":\"value\"}", + "f4,{\"f5\":99,\"f6\":[1,true,\"foo\"]}", + "f7,true"}; + Array array = conn.createArrayOf("VARCHAR", strArr); + PhoenixArray resultArray = (PhoenixArray) rs.getArray(1); + assertEquals("JsonEach return data is not as expected.", resultArray, + array); + assertFalse(rs.next()); + + } finally { + conn.close(); + } + } + + @Test + public void testJsonEachWithNullKey() throws Exception { + Connection conn = getConnection(); + + try{ + String json = + "100"; + populateJsonTable(conn, json, "valueOne"); + + String selectQuery = "SELECT json_each(col1) FROM testJson WHERE pk = 'valueOne'"; + PreparedStatement stmt = conn.prepareStatement(selectQuery); + ResultSet rs = stmt.executeQuery(); + assertTrue(rs.next()); + + PhoenixArray resultArray = (PhoenixArray) rs.getArray(1); + assertEquals("JsonEach return data is not as expected.", resultArray, + null); + assertFalse(rs.next()); + + } finally { + conn.close(); + } + } + private void populateJsonTable(Connection conn, String json, String pk) + throws SQLException { + 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.setString(2, json); + stmt.execute(); + conn.commit(); + } + + private Connection getConnection() throws SQLException { + Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); + Connection conn = DriverManager.getConnection(getUrl(), props); + conn.setAutoCommit(false); + return conn; + } +} diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/JsonObjectKeysFunctionIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/JsonObjectKeysFunctionIT.java new file mode 100755 index 00000000000..79ef7da52d3 --- /dev/null +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/JsonObjectKeysFunctionIT.java @@ -0,0 +1,108 @@ +/* + * 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 org.apache.phoenix.schema.types.PhoenixArray; +import org.apache.phoenix.util.PropertiesUtil; +import org.junit.Test; + +import java.sql.*; +import java.util.Properties; + +import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; +import static org.junit.Assert.*; + +/** + * End to end test for {@link org.apache.phoenix.expression.function.JsonObjectKeysFunction}. + * + */ +public class JsonObjectKeysFunctionIT extends BaseHBaseManagedTimeIT { + + @Test + public void testJsonObjectKeys() throws Exception { + Connection conn = getConnection(); + + try{ + String json = + "{\"f2\":{\"f3\":\"value\"},\"f4\":{\"f5\":99,\"f6\":[1,true,\"foo\"]},\"f7\":true}"; + populateJsonTable(conn, json, "valueOne"); + + String selectQuery = "SELECT json_object_keys(col1) FROM testJson WHERE pk = 'valueOne'"; + PreparedStatement stmt = conn.prepareStatement(selectQuery); + ResultSet rs = stmt.executeQuery(); + assertTrue(rs.next()); + String[] strArr = new String[]{ + "f2", + "f4", + "f7"}; + Array array = conn.createArrayOf("VARCHAR", strArr); + PhoenixArray resultArray = (PhoenixArray) rs.getArray(1); + assertEquals("JsonObjectKeys return data is not as expected.", resultArray, + array); + assertFalse(rs.next()); + + } finally { + conn.close(); + } + } + + @Test + public void testJsonObjectKeysWithNullKey() throws Exception { + Connection conn = getConnection(); + + try{ + String json = + "[1,2,3]"; + populateJsonTable(conn, json, "valueOne"); + + String selectQuery = "SELECT json_object_keys(col1) FROM testJson WHERE pk = 'valueOne'"; + PreparedStatement stmt = conn.prepareStatement(selectQuery); + ResultSet rs = stmt.executeQuery(); + assertTrue(rs.next()); + PhoenixArray resultArray = (PhoenixArray) rs.getArray(1); + assertEquals("JsonObjectKeys return data is not as expected.", resultArray, + null); + assertFalse(rs.next()); + + } finally { + conn.close(); + } + } + + + private void populateJsonTable(Connection conn, String json, String pk) + throws SQLException { + 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.setString(2, json); + stmt.execute(); + conn.commit(); + } + + private Connection getConnection() throws SQLException { + Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); + Connection conn = DriverManager.getConnection(getUrl(), props); + conn.setAutoCommit(false); + return conn; + } +} diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/JsonPopulateRecordFunctionIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/JsonPopulateRecordFunctionIT.java new file mode 100755 index 00000000000..5c8c4842542 --- /dev/null +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/JsonPopulateRecordFunctionIT.java @@ -0,0 +1,101 @@ +/* + * 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 org.apache.phoenix.schema.types.PhoenixArray; +import org.apache.phoenix.util.PropertiesUtil; +import org.junit.Test; + +import java.sql.*; +import java.util.Properties; + +import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; +import static org.junit.Assert.*; + +/** + * End to end test for {@link org.apache.phoenix.expression.function.JsonPopulateRecordFunction}. + * + */ +public class JsonPopulateRecordFunctionIT extends BaseHBaseManagedTimeIT { + + @Test + public void testJsonPopulateRecord() throws Exception { + Connection conn = getConnection(); + + try{ + String json = "[{\"a\":1,\"b\":2}]"; + populateJsonTable(conn, json, "valueOne"); + + String selectQuery = "SELECT json_populate_record(ARRAY['a','b'],col1) FROM testJson WHERE pk = 'valueOne'"; + PreparedStatement stmt = conn.prepareStatement(selectQuery); + ResultSet rs = stmt.executeQuery(); + assertTrue(rs.next()); + String expectedstr = "1,2"; + assertEquals("json_populate_record return data is not as expected.", expectedstr, + rs.getString(1)); + assertFalse(rs.next()); + + + } finally { + conn.close(); + } + } + @Test + public void testJsonPopulateRecordWithNullValue() throws Exception { + Connection conn = getConnection(); + + try{ + String json = "[{\"a\":1,\"c\":2}]"; + populateJsonTable(conn, json, "valueOne"); + + String selectQuery = "SELECT json_populate_record(ARRAY['a','b'],col1) FROM testJson WHERE pk = 'valueOne'"; + PreparedStatement stmt = conn.prepareStatement(selectQuery); + ResultSet rs = stmt.executeQuery(); + assertTrue(rs.next()); + String expectedstr = "1,null"; + assertEquals("json_populate_record return data is not as expected.", expectedstr, + rs.getString(1)); + assertFalse(rs.next()); + + + } finally { + conn.close(); + } + } + + private void populateJsonTable(Connection conn, String json, String pk) + throws SQLException { + 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.setString(2, json); + stmt.execute(); + conn.commit(); + } + + private Connection getConnection() throws SQLException { + Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); + Connection conn = DriverManager.getConnection(getUrl(), props); + conn.setAutoCommit(false); + return conn; + } +} diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/JsonPopulateRecordSetFunctionIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/JsonPopulateRecordSetFunctionIT.java new file mode 100755 index 00000000000..fd02fed7afc --- /dev/null +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/JsonPopulateRecordSetFunctionIT.java @@ -0,0 +1,139 @@ +/* + * 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 org.apache.phoenix.exception.SQLExceptionCode; +import org.apache.phoenix.schema.types.PhoenixArray; +import org.apache.phoenix.util.PropertiesUtil; +import org.junit.Test; + +import java.sql.*; +import java.util.Properties; + +import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; +import static org.junit.Assert.*; + +/** + * End to end test for {@link org.apache.phoenix.expression.function.JsonPopulateRecordSetFunction}. + * + */ +public class JsonPopulateRecordSetFunctionIT extends BaseHBaseManagedTimeIT { + + @Test + public void testJsonPopulateRecordSet() throws Exception { + Connection conn = getConnection(); + + try{ + String json = "[{\"a\":1,\"b\":2,\"c\":3},{\"a\":3,\"e\":4,\"b\":4},{\"a\":6,\"b\":8}]"; + populateJsonTable(conn, json, "valueOne"); + + String selectQuery = "SELECT json_populate_recordset(ARRAY['a','b'],col1) FROM testJson WHERE pk = 'valueOne'"; + PreparedStatement stmt = conn.prepareStatement(selectQuery); + ResultSet rs = stmt.executeQuery(); + assertTrue(rs.next()); + String[] strArr = new String[]{ + "1,2", + "3,4", + "6,8"}; + Array array = conn.createArrayOf("VARCHAR", strArr); + PhoenixArray resultArray = (PhoenixArray) rs.getArray(1); + assertEquals("json_populate_recordset return data is not as expected.", resultArray, + array); + assertFalse(rs.next()); + + } finally { + conn.close(); + } + } + + @Test + public void testJsonPopulateRecordSetWithNullValues() throws Exception { + Connection conn = getConnection(); + + try{ + String json = "[{\"a1\":1,\"b1\":2,\"f3\":false},{\"a\":3,\"e\":4},{\"b\":\"hello\"}]"; + populateJsonTable(conn, json, "valueOne"); + + String selectQuery = "SELECT json_populate_recordset(ARRAY['a','b'],col1) FROM testJson WHERE pk = 'valueOne'"; + PreparedStatement stmt = conn.prepareStatement(selectQuery); + ResultSet rs = stmt.executeQuery(); + assertTrue(rs.next()); + String[] strArr = new String[]{ + "null,null", + "3,null", + "null,\"hello\""}; + Array array = conn.createArrayOf("VARCHAR", strArr); + PhoenixArray resultArray = (PhoenixArray) rs.getArray(1); + assertEquals("json_populate_recordset return data is not as expected.", resultArray, + array); + assertFalse(rs.next()); + + } finally { + conn.close(); + } + } + + @Test + public void testJsonPopulateRecordSetWithInvalidJsonInput() throws Exception { + Connection conn = getConnection(); + String json = "{\"a\":1,\"b\":2}"; + String pk = "valueOne"; + try { + populateJsonTable(conn, json, pk); + + String selectQuery = "SELECT json_populate_recordset(ARRAY['a','b'],col1) FROM testJson WHERE pk = 'valueOne'"; + + try { + PreparedStatement stmt = conn.prepareStatement(selectQuery); + ResultSet rs = stmt.executeQuery(); + assertTrue(rs.next()); + rs.getArray(1); + fail("The Json Node should be an array!"); + } catch (SQLException sqe) { + assertEquals("SQL error code is not as expected.", + SQLExceptionCode.JSON_NODE_MISMATCH.getErrorCode(), sqe.getErrorCode()); + assertEquals("SQL state is not expected.", "22001", + sqe.getSQLState()); + } + + } finally { + conn.close(); + } + } + + private void populateJsonTable(Connection conn, String json, String pk) + throws SQLException { + 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.setString(2, json); + stmt.execute(); + conn.commit(); + } + + private Connection getConnection() throws SQLException { + Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); + Connection conn = DriverManager.getConnection(getUrl(), props); + conn.setAutoCommit(false); + return conn; + } +} diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ToJsonFunctionIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ToJsonFunctionIT.java new file mode 100755 index 00000000000..1c9553a3acd --- /dev/null +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ToJsonFunctionIT.java @@ -0,0 +1,117 @@ +/* + * 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 org.apache.phoenix.query.BaseTest; +import org.apache.phoenix.util.PhoenixRuntime; +import org.apache.phoenix.util.PropertiesUtil; +import org.junit.Test; + +import java.sql.*; +import java.util.Properties; + +import static org.apache.phoenix.util.TestUtil.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * End to end test for {@link org.apache.phoenix.expression.function.ToJsonFunction}. + * + */ +public class ToJsonFunctionIT extends BaseHBaseManagedTimeIT { + private static final String TABLE_WITH_ALL_TYPES = "TABLE_WITH_ALL_TYPES"; + + @Test + public void testToJsonWithAllTypes() throws Exception { + // create the table + createTableWithAllTypes(getUrl()); + + Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); + Connection conn = DriverManager.getConnection(getUrl(), props); + conn.setAutoCommit(false); + try { + // populate the table with data + PreparedStatement stmt = + conn.prepareStatement("UPSERT INTO " + + TABLE_WITH_ALL_TYPES + + "(pk, BOOLEAN_COL, BYTE_COl, DOUBLE_COL, FLOAT_COL, INT_COL, LONG_COL, SHORT_COL, STRING_COL)" + + " VALUES ('valueOne', " + + "true," + + "11, " + + "67.78," + + "12.23," + + "5555," + + "7777777," + + "333," + + "'string')"); + stmt.execute(); + conn.commit(); + + stmt = + conn.prepareStatement("SELECT pk, " + + "to_json(boolean_col), " + + "to_json(byte_col), " + + "to_json(double_col), " + + "to_json(float_col), " + + "to_json(int_col), " + + "to_json(long_col), " + + "to_json(short_col)," + + "to_json(string_col) FROM " + + TABLE_WITH_ALL_TYPES + + " where pk = 'valueOne'"); + + ResultSet rs = stmt.executeQuery(); + assertTrue(rs.next()); + + assertEquals("valueOne", rs.getString(1)); + assertToJson(rs, 2, "true"); + assertToJson(rs, 3, "11"); + assertToJson(rs, 4, "67.78"); + assertToJson(rs, 5, "12.23"); + assertToJson(rs, 6, "5555"); + assertToJson(rs, 7, "7777777"); + assertToJson(rs, 8, "333"); + assertToJson(rs, 9, "\"string\""); + }finally { + conn.close(); + } + } + + + private void assertToJson(ResultSet rs, int arrayIndex, String expectedJson) + throws SQLException { + assertEquals("Json data read from DB is not as expected.",expectedJson, rs.getString(arrayIndex)); + } + + + private static void createTableWithAllTypes(String url) throws SQLException { + String ddlStmt = "create table " + + TABLE_WITH_ALL_TYPES + + " (pk VARCHAR NOT NULL PRIMARY KEY,\n" + + " boolean_col boolean,\n" + + " byte_col tinyint,\n" + + " double_col double,\n" + + " float_col float,\n" + + " int_col integer,\n" + + " long_col bigint,\n" + + " short_col smallint,\n" + + " string_col varchar(100) " + + ")"; + createTestTable(url, ddlStmt); + } +} 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 674766175f9..8590e08858b 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 @@ -87,6 +87,7 @@ public SQLException newException(SQLExceptionInfo info) { 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."), + JSON_NODE_MISMATCH(220, "22001", "json node should be an array."), /** * Constraint Violation (errorcode 03, sqlstate 23) 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 old mode 100644 new mode 100755 index 51f40898b4b..af27cda5f2c --- a/phoenix-core/src/main/java/org/apache/phoenix/expression/ExpressionType.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/expression/ExpressionType.java @@ -19,95 +19,7 @@ import java.util.Map; -import org.apache.phoenix.expression.function.AbsFunction; -import org.apache.phoenix.expression.function.ArrayAllComparisonExpression; -import org.apache.phoenix.expression.function.ArrayAnyComparisonExpression; -import org.apache.phoenix.expression.function.ArrayAppendFunction; -import org.apache.phoenix.expression.function.ArrayConcatFunction; -import org.apache.phoenix.expression.function.ArrayElemRefExpression; -import org.apache.phoenix.expression.function.ArrayIndexFunction; -import org.apache.phoenix.expression.function.ArrayLengthFunction; -import org.apache.phoenix.expression.function.ArrayPrependFunction; -import org.apache.phoenix.expression.function.ByteBasedRegexpReplaceFunction; -import org.apache.phoenix.expression.function.ByteBasedRegexpSplitFunction; -import org.apache.phoenix.expression.function.ByteBasedRegexpSubstrFunction; -import org.apache.phoenix.expression.function.CbrtFunction; -import org.apache.phoenix.expression.function.CeilDateExpression; -import org.apache.phoenix.expression.function.CeilDecimalExpression; -import org.apache.phoenix.expression.function.CeilFunction; -import org.apache.phoenix.expression.function.CeilTimestampExpression; -import org.apache.phoenix.expression.function.CoalesceFunction; -import org.apache.phoenix.expression.function.ConvertTimezoneFunction; -import org.apache.phoenix.expression.function.CountAggregateFunction; -import org.apache.phoenix.expression.function.DayOfMonthFunction; -import org.apache.phoenix.expression.function.DecodeFunction; -import org.apache.phoenix.expression.function.DistinctCountAggregateFunction; -import org.apache.phoenix.expression.function.DistinctValueAggregateFunction; -import org.apache.phoenix.expression.function.EncodeFunction; -import org.apache.phoenix.expression.function.ExpFunction; -import org.apache.phoenix.expression.function.ExternalSqlTypeIdFunction; -import org.apache.phoenix.expression.function.FirstValueFunction; -import org.apache.phoenix.expression.function.FloorDateExpression; -import org.apache.phoenix.expression.function.FloorDecimalExpression; -import org.apache.phoenix.expression.function.FloorFunction; -import org.apache.phoenix.expression.function.HourFunction; -import org.apache.phoenix.expression.function.IndexStateNameFunction; -import org.apache.phoenix.expression.function.InstrFunction; -import org.apache.phoenix.expression.function.InvertFunction; -import org.apache.phoenix.expression.function.LTrimFunction; -import org.apache.phoenix.expression.function.LastValueFunction; -import org.apache.phoenix.expression.function.LengthFunction; -import org.apache.phoenix.expression.function.LnFunction; -import org.apache.phoenix.expression.function.LogFunction; -import org.apache.phoenix.expression.function.LowerFunction; -import org.apache.phoenix.expression.function.LpadFunction; -import org.apache.phoenix.expression.function.MD5Function; -import org.apache.phoenix.expression.function.MaxAggregateFunction; -import org.apache.phoenix.expression.function.MinAggregateFunction; -import org.apache.phoenix.expression.function.MinuteFunction; -import org.apache.phoenix.expression.function.MonthFunction; -import org.apache.phoenix.expression.function.NowFunction; -import org.apache.phoenix.expression.function.NthValueFunction; -import org.apache.phoenix.expression.function.PercentRankAggregateFunction; -import org.apache.phoenix.expression.function.PercentileContAggregateFunction; -import org.apache.phoenix.expression.function.PercentileDiscAggregateFunction; -import org.apache.phoenix.expression.function.PowerFunction; -import org.apache.phoenix.expression.function.RTrimFunction; -import org.apache.phoenix.expression.function.RandomFunction; -import org.apache.phoenix.expression.function.RegexpReplaceFunction; -import org.apache.phoenix.expression.function.RegexpSplitFunction; -import org.apache.phoenix.expression.function.RegexpSubstrFunction; -import org.apache.phoenix.expression.function.ReverseFunction; -import org.apache.phoenix.expression.function.RoundDateExpression; -import org.apache.phoenix.expression.function.RoundDecimalExpression; -import org.apache.phoenix.expression.function.RoundFunction; -import org.apache.phoenix.expression.function.RoundTimestampExpression; -import org.apache.phoenix.expression.function.SQLIndexTypeFunction; -import org.apache.phoenix.expression.function.SQLTableTypeFunction; -import org.apache.phoenix.expression.function.SQLViewTypeFunction; -import org.apache.phoenix.expression.function.SecondFunction; -import org.apache.phoenix.expression.function.SignFunction; -import org.apache.phoenix.expression.function.SqlTypeNameFunction; -import org.apache.phoenix.expression.function.SqrtFunction; -import org.apache.phoenix.expression.function.StddevPopFunction; -import org.apache.phoenix.expression.function.StddevSampFunction; -import org.apache.phoenix.expression.function.StringBasedRegexpReplaceFunction; -import org.apache.phoenix.expression.function.StringBasedRegexpSplitFunction; -import org.apache.phoenix.expression.function.StringBasedRegexpSubstrFunction; -import org.apache.phoenix.expression.function.SubstrFunction; -import org.apache.phoenix.expression.function.SumAggregateFunction; -import org.apache.phoenix.expression.function.TimezoneOffsetFunction; -import org.apache.phoenix.expression.function.ToCharFunction; -import org.apache.phoenix.expression.function.ToDateFunction; -import org.apache.phoenix.expression.function.ToNumberFunction; -import org.apache.phoenix.expression.function.ToTimeFunction; -import org.apache.phoenix.expression.function.ToTimestampFunction; -import org.apache.phoenix.expression.function.TrimFunction; -import org.apache.phoenix.expression.function.TruncFunction; -import org.apache.phoenix.expression.function.UDFExpression; -import org.apache.phoenix.expression.function.UpperFunction; -import org.apache.phoenix.expression.function.WeekFunction; -import org.apache.phoenix.expression.function.YearFunction; +import org.apache.phoenix.expression.function.*; import com.google.common.collect.Maps; @@ -247,7 +159,15 @@ public enum ExpressionType { LogFunction(LogFunction.class), ExpFunction(ExpFunction.class), PowerFunction(PowerFunction.class), - ArrayConcatFunction(ArrayConcatFunction.class) + ArrayConcatFunction(ArrayConcatFunction.class), + JsonArrayLengthFunction(JsonArrayLengthFunction.class), + JsonArrayElementsFunction(JsonArrayElementsFunction.class), + JsonEachFunction(JsonEachFunction.class), + JsonObjectKeysFunction(JsonObjectKeysFunction.class), + JsonPopulateRecordFunction(JsonPopulateRecordFunction.class), + JsonPopulateRecordSetFunction(JsonPopulateRecordSetFunction.class), + ToJsonFunction(ToJsonFunction.class), + ArrayToJsonFunction(ArrayToJsonFunction.class) ; ExpressionType(Class clazz) { diff --git a/phoenix-core/src/main/java/org/apache/phoenix/expression/function/ArrayToJsonFunction.java b/phoenix-core/src/main/java/org/apache/phoenix/expression/function/ArrayToJsonFunction.java new file mode 100755 index 00000000000..aef38087f80 --- /dev/null +++ b/phoenix-core/src/main/java/org/apache/phoenix/expression/function/ArrayToJsonFunction.java @@ -0,0 +1,99 @@ +/* + * 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.expression.function; + +import org.apache.hadoop.hbase.io.ImmutableBytesWritable; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.phoenix.exception.SQLExceptionCode; +import org.apache.phoenix.exception.SQLExceptionInfo; +import org.apache.phoenix.expression.Expression; +import org.apache.phoenix.parse.FunctionParseNode; +import org.apache.phoenix.schema.IllegalDataException; +import org.apache.phoenix.schema.SortOrder; +import org.apache.phoenix.schema.json.PhoenixJson; +import org.apache.phoenix.schema.tuple.Tuple; +import org.apache.phoenix.schema.types.*; +import org.apache.phoenix.util.ByteUtil; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.List; + + +@FunctionParseNode.BuiltInFunction(name=ArrayToJsonFunction.NAME, args={ + @FunctionParseNode.Argument(allowedTypes={PBinaryArray.class, PVarbinaryArray.class})}) +public class ArrayToJsonFunction extends ScalarFunction { + public static final String NAME = "ARRAY_TO_JSON"; + + public ArrayToJsonFunction() { + } + + public ArrayToJsonFunction(List children) throws SQLException { + super(children); + } + + @Override + public boolean evaluate(Tuple tuple, ImmutableBytesWritable ptr) { + Expression arrayExpr = getChildren().get(0); + + if (!arrayExpr.evaluate(tuple, ptr)) { + return false; + }else if (ptr.getLength() == 0) { + return true; + } + try { + PDataType baseType = PDataType.fromTypeId(arrayExpr.getDataType() + .getSqlType() + - PDataType.ARRAY_TYPE_BASE); + int length = PArrayDataType.getArrayLength(ptr, baseType, arrayExpr.getMaxLength()); + StringBuilder builder = new StringBuilder("["); + for(int i=1;i<=length;i++){ + ImmutableBytesWritable tmp = new ImmutableBytesWritable(ptr.get(),ptr.getOffset(),ptr.getLength()); + PArrayDataType.positionAtArrayElement(tmp, i - 1,baseType, arrayExpr.getMaxLength()); + Object re = baseType.toObject(tmp.get(),tmp.getOffset(),tmp.getLength()); + builder.append(PhoenixJson.dataToJsonValue(baseType, re)); + if(i != length) + builder.append(","); + } + builder.append("]"); + String str = builder.toString(); + PhoenixJson phoenixJson = PhoenixJson.getInstance(str); + byte[] json = PJson.INSTANCE.toBytes(phoenixJson); + ptr.set(json); + } catch (SQLException sqe) { + new IllegalDataException(sqe); + } + return true; + } + + @Override + public SortOrder getSortOrder() { + return getChildren().get(0).getSortOrder(); + } + + @Override + public PDataType getDataType() { + return PJson.INSTANCE; + } + + @Override + public String getName() { + return NAME; + } + +} diff --git a/phoenix-core/src/main/java/org/apache/phoenix/expression/function/JsonArrayElementsFunction.java b/phoenix-core/src/main/java/org/apache/phoenix/expression/function/JsonArrayElementsFunction.java new file mode 100755 index 00000000000..12cd1fbcbe2 --- /dev/null +++ b/phoenix-core/src/main/java/org/apache/phoenix/expression/function/JsonArrayElementsFunction.java @@ -0,0 +1,109 @@ +/* + * 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.expression.function; + +import org.apache.hadoop.hbase.io.ImmutableBytesWritable; +import org.apache.phoenix.compile.KeyPart; +import org.apache.phoenix.exception.SQLExceptionCode; +import org.apache.phoenix.exception.SQLExceptionInfo; +import org.apache.phoenix.expression.Expression; +import org.apache.phoenix.parse.FunctionParseNode.Argument; +import org.apache.phoenix.parse.FunctionParseNode.BuiltInFunction; +import org.apache.phoenix.schema.IllegalDataException; +import org.apache.phoenix.schema.json.PhoenixJson; +import org.apache.phoenix.schema.tuple.Tuple; +import org.apache.phoenix.schema.types.*; +import org.apache.phoenix.util.ByteUtil; + +import java.sql.SQLException; +import java.util.List; + +@BuiltInFunction(name = JsonArrayElementsFunction.NAME, args = { + @Argument(allowedTypes = { PJson.class })}) +public class JsonArrayElementsFunction extends ScalarFunction { + public static final String NAME = "JSON_ARRAY_ELEMENTS"; + + public JsonArrayElementsFunction() { + super(); + } + + public JsonArrayElementsFunction(List children) { + super(children); + } + + @Override + public boolean evaluate(Tuple tuple, ImmutableBytesWritable ptr) { + + Expression jsonExpression = this.children.get(0); + if (!jsonExpression.evaluate(tuple, ptr)) { + return false; + }else if (ptr.getLength() == 0) { + return true; + } + try { + PhoenixJson phoenixJson = + (PhoenixJson) PJson.INSTANCE.toObject(ptr.get(), ptr.getOffset(), + ptr.getLength()); + Object[] elements = phoenixJson.getJsonArrayElements(); + if(elements == null || elements.length == 0){ + ptr.set(ByteUtil.EMPTY_BYTE_ARRAY); + }else{ + PhoenixArray pa = PArrayDataType.instantiatePhoenixArray(PVarchar.INSTANCE, elements); + byte[] array = PVarcharArray.INSTANCE.toBytes(pa); + ptr.set(array); + } + } + catch (SQLException sqe) { + throw new IllegalDataException(sqe); + } + + return true; + } + + @SuppressWarnings("rawtypes") + @Override + public PDataType getDataType() { + return PVarcharArray.INSTANCE; + } + + @Override + public String getName() { + return NAME; + } + + @Override + public boolean isNullable() { + return PVarcharArray.INSTANCE.isNullable(); + } + + @Override + public int getKeyFormationTraversalIndex() { + return NO_TRAVERSAL; + } + + @Override + public KeyPart newKeyPart(KeyPart childPart) { + return null; + } + + @Override + public OrderPreserving preservesOrder() { + return OrderPreserving.NO; + } + +} diff --git a/phoenix-core/src/main/java/org/apache/phoenix/expression/function/JsonArrayLengthFunction.java b/phoenix-core/src/main/java/org/apache/phoenix/expression/function/JsonArrayLengthFunction.java new file mode 100755 index 00000000000..21f6455b7c8 --- /dev/null +++ b/phoenix-core/src/main/java/org/apache/phoenix/expression/function/JsonArrayLengthFunction.java @@ -0,0 +1,102 @@ +/* + * 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.expression.function; + +import org.apache.hadoop.hbase.io.ImmutableBytesWritable; +import org.apache.phoenix.compile.KeyPart; +import org.apache.phoenix.exception.SQLExceptionCode; +import org.apache.phoenix.exception.SQLExceptionInfo; +import org.apache.phoenix.expression.Expression; +import org.apache.phoenix.parse.FunctionParseNode.Argument; +import org.apache.phoenix.parse.FunctionParseNode.BuiltInFunction; +import org.apache.phoenix.schema.IllegalDataException; +import org.apache.phoenix.schema.json.PhoenixJson; +import org.apache.phoenix.schema.tuple.Tuple; +import org.apache.phoenix.schema.types.*; + +import java.sql.SQLException; +import java.util.List; + +@BuiltInFunction(name = JsonArrayLengthFunction.NAME, args = { + @Argument(allowedTypes = { PJson.class })}) +public class JsonArrayLengthFunction extends ScalarFunction { + public static final String NAME = "JSON_ARRAY_LENGTH"; + + public JsonArrayLengthFunction() { + super(); + } + + public JsonArrayLengthFunction(List children) { + super(children); + } + + @Override + public boolean evaluate(Tuple tuple, ImmutableBytesWritable ptr) { + + Expression jsonExpression = this.children.get(0); + if (!jsonExpression.evaluate(tuple, ptr)) { + return false; + }else if (ptr.getLength() == 0) { + return true; + } + try{ + PhoenixJson phoenixJson = + (PhoenixJson) PJson.INSTANCE.toObject(ptr.get(), ptr.getOffset(), + ptr.getLength()); + int length = phoenixJson.getJsonArrayLength(); + byte[] array = PInteger.INSTANCE.toBytes(length); + ptr.set(array); + } catch (SQLException sqe) { + throw new IllegalDataException(sqe); + } + + return true; + } + + @SuppressWarnings("rawtypes") + @Override + public PDataType getDataType() { + return PInteger.INSTANCE; + } + + @Override + public String getName() { + return NAME; + } + + @Override + public boolean isNullable() { + return PInteger.INSTANCE.isNullable(); + } + + @Override + public int getKeyFormationTraversalIndex() { + return NO_TRAVERSAL; + } + + @Override + public KeyPart newKeyPart(KeyPart childPart) { + return null; + } + + @Override + public OrderPreserving preservesOrder() { + return OrderPreserving.NO; + } + +} diff --git a/phoenix-core/src/main/java/org/apache/phoenix/expression/function/JsonEachFunction.java b/phoenix-core/src/main/java/org/apache/phoenix/expression/function/JsonEachFunction.java new file mode 100755 index 00000000000..fdd8b4ac9c1 --- /dev/null +++ b/phoenix-core/src/main/java/org/apache/phoenix/expression/function/JsonEachFunction.java @@ -0,0 +1,100 @@ +/* + * 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.expression.function; + +import org.apache.hadoop.hbase.io.ImmutableBytesWritable; +import org.apache.phoenix.compile.KeyPart; +import org.apache.phoenix.expression.Expression; +import org.apache.phoenix.parse.FunctionParseNode.Argument; +import org.apache.phoenix.parse.FunctionParseNode.BuiltInFunction; +import org.apache.phoenix.schema.json.PhoenixJson; +import org.apache.phoenix.schema.tuple.Tuple; +import org.apache.phoenix.schema.types.*; +import org.apache.phoenix.util.ByteUtil; + +import java.util.List; + +@BuiltInFunction(name = JsonEachFunction.NAME, args = { + @Argument(allowedTypes = { PJson.class })}) +public class JsonEachFunction extends ScalarFunction { + public static final String NAME = "JSON_EACH"; + + public JsonEachFunction() { + super(); + } + + public JsonEachFunction(List children) { + super(children); + } + + @Override + public boolean evaluate(Tuple tuple, ImmutableBytesWritable ptr) { + + Expression jsonExpression = this.children.get(0); + if (!jsonExpression.evaluate(tuple, ptr)) { + return false; + }else if (ptr.getLength() == 0) { + return true; + } + PhoenixJson phoenixJson = + (PhoenixJson) PJson.INSTANCE.toObject(ptr.get(), ptr.getOffset(), + ptr.getLength()); + Object[] elements = phoenixJson.getJsonFields(); + if(elements == null || elements.length == 0){ + ptr.set(ByteUtil.EMPTY_BYTE_ARRAY); + }else{ + PhoenixArray pa = PArrayDataType.instantiatePhoenixArray(PVarchar.INSTANCE, elements); + byte[] array = PVarcharArray.INSTANCE.toBytes(pa); + ptr.set(array); + } + + return true; + } + + @SuppressWarnings("rawtypes") + @Override + public PDataType getDataType() { + return PVarcharArray.INSTANCE; + } + + @Override + public String getName() { + return NAME; + } + + @Override + public boolean isNullable() { + return PVarcharArray.INSTANCE.isNullable(); + } + + @Override + public int getKeyFormationTraversalIndex() { + return NO_TRAVERSAL; + } + + @Override + public KeyPart newKeyPart(KeyPart childPart) { + return null; + } + + @Override + public OrderPreserving preservesOrder() { + return OrderPreserving.NO; + } + +} diff --git a/phoenix-core/src/main/java/org/apache/phoenix/expression/function/JsonObjectKeysFunction.java b/phoenix-core/src/main/java/org/apache/phoenix/expression/function/JsonObjectKeysFunction.java new file mode 100755 index 00000000000..60b6370134b --- /dev/null +++ b/phoenix-core/src/main/java/org/apache/phoenix/expression/function/JsonObjectKeysFunction.java @@ -0,0 +1,100 @@ +/* + * 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.expression.function; + +import org.apache.hadoop.hbase.io.ImmutableBytesWritable; +import org.apache.phoenix.compile.KeyPart; +import org.apache.phoenix.expression.Expression; +import org.apache.phoenix.parse.FunctionParseNode.Argument; +import org.apache.phoenix.parse.FunctionParseNode.BuiltInFunction; +import org.apache.phoenix.schema.json.PhoenixJson; +import org.apache.phoenix.schema.tuple.Tuple; +import org.apache.phoenix.schema.types.*; +import org.apache.phoenix.util.ByteUtil; + +import java.util.List; + +@BuiltInFunction(name = JsonObjectKeysFunction.NAME, args = { + @Argument(allowedTypes = { PJson.class })}) +public class JsonObjectKeysFunction extends ScalarFunction { + public static final String NAME = "JSON_OBJECT_KEYS"; + + public JsonObjectKeysFunction() { + super(); + } + + public JsonObjectKeysFunction(List children) { + super(children); + } + + @Override + public boolean evaluate(Tuple tuple, ImmutableBytesWritable ptr) { + + Expression jsonExpression = this.children.get(0); + if (!jsonExpression.evaluate(tuple, ptr)) { + return false; + }else if (ptr.getLength() == 0) { + return true; + } + PhoenixJson phoenixJson = + (PhoenixJson) PJson.INSTANCE.toObject(ptr.get(), ptr.getOffset(), + ptr.getLength()); + Object[] elements = phoenixJson.getJsonObjectKeys(); + if(elements == null || elements.length == 0){ + ptr.set(ByteUtil.EMPTY_BYTE_ARRAY); + }else{ + PhoenixArray pa = PArrayDataType.instantiatePhoenixArray(PVarchar.INSTANCE, elements); + byte[] array = PVarcharArray.INSTANCE.toBytes(pa); + ptr.set(array); + } + + return true; + } + + @SuppressWarnings("rawtypes") + @Override + public PDataType getDataType() { + return PVarcharArray.INSTANCE; + } + + @Override + public String getName() { + return NAME; + } + + @Override + public boolean isNullable() { + return PVarcharArray.INSTANCE.isNullable(); + } + + @Override + public int getKeyFormationTraversalIndex() { + return NO_TRAVERSAL; + } + + @Override + public KeyPart newKeyPart(KeyPart childPart) { + return null; + } + + @Override + public OrderPreserving preservesOrder() { + return OrderPreserving.NO; + } + +} diff --git a/phoenix-core/src/main/java/org/apache/phoenix/expression/function/JsonPopulateRecordFunction.java b/phoenix-core/src/main/java/org/apache/phoenix/expression/function/JsonPopulateRecordFunction.java new file mode 100755 index 00000000000..82cdeef01d1 --- /dev/null +++ b/phoenix-core/src/main/java/org/apache/phoenix/expression/function/JsonPopulateRecordFunction.java @@ -0,0 +1,114 @@ +/* + * 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.expression.function; + +import org.apache.hadoop.hbase.io.ImmutableBytesWritable; +import org.apache.phoenix.compile.KeyPart; +import org.apache.phoenix.exception.SQLExceptionCode; +import org.apache.phoenix.exception.SQLExceptionInfo; +import org.apache.phoenix.expression.Expression; +import org.apache.phoenix.parse.FunctionParseNode.Argument; +import org.apache.phoenix.parse.FunctionParseNode.BuiltInFunction; +import org.apache.phoenix.schema.IllegalDataException; +import org.apache.phoenix.schema.json.PhoenixJson; +import org.apache.phoenix.schema.tuple.Tuple; +import org.apache.phoenix.schema.types.*; +import org.apache.phoenix.util.ByteUtil; + +import java.sql.SQLException; +import java.util.List; + +@BuiltInFunction(name = JsonPopulateRecordFunction.NAME, args = { + @Argument(allowedTypes = { PVarcharArray.class }), + @Argument(allowedTypes = { PJson.class }) + }) +public class JsonPopulateRecordFunction extends ScalarFunction { + public static final String NAME = "JSON_POPULATE_RECORD"; + + public JsonPopulateRecordFunction() { + super(); + } + + public JsonPopulateRecordFunction(List children) { + super(children); + } + + @Override + public boolean evaluate(Tuple tuple, ImmutableBytesWritable ptr) { + + Expression typeArrayExpression = children.get(0); + if (!typeArrayExpression.evaluate(tuple, ptr)) { + return false; + }else if (ptr.getLength() == 0) { + return true; + } + + PhoenixArray phoenixArray = (PhoenixArray) PVarcharArray.INSTANCE.toObject(ptr); + Expression jsonExpression = this.children.get(1); + if (!jsonExpression.evaluate(tuple, ptr)) { + return false; + } + if (ptr.getLength() == 0) { + return false; + } + PhoenixJson phoenixJson = + (PhoenixJson) PJson.INSTANCE.toObject(ptr.get(), ptr.getOffset(), + ptr.getLength()); + try { + String[] types = (String[]) phoenixArray.getArray(); + String records = phoenixJson.jsonPopulateRecord(types); + byte[] array = PVarchar.INSTANCE.toBytes(records); + ptr.set(array); + } catch (SQLException sqe) { + throw new IllegalDataException(sqe); + } + return true; + } + + @SuppressWarnings("rawtypes") + @Override + public PDataType getDataType() { + return PVarchar.INSTANCE; + } + + @Override + public String getName() { + return NAME; + } + + @Override + public boolean isNullable() { + return PVarchar.INSTANCE.isNullable(); + } + + @Override + public int getKeyFormationTraversalIndex() { + return NO_TRAVERSAL; + } + + @Override + public KeyPart newKeyPart(KeyPart childPart) { + return null; + } + + @Override + public OrderPreserving preservesOrder() { + return OrderPreserving.NO; + } + +} diff --git a/phoenix-core/src/main/java/org/apache/phoenix/expression/function/JsonPopulateRecordSetFunction.java b/phoenix-core/src/main/java/org/apache/phoenix/expression/function/JsonPopulateRecordSetFunction.java new file mode 100755 index 00000000000..234c6749a19 --- /dev/null +++ b/phoenix-core/src/main/java/org/apache/phoenix/expression/function/JsonPopulateRecordSetFunction.java @@ -0,0 +1,114 @@ +/* + * 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.expression.function; + +import org.apache.hadoop.hbase.io.ImmutableBytesWritable; +import org.apache.phoenix.compile.KeyPart; +import org.apache.phoenix.exception.SQLExceptionCode; +import org.apache.phoenix.exception.SQLExceptionInfo; +import org.apache.phoenix.expression.Expression; +import org.apache.phoenix.parse.FunctionParseNode.Argument; +import org.apache.phoenix.parse.FunctionParseNode.BuiltInFunction; +import org.apache.phoenix.schema.IllegalDataException; +import org.apache.phoenix.schema.json.PhoenixJson; +import org.apache.phoenix.schema.tuple.Tuple; +import org.apache.phoenix.schema.types.*; + +import java.sql.SQLException; +import java.util.List; + +@BuiltInFunction(name = JsonPopulateRecordSetFunction.NAME, args = { + @Argument(allowedTypes = { PVarcharArray.class }), + @Argument(allowedTypes = { PJson.class }) + }) +public class JsonPopulateRecordSetFunction extends ScalarFunction { + public static final String NAME = "JSON_POPULATE_RECORDSET"; + + public JsonPopulateRecordSetFunction() { + super(); + } + + public JsonPopulateRecordSetFunction(List children) { + super(children); + } + + @Override + public boolean evaluate(Tuple tuple, ImmutableBytesWritable ptr) { + + Expression typeArrayExpression = children.get(0); + if (!typeArrayExpression.evaluate(tuple, ptr)) { + return false; + }else if (ptr.getLength() == 0) { + return true; + } + + PhoenixArray phoenixArray = (PhoenixArray) PVarcharArray.INSTANCE.toObject(ptr); + Expression jsonExpression = this.children.get(1); + if (!jsonExpression.evaluate(tuple, ptr)) { + return false; + } + if (ptr.getLength() == 0) { + return false; + } + PhoenixJson phoenixJson = + (PhoenixJson) PJson.INSTANCE.toObject(ptr.get(), ptr.getOffset(), + ptr.getLength()); + try { + String[] types = (String[]) phoenixArray.getArray(); + Object[] records = phoenixJson.jsonPopulateRecordSet(types); + PhoenixArray pa = PArrayDataType.instantiatePhoenixArray(PVarchar.INSTANCE, records); + byte[] array = PVarcharArray.INSTANCE.toBytes(pa); + ptr.set(array); + } catch (SQLException sqe) { + throw new IllegalDataException(sqe); + } + return true; + } + + @SuppressWarnings("rawtypes") + @Override + public PDataType getDataType() { + return PVarcharArray.INSTANCE; + } + + @Override + public String getName() { + return NAME; + } + + @Override + public boolean isNullable() { + return PVarcharArray.INSTANCE.isNullable(); + } + + @Override + public int getKeyFormationTraversalIndex() { + return NO_TRAVERSAL; + } + + @Override + public KeyPart newKeyPart(KeyPart childPart) { + return null; + } + + @Override + public OrderPreserving preservesOrder() { + return OrderPreserving.NO; + } + +} diff --git a/phoenix-core/src/main/java/org/apache/phoenix/expression/function/ToJsonFunction.java b/phoenix-core/src/main/java/org/apache/phoenix/expression/function/ToJsonFunction.java new file mode 100755 index 00000000000..acaeb3d37da --- /dev/null +++ b/phoenix-core/src/main/java/org/apache/phoenix/expression/function/ToJsonFunction.java @@ -0,0 +1,106 @@ +/* + * 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.expression.function; + +import org.apache.hadoop.hbase.io.ImmutableBytesWritable; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.phoenix.compile.KeyPart; +import org.apache.phoenix.exception.SQLExceptionCode; +import org.apache.phoenix.exception.SQLExceptionInfo; +import org.apache.phoenix.expression.Expression; +import org.apache.phoenix.parse.FunctionParseNode.Argument; +import org.apache.phoenix.parse.FunctionParseNode.BuiltInFunction; +import org.apache.phoenix.schema.IllegalDataException; +import org.apache.phoenix.schema.json.PhoenixJson; +import org.apache.phoenix.schema.tuple.Tuple; +import org.apache.phoenix.schema.types.*; +import org.apache.phoenix.util.ByteUtil; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.List; + +@BuiltInFunction(name = ToJsonFunction.NAME, args = { + @Argument(allowedTypes={PBinary.class, PVarbinary.class})}) +public class ToJsonFunction extends ScalarFunction { + public static final String NAME = "TO_JSON"; + + public ToJsonFunction() { + super(); + } + + public ToJsonFunction(List children) { + super(children); + } + + @Override + public boolean evaluate(Tuple tuple, ImmutableBytesWritable ptr) { + + Expression expression = this.children.get(0); + if (!expression.evaluate(tuple, ptr)) { + return false; + }else if (ptr.getLength() == 0) { + return true; + } + PDataType baseType = expression.getDataType(); + Object re =baseType.toObject(ptr); + String jsons = PhoenixJson.dataToJsonValue(baseType, re); + try { + PhoenixJson phoenixJson = PhoenixJson.getInstance(jsons); + byte[] json = PJson.INSTANCE.toBytes(phoenixJson); + ptr.set(json); + } catch (SQLException sqe) { + new IllegalDataException(new SQLExceptionInfo.Builder(SQLExceptionCode.ILLEGAL_DATA) + .setRootCause(sqe).build().buildException()); + } + + return true; + } + + @SuppressWarnings("rawtypes") + @Override + public PDataType getDataType() { + return PJson.INSTANCE; + } + + @Override + public String getName() { + return NAME; + } + + @Override + public boolean isNullable() { + return PJson.INSTANCE.isNullable(); + } + + @Override + public int getKeyFormationTraversalIndex() { + return NO_TRAVERSAL; + } + + @Override + public KeyPart newKeyPart(KeyPart childPart) { + return null; + } + + @Override + public OrderPreserving preservesOrder() { + return OrderPreserving.NO; + } + +} 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 old mode 100644 new mode 100755 index bbd35fa5e92..0eaea72c156 --- 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 @@ -20,13 +20,18 @@ import java.io.IOException; import java.sql.SQLException; +import java.text.Format; import java.util.Arrays; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; 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.apache.phoenix.schema.types.*; import org.codehaus.jackson.JsonFactory; import org.codehaus.jackson.JsonNode; import org.codehaus.jackson.JsonParser; @@ -231,4 +236,201 @@ private PhoenixJson getPhoenixJsonInternal(String[] paths) { } return new PhoenixJson(node, node.toString()); } + + /** + * If the current {@link PhoenixJson} is a JsonArray,then it returns the length of the JsonArray. + *

For example:[1,2,3] ,it will return 3 + *

Its required for json_array_length(). + * @throws SQLException + */ + public int getJsonArrayLength() throws SQLException { + if(this.rootNode.isArray()){ + return this.rootNode.size(); + }else{ + throw new SQLExceptionInfo.Builder(SQLExceptionCode.JSON_NODE_MISMATCH) + .build().buildException(); + } +} + + /** + * If the current {@link PhoenixJson} is a JsonArray,then it returns the set of array elements. + *

For example:[1,false,[2,"string"]] + * it will return (new Object[]{"1","false","[2,\"string\"]"}) + *

Its required for json_array_elements(). + * @return {@link String []} as the set of JSON elements + * @throws SQLException + */ + public Object[] getJsonArrayElements() throws SQLException { + if(this.rootNode.isArray()) { + List elementlist = new ArrayList(); + Iterator elements = this.rootNode.getElements(); + while (elements.hasNext()) { + JsonNode e = elements.next(); + elementlist.add(e.toString()); + } + return elementlist.toArray(); + }else{ + throw new SQLExceptionInfo.Builder(SQLExceptionCode.JSON_NODE_MISMATCH) + .build().buildException(); + } + } + /** + * It returns the set of JSON keys for the current {@link PhoenixJson}.Only the outermost keys will be generated + *

For example:{"f1":"abc","f2":{"f3":"a", "f4":"b"}} + * it will return (new Object[]{"f1","f2"}) + *

Its required for json_object_keys(). + * @return {@link String []} as the set of JSON keys + */ + public Object[] getJsonObjectKeys() { + if(this.rootNode.isObject()){ + List elementlist = new ArrayList(); + Iterator fieldnames = this.rootNode.getFieldNames(); + while(fieldnames.hasNext()){ + elementlist.add(fieldnames.next()); + } + return elementlist.toArray(); + }else{ + return null; + } + + } + + /** + * It returns the SET of JSON key/value pairs for the current {@link PhoenixJson}.Only the outermost key/value will be generated + * Probably it seems we should use a special SET class which can hold different types of elements. + * but we use the {@link org.apache.phoenix.schema.types.PVarcharArray} as an alternative of SET TYPE + * when implementing the build-in function ,so we use {@link String} directly to store the pair + * and in this case we use "," to separate key and value + *

For example:{"f1":"abc","f2":"edf"} + * it will return (new Object[]{"f1,abc","f2,edf"}) + * + *

Its required for json_each(). + * @return {@link String []} as the SET of JSON key/value pairs + */ + public Object[] getJsonFields() { + if(this.rootNode.isObject()){ + List elementlist = new ArrayList(); + Iterator> fields = this.rootNode.getFields(); + while(fields.hasNext()){ + Map.Entry entry = fields.next(); + StringBuilder fieldBuilder = new StringBuilder(); + fieldBuilder.append(entry.getKey()); + fieldBuilder.append(","); + fieldBuilder.append(entry.getValue().toString()); + elementlist.add(fieldBuilder.toString()); + } + return elementlist.toArray(); + }else{ + return null; + } + + } + /** + * Expands the object in current {@link PhoenixJson} to a record whose columns match the record type defined by base. + * Conversion will be best effort; columns in base with no corresponding key will be left null. + * If a column is specified more than once, the last value is used. + * Also use "," to separate each columns + *

For example:types :{"a","b"} json: {"a":"1","b":"2"} + * it will return new String("1,2") + * + *

Its required for json_populate_record(). + * @param types {@link String} the record type + * @return {@link String} as the result record + */ + public String jsonPopulateRecord(String [] types) { + return jsonPopulateRecord(this.rootNode,types); + } + + private String jsonPopulateRecord(JsonNode e,String [] types) { + StringBuilder recordsBuilder = new StringBuilder(); + for(int i =0 ;i < types.length; i++){ + List nodelist =e.findValues(types[i]); + if(nodelist.size()!=0){ + recordsBuilder.append(nodelist.get(0).toString()); + }else{ + recordsBuilder.append("null"); + } + if(i != types.length-1){ + recordsBuilder.append(","); + } + } + return recordsBuilder.toString(); + } + /** + * Expands the outermost set of objects in current {@link PhoenixJson} to a SET of records whose columns match the record type defined by base. + * Conversion will be best effort; columns in base with no corresponding key will be left null. + * If a column is specified more than once, the last value is used. + * Also use "," to separate each columns + *

For example:types :{"a","b"} json: {[{"a":"1","b":"2"},{"a":"3","b":"4"}]} + * it will return (new Object[]{"1,2","3,4"}) + * + *

Its required for json_populate_recordset(). + * @param types {@link String} the record type + * @return {@link String []} as the SET of records + */ + public Object[] jsonPopulateRecordSet(String[] types)throws SQLException { + if(this.rootNode.isArray()) { + List recordsList = new ArrayList(); + Iterator elements = this.rootNode.getElements(); + while(elements.hasNext()){ + JsonNode e = elements.next(); + recordsList.add(jsonPopulateRecord(e,types)); + } + return recordsList.toArray(); + }else{ + throw new SQLExceptionInfo.Builder(SQLExceptionCode.JSON_NODE_MISMATCH) + .build().buildException(); + } + + } + + + /** + * Returns the value as JSON. + *

If the data type is not built in, and there is a cast from the type to json, + * the cast function will be used to perform the conversion. + * Otherwise, for any value other than a number, a Boolean, or a null value, + * the text representation will be used, escaped and quoted so that it is legal JSON. + * If the formatter is given ,perform the conversion based on it. + * + *

Its required for to_json(),array_to_json(). + * @param targetType {@link PDataType} type of the value + * @param obj {@link Object} the value object + * @param formatter {@link Format} format of the value + * @return {@link String} as JSON + */ + public static String dataToJsonValue(PDataType targetType, Object obj,Format formatter) { + StringBuilder valueBuilder = new StringBuilder(); + if (obj != null) { + if (PDataType.equalsAny(targetType, PVarchar.INSTANCE,PChar.INSTANCE)) { + valueBuilder.append("\""); + String tmp = PVarchar.INSTANCE.toStringLiteral(obj,formatter); + valueBuilder.append(tmp.substring(1,tmp.length()-1)); + valueBuilder.append("\""); + }else if (PDataType.equalsAny(targetType,PDate.INSTANCE,PTime.INSTANCE,PTimestamp.INSTANCE)){ + valueBuilder.append("\""); + String tmp = PVarchar.INSTANCE.toStringLiteral(obj,formatter); + valueBuilder.append(tmp.substring(1,tmp.length()-1)); + valueBuilder.append("\""); + }else if (targetType.isFixedWidth()) { + valueBuilder.append(targetType.toStringLiteral(obj, formatter)); + } else{ + valueBuilder.append("\""); + String tmp = PVarchar.INSTANCE.toStringLiteral(obj,formatter); + valueBuilder.append(tmp.substring(1,tmp.length()-1)); + valueBuilder.append("\""); + } + + }else{ + valueBuilder.append("null"); + } + return valueBuilder.toString() ; + } + + public static String dataToJsonValue(PDataType targetType, Object obj){ + return dataToJsonValue(targetType,obj,null); + } + + + }