PHOENIX-2313 TypeMismatchException thrown while querying a table that has an index with a Boolean
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/BaseMutableIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/BaseMutableIndexIT.java
index 68998cf..ea12245 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/BaseMutableIndexIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/BaseMutableIndexIT.java
@@ -118,12 +118,17 @@
             "create " + (localIndex ? "LOCAL" : "") + " index i on t(b)");
         conn1.createStatement().execute("upsert into t values(1,true,'foo')");
         conn1.createStatement().execute("upsert into t values(2,false,'foo')");
+        conn1.createStatement().execute("upsert into t values(3)");
         conn1.commit();
         ResultSet rs = conn1.createStatement().executeQuery("select b from t");
-        rs.next();
-        assertEquals(true, rs.getBoolean(1));
-        rs.next();
+        assertTrue(rs.next());
         assertEquals(false, rs.getBoolean(1));
+        assertTrue(rs.wasNull());
+        assertTrue(rs.next());
+        assertEquals(false, rs.getBoolean(1));
+        assertTrue(rs.next());
+        assertEquals(true, rs.getBoolean(1));
+        assertFalse(rs.next());
     }
 
     @Test
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PBoolean.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PBoolean.java
index 9892426..f90d70b 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PBoolean.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PBoolean.java
@@ -27,122 +27,120 @@
 
 public class PBoolean extends PDataType<Boolean> {
 
-  public static final PBoolean INSTANCE = new PBoolean();
+    public static final PBoolean INSTANCE = new PBoolean();
 
-  private PBoolean() {
-    super("BOOLEAN", Types.BOOLEAN, Boolean.class, null, 21);
-  }
-
-  @Override
-  public byte[] toBytes(Object object) {
-    if (object == null) {
-      // TODO: review - return null?
-      throw newIllegalDataException(this + " may not be null");
+    private PBoolean() {
+        super("BOOLEAN", Types.BOOLEAN, Boolean.class, null, 21);
     }
-    return ((Boolean) object).booleanValue() ? TRUE_BYTES : FALSE_BYTES;
-  }
 
-  @Override
-  public int toBytes(Object object, byte[] bytes, int offset) {
-    if (object == null) {
-      // TODO: review - return null?
-      throw newIllegalDataException(this + " may not be null");
-    }
-    bytes[offset] = ((Boolean) object).booleanValue() ? TRUE_BYTE : FALSE_BYTE;
-    return BOOLEAN_LENGTH;
-  }
-
-  @Override
-  public byte[] toBytes(Object object, SortOrder sortOrder) {
-    if (object == null) {
-      // TODO: review - return null?
-      throw newIllegalDataException(this + " may not be null");
-    }
-    return ((Boolean) object).booleanValue() ^ sortOrder == SortOrder.ASC ?
-        TRUE_BYTES :
-        FALSE_BYTES;
-  }
-
-  @Override
-  public Boolean toObject(byte[] bytes, int offset, int length, PDataType actualType,
-      SortOrder sortOrder, Integer maxLength, Integer scale) {
-    Preconditions.checkNotNull(sortOrder);
-    if (length == 0) {
-      return null;
-    }
-    if (actualType == this) {
-      if (length > 1) {
-        throw newIllegalDataException("BOOLEAN may only be a single byte");
-      }
-      return ((bytes[offset] == FALSE_BYTE ^ sortOrder == SortOrder.DESC) ?
-          Boolean.FALSE :
-          Boolean.TRUE);
-    } else if (actualType == PDecimal.INSTANCE) {
-      // false translated to the ZERO_BYTE
-      return ((bytes[offset] == ZERO_BYTE ^ sortOrder == SortOrder.DESC) ?
-          Boolean.FALSE :
-          Boolean.TRUE);
-    }
-    throwConstraintViolationException(actualType, this);
-    return null;
-  }
-
-  @Override
-  public boolean isCoercibleTo(PDataType targetType) {
-    return super.isCoercibleTo(targetType) || targetType.equals(PBinary.INSTANCE);
-  }
-
-  @Override
-  public boolean isCastableTo(PDataType targetType) {
-    // Allow cast to BOOLEAN so it can be used in an index or group by
-    return super.isCastableTo(targetType) || targetType.equals(PDecimal.INSTANCE);
-  }
-
-  @Override
-  public boolean isFixedWidth() {
-    return true;
-  }
-
-  @Override
-  public Integer getByteSize() {
-    return BOOLEAN_LENGTH;
-  }
-
-  @Override
-  public int estimateByteSize(Object o) {
-    return BOOLEAN_LENGTH;
-  }
-
-  @Override
-  public int compareTo(Object lhs, Object rhs, PDataType rhsType) {
-    return Booleans.compare((Boolean) lhs, (Boolean) rhs);
-  }
-
-  @Override
-  public Object toObject(String value) {
-    return Boolean.parseBoolean(value);
-  }
-
-  @Override
-  public Object toObject(Object object, PDataType actualType) {
-    if (actualType == this || object == null) {
-      return object;
-    }
-    if (actualType == PVarbinary.INSTANCE || actualType == PBinary.INSTANCE) {
-      byte[] bytes = (byte[]) object;
-      return toObject(bytes, 0, bytes.length);
-    }
-        if (actualType == PDecimal.INSTANCE) {
-            return ((BigDecimal) object).equals(BigDecimal.ONE) ? Boolean.TRUE : Boolean.FALSE;
+    @Override
+    public byte[] toBytes(Object object) {
+        if (object == null) {
+            // TODO: review - return null?
+            throw newIllegalDataException(this + " may not be null");
         }
-    return throwConstraintViolationException(actualType, this);
-  }
+        return ((Boolean) object).booleanValue() ? TRUE_BYTES : FALSE_BYTES;
+    }
 
-  @Override
-  public Object getSampleValue(Integer maxLength, Integer arrayLength) {
-    return RANDOM.get().nextBoolean();
-  }
-  
+    @Override
+    public int toBytes(Object object, byte[] bytes, int offset) {
+        if (object == null) {
+            // TODO: review - return null?
+            throw newIllegalDataException(this + " may not be null");
+        }
+        bytes[offset] = ((Boolean) object).booleanValue() ? TRUE_BYTE : FALSE_BYTE;
+        return BOOLEAN_LENGTH;
+    }
+
+    @Override
+    public byte[] toBytes(Object object, SortOrder sortOrder) {
+        if (object == null) {
+            // TODO: review - return null?
+            throw newIllegalDataException(this + " may not be null");
+        }
+        return ((Boolean) object).booleanValue() ^ sortOrder == SortOrder.ASC ?
+                FALSE_BYTES :
+                    TRUE_BYTES;
+    }
+
+    @Override
+    public Boolean toObject(byte[] bytes, int offset, int length, PDataType actualType,
+            SortOrder sortOrder, Integer maxLength, Integer scale) {
+        Preconditions.checkNotNull(sortOrder);
+        if (length == 0) {
+            return null;
+        }
+        if (actualType == this) {
+            if (length > 1) {
+                throw newIllegalDataException("BOOLEAN may only be a single byte");
+            }
+            return ((bytes[offset] == FALSE_BYTE ^ sortOrder == SortOrder.DESC) ?
+                    Boolean.FALSE :
+                        Boolean.TRUE);
+        } else if (actualType == PDecimal.INSTANCE) {
+            // false translated to the ZERO_BYTE
+            return sortOrder == SortOrder.DESC ? SortOrder.invert(bytes[offset]) != ZERO_BYTE : bytes[offset] != ZERO_BYTE;
+        }
+        throwConstraintViolationException(actualType, this);
+        return null;
+    }
+
+    @Override
+    public boolean isCoercibleTo(PDataType targetType) {
+        return super.isCoercibleTo(targetType) || targetType.equals(PBinary.INSTANCE);
+    }
+
+    @Override
+    public boolean isCastableTo(PDataType targetType) {
+        // Allow cast to BOOLEAN so it can be used in an index or group by
+        return super.isCastableTo(targetType) || targetType.equals(PDecimal.INSTANCE);
+    }
+
+    @Override
+    public boolean isFixedWidth() {
+        return true;
+    }
+
+    @Override
+    public Integer getByteSize() {
+        return BOOLEAN_LENGTH;
+    }
+
+    @Override
+    public int estimateByteSize(Object o) {
+        return BOOLEAN_LENGTH;
+    }
+
+    @Override
+    public int compareTo(Object lhs, Object rhs, PDataType rhsType) {
+        return Booleans.compare((Boolean) lhs, (Boolean) rhs);
+    }
+
+    @Override
+    public Object toObject(String value) {
+        return Boolean.parseBoolean(value);
+    }
+
+    @Override
+    public Object toObject(Object object, PDataType actualType) {
+        if (actualType == this || object == null) {
+            return object;
+        }
+        if (actualType == PVarbinary.INSTANCE || actualType == PBinary.INSTANCE) {
+            byte[] bytes = (byte[]) object;
+            return toObject(bytes, 0, bytes.length);
+        }
+        if (actualType == PDecimal.INSTANCE) {
+            return ((BigDecimal) object).equals(BigDecimal.ZERO) ? Boolean.FALSE : Boolean.TRUE;
+        }
+        return throwConstraintViolationException(actualType, this);
+    }
+
+    @Override
+    public Object getSampleValue(Integer maxLength, Integer arrayLength) {
+        return RANDOM.get().nextBoolean();
+    }
+
     @Override
     public PhoenixArrayFactory getArrayFactory() {
         return new PhoenixArrayFactory() {
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/schema/types/PDataTypeTest.java b/phoenix-core/src/test/java/org/apache/phoenix/schema/types/PDataTypeTest.java
index 5657c22..7a04aeb 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/schema/types/PDataTypeTest.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/schema/types/PDataTypeTest.java
@@ -1799,4 +1799,37 @@
             assertTrue(Bytes.compareTo(array, QueryConstants.DESC_SEPARATOR_BYTE_ARRAY) <= 0);
         }
     }
+    
+    @Test
+    public void testBoolean() {
+        byte[] bytes = PBoolean.INSTANCE.toBytes(Boolean.TRUE);
+        assertEquals(1, bytes[0]);
+        bytes = PBoolean.INSTANCE.toBytes(Boolean.FALSE);
+        assertEquals(0, bytes[0]);
+        
+        bytes = PBoolean.INSTANCE.toBytes(Boolean.TRUE, SortOrder.DESC);
+        assertEquals(0, bytes[0]);
+        bytes = PBoolean.INSTANCE.toBytes(Boolean.FALSE, SortOrder.DESC);
+        assertEquals(1, bytes[0]);
+        
+        Object dec = PDecimal.INSTANCE.toObject(Boolean.TRUE, PBoolean.INSTANCE);
+        bytes = PDecimal.INSTANCE.toBytes(dec);
+        Object b = PBoolean.INSTANCE.toObject(bytes, 0, bytes.length, PDecimal.INSTANCE, SortOrder.ASC);
+        assertEquals(true, b);
+        
+        dec = PDecimal.INSTANCE.toObject(Boolean.FALSE, PBoolean.INSTANCE);
+        bytes = PDecimal.INSTANCE.toBytes(dec);
+        b = PBoolean.INSTANCE.toObject(bytes, 0, bytes.length, PDecimal.INSTANCE, SortOrder.ASC);
+        assertEquals(false, b);
+        
+        dec = PDecimal.INSTANCE.toObject(Boolean.TRUE, PBoolean.INSTANCE);
+        bytes = PDecimal.INSTANCE.toBytes(dec, SortOrder.DESC);
+        b = PBoolean.INSTANCE.toObject(bytes, 0, bytes.length, PDecimal.INSTANCE, SortOrder.DESC);
+        assertEquals(true, b);
+        
+        dec = PDecimal.INSTANCE.toObject(Boolean.FALSE, PBoolean.INSTANCE);
+        bytes = PDecimal.INSTANCE.toBytes(dec, SortOrder.DESC);
+        b = PBoolean.INSTANCE.toObject(bytes, 0, bytes.length, PDecimal.INSTANCE, SortOrder.DESC);
+        assertEquals(false, b);
+    }
 }