/**
  * Encode the primary key values from the table as a byte array. The values must be in the same
  * order as the primary key constraint. If the connection and table are both tenant-specific, the
  * tenant ID column must not be present in the values.
  *
  * @param conn an open connection
  * @param fullTableName the full table name
  * @param values the values of the primary key columns ordered in the same order as the primary
  *     key constraint
  * @return the encoded byte array
  * @throws SQLException if the table cannot be found or the incorrect number of of values are
  *     provided
  * @see #decodePK(Connection, String, byte[]) to decode the byte[] back to the values
  */
 public static byte[] encodePK(Connection conn, String fullTableName, Object[] values)
     throws SQLException {
   PTable table = getTable(conn, fullTableName);
   PhoenixConnection pconn = conn.unwrap(PhoenixConnection.class);
   int offset =
       (table.getBucketNum() == null ? 0 : 1)
           + (table.isMultiTenant() && pconn.getTenantId() != null ? 1 : 0);
   List<PColumn> pkColumns = table.getPKColumns();
   if (pkColumns.size() - offset != values.length) {
     throw new SQLException(
         "Expected " + (pkColumns.size() - offset) + " but got " + values.length);
   }
   PDataType type = null;
   TrustedByteArrayOutputStream output =
       new TrustedByteArrayOutputStream(table.getRowKeySchema().getEstimatedValueLength());
   try {
     for (int i = offset; i < pkColumns.size(); i++) {
       if (type != null && !type.isFixedWidth()) {
         output.write(QueryConstants.SEPARATOR_BYTE);
       }
       type = pkColumns.get(i).getDataType();
       byte[] value = type.toBytes(values[i - offset]);
       output.write(value);
     }
     return output.toByteArray();
   } finally {
     try {
       output.close();
     } catch (IOException e) {
       throw new RuntimeException(e); // Impossible
     }
   }
 }
 private void evaluateAndAssertResult(
     Expression expression, Object expectedResult, String context) {
   context = context == null ? "" : context;
   ImmutableBytesWritable ptr = new ImmutableBytesWritable();
   assertTrue(expression.evaluate(null, ptr));
   PDataType dataType = expression.getDataType();
   SortOrder sortOrder = expression.getSortOrder();
   Object result =
       dataType.toObject(ptr.get(), ptr.getOffset(), ptr.getLength(), dataType, sortOrder);
   assertEquals(context, expectedResult, result);
 }
 @Override
 public int hashCode() {
   final int prime = 31;
   int result = 1;
   result = prime * result + (isNullable() ? 1231 : 1237);
   Integer maxLength = this.getMaxLength();
   result = prime * result + ((maxLength == null) ? 0 : maxLength.hashCode());
   PDataType type = this.getDataType();
   result = prime * result + ((type == null) ? 0 : type.hashCode());
   return result;
 }
 @Override
 public final Object getValue(Tuple tuple, PDataType type, ImmutableBytesWritable ptr)
     throws SQLException {
   try {
     Expression expression = getExpression();
     if (!expression.evaluate(tuple, ptr)) {
       return null;
     }
     if (ptr.getLength() == 0) {
       return null;
     }
     return type.toObject(
         ptr,
         expression.getDataType(),
         expression.getSortOrder(),
         expression.getMaxLength(),
         expression.getScale());
   } catch (RuntimeException e) {
     // FIXME: Expression.evaluate does not throw SQLException
     // so this will unwrap throws from that.
     if (e.getCause() instanceof SQLException) {
       throw (SQLException) e.getCause();
     }
     throw e;
   }
 }
 public static Expression convertToRoundExpressionIfNeeded(
     PDataType fromDataType, PDataType targetDataType, List<Expression> expressions)
     throws SQLException {
   Expression firstChildExpr = expressions.get(0);
   if (fromDataType == targetDataType) {
     return firstChildExpr;
   } else if (fromDataType == PDataType.DECIMAL && targetDataType.isCoercibleTo(PDataType.LONG)) {
     return new RoundDecimalExpression(expressions);
   } else if ((fromDataType == PDataType.TIMESTAMP || fromDataType == PDataType.UNSIGNED_TIMESTAMP)
       && targetDataType.isCoercibleTo(PDataType.DATE)) {
     return RoundTimestampExpression.create(expressions);
   } else if (!fromDataType.isCoercibleTo(targetDataType)) {
     throw TypeMismatchException.newException(
         fromDataType, targetDataType, firstChildExpr.toString());
   }
   return firstChildExpr;
 }
 private void appendPKColumnValue(StringBuilder buf, byte[] range, Boolean isNull, int slotIndex) {
   if (Boolean.TRUE.equals(isNull)) {
     buf.append("null");
     return;
   }
   if (Boolean.FALSE.equals(isNull)) {
     buf.append("not null");
     return;
   }
   if (range.length == 0) {
     buf.append('*');
     return;
   }
   ScanRanges scanRanges = context.getScanRanges();
   PDataType type = scanRanges.getSchema().getField(slotIndex).getDataType();
   ColumnModifier modifier = tableRef.getTable().getPKColumns().get(slotIndex).getColumnModifier();
   if (modifier != null) {
     buf.append('~');
     range = modifier.apply(range, 0, new byte[range.length], 0, range.length);
   }
   Format formatter = context.getConnection().getFormatter(type);
   buf.append(type.toStringLiteral(range, formatter));
 }
 @Override
 public void readFields(DataInput input) throws IOException {
   // read/write type ordinal, maxLength presence, scale presence and isNullable bit together to
   // save space
   int typeAndFlag = WritableUtils.readVInt(input);
   isNullable = (typeAndFlag & 0x01) != 0;
   if ((typeAndFlag & 0x02) != 0) {
     scale = WritableUtils.readVInt(input);
   }
   if ((typeAndFlag & 0x04) != 0) {
     maxLength = WritableUtils.readVInt(input);
   }
   type = PDataType.values()[typeAndFlag >>> 3];
   sortOrder = SortOrder.fromSystemValue(WritableUtils.readVInt(input));
 }
 @Override
 public void write(DataOutput output) throws IOException {
   // read/write type ordinal, maxLength presence, scale presence and isNullable bit together to
   // save space
   int typeAndFlag =
       (isNullable ? 1 : 0)
           | ((scale != null ? 1 : 0) << 1)
           | ((maxLength != null ? 1 : 0) << 2)
           | (type.ordinal() << 3);
   WritableUtils.writeVInt(output, typeAndFlag);
   if (scale != null) {
     WritableUtils.writeVInt(output, scale);
   }
   if (maxLength != null) {
     WritableUtils.writeVInt(output, maxLength);
   }
   WritableUtils.writeVInt(output, sortOrder.getSystemValue());
 }
 @Override
 public boolean evaluate(Tuple tuple, ImmutableBytesWritable ptr) {
   tuple.getKey(ptr);
   int offset = accessor.getOffset(ptr.get(), ptr.getOffset());
   // Null is represented in the last expression of a multi-part key
   // by the bytes not being present.
   int maxOffset = ptr.getOffset() + ptr.getLength();
   if (offset < maxOffset) {
     byte[] buffer = ptr.get();
     int fixedByteSize = -1;
     // FIXME: fixedByteSize <= maxByteSize ? fixedByteSize : 0 required because HBase passes bogus
     // keys to filter to position scan (HBASE-6562)
     if (fromType.isFixedWidth()) {
       fixedByteSize = getByteSize();
       fixedByteSize = fixedByteSize <= maxOffset ? fixedByteSize : 0;
     }
     int length =
         fixedByteSize >= 0 ? fixedByteSize : accessor.getLength(buffer, offset, maxOffset);
     // In the middle of the key, an empty variable length byte array represents null
     if (length > 0) {
       /*
       if (type == fromType) {
           ptr.set(buffer,offset,length);
       } else {
           ptr.set(type.toBytes(type.toObject(buffer, offset, length, fromType)));
       }
       */
       ptr.set(buffer, offset, length);
       type.coerceBytes(ptr, fromType, getSortOrder(), getSortOrder());
     } else {
       ptr.set(ByteUtil.EMPTY_BYTE_ARRAY);
     }
     return true;
   }
   return false;
 }
 CastParseNode(ParseNode expr, String dataType) {
   super(expr);
   dt = PDataType.fromSqlTypeName(dataType);
 }
 /**
  * Get list of ColumnInfos that contain Column Name and its associated PDataType for an import.
  * The supplied list of columns can be null -- if it is non-null, it represents a user-supplied
  * list of columns to be imported.
  *
  * @param conn Phoenix connection from which metadata will be read
  * @param tableName Phoenix table name whose columns are to be checked. Can include a schema name
  * @param columns user-supplied list of import columns, can be null
  * @param strict if true, an exception will be thrown if unknown columns are supplied
  */
 public static List<ColumnInfo> generateColumnInfo(
     Connection conn, String tableName, List<String> columns, boolean strict) throws SQLException {
   Map<String, Integer> columnNameToTypeMap = Maps.newLinkedHashMap();
   DatabaseMetaData dbmd = conn.getMetaData();
   int unfoundColumnCount = 0;
   // TODO: escape wildcard characters here because we don't want that
   // behavior here
   String escapedTableName = StringUtil.escapeLike(tableName);
   String[] schemaAndTable = escapedTableName.split("\\.");
   ResultSet rs = null;
   try {
     rs =
         dbmd.getColumns(
             null,
             (schemaAndTable.length == 1 ? "" : schemaAndTable[0]),
             (schemaAndTable.length == 1 ? escapedTableName : schemaAndTable[1]),
             null);
     while (rs.next()) {
       String sqlTypeName = rs.getString(QueryUtil.DATA_TYPE_NAME_POSITION);
       columnNameToTypeMap.put(
           rs.getString(QueryUtil.COLUMN_NAME_POSITION),
           PDataType.fromSqlTypeName(sqlTypeName).getSqlType());
     }
     if (columnNameToTypeMap.isEmpty()) {
       throw new IllegalArgumentException("Table " + tableName + " not found");
     }
   } finally {
     if (rs != null) {
       rs.close();
     }
   }
   List<ColumnInfo> columnInfoList = Lists.newArrayList();
   if (columns == null) {
     for (Map.Entry<String, Integer> entry : columnNameToTypeMap.entrySet()) {
       columnInfoList.add(new ColumnInfo(entry.getKey(), entry.getValue()));
     }
   } else {
     // Leave "null" as indication to skip b/c it doesn't exist
     for (int i = 0; i < columns.size(); i++) {
       String columnName = columns.get(i).trim();
       Integer sqlType = columnNameToTypeMap.get(columnName);
       if (sqlType == null) {
         if (strict) {
           throw new SQLExceptionInfo.Builder(SQLExceptionCode.COLUMN_NOT_FOUND)
               .setColumnName(columnName)
               .setTableName(tableName)
               .build()
               .buildException();
         }
         unfoundColumnCount++;
       } else {
         columnInfoList.add(new ColumnInfo(columnName, sqlType));
       }
     }
     if (unfoundColumnCount == columns.size()) {
       throw new SQLExceptionInfo.Builder(SQLExceptionCode.COLUMN_NOT_FOUND)
           .setColumnName(Arrays.toString(columns.toArray(new String[0])))
           .setTableName(tableName)
           .build()
           .buildException();
     }
   }
   return columnInfoList;
 }
 private static String getTypeDisplayString(PDataType type, Integer precision, Integer scale) {
   return type.toString() + "(" + precision + "," + scale + ")";
 }