Repository: drill
Updated Branches:
  refs/heads/master eb199d614 -> 7289cabb5


DRILL-2350: Improve exception handling and error messages in JSON reader.


Project: http://git-wip-us.apache.org/repos/asf/drill/repo
Commit: http://git-wip-us.apache.org/repos/asf/drill/commit/319b94c0
Tree: http://git-wip-us.apache.org/repos/asf/drill/tree/319b94c0
Diff: http://git-wip-us.apache.org/repos/asf/drill/diff/319b94c0

Branch: refs/heads/master
Commit: 319b94c01c798af0ff0ff95b8b3af2e80d2a5ef7
Parents: eb199d6
Author: Parth Chandra <pchan...@maprtech.com>
Authored: Thu Apr 9 17:11:14 2015 -0700
Committer: Parth Chandra <pchan...@maprtech.com>
Committed: Fri Apr 24 16:21:53 2015 -0700

----------------------------------------------------------------------
 .../src/main/codegen/includes/vv_imports.ftl    |  3 +
 .../src/main/codegen/templates/ListWriters.java | 23 +++--
 .../exec/store/easy/json/JSONRecordReader.java  | 23 ++---
 .../exec/store/easy/json/JsonProcessor.java     | 13 +++
 .../easy/json/reader/BaseJsonProcessor.java     | 26 ++++++
 .../exec/vector/complex/fn/JsonReader.java      | 97 ++++++++++++++------
 .../exec/store/json/TestJsonRecordReader.java   | 18 ++++
 .../test/resources/jsoninput/DRILL-2350.json    |  1 +
 8 files changed, 160 insertions(+), 44 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/drill/blob/319b94c0/exec/java-exec/src/main/codegen/includes/vv_imports.ftl
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/main/codegen/includes/vv_imports.ftl 
b/exec/java-exec/src/main/codegen/includes/vv_imports.ftl
index 371d8d0..d0f6291 100644
--- a/exec/java-exec/src/main/codegen/includes/vv_imports.ftl
+++ b/exec/java-exec/src/main/codegen/includes/vv_imports.ftl
@@ -21,9 +21,12 @@ import io.netty.buffer.*;
 
 import org.apache.commons.lang3.ArrayUtils;
 
+import org.apache.drill.common.exceptions.UserException;
 import org.apache.drill.exec.expr.fn.impl.StringFunctionUtil;
 import org.apache.drill.exec.memory.*;
 import org.apache.drill.exec.proto.SchemaDefProtos;
+import org.apache.drill.exec.proto.UserBitShared;
+import org.apache.drill.exec.proto.UserBitShared.DrillPBError;
 import org.apache.drill.exec.proto.UserBitShared.SerializedField;
 import org.apache.drill.exec.record.*;
 import org.apache.drill.exec.vector.*;

http://git-wip-us.apache.org/repos/asf/drill/blob/319b94c0/exec/java-exec/src/main/codegen/templates/ListWriters.java
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/main/codegen/templates/ListWriters.java 
b/exec/java-exec/src/main/codegen/templates/ListWriters.java
index 29708d7..6df4248 100644
--- a/exec/java-exec/src/main/codegen/templates/ListWriters.java
+++ b/exec/java-exec/src/main/codegen/templates/ListWriters.java
@@ -97,8 +97,8 @@ public class ${mode}ListWriter extends AbstractFieldWriter{
     case IN_MAP:
       return writer;
     }
-    
-    throw new IllegalStateException(String.format("Needed to be in state INIT 
or IN_MAP but in mode %s", mode.name()));
+
+  throw UserException.unsupportedError().message(getUnsupportedErrorMsg("MAP", 
mode.name())).build();
 
   }
   
@@ -116,8 +116,8 @@ public class ${mode}ListWriter extends AbstractFieldWriter{
     case IN_LIST:
       return writer;
     }
-    
-    throw new IllegalStateException(String.format("Needed to be in state INIT 
or IN_LIST but in mode %s", mode.name()));
+
+  throw 
UserException.unsupportedError().message(getUnsupportedErrorMsg("LIST", 
mode.name())).build();
 
   }
   
@@ -143,8 +143,9 @@ public class ${mode}ListWriter extends AbstractFieldWriter{
     case IN_${upperName}:
       return writer;
     }
-    
-    throw new IllegalStateException(String.format("Needed to be in state INIT 
or IN_${upperName} but in mode %s", mode.name()));
+
+  throw 
UserException.unsupportedError().message(getUnsupportedErrorMsg("${upperName}", 
mode.name())).build();
+
   }
   </#list></#list>
 
@@ -198,7 +199,15 @@ public class ${mode}ListWriter extends AbstractFieldWriter{
   }
   </#if>
 
-}
+  private String getUnsupportedErrorMsg(String expected, String found ){
+    String f = found.substring(3);
+    return String.format("In a list of type %s, encountered a value of type 
%s. "+
+      "Drill does not support lists of different types.",
+       f, expected
+    );
+  }
+
+  }
 </#list>
 
 

http://git-wip-us.apache.org/repos/asf/drill/blob/319b94c0/exec/java-exec/src/main/java/org/apache/drill/exec/store/easy/json/JSONRecordReader.java
----------------------------------------------------------------------
diff --git 
a/exec/java-exec/src/main/java/org/apache/drill/exec/store/easy/json/JSONRecordReader.java
 
b/exec/java-exec/src/main/java/org/apache/drill/exec/store/easy/json/JSONRecordReader.java
index b41de31..2cccc64 100644
--- 
a/exec/java-exec/src/main/java/org/apache/drill/exec/store/easy/json/JSONRecordReader.java
+++ 
b/exec/java-exec/src/main/java/org/apache/drill/exec/store/easy/json/JSONRecordReader.java
@@ -124,7 +124,7 @@ public class JSONRecordReader extends AbstractRecordReader {
       }
       setupParser();
     }catch(final Exception e){
-      handleAndRaise("Failure reading JSON file.", e);
+      handleAndRaise("Failure reading JSON file", e);
     }
   }
 
@@ -159,12 +159,15 @@ public class JSONRecordReader extends 
AbstractRecordReader {
       columnNr = ex.getLocation().getColumnNr();
     }
 
-    throw UserException.dataReadError(e)
-      .message("%s - %s", suffix, message)
-      .addContext("Filename", hadoopPath.toUri().getPath())
-      .addContext("Record", recordCount + 1)
-      .addContext("Column", columnNr)
-      .build();
+    UserException.Builder exceptionBuilder = UserException.dataReadError(e)
+            .message("%s - %s", suffix, message);
+    if (columnNr > 0) {
+      exceptionBuilder.pushContext("Column ", columnNr);
+    }
+    exceptionBuilder.pushContext("Record ", recordCount + 1)
+            .pushContext("File ", hadoopPath.toUri().getPath());
+
+    throw exceptionBuilder.build();
   }
 
 
@@ -208,10 +211,8 @@ public class JSONRecordReader extends AbstractRecordReader 
{
 
       return recordCount;
 
-    } catch (final JsonParseException e) {
-      handleAndRaise("Error parsing JSON.", e);
-    } catch (final IOException e) {
-      handleAndRaise("Error reading JSON.", e);
+    } catch (final Exception e) {
+      handleAndRaise("Error parsing JSON", e);
     }
     // this is never reached
     return 0;

http://git-wip-us.apache.org/repos/asf/drill/blob/319b94c0/exec/java-exec/src/main/java/org/apache/drill/exec/store/easy/json/JsonProcessor.java
----------------------------------------------------------------------
diff --git 
a/exec/java-exec/src/main/java/org/apache/drill/exec/store/easy/json/JsonProcessor.java
 
b/exec/java-exec/src/main/java/org/apache/drill/exec/store/easy/json/JsonProcessor.java
index b310818..4d8d4ba 100644
--- 
a/exec/java-exec/src/main/java/org/apache/drill/exec/store/easy/json/JsonProcessor.java
+++ 
b/exec/java-exec/src/main/java/org/apache/drill/exec/store/easy/json/JsonProcessor.java
@@ -20,6 +20,8 @@ package org.apache.drill.exec.store.easy.json;
 import java.io.IOException;
 import java.io.InputStream;
 
+import org.apache.drill.common.exceptions.UserException;
+import org.apache.drill.exec.proto.UserBitShared;
 import org.apache.drill.exec.vector.complex.writer.BaseWriter;
 
 import com.fasterxml.jackson.databind.JsonNode;
@@ -37,4 +39,15 @@ public interface JsonProcessor {
   void setSource(JsonNode node);
 
   void ensureAtLeastOneField(BaseWriter.ComplexWriter writer);
+
+  public UserException.Builder getExceptionWithContext(UserException.Builder 
exceptionBuilder,
+                                                       String field,
+                                                       String msg,
+                                                       Object... args);
+
+  public UserException.Builder getExceptionWithContext(Throwable exception,
+                                                       String field,
+                                                       String msg,
+                                                       Object... args);
+
 }

http://git-wip-us.apache.org/repos/asf/drill/blob/319b94c0/exec/java-exec/src/main/java/org/apache/drill/exec/store/easy/json/reader/BaseJsonProcessor.java
----------------------------------------------------------------------
diff --git 
a/exec/java-exec/src/main/java/org/apache/drill/exec/store/easy/json/reader/BaseJsonProcessor.java
 
b/exec/java-exec/src/main/java/org/apache/drill/exec/store/easy/json/reader/BaseJsonProcessor.java
index 718bb09..7833631 100644
--- 
a/exec/java-exec/src/main/java/org/apache/drill/exec/store/easy/json/reader/BaseJsonProcessor.java
+++ 
b/exec/java-exec/src/main/java/org/apache/drill/exec/store/easy/json/reader/BaseJsonProcessor.java
@@ -29,6 +29,7 @@ import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.TreeTraversingParser;
 import com.google.common.base.Preconditions;
+import org.apache.drill.common.exceptions.UserException;
 
 public abstract class BaseJsonProcessor implements JsonProcessor {
 
@@ -53,4 +54,29 @@ public abstract class BaseJsonProcessor implements 
JsonProcessor {
     this.parser = new TreeTraversingParser(node);
   }
 
+  @Override
+  public UserException.Builder getExceptionWithContext(UserException.Builder 
exceptionBuilder,
+                                                       String field,
+                                                       String msg,
+                                                       Object... args) {
+    if (msg != null){
+      exceptionBuilder.message(msg, args);
+    }
+    if(field!=null) {
+      exceptionBuilder.pushContext("Field ", field);
+    }
+    exceptionBuilder.pushContext("Column ", 
parser.getCurrentLocation().getColumnNr()+1)
+            .pushContext("Line ", parser.getCurrentLocation().getLineNr());
+    return exceptionBuilder;
+  }
+
+  @Override
+  public UserException.Builder getExceptionWithContext(Throwable e,
+                                                       String field,
+                                                       String msg,
+                                                       Object... args) {
+    UserException.Builder exceptionBuilder = UserException.dataReadError(e);
+    return getExceptionWithContext(exceptionBuilder, field, msg, args);
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/drill/blob/319b94c0/exec/java-exec/src/main/java/org/apache/drill/exec/vector/complex/fn/JsonReader.java
----------------------------------------------------------------------
diff --git 
a/exec/java-exec/src/main/java/org/apache/drill/exec/vector/complex/fn/JsonReader.java
 
b/exec/java-exec/src/main/java/org/apache/drill/exec/vector/complex/fn/JsonReader.java
index c196fd2..696fe7a 100644
--- 
a/exec/java-exec/src/main/java/org/apache/drill/exec/vector/complex/fn/JsonReader.java
+++ 
b/exec/java-exec/src/main/java/org/apache/drill/exec/vector/complex/fn/JsonReader.java
@@ -23,7 +23,7 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.util.List;
 
-import org.apache.drill.common.exceptions.DrillRuntimeException;
+import org.apache.drill.common.exceptions.UserException;
 import org.apache.drill.common.expression.PathSegment;
 import org.apache.drill.common.expression.SchemaPath;
 import org.apache.drill.exec.physical.base.GroupScan;
@@ -65,6 +65,10 @@ public class JsonReader extends BaseJsonProcessor {
    * Whether the reader is currently in a situation where we are unwrapping an 
outer list.
    */
   private boolean inOuterList;
+  /**
+   * The name of the current field being parsed. For Error messages.
+   */
+  private String currentFieldName;
 
   private FieldSelection selection;
 
@@ -82,6 +86,7 @@ public class JsonReader extends BaseJsonProcessor {
     this.columns = columns;
     this.mapOutput = new MapVectorOutput(workingBuffer);
     this.listOutput = new ListVectorOutput(workingBuffer);
+    this.currentFieldName="<none>";
   }
 
   @Override
@@ -146,7 +151,11 @@ public class JsonReader extends BaseJsonProcessor {
     case WRITE_SUCCEED:
       break;
     default:
-      throw new IllegalStateException();
+      throw
+        getExceptionWithContext(
+          UserException.dataReadError(), currentFieldName, null)
+          .message("Failure while reading JSON. (Got an invalid read state %s 
)", readState.toString())
+          .build();
     }
 
     return readState;
@@ -155,9 +164,13 @@ public class JsonReader extends BaseJsonProcessor {
   private void confirmLast() throws IOException{
     parser.nextToken();
     if(!parser.isClosed()){
-      throw new JsonParseException("Drill attempted to unwrap a toplevel list "
-        + "in your document.  However, it appears that there is trailing 
content after this top level list.  Drill only "
-        + "supports querying a set of distinct maps or a single json array 
with multiple inner maps.", parser.getCurrentLocation());
+      throw
+        getExceptionWithContext(
+          UserException.dataReadError(), currentFieldName, null)
+        .message("Drill attempted to unwrap a toplevel list "
+          + "in your document.  However, it appears that there is trailing 
content after this top level list.  Drill only "
+          + "supports querying a set of distinct maps or a single json array 
with multiple inner maps.")
+        .build();
     }
   }
 
@@ -168,8 +181,12 @@ public class JsonReader extends BaseJsonProcessor {
       break;
     case START_ARRAY:
       if(inOuterList){
-        throw new JsonParseException("The top level of your document must 
either be a single array of maps or a set "
-            + "of white space delimited maps.", parser.getCurrentLocation());
+        throw
+          getExceptionWithContext(
+            UserException.dataReadError(), currentFieldName, null)
+          .message("The top level of your document must either be a single 
array of maps or a set "
+            + "of white space delimited maps.")
+          .build();
       }
 
       if(skipOuterList){
@@ -178,8 +195,12 @@ public class JsonReader extends BaseJsonProcessor {
           inOuterList = true;
           writeDataSwitch(writer.rootAsMap());
         }else{
-          throw new JsonParseException("The top level of your document must 
either be a single array of maps or a set "
-              + "of white space delimited maps.", parser.getCurrentLocation());
+          throw
+            getExceptionWithContext(
+              UserException.dataReadError(), currentFieldName, null)
+            .message("The top level of your document must either be a single 
array of maps or a set "
+              + "of white space delimited maps.")
+            .build();
         }
 
       }else{
@@ -192,16 +213,22 @@ public class JsonReader extends BaseJsonProcessor {
         confirmLast();
         return ReadState.END_OF_STREAM;
       }else{
-        throw new JsonParseException(String.format("Failure while parsing 
JSON.  Ran across unexpected %s.", JsonToken.END_ARRAY), 
parser.getCurrentLocation());
+        throw
+          getExceptionWithContext(
+            UserException.dataReadError(), currentFieldName, null)
+          .message("Failure while parsing JSON.  Ran across unexpected %s.", 
JsonToken.END_ARRAY)
+          .build();
       }
 
     case NOT_AVAILABLE:
       return ReadState.END_OF_STREAM;
     default:
-      throw new JsonParseException(String.format(
-          "Failure while parsing JSON.  Found token of [%s]  Drill currently 
only supports parsing "
-              + "json strings that contain either lists or maps.  The root 
object cannot be a scalar.", t),
-          parser.getCurrentLocation());
+      throw
+        getExceptionWithContext(
+          UserException.dataReadError(), currentFieldName, null)
+          .message("Failure while parsing JSON.  Found token of [%s].  Drill 
currently only supports parsing "
+              + "json strings that contain either lists or maps.  The root 
object cannot be a scalar.", t)
+          .build();
     }
 
     return ReadState.WRITE_SUCCEED;
@@ -266,7 +293,7 @@ public class JsonReader extends BaseJsonProcessor {
       assert t == JsonToken.FIELD_NAME : String.format("Expected FIELD_NAME 
but got %s.", t.name());
 
       final String fieldName = parser.getText();
-
+      this.currentFieldName = fieldName;
       FieldSelection childSelection = selection.getChild(fieldName);
       if (childSelection.isNeverValid()) {
         consumeEntireNextValue();
@@ -312,8 +339,11 @@ public class JsonReader extends BaseJsonProcessor {
         break;
 
       default:
-        throw new IllegalStateException("Unexpected token " + 
parser.getCurrentToken());
-
+        throw
+          getExceptionWithContext(
+            UserException.dataReadError(), currentFieldName, null)
+          .message("Unexpected token %s", parser.getCurrentToken())
+          .build();
       }
 
     }
@@ -343,6 +373,7 @@ public class JsonReader extends BaseJsonProcessor {
       assert t == JsonToken.FIELD_NAME : String.format("Expected FIELD_NAME 
but got %s.", t.name());
 
       final String fieldName = parser.getText();
+      this.currentFieldName = fieldName;
       FieldSelection childSelection = selection.getChild(fieldName);
       if (childSelection.isNeverValid()) {
         consumeEntireNextValue();
@@ -375,8 +406,11 @@ public class JsonReader extends BaseJsonProcessor {
         break;
 
       default:
-        throw new IllegalStateException("Unexpected token " + 
parser.getCurrentToken());
-
+        throw
+          getExceptionWithContext(
+            UserException.dataReadError(), currentFieldName, null)
+          .message("Unexpected token %s", parser.getCurrentToken())
+          .build();
       }
     }
     map.end();
@@ -426,7 +460,7 @@ public class JsonReader extends BaseJsonProcessor {
   private void writeData(ListWriter list) throws IOException {
     list.start();
     outside: while (true) {
-
+      try {
       switch (parser.nextToken()) {
       case START_ARRAY:
         writeData(list.list());
@@ -452,9 +486,11 @@ public class JsonReader extends BaseJsonProcessor {
         break;
       }
       case VALUE_NULL:
-        throw new DrillRuntimeException("Drill does not currently null values 
in lists. "
-            + "Please set `store.json.all_text_mode` to true to read lists 
containing nulls. "
-            + "Be advised that this will treat JSON null values as string 
containing the word 'null'.");
+        throw UserException.unsupportedError()
+          .message("Null values are not supported in lists by default. " +
+            "Please set `store.json.all_text_mode` to true to read lists 
containing nulls. " +
+            "Be advised that this will treat JSON null values as a string 
containing the word 'null'.")
+          .build();
       case VALUE_NUMBER_FLOAT:
         list.float8().writeFloat8(parser.getDoubleValue());
         atLeastOneWrite = true;
@@ -468,8 +504,13 @@ public class JsonReader extends BaseJsonProcessor {
         atLeastOneWrite = true;
         break;
       default:
-        throw new IllegalStateException("Unexpected token " + 
parser.getCurrentToken());
-      }
+        throw UserException.dataReadError()
+          .message("Unexpected token %s", parser.getCurrentToken())
+          .build();
+    }
+    } catch (Exception e) {
+      throw getExceptionWithContext(e, this.currentFieldName, null).build();
+    }
     }
     list.end();
 
@@ -503,7 +544,11 @@ public class JsonReader extends BaseJsonProcessor {
         atLeastOneWrite = true;
         break;
       default:
-        throw new IllegalStateException("Unexpected token " + 
parser.getCurrentToken());
+        throw
+          getExceptionWithContext(
+            UserException.dataReadError(), currentFieldName, null)
+          .message("Unexpected token %s", parser.getCurrentToken())
+          .build();
       }
     }
     list.end();

http://git-wip-us.apache.org/repos/asf/drill/blob/319b94c0/exec/java-exec/src/test/java/org/apache/drill/exec/store/json/TestJsonRecordReader.java
----------------------------------------------------------------------
diff --git 
a/exec/java-exec/src/test/java/org/apache/drill/exec/store/json/TestJsonRecordReader.java
 
b/exec/java-exec/src/test/java/org/apache/drill/exec/store/json/TestJsonRecordReader.java
index 8b09e80..44e2a2d 100644
--- 
a/exec/java-exec/src/test/java/org/apache/drill/exec/store/json/TestJsonRecordReader.java
+++ 
b/exec/java-exec/src/test/java/org/apache/drill/exec/store/json/TestJsonRecordReader.java
@@ -18,7 +18,12 @@
 package org.apache.drill.exec.store.json;
 
 import org.apache.drill.BaseTestQuery;
+import org.apache.drill.common.exceptions.UserException;
+import org.apache.drill.exec.proto.UserBitShared;
 import org.junit.Test;
+import org.junit.Assert;
+
+import static org.junit.Assert.assertEquals;
 
 
 public class TestJsonRecordReader extends BaseTestQuery{
@@ -68,4 +73,17 @@ public class TestJsonRecordReader extends BaseTestQuery{
     testNoResult("alter session set `store.json.all_text_mode`= true");
     test("select * from cp.`jsoninput/big_numeric.json`");
   }
+
+  @Test
+  public void testExceptionHandling() throws Exception {
+    try {
+      test("select * from cp.`jsoninput/DRILL-2350.json`");
+    } catch(UserException e) {
+      
Assert.assertEquals(UserBitShared.DrillPBError.ErrorType.UNSUPPORTED_OPERATION, 
e.getOrCreatePBError(false).getErrorType());
+      String s = e.getMessage();
+      assertEquals("Expected Unsupported Operation Exception.", true, 
s.contains("Drill does not support lists of different types."));
+    }
+
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/drill/blob/319b94c0/exec/java-exec/src/test/resources/jsoninput/DRILL-2350.json
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/test/resources/jsoninput/DRILL-2350.json 
b/exec/java-exec/src/test/resources/jsoninput/DRILL-2350.json
new file mode 100644
index 0000000..3433e89
--- /dev/null
+++ b/exec/java-exec/src/test/resources/jsoninput/DRILL-2350.json
@@ -0,0 +1 @@
+{ "loc" : [ -75.2, 40 ] }

Reply via email to