-
Notifications
You must be signed in to change notification settings - Fork 921
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[KYUUBI #6266] Kyuubi BeeLine supports JSON output format.
# 🔍 Description ## Issue References 🔗 This pull request fixes #6266 ## Describe Your Solution 🔧 Kyuubi BeeLine supports JSON output format. This pr cherrypick HIVE-20447, during this process, no incompatibility occurred in the code. ## Types of changes 🔖 - [ ] Bugfix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) ## Test Plan 🧪 New UT --- # Checklist 📝 - [x] This patch was not authored or co-authored using [Generative Tooling](https://www.apache.org/legal/generative-tooling.html) **Be nice. Be informative.** Closes #6282 from slfan1989/beeline_support_json. Closes #6266 0749500 [Shilun Fan] [KYUUBI #6266] Kyuubi BeeLine supports JSON output format. Authored-by: Shilun Fan <[email protected]> Signed-off-by: Cheng Pan <[email protected]>
- Loading branch information
Showing
6 changed files
with
403 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
56 changes: 56 additions & 0 deletions
56
kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/JSONFileOutputFormat.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
/* | ||
* 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.hive.beeline; | ||
|
||
import com.fasterxml.jackson.core.util.MinimalPrettyPrinter; | ||
import java.io.ByteArrayOutputStream; | ||
import java.io.IOException; | ||
import java.nio.charset.StandardCharsets; | ||
|
||
/** | ||
* OutputFormat for hive JSON file format. Removes "{ "resultset": [...] }" wrapping and prints one | ||
* object per line. This output format matches the same format as a Hive table created with JSONFILE | ||
* file format: CREATE TABLE ... STORED AS JSONFILE; e.g. {"name":"Ritchie | ||
* Tiger","age":40,"is_employed":true,"college":"RIT"} {"name":"Bobby | ||
* Tables","age":8,"is_employed":false,"college":null} ... | ||
* | ||
* <p>Note the lack of "," at the end of lines. | ||
*/ | ||
public class JSONFileOutputFormat extends JSONOutputFormat { | ||
|
||
JSONFileOutputFormat(BeeLine beeLine) { | ||
super(beeLine); | ||
this.generator.setPrettyPrinter(new MinimalPrettyPrinter("\n")); | ||
} | ||
|
||
@Override | ||
void printHeader(Rows.Row header) {} | ||
|
||
@Override | ||
void printFooter(Rows.Row header) { | ||
ByteArrayOutputStream buf = (ByteArrayOutputStream) generator.getOutputTarget(); | ||
try { | ||
generator.flush(); | ||
String out = buf.toString(StandardCharsets.UTF_8.name()); | ||
beeLine.output(out); | ||
} catch (IOException e) { | ||
beeLine.handleException(e); | ||
} | ||
buf.reset(); | ||
} | ||
} |
123 changes: 123 additions & 0 deletions
123
kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/JSONOutputFormat.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
/* | ||
* 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. | ||
*/ | ||
|
||
/* | ||
* This source file is based on code taken from SQLLine 1.9 | ||
* See SQLLine notice in LICENSE | ||
*/ | ||
package org.apache.hive.beeline; | ||
|
||
import com.fasterxml.jackson.core.JsonEncoding; | ||
import com.fasterxml.jackson.core.JsonFactory; | ||
import com.fasterxml.jackson.core.JsonGenerator; | ||
import java.io.ByteArrayOutputStream; | ||
import java.io.IOException; | ||
import java.nio.charset.StandardCharsets; | ||
import java.sql.SQLException; | ||
import java.sql.Types; | ||
|
||
/** | ||
* OutputFormat for standard JSON. | ||
* {"resultset":[{"String":"a","Int":1,"Decimal":3.14,"Bool":true,"Null":null},{"String":"b","Int":2,"Decimal":2.718,"Bool":false,"Null":null}]} | ||
*/ | ||
public class JSONOutputFormat extends AbstractOutputFormat { | ||
protected final BeeLine beeLine; | ||
protected JsonGenerator generator; | ||
|
||
/** @param beeLine */ | ||
JSONOutputFormat(BeeLine beeLine) { | ||
this.beeLine = beeLine; | ||
try { | ||
this.generator = | ||
new JsonFactory().createGenerator(new ByteArrayOutputStream(), JsonEncoding.UTF8); | ||
} catch (IOException e) { | ||
beeLine.handleException(e); | ||
} | ||
} | ||
|
||
@Override | ||
void printHeader(Rows.Row header) { | ||
try { | ||
generator.writeStartObject(); | ||
generator.writeArrayFieldStart("resultset"); | ||
} catch (IOException e) { | ||
beeLine.handleException(e); | ||
} | ||
} | ||
|
||
@Override | ||
void printFooter(Rows.Row header) { | ||
ByteArrayOutputStream buf = (ByteArrayOutputStream) generator.getOutputTarget(); | ||
try { | ||
generator.writeEndArray(); | ||
generator.writeEndObject(); | ||
generator.flush(); | ||
String out = buf.toString(StandardCharsets.UTF_8.name()); | ||
beeLine.output(out); | ||
} catch (IOException e) { | ||
beeLine.handleException(e); | ||
} | ||
buf.reset(); | ||
} | ||
|
||
@Override | ||
void printRow(Rows rows, Rows.Row header, Rows.Row row) { | ||
String[] head = header.values; | ||
String[] vals = row.values; | ||
|
||
try { | ||
generator.writeStartObject(); | ||
for (int i = 0; (i < head.length) && (i < vals.length); i++) { | ||
generator.writeFieldName(head[i]); | ||
switch (rows.rsMeta.getColumnType(i + 1)) { | ||
case Types.TINYINT: | ||
case Types.SMALLINT: | ||
case Types.INTEGER: | ||
case Types.BIGINT: | ||
case Types.REAL: | ||
case Types.FLOAT: | ||
case Types.DOUBLE: | ||
case Types.DECIMAL: | ||
case Types.NUMERIC: | ||
case Types.ROWID: | ||
generator.writeNumber(vals[i]); | ||
break; | ||
case Types.BINARY: | ||
case Types.BLOB: | ||
case Types.VARBINARY: | ||
case Types.LONGVARBINARY: | ||
generator.writeString(vals[i]); | ||
break; | ||
case Types.NULL: | ||
generator.writeNull(); | ||
break; | ||
case Types.BOOLEAN: | ||
generator.writeBoolean(Boolean.parseBoolean(vals[i])); | ||
break; | ||
default: | ||
generator.writeString(vals[i]); | ||
} | ||
} | ||
generator.writeEndObject(); | ||
} catch (IOException e) { | ||
beeLine.handleException(e); | ||
} catch (SQLException e) { | ||
beeLine.handleSQLException(e); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
109 changes: 109 additions & 0 deletions
109
kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestJSONFileOutputFormat.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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.hive.beeline; | ||
|
||
import static org.junit.Assert.assertEquals; | ||
import static org.mockito.ArgumentMatchers.anyInt; | ||
import static org.mockito.Mockito.mock; | ||
import static org.mockito.Mockito.spy; | ||
import static org.mockito.Mockito.when; | ||
|
||
import java.sql.ResultSet; | ||
import java.sql.ResultSetMetaData; | ||
import java.sql.SQLException; | ||
import java.sql.Types; | ||
import org.junit.Before; | ||
import org.junit.Test; | ||
import org.mockito.ArgumentCaptor; | ||
import org.mockito.Mockito; | ||
import org.mockito.invocation.InvocationOnMock; | ||
import org.mockito.stubbing.Answer; | ||
|
||
public class TestJSONFileOutputFormat { | ||
private final String[][] mockRowData = { | ||
{"aaa", "1", "3.14", "true", "", "SGVsbG8sIFdvcmxkIQ"}, | ||
{"bbb", "2", "2.718", "false", "Null", "RWFzdGVyCgllZ2cu"} | ||
}; | ||
|
||
public ResultSet mockResultSet; | ||
public TestBufferedRows.MockRow mockRow; | ||
|
||
@Test | ||
public final void testPrint() throws SQLException { | ||
BeeLine mockBeeline = spy(BeeLine.class); | ||
ArgumentCaptor<String> captureOutput = ArgumentCaptor.forClass(String.class); | ||
Mockito.doNothing().when(mockBeeline).output(captureOutput.capture()); | ||
BufferedRows bfRows = new BufferedRows(mockBeeline, mockResultSet); | ||
JSONFileOutputFormat instance = new JSONFileOutputFormat(mockBeeline); | ||
instance.print(bfRows); | ||
String expResult = | ||
"{\"String\":\"aaa\",\"Int\":1,\"Decimal\":3.14,\"Bool\":true,\"Null\":null,\"Binary\":\"SGVsbG8sIFdvcmxkIQ\"}\n{\"String\":\"bbb\",\"Int\":2,\"Decimal\":2.718,\"Bool\":false,\"Null\":null,\"Binary\":\"RWFzdGVyCgllZ2cu\"}"; | ||
assertEquals(expResult, captureOutput.getValue()); | ||
} | ||
|
||
@Before | ||
public void setupMockData() throws SQLException { | ||
mockResultSet = mock(ResultSet.class); | ||
ResultSetMetaData mockResultSetMetaData = mock(ResultSetMetaData.class); | ||
when(mockResultSetMetaData.getColumnCount()).thenReturn(6); | ||
when(mockResultSetMetaData.getColumnLabel(1)).thenReturn("String"); | ||
when(mockResultSetMetaData.getColumnLabel(2)).thenReturn("Int"); | ||
when(mockResultSetMetaData.getColumnLabel(3)).thenReturn("Decimal"); | ||
when(mockResultSetMetaData.getColumnLabel(4)).thenReturn("Bool"); | ||
when(mockResultSetMetaData.getColumnLabel(5)).thenReturn("Null"); | ||
when(mockResultSetMetaData.getColumnLabel(6)).thenReturn("Binary"); | ||
|
||
when(mockResultSetMetaData.getColumnType(1)).thenReturn(Types.VARCHAR); | ||
when(mockResultSetMetaData.getColumnType(2)).thenReturn(Types.INTEGER); | ||
when(mockResultSetMetaData.getColumnType(3)).thenReturn(Types.DECIMAL); | ||
when(mockResultSetMetaData.getColumnType(4)).thenReturn(Types.BOOLEAN); | ||
when(mockResultSetMetaData.getColumnType(5)).thenReturn(Types.NULL); | ||
when(mockResultSetMetaData.getColumnType(6)).thenReturn(Types.BINARY); | ||
|
||
when(mockResultSet.getMetaData()).thenReturn(mockResultSetMetaData); | ||
|
||
mockRow = new TestBufferedRows.MockRow(); | ||
when(mockResultSet.next()) | ||
.thenAnswer( | ||
new Answer<Boolean>() { | ||
private int mockRowDataIndex = 0; | ||
|
||
@Override | ||
public Boolean answer(final InvocationOnMock invocation) { | ||
if (mockRowDataIndex < mockRowData.length) { | ||
mockRow.setCurrentRowData(mockRowData[mockRowDataIndex]); | ||
mockRowDataIndex++; | ||
return true; | ||
} else { | ||
return false; | ||
} | ||
} | ||
}); | ||
|
||
when(mockResultSet.getObject(anyInt())) | ||
.thenAnswer( | ||
new Answer<String>() { | ||
@Override | ||
public String answer(final InvocationOnMock invocation) { | ||
Object[] args = invocation.getArguments(); | ||
int index = ((Integer) args[0]); | ||
return mockRow.getColumn(index); | ||
} | ||
}); | ||
} | ||
} |
Oops, something went wrong.