PHOENIX-2304 NullPointerException when using an index and a char array (Julian 
Jaffe, Navis, James Taylor)


Project: http://git-wip-us.apache.org/repos/asf/phoenix/repo
Commit: http://git-wip-us.apache.org/repos/asf/phoenix/commit/28007f80
Tree: http://git-wip-us.apache.org/repos/asf/phoenix/tree/28007f80
Diff: http://git-wip-us.apache.org/repos/asf/phoenix/diff/28007f80

Branch: refs/heads/txn
Commit: 28007f804dd3d132d39169f532be050717c3526d
Parents: 18d0633
Author: James Taylor <[email protected]>
Authored: Mon Oct 19 13:44:35 2015 -0700
Committer: James Taylor <[email protected]>
Committed: Mon Oct 19 13:44:35 2015 -0700

----------------------------------------------------------------------
 .../phoenix/exception/SQLExceptionCode.java     |  76 +++----
 .../org/apache/phoenix/parse/ColumnDef.java     | 228 +++++++++----------
 .../phoenix/compile/QueryOptimizerTest.java     |  17 ++
 .../apache/phoenix/parse/QueryParserTest.java   |  99 ++++----
 4 files changed, 213 insertions(+), 207 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/phoenix/blob/28007f80/phoenix-core/src/main/java/org/apache/phoenix/exception/SQLExceptionCode.java
----------------------------------------------------------------------
diff --git 
a/phoenix-core/src/main/java/org/apache/phoenix/exception/SQLExceptionCode.java 
b/phoenix-core/src/main/java/org/apache/phoenix/exception/SQLExceptionCode.java
index 9448443..53a13be 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
@@ -46,9 +46,9 @@ import com.google.common.collect.Maps;
 
 
 /**
- * Various SQLException Information. Including a vender-specific errorcode and 
a standard SQLState.
- * 
- * 
+ * Various SQLException Information. Including a vendor-specific errorcode and 
a standard SQLState.
+ *
+ *
  * @since 1.0
  */
 public enum SQLExceptionCode {
@@ -59,7 +59,7 @@ public enum SQLExceptionCode {
     IO_EXCEPTION(101, "08000", "Unexpected IO exception."),
     MALFORMED_CONNECTION_URL(102, "08001", "Malformed connection url."),
     CANNOT_ESTABLISH_CONNECTION(103, "08004", "Unable to establish 
connection."),
-    
+
     /**
      * Data Exception (errorcode 02, sqlstate 22)
      */
@@ -74,19 +74,17 @@ public enum SQLExceptionCode {
     VALUE_IN_UPSERT_NOT_CONSTANT(204, "22008", "Values in UPSERT must evaluate 
to a constant."),
     MALFORMED_URL(205, "22009", "Malformed URL."),
     DATA_EXCEEDS_MAX_CAPACITY(206, "22003", "The data exceeds the max capacity 
for the data type."),
-    MISSING_CHAR_LENGTH(207, "22003", "Missing length for CHAR."),
-    NONPOSITIVE_CHAR_LENGTH(208, "22003", "CHAR or VARCHAR must have a 
positive length."),
+    MISSING_MAX_LENGTH(207, "22004", "Max length must be specified for type."),
+    NONPOSITIVE_MAX_LENGTH(208, "22006", "Max length must have a positive 
length for type."),
     DECIMAL_PRECISION_OUT_OF_RANGE(209, "22003", "Decimal precision outside of 
range. Should be within 1 and " + PDataType.MAX_PRECISION + "."),
-    MISSING_BINARY_LENGTH(210, "22003", "Missing length for BINARY."),
-    NONPOSITIVE_BINARY_LENGTH(211, "22003", "BINARY must have a positive 
length."),
     SERVER_ARITHMETIC_ERROR(212, "22012", "Arithmetic error on server."),
     VALUE_OUTSIDE_RANGE(213,"22003","Value outside range."),
     VALUE_IN_LIST_NOT_CONSTANT(214, "22008", "Values in IN must evaluate to a 
constant."),
     SINGLE_ROW_SUBQUERY_RETURNS_MULTIPLE_ROWS(215, "22015", "Single-row 
sub-query returns more than one row."),
     SUBQUERY_RETURNS_DIFFERENT_NUMBER_OF_FIELDS(216, "22016", "Sub-query must 
return the same number of fields as the left-hand-side expression of 'IN'."),
-    AMBIGUOUS_JOIN_CONDITION(217, "22017", "Amibiguous or non-equi join 
condition specified. Consider using table list with where clause."),
-    CONSTRAINT_VIOLATION(218, "22018", "Constraint violatioin."),
-    
+    AMBIGUOUS_JOIN_CONDITION(217, "22017", "Ambiguous or non-equi join 
condition specified. Consider using table list with where clause."),
+    CONSTRAINT_VIOLATION(218, "22018", "Constraint violation."),
+
     /**
      * Constraint Violation (errorcode 03, sqlstate 23)
      */
@@ -97,13 +95,13 @@ public enum SQLExceptionCode {
         }
     }),
     CANNOT_INDEX_COLUMN_ON_TYPE(302, "23100", "The column cannot be index due 
to its type."),
-    
+
     /**
      * Invalid Cursor State (errorcode 04, sqlstate 24)
      */
     CURSOR_BEFORE_FIRST_ROW(401, "24015","Cursor before first row."),
     CURSOR_PAST_LAST_ROW(402, "24016", "Cursor past last row."),
-    
+
     /**
      * Syntax Error or Access Rule Violation (errorcode 05, sqlstate 42)
      */
@@ -152,22 +150,22 @@ public enum SQLExceptionCode {
      *  Invalid Transaction State (errorcode 05, sqlstate 25)
      */
      READ_ONLY_CONNECTION(518,"25502","Mutations are not permitted for a 
read-only connection."),
- 
+
      VARBINARY_ARRAY_NOT_SUPPORTED(519, "42896", "VARBINARY ARRAY is not 
supported"),
-    
+
      /**
       *  Expression Index exceptions.
       */
-     AGGREGATE_EXPRESSION_NOT_ALLOWED_IN_INDEX(520, "42897", "Aggreagaate 
expression not allowed in an index"),
+     AGGREGATE_EXPRESSION_NOT_ALLOWED_IN_INDEX(520, "42897", "Aggregate 
expression not allowed in an index"),
      NON_DETERMINISTIC_EXPRESSION_NOT_ALLOWED_IN_INDEX(521, "42898", 
"Non-deterministic expression not allowed in an index"),
      STATELESS_EXPRESSION_NOT_ALLOWED_IN_INDEX(522, "42899", "Stateless 
expression not allowed in an index"),
 
-     /** 
+     /**
       * Union All related errors
       */
      SELECT_COLUMN_NUM_IN_UNIONALL_DIFFS(525, "42902", "SELECT column number 
differs in a Union All query is not allowed"),
      SELECT_COLUMN_TYPE_IN_UNIONALL_DIFFS(526, "42903", "SELECT column types 
differ in a Union All query is not allowed"),
-     
+
      /**
       * Row timestamp column related errors
       */
@@ -177,10 +175,10 @@ public enum SQLExceptionCode {
      ROWTIMESTAMP_COL_INVALID_TYPE(530, "42907", "A column can be added as 
ROW_TIMESTAMP only if it is of type DATE, BIGINT, TIME OR TIMESTAMP"),
      ROWTIMESTAMP_NOT_ALLOWED_ON_VIEW(531, "42908", "Declaring a column as 
row_timestamp is not allowed for views"),
      INVALID_SCN(532, "42909", "Value of SCN cannot be less than zero"),
-     /** 
+     /**
      * HBase and Phoenix specific implementation defined sub-classes.
      * Column family related exceptions.
-     * 
+     *
      * For the following exceptions, use errorcode 10.
      */
     SINGLE_PK_MAY_NOT_BE_NULL(1000, "42I00", "Single column primary key may 
not be NULL."),
@@ -237,11 +235,11 @@ public enum SQLExceptionCode {
     NO_MUTABLE_INDEXES(1026, "42Y85", "Mutable secondary indexes are only 
supported for HBase version " + 
MetaDataUtil.decodeHBaseVersionAsString(PhoenixDatabaseMetaData.MUTABLE_SI_VERSION_THRESHOLD)
 + " and above."),
     INVALID_FILTER_ON_IMMUTABLE_ROWS(1027, "42Y86", "All columns referenced in 
a WHERE clause must be available in every index for a table with immutable 
rows."),
     INVALID_INDEX_STATE_TRANSITION(1028, "42Y87", "Invalid index state 
transition."),
-    INVALID_MUTABLE_INDEX_CONFIG(1029, "42Y88", "Mutable secondary indexes 
must have the " 
-            + IndexManagementUtil.WAL_EDIT_CODEC_CLASS_KEY + " property set to 
" 
+    INVALID_MUTABLE_INDEX_CONFIG(1029, "42Y88", "Mutable secondary indexes 
must have the "
+            + IndexManagementUtil.WAL_EDIT_CODEC_CLASS_KEY + " property set to 
"
             +  IndexManagementUtil.INDEX_WAL_EDIT_CODEC_CLASS_NAME + " in the 
hbase-sites.xml of every region server"),
-            
-            
+
+
     CANNOT_CREATE_TENANT_SPECIFIC_TABLE(1030, "42Y89", "Cannot create table 
for tenant-specific connection"),
     DEFAULT_COLUMN_FAMILY_ONLY_ON_CREATE_TABLE(1034, "42Y93", "Default column 
family may only be specified when creating a table."),
     INSUFFICIENT_MULTI_TENANT_COLUMNS(1040, "42Y96", "A MULTI_TENANT table 
must have two or more PK columns with the first column being NOT NULL."),
@@ -255,8 +253,8 @@ public enum SQLExceptionCode {
     CANNOT_ALTER_PROPERTY(1051, "43A08", "Property can be specified or changed 
only when creating a table"),
     CANNOT_SET_PROPERTY_FOR_COLUMN_NOT_ADDED(1052, "43A09", "Property cannot 
be specified for a column family that is not being added or modified"),
     CANNOT_SET_TABLE_PROPERTY_ADD_COLUMN(1053, "43A10", "Table level property 
cannot be set when adding a column"),
-    
-    NO_LOCAL_INDEXES(1054, "43A11", "Local secondary indexes are not supported 
for HBase versions " + 
+
+    NO_LOCAL_INDEXES(1054, "43A11", "Local secondary indexes are not supported 
for HBase versions " +
         
MetaDataUtil.decodeHBaseVersionAsString(PhoenixDatabaseMetaData.MIN_LOCAL_SI_VERSION_DISALLOW)
 + " through " + 
MetaDataUtil.decodeHBaseVersionAsString(PhoenixDatabaseMetaData.MAX_LOCAL_SI_VERSION_DISALLOW)
 + " inclusive."),
     UNALLOWED_LOCAL_INDEXES(1055, "43A12", "Local secondary indexes are 
configured to not be allowed."),
     DESC_VARBINARY_NOT_SUPPORTED(1056, "43A13", "Descending VARBINARY columns 
not supported"),
@@ -288,18 +286,18 @@ public enum SQLExceptionCode {
     SEQUENCE_VAL_REACHED_MAX_VALUE(1212, "42Z12", "Reached MAXVALUE of 
sequence"),
     SEQUENCE_VAL_REACHED_MIN_VALUE(1213, "42Z13", "Reached MINVALUE of 
sequence"),
     INCREMENT_BY_MUST_NOT_BE_ZERO(1214, "42Z14", "Sequence INCREMENT BY value 
cannot be zero"),
-    NUM_SEQ_TO_ALLOCATE_MUST_BE_CONSTANT(1215, "42Z15", "Sequence NEXT n 
VALUES FOR must be a postive integer or constant." ),
+    NUM_SEQ_TO_ALLOCATE_MUST_BE_CONSTANT(1215, "42Z15", "Sequence NEXT n 
VALUES FOR must be a positive integer or constant." ),
     NUM_SEQ_TO_ALLOCATE_NOT_SUPPORTED(1216, "42Z16", "Sequence NEXT n VALUES 
FOR is not supported for Sequences with the CYCLE flag" ),
-                    
+
     /** Parser error. (errorcode 06, sqlState 42P) */
-    PARSER_ERROR(601, "42P00", "Syntax error.", Factory.SYTAX_ERROR),
-    MISSING_TOKEN(602, "42P00", "Syntax error.", Factory.SYTAX_ERROR),
-    UNWANTED_TOKEN(603, "42P00", "Syntax error.", Factory.SYTAX_ERROR),
-    MISMATCHED_TOKEN(604, "42P00", "Syntax error.", Factory.SYTAX_ERROR),
-    UNKNOWN_FUNCTION(605, "42P00", "Syntax error.", Factory.SYTAX_ERROR),
-    
+    PARSER_ERROR(601, "42P00", "Syntax error.", Factory.SYNTAX_ERROR),
+    MISSING_TOKEN(602, "42P00", "Syntax error.", Factory.SYNTAX_ERROR),
+    UNWANTED_TOKEN(603, "42P00", "Syntax error.", Factory.SYNTAX_ERROR),
+    MISMATCHED_TOKEN(604, "42P00", "Syntax error.", Factory.SYNTAX_ERROR),
+    UNKNOWN_FUNCTION(605, "42P00", "Syntax error.", Factory.SYNTAX_ERROR),
+
     /**
-     * Implementation defined class. Execution exceptions (errorcode 11, 
sqlstate XCL). 
+     * Implementation defined class. Execution exceptions (errorcode 11, 
sqlstate XCL).
      */
     RESULTSET_CLOSED(1101, "XCL01", "ResultSet is closed."),
     GET_TABLE_REGIONS_FAIL(1102, "XCL02", "Cannot get all table regions"),
@@ -316,7 +314,7 @@ public enum SQLExceptionCode {
     }),
     CANNOT_SPLIT_LOCAL_INDEX(1109,"XCL09", "Local index may not be pre-split"),
     CANNOT_SALT_LOCAL_INDEX(1110,"XCL10", "Local index may not be salted"),
-    
+
     /**
      * Implementation defined class. Phoenix internal error. (errorcode 20, 
sqlstate INT).
      */
@@ -359,7 +357,7 @@ public enum SQLExceptionCode {
     private final Factory factory;
 
     private SQLExceptionCode(int errorCode, String sqlState, String message) {
-        this(errorCode, sqlState, message, Factory.DEFAULTY);
+        this(errorCode, sqlState, message, Factory.DEFAULT);
     }
 
     private SQLExceptionCode(int errorCode, String sqlState, String message, 
Factory factory) {
@@ -391,7 +389,7 @@ public enum SQLExceptionCode {
     }
 
     public static interface Factory {
-        public static final Factory DEFAULTY = new Factory() {
+        public static final Factory DEFAULT = new Factory() {
 
             @Override
             public SQLException newException(SQLExceptionInfo info) {
@@ -399,7 +397,7 @@ public enum SQLExceptionCode {
             }
             
         };
-        public static final Factory SYTAX_ERROR = new Factory() {
+        public static final Factory SYNTAX_ERROR = new Factory() {
 
             @Override
             public SQLException newException(SQLExceptionInfo info) {

http://git-wip-us.apache.org/repos/asf/phoenix/blob/28007f80/phoenix-core/src/main/java/org/apache/phoenix/parse/ColumnDef.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/parse/ColumnDef.java 
b/phoenix-core/src/main/java/org/apache/phoenix/parse/ColumnDef.java
index ebee43b..278b4aa 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/parse/ColumnDef.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/parse/ColumnDef.java
@@ -22,12 +22,9 @@ import java.sql.SQLException;
 import org.apache.phoenix.exception.SQLExceptionCode;
 import org.apache.phoenix.exception.SQLExceptionInfo;
 import org.apache.phoenix.schema.SortOrder;
-import org.apache.phoenix.schema.types.PBinary;
-import org.apache.phoenix.schema.types.PChar;
 import org.apache.phoenix.schema.types.PDataType;
 import org.apache.phoenix.schema.types.PDecimal;
 import org.apache.phoenix.schema.types.PVarbinary;
-import org.apache.phoenix.schema.types.PVarchar;
 import org.apache.phoenix.util.SchemaUtil;
 
 import com.google.common.base.Preconditions;
@@ -42,7 +39,7 @@ import com.google.common.base.Preconditions;
  */
 public class ColumnDef {
     private final ColumnName columnDefName;
-    private PDataType dataType;
+    private final PDataType dataType;
     private final Boolean isNull;
     private final Integer maxLength;
     private final Integer scale;
@@ -52,98 +49,91 @@ public class ColumnDef {
     private final Integer arrSize;
     private final String expressionStr;
     private final boolean isRowTimestamp;
- 
+
     ColumnDef(ColumnName columnDefName, String sqlTypeName, boolean isArray, 
Integer arrSize, Boolean isNull, Integer maxLength,
-                           Integer scale, boolean isPK, SortOrder sortOrder, 
String expressionStr, boolean isRowTimestamp) {
-        try {
-         Preconditions.checkNotNull(sortOrder);
-            PDataType localType = null;
-         this.columnDefName = columnDefName;
-         this.isArray = isArray;
-         // TODO : Add correctness check for arrSize.  Should this be ignored 
as in postgres
-         // Also add what is the limit that we would support.  Are we going to 
support a
-         //  fixed size or like postgres allow infinite.  May be the data 
types max limit can 
-         // be used for the array size (May be too big)
-         if(this.isArray) {
-                localType = sqlTypeName == null ? null : 
PDataType.fromTypeId(PDataType.sqlArrayType(SchemaUtil.normalizeIdentifier(sqlTypeName)));
-                this.dataType = sqlTypeName == null ? null : 
PDataType.fromSqlTypeName(SchemaUtil.normalizeIdentifier(sqlTypeName));
-             this.arrSize = arrSize; // Can only be non negative based on 
parsing
-             if (this.dataType == PVarbinary.INSTANCE) {
-                 throw new 
SQLExceptionInfo.Builder(SQLExceptionCode.VARBINARY_ARRAY_NOT_SUPPORTED)
-                 
.setColumnName(columnDefName.getColumnName()).build().buildException();
-             }
-         } else {
-             this.dataType = sqlTypeName == null ? null : 
PDataType.fromSqlTypeName(SchemaUtil.normalizeIdentifier(sqlTypeName));
-             this.arrSize = null;
-         }
-         
-         this.isNull = isNull;
-         if (this.dataType == PChar.INSTANCE) {
-             if (maxLength == null) {
-                 throw new 
SQLExceptionInfo.Builder(SQLExceptionCode.MISSING_CHAR_LENGTH)
-                     
.setColumnName(columnDefName.getColumnName()).build().buildException();
-             }
-             if (maxLength < 1) {
-                 throw new 
SQLExceptionInfo.Builder(SQLExceptionCode.NONPOSITIVE_CHAR_LENGTH)
-                     
.setColumnName(columnDefName.getColumnName()).build().buildException();
-             }
-             scale = null;
-         } else if (this.dataType == PVarchar.INSTANCE) {
-             if (maxLength != null && maxLength < 1) {
-                 throw new 
SQLExceptionInfo.Builder(SQLExceptionCode.NONPOSITIVE_CHAR_LENGTH)
-                     
.setColumnName(columnDefName.getColumnName()).build().buildException(); 
-             }
-             scale = null;
-         } else if (this.dataType == PDecimal.INSTANCE) {
-             // for deciaml, 1 <= maxLength <= PDataType.MAX_PRECISION;
-             if (maxLength != null) {
-                 if (maxLength < 1 || maxLength > PDataType.MAX_PRECISION) {
-                     throw new 
SQLExceptionInfo.Builder(SQLExceptionCode.DECIMAL_PRECISION_OUT_OF_RANGE)
-                         
.setColumnName(columnDefName.getColumnName()).build().buildException();
-                 }
-                 // When a precision is specified and a scale is not 
specified, it is set to 0. 
-                 // 
-                 // This is the standard as specified in
-                 // 
http://docs.oracle.com/cd/B28359_01/server.111/b28318/datatype.htm#CNCPT1832
-                 // and 
-                 // 
http://docs.oracle.com/javadb/10.6.2.1/ref/rrefsqlj15260.html.
-                 // Otherwise, if scale is bigger than maxLength, just set it 
to the maxLength;
-                 //
-                 // When neither a precision nor a scale is specified, the 
precision and scale is
-                 // ignored. All decimal are stored with as much decimal 
points as possible.
-                 scale = scale == null ? PDataType.DEFAULT_SCALE : scale > 
maxLength ? maxLength : scale; 
-             }
-         } else if (this.dataType == PBinary.INSTANCE) {
-             if (maxLength == null) {
-                 throw new 
SQLExceptionInfo.Builder(SQLExceptionCode.MISSING_BINARY_LENGTH)
-                     
.setColumnName(columnDefName.getColumnName()).build().buildException();
-             }
-             if (maxLength < 1) {
-                 throw new 
SQLExceptionInfo.Builder(SQLExceptionCode.NONPOSITIVE_BINARY_LENGTH)
-                     
.setColumnName(columnDefName.getColumnName()).build().buildException();
-             }
-             scale = null;
-         } else {
-             // ignore maxLength and scale for other types.
-             maxLength = null;
-             scale = null;
-         }
-         this.maxLength = maxLength;
-         this.scale = scale;
-         this.isPK = isPK;
-         this.sortOrder = sortOrder;
-         if(this.isArray) {
-             this.dataType = localType;
-         }
-         this.expressionStr = expressionStr;
-         this.isRowTimestamp = isRowTimestamp;
-     } catch (SQLException e) {
-         throw new ParseException(e);
-     }
+            Integer scale, boolean isPK, SortOrder sortOrder, String 
expressionStr, boolean isRowTimestamp) {
+        try {
+            Preconditions.checkNotNull(sortOrder);
+            PDataType baseType;
+            PDataType dataType;
+            this.columnDefName = columnDefName;
+            // TODO : Add correctness check for arrSize.  Should this be 
ignored as in postgres
+            // Also add what is the limit that we would support.  Are we going 
to support a
+            //  fixed size or like postgres allow infinite.  May be the data 
types max limit can 
+            // be used for the array size (May be too big)
+            if (isArray) {
+                this.isArray = true;
+                dataType = sqlTypeName == null ? null : 
PDataType.fromTypeId(PDataType.sqlArrayType(SchemaUtil.normalizeIdentifier(sqlTypeName)));
+                baseType = sqlTypeName == null ? null : 
PDataType.fromSqlTypeName(SchemaUtil.normalizeIdentifier(sqlTypeName));
+                this.arrSize = arrSize; // Can only be non negative based on 
parsing
+                if (baseType == PVarbinary.INSTANCE) {
+                    throw new 
SQLExceptionInfo.Builder(SQLExceptionCode.VARBINARY_ARRAY_NOT_SUPPORTED)
+                    
.setColumnName(columnDefName.getColumnName()).build().buildException();
+                }
+            } else {
+                baseType = dataType = sqlTypeName == null ? null : 
PDataType.fromSqlTypeName(SchemaUtil.normalizeIdentifier(sqlTypeName));
+                if (this.isArray = dataType != null && dataType.isArrayType()) 
{
+                    baseType = PDataType.arrayBaseType(dataType);
+                }
+                this.arrSize = null;
+            }
+
+            this.isNull = isNull;
+            if (baseType == PDecimal.INSTANCE) {
+                // for deciaml, 1 <= maxLength <= PDataType.MAX_PRECISION;
+                if (maxLength == null) {
+                    scale = null;
+                } else {
+                    if (maxLength < 1 || maxLength > PDataType.MAX_PRECISION) {
+                        throw new 
SQLExceptionInfo.Builder(SQLExceptionCode.DECIMAL_PRECISION_OUT_OF_RANGE)
+                        
.setColumnName(columnDefName.getColumnName()).build().buildException();
+                    }
+                    // When a precision is specified and a scale is not 
specified, it is set to 0. 
+                    // 
+                    // This is the standard as specified in
+                    // 
http://docs.oracle.com/cd/B28359_01/server.111/b28318/datatype.htm#CNCPT1832
+                    // and 
+                    // 
http://docs.oracle.com/javadb/10.6.2.1/ref/rrefsqlj15260.html.
+                    // Otherwise, if scale is bigger than maxLength, just set 
it to the maxLength;
+                    //
+                    // When neither a precision nor a scale is specified, the 
precision and scale is
+                    // ignored. All decimal are stored with as much decimal 
points as possible.
+                    scale = scale == null ? PDataType.DEFAULT_SCALE : scale > 
maxLength ? maxLength : scale; 
+                }
+            } else {
+                if (maxLength != null && maxLength < 1) {
+                    throw new 
SQLExceptionInfo.Builder(SQLExceptionCode.NONPOSITIVE_MAX_LENGTH)
+                    
.setColumnName(columnDefName.getColumnName()).build().buildException();
+                }
+                scale = null;
+                if (baseType == null) {
+                    maxLength = null;
+                } else if (baseType.isFixedWidth()) {
+                    if (baseType.getByteSize() == null) {
+                        if (maxLength == null) {
+                            throw new 
SQLExceptionInfo.Builder(SQLExceptionCode.MISSING_MAX_LENGTH)
+                            
.setColumnName(columnDefName.getColumnName()).build().buildException();
+                        }
+                    } else {
+                        maxLength = null;
+                    }
+                }
+            }
+            this.maxLength = maxLength;
+            this.scale = scale;
+            this.isPK = isPK;
+            this.sortOrder = sortOrder;
+            this.dataType = dataType;
+            this.expressionStr = expressionStr;
+            this.isRowTimestamp = isRowTimestamp;
+        } catch (SQLException e) {
+            throw new ParseException(e);
+        }
     }
+
     ColumnDef(ColumnName columnDefName, String sqlTypeName, Boolean isNull, 
Integer maxLength,
             Integer scale, boolean isPK, SortOrder sortOrder, String 
expressionStr, boolean isRowTimestamp) {
-       this(columnDefName, sqlTypeName, false, 0, isNull, maxLength, scale, 
isPK, sortOrder, expressionStr, isRowTimestamp);
+        this(columnDefName, sqlTypeName, false, 0, isNull, maxLength, scale, 
isPK, sortOrder, expressionStr, isRowTimestamp);
     }
 
     public ColumnName getColumnDefName() {
@@ -175,45 +165,45 @@ public class ColumnDef {
     public boolean isPK() {
         return isPK;
     }
-    
+
     public SortOrder getSortOrder() {
-       return sortOrder;
+        return sortOrder;
+    }
+
+    public boolean isArray() {
+        return isArray;
     }
-        
-       public boolean isArray() {
-               return isArray;
-       }
-
-       public Integer getArraySize() {
-               return arrSize;
-       }
-
-       public String getExpression() {
-               return expressionStr;
-       }
-       
-       public boolean isRowTimestamp() {
-           return isRowTimestamp;
-       }
-       @Override
+
+    public Integer getArraySize() {
+        return arrSize;
+    }
+
+    public String getExpression() {
+        return expressionStr;
+    }
+
+    public boolean isRowTimestamp() {
+        return isRowTimestamp;
+    }
+    @Override
     public String toString() {
-           StringBuilder buf = new 
StringBuilder(columnDefName.getColumnNode().toString());
-           buf.append(' ');
+        StringBuilder buf = new 
StringBuilder(columnDefName.getColumnNode().toString());
+        buf.append(' ');
         buf.append(dataType.getSqlTypeName());
         if (maxLength != null) {
             buf.append('(');
             buf.append(maxLength);
             if (scale != null) {
-              buf.append(',');
-              buf.append(scale); // has both max length and scale. For ex- 
decimal(10,2)
+                buf.append(',');
+                buf.append(scale); // has both max length and scale. For ex- 
decimal(10,2)
             }       
             buf.append(')');
-       }
+        }
         if (isArray) {
             buf.append(' ');
             buf.append(PDataType.ARRAY_TYPE_SUFFIX);
             buf.append(' ');
         }
-           return buf.toString();
-       }
-}
+        return buf.toString();
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/phoenix/blob/28007f80/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryOptimizerTest.java
----------------------------------------------------------------------
diff --git 
a/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryOptimizerTest.java 
b/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryOptimizerTest.java
index cd51683..e528d3b 100644
--- 
a/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryOptimizerTest.java
+++ 
b/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryOptimizerTest.java
@@ -38,6 +38,7 @@ import org.apache.phoenix.jdbc.PhoenixPreparedStatement;
 import org.apache.phoenix.jdbc.PhoenixStatement;
 import org.apache.phoenix.query.BaseConnectionlessQueryTest;
 import org.apache.phoenix.query.QueryConstants;
+import org.apache.phoenix.schema.PColumn;
 import org.apache.phoenix.schema.PTableType;
 import org.apache.phoenix.util.PhoenixRuntime;
 import org.apache.phoenix.util.QueryUtil;
@@ -390,6 +391,22 @@ public class QueryOptimizerTest extends 
BaseConnectionlessQueryTest {
         assertEquals("CLIENT PARALLEL 1-WAY SKIP SCAN ON 15 KEYS OVER 
INDEX_TEST_TABLE_INDEX_F ['1','1111'] - ['5','3333']", 
QueryUtil.getExplainPlan(rs));
     }
 
+    @Test
+    public void testCharArrayLength() throws Exception {
+        Connection conn = DriverManager.getConnection(getUrl());
+        conn.createStatement().execute(
+                "CREATE TABLE TEST.TEST (testInt INTEGER, testCharArray 
CHAR(3)[], testByteArray BINARY(7)[], " +
+                "CONSTRAINT test_pk PRIMARY KEY(testInt)) 
DEFAULT_COLUMN_FAMILY='T'");
+        conn.createStatement().execute("CREATE INDEX TEST_INDEX ON TEST.TEST 
(testInt) INCLUDE (testCharArray, testByteArray)");
+        PhoenixStatement stmt = 
conn.createStatement().unwrap(PhoenixStatement.class);
+
+        QueryPlan plan = stmt.optimizeQuery("SELECT /*+ INDEX(TEST.TEST 
TEST_INDEX)*/ testCharArray,testByteArray FROM TEST.TEST");
+        List<PColumn> columns = plan.getTableRef().getTable().getColumns();
+        assertEquals(3, columns.size());
+        assertEquals(3, columns.get(1).getMaxLength().intValue());
+        assertEquals(7, columns.get(2).getMaxLength().intValue());
+    }
+
     private void testAssertQueryPlanDetails(boolean multitenant, boolean 
useIndex, boolean salted) throws Exception {
         String sql;
         PreparedStatement stmt;

http://git-wip-us.apache.org/repos/asf/phoenix/blob/28007f80/phoenix-core/src/test/java/org/apache/phoenix/parse/QueryParserTest.java
----------------------------------------------------------------------
diff --git 
a/phoenix-core/src/test/java/org/apache/phoenix/parse/QueryParserTest.java 
b/phoenix-core/src/test/java/org/apache/phoenix/parse/QueryParserTest.java
index 63c9e42..5363042 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/parse/QueryParserTest.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/parse/QueryParserTest.java
@@ -376,41 +376,41 @@ public class QueryParserTest {
 
     @Test
     public void testParseCreateTableInlinePrimaryKeyWithOrder() throws 
Exception {
-       for (String order : new String[]{"asc", "desc"}) {
+        for (String order : new String[]{"asc", "desc"}) {
             String s = "create table core.entity_history_archive (id char(15) 
primary key ${o})".replace("${o}", order);
-               CreateTableStatement stmt = (CreateTableStatement)new 
SQLParser((s)).parseStatement();
-               List<ColumnDef> columnDefs = stmt.getColumnDefs();
-               assertEquals(1, columnDefs.size());
-               assertEquals(SortOrder.fromDDLValue(order), 
columnDefs.iterator().next().getSortOrder()); 
-       }
+            CreateTableStatement stmt = (CreateTableStatement)new 
SQLParser((s)).parseStatement();
+            List<ColumnDef> columnDefs = stmt.getColumnDefs();
+            assertEquals(1, columnDefs.size());
+            assertEquals(SortOrder.fromDDLValue(order), 
columnDefs.iterator().next().getSortOrder()); 
+        }
     }
     
     @Test
     public void testParseCreateTableOrderWithoutPrimaryKeyFails() throws 
Exception {
-       for (String order : new String[]{"asc", "desc"}) {
-               String stmt = "create table core.entity_history_archive (id 
varchar(20) ${o})".replace("${o}", order);
-               try {
-                       new SQLParser((stmt)).parseStatement();
-                       fail("Expected parse exception to be thrown");
-               } catch (SQLException e) {
-                       String errorMsg = "ERROR 603 (42P00): Syntax error. 
Unexpected input. Expecting \"RPAREN\", got \"${o}\"".replace("${o}", order);
-                       assertTrue("Expected message to contain \"" + errorMsg 
+ "\" but got \"" + e.getMessage() + "\"", e.getMessage().contains(errorMsg));
-               }
-       }
+        for (String order : new String[]{"asc", "desc"}) {
+            String stmt = "create table core.entity_history_archive (id 
varchar(20) ${o})".replace("${o}", order);
+            try {
+                new SQLParser((stmt)).parseStatement();
+                fail("Expected parse exception to be thrown");
+            } catch (SQLException e) {
+                String errorMsg = "ERROR 603 (42P00): Syntax error. Unexpected 
input. Expecting \"RPAREN\", got \"${o}\"".replace("${o}", order);
+                assertTrue("Expected message to contain \"" + errorMsg + "\" 
but got \"" + e.getMessage() + "\"", e.getMessage().contains(errorMsg));
+            }
+        }
     }
     
     @Test
     public void testParseCreateTablePrimaryKeyConstraintWithOrder() throws 
Exception {
-       for (String order : new String[]{"asc", "desc"}) {
-               String s = "create table core.entity_history_archive (id 
CHAR(15), name VARCHAR(150), constraint pk primary key (id ${o}, name 
${o}))".replace("${o}", order);
-               CreateTableStatement stmt = (CreateTableStatement)new 
SQLParser((s)).parseStatement();
-               PrimaryKeyConstraint pkConstraint = 
stmt.getPrimaryKeyConstraint();
-               List<Pair<ColumnName,SortOrder>> columns = 
pkConstraint.getColumnNames();
-               assertEquals(2, columns.size());
-               for (Pair<ColumnName,SortOrder> pair : columns) {
-                       assertEquals(SortOrder.fromDDLValue(order), 
pkConstraint.getColumnWithSortOrder(pair.getFirst()).getSecond());
-               }               
-       }
+        for (String order : new String[]{"asc", "desc"}) {
+            String s = "create table core.entity_history_archive (id CHAR(15), 
name VARCHAR(150), constraint pk primary key (id ${o}, name 
${o}))".replace("${o}", order);
+            CreateTableStatement stmt = (CreateTableStatement)new 
SQLParser((s)).parseStatement();
+            PrimaryKeyConstraint pkConstraint = stmt.getPrimaryKeyConstraint();
+            List<Pair<ColumnName,SortOrder>> columns = 
pkConstraint.getColumnNames();
+            assertEquals(2, columns.size());
+            for (Pair<ColumnName,SortOrder> pair : columns) {
+                assertEquals(SortOrder.fromDDLValue(order), 
pkConstraint.getColumnWithSortOrder(pair.getFirst()).getSecond());
+            }           
+        }
     }
 
     @Test
@@ -439,30 +439,31 @@ public class QueryParserTest {
     }
 
     @Test
-       public void testCreateSequence() throws Exception {
-               String sql = ((
-                               "create sequence foo.bar\n" + 
-                                               "start with 0\n"        + 
-                                               "increment by 1\n"));
-               parseQuery(sql);
-       }
-       
-       @Test
-       public void testNextValueForSelect() throws Exception {
-               String sql = ((
-                               "select next value for foo.bar \n" + 
-                                               "from 
core.custom_entity_data\n"));                                             
-               parseQuery(sql);
-       }
-       
-       @Test
+    public void testCreateSequence() throws Exception {
+        String sql = ((
+                "create sequence foo.bar\n" + 
+                        "start with 0\n"    + 
+                        "increment by 1\n"));
+        parseQuery(sql);
+    }
+    
+    @Test
+    public void testNextValueForSelect() throws Exception {
+        String sql = ((
+                "select next value for foo.bar \n" + 
+                        "from core.custom_entity_data\n"));                    
 
+        parseQuery(sql);
+    }
+    
+    @Test
     public void testNextValueForWhere() throws Exception {
         String sql = ((
                 "upsert into core.custom_entity_data\n" + 
                         "select next value for foo.bar from 
core.custom_entity_data\n"));                    
         parseQuery(sql);
     }
-       
+
+    @Test
     public void testBadCharDef() throws Exception {
         try {
             String sql = ("CREATE TABLE IF NOT EXISTS testBadVarcharDef" + 
@@ -470,7 +471,7 @@ public class QueryParserTest {
             parseQuery(sql);
             fail("Should have caught bad char definition.");
         } catch (SQLException e) {
-            assertTrue(e.getMessage(), e.getMessage().contains("ERROR 208 
(22003): CHAR or VARCHAR must have a positive length. columnName=COL"));
+            
assertEquals(SQLExceptionCode.NONPOSITIVE_MAX_LENGTH.getErrorCode(), 
e.getErrorCode());
         }
         try {
             String sql = ("CREATE TABLE IF NOT EXISTS testBadVarcharDef" + 
@@ -478,7 +479,7 @@ public class QueryParserTest {
             parseQuery(sql);
             fail("Should have caught bad char definition.");
         } catch (SQLException e) {
-            assertTrue(e.getMessage(), e.getMessage().contains("ERROR 207 
(22003): Missing length for CHAR. columnName=COL"));
+            assertEquals(SQLExceptionCode.MISSING_MAX_LENGTH.getErrorCode(), 
e.getErrorCode());
         }
     }
 
@@ -490,7 +491,7 @@ public class QueryParserTest {
             parseQuery(sql);
             fail("Should have caught bad varchar definition.");
         } catch (SQLException e) {
-            assertTrue(e.getMessage(), e.getMessage().contains("ERROR 208 
(22003): CHAR or VARCHAR must have a positive length. columnName=COL"));
+            
assertEquals(SQLExceptionCode.NONPOSITIVE_MAX_LENGTH.getErrorCode(), 
e.getErrorCode());
         }
     }
 
@@ -522,7 +523,7 @@ public class QueryParserTest {
             parseQuery(sql);
             fail("Should have caught bad binary definition.");
         } catch (SQLException e) {
-            assertTrue(e.getMessage(), e.getMessage().contains("ERROR 211 
(22003): BINARY must have a positive length. columnName=COL"));
+            
assertEquals(SQLExceptionCode.NONPOSITIVE_MAX_LENGTH.getErrorCode(), 
e.getErrorCode());
         }
         try {
             String sql = ("CREATE TABLE IF NOT EXISTS testBadVarcharDef" + 
@@ -530,7 +531,7 @@ public class QueryParserTest {
             parseQuery(sql);
             fail("Should have caught bad char definition.");
         } catch (SQLException e) {
-            assertTrue(e.getMessage(), e.getMessage().contains("ERROR 210 
(22003): Missing length for BINARY. columnName=COL"));
+            assertEquals(SQLExceptionCode.MISSING_MAX_LENGTH.getErrorCode(), 
e.getErrorCode());
         }
     }
 

Reply via email to