blob: 8f04c38f06cb7c659d2ab1be333a58daca9bc818 [file] [log] [blame]
/**
* 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.hadoop.vertica;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.sql.Date;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import org.apache.hadoop.io.BooleanWritable;
import org.apache.hadoop.io.ByteWritable;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.DoubleWritable;
import org.apache.hadoop.io.FloatWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.VIntWritable;
import org.apache.hadoop.io.VLongWritable;
import org.apache.hadoop.io.Writable;
import org.apache.hadoop.util.StringUtils;
/**
* Serializable record for records returned from and written to Vertica
*
*/
public class VerticaRecord implements Writable {
ResultSet results = null;
ResultSetMetaData meta = null;
int columns = 0;
List<Integer> types = null;
List<Object> values = null;
List<String> names = null;
boolean dateString;
String delimiter = VerticaConfiguration.DELIMITER;
String terminator = VerticaConfiguration.RECORD_TERMINATER;
DateFormat datefmt = new SimpleDateFormat("yyyyMMdd");
DateFormat timefmt = new SimpleDateFormat("HHmmss");
DateFormat tmstmpfmt = new SimpleDateFormat("yyyyMMddHHmmss");
DateFormat sqlfmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public List<Object> getValues() {
return values;
}
public List<Integer> getTypes() {
return types;
}
/**
* Create a new VerticaRecord class out of a query result set
*
* @param results
* ResultSet returned from running input split query
* @param dateString
* True if dates should be marshaled as strings
* @throws SQLException
*/
VerticaRecord(ResultSet results, boolean dateString) throws SQLException {
this.results = results;
this.dateString = dateString;
meta = results.getMetaData();
columns = meta.getColumnCount();
names = new ArrayList<String>(columns);
types = new ArrayList<Integer>(columns);
values = new ArrayList<Object>(columns);
for (int i = 0; i < columns; i++) {
names.add(meta.getCatalogName(i + 1));
types.add(meta.getColumnType(i + 1));
values.add(null);
}
}
public VerticaRecord() {
this.types = new ArrayList<Integer>();
this.values = new ArrayList<Object>();
}
public VerticaRecord(List<String> names, List<Integer> types) {
this.names = names;
this.types = types;
values = new ArrayList<Object>();
for (@SuppressWarnings("unused")
Integer type : types)
values.add(null);
columns = values.size();
}
public VerticaRecord(List<Object> values, boolean parseTypes) {
this.types = new ArrayList<Integer>();
this.values = values;
columns = values.size();
objectTypes();
}
/**
* Test interface for junit tests that do not require a database
*
* @param types
* @param values
* @param dateString
*/
public VerticaRecord(List<String> names, List<Integer> types,
List<Object> values, boolean dateString) {
this.names = names;
this.types = types;
this.values = values;
this.dateString = dateString;
columns = types.size();
if (types.size() == 0)
objectTypes();
}
public Object get(String name) throws Exception {
if (names == null || names.size() == 0)
throw new Exception("Cannot set record by name if names not initialized");
int i = names.indexOf(name);
return get(i);
}
public Object get(int i) {
if (i >= values.size())
throw new IndexOutOfBoundsException("Index " + i
+ " greater than input size " + values.size());
return values.get(i);
}
public void set(String name, Object value) throws Exception {
if (names == null || names.size() == 0)
throw new Exception("Cannot set record by name if names not initialized");
int i = names.indexOf(name);
set(i, value);
}
/**
* set a value, 0 indexed
*
* @param i
*/
public void set(Integer i, Object value) {
set(i, value, false);
}
/**
* set a value, 0 indexed
*
* @param i
*/
public void set(Integer i, Object value, boolean validate) {
if (i >= values.size())
throw new IndexOutOfBoundsException("Index " + i
+ " greater than input size " + values.size());
if (validate && value != null) {
Integer type = types.get(i);
switch (type) {
case Types.BIGINT:
if (!(value instanceof Long) && !(value instanceof Integer)
&& !(value instanceof Short) && !(value instanceof LongWritable)
&& !(value instanceof VLongWritable)
&& !(value instanceof VIntWritable))
throw new ClassCastException("Cannot cast "
+ value.getClass().getName() + " to Long");
break;
case Types.INTEGER:
if (!(value instanceof Integer) && !(value instanceof Short)
&& !(value instanceof VIntWritable))
throw new ClassCastException("Cannot cast "
+ value.getClass().getName() + " to Integer");
break;
case Types.TINYINT:
case Types.SMALLINT:
if (!(value instanceof Short))
throw new ClassCastException("Cannot cast "
+ value.getClass().getName() + " to Short");
break;
case Types.REAL:
case Types.DECIMAL:
case Types.NUMERIC:
if (!(value instanceof BigDecimal))
throw new ClassCastException("Cannot cast "
+ value.getClass().getName() + " to BigDecimal");
case Types.DOUBLE:
if (!(value instanceof Double) && !(value instanceof Float)
&& !(value instanceof DoubleWritable)
&& !(value instanceof FloatWritable))
throw new ClassCastException("Cannot cast "
+ value.getClass().getName() + " to Double");
break;
case Types.FLOAT:
if (!(value instanceof Float) && !(value instanceof FloatWritable))
throw new ClassCastException("Cannot cast "
+ value.getClass().getName() + " to Float");
break;
case Types.BINARY:
case Types.LONGVARBINARY:
case Types.VARBINARY:
if (!(value instanceof byte[]) && !(value instanceof BytesWritable))
throw new ClassCastException("Cannot cast "
+ value.getClass().getName() + " to byte[]");
break;
case Types.BIT:
case Types.BOOLEAN:
if (!(value instanceof Boolean) && !(value instanceof BooleanWritable)
&& !(value instanceof ByteWritable))
throw new ClassCastException("Cannot cast "
+ value.getClass().getName() + " to Boolean");
break;
case Types.CHAR:
if (!(value instanceof Character) && !(value instanceof String))
throw new ClassCastException("Cannot cast "
+ value.getClass().getName() + " to Character");
break;
case Types.LONGNVARCHAR:
case Types.LONGVARCHAR:
case Types.NCHAR:
case Types.NVARCHAR:
case Types.VARCHAR:
if (!(value instanceof String) && !(value instanceof Text))
throw new ClassCastException("Cannot cast "
+ value.getClass().getName() + " to String");
break;
case Types.DATE:
if (!(value instanceof Date) && !(value instanceof java.util.Date))
throw new ClassCastException("Cannot cast "
+ value.getClass().getName() + " to Date");
break;
case Types.TIME:
if (!(value instanceof Time) && !(value instanceof java.util.Date))
throw new ClassCastException("Cannot cast "
+ value.getClass().getName() + " to Time");
break;
case Types.TIMESTAMP:
if (!(value instanceof Timestamp) && !(value instanceof java.util.Date))
throw new ClassCastException("Cannot cast "
+ value.getClass().getName() + " to Timestamp");
break;
default:
throw new RuntimeException("Unknown type value " + types.get(i));
}
}
values.set(i, value);
}
public boolean next() throws SQLException {
if (results.next()) {
for (int i = 1; i <= columns; i++)
values.set(i - 1, results.getObject(i));
return true;
}
return false;
}
private void objectTypes() {
for (Object obj : values) {
if (obj == null) {
this.types.add(Types.NULL);
} else if (obj instanceof Long) {
this.types.add(Types.BIGINT);
} else if (obj instanceof LongWritable) {
this.types.add(Types.BIGINT);
} else if (obj instanceof VLongWritable) {
this.types.add(Types.BIGINT);
} else if (obj instanceof VIntWritable) {
this.types.add(Types.INTEGER);
} else if (obj instanceof Integer) {
this.types.add(Types.INTEGER);
} else if (obj instanceof Short) {
this.types.add(Types.SMALLINT);
} else if (obj instanceof BigDecimal) {
this.types.add(Types.NUMERIC);
} else if (obj instanceof DoubleWritable) {
this.types.add(Types.DOUBLE);
} else if (obj instanceof Double) {
this.types.add(Types.DOUBLE);
} else if (obj instanceof Float) {
this.types.add(Types.FLOAT);
} else if (obj instanceof FloatWritable) {
this.types.add(Types.FLOAT);
} else if (obj instanceof byte[]) {
this.types.add(Types.BINARY);
} else if (obj instanceof ByteWritable) {
this.types.add(Types.BINARY);
} else if (obj instanceof Boolean) {
this.types.add(Types.BOOLEAN);
} else if (obj instanceof BooleanWritable) {
this.types.add(Types.BOOLEAN);
} else if (obj instanceof Character) {
this.types.add(Types.CHAR);
} else if (obj instanceof String) {
this.types.add(Types.VARCHAR);
} else if (obj instanceof BytesWritable) {
this.types.add(Types.VARCHAR);
} else if (obj instanceof Text) {
this.types.add(Types.VARCHAR);
} else if (obj instanceof java.util.Date) {
this.types.add(Types.DATE);
} else if (obj instanceof Date) {
this.types.add(Types.DATE);
} else if (obj instanceof Time) {
this.types.add(Types.TIME);
} else if (obj instanceof Timestamp) {
this.types.add(Types.TIMESTAMP);
} else {
throw new RuntimeException("Unknown type " + obj.getClass().getName()
+ " passed to Vertica Record");
}
}
}
public String toSQLString() {
return toSQLString(delimiter, terminator);
}
public String toSQLString(String delimiterArg, String terminatorArg) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < columns; i++) {
Object obj = values.get(i);
Integer type = types.get(i);
// switch statement uses fall through to handle type variations
// e.g. type specified as BIGINT but passed in as Integer
switch (type) {
case Types.NULL:
sb.append("");
break;
case Types.BIGINT:
if (obj instanceof Long) {
sb.append(obj.toString());
break;
}
case Types.INTEGER:
if (obj instanceof Integer) {
sb.append(obj.toString());
break;
}
case Types.TINYINT:
case Types.SMALLINT:
if (obj instanceof Short) {
sb.append(obj.toString());
break;
}
if (obj instanceof LongWritable) {
sb.append(((LongWritable) obj).get());
break;
}
if (obj instanceof VLongWritable) {
sb.append(((VLongWritable) obj).get());
break;
}
if (obj instanceof VIntWritable) {
sb.append(((VIntWritable) obj).get());
break;
}
case Types.REAL:
case Types.DECIMAL:
case Types.NUMERIC:
if (obj instanceof BigDecimal) {
sb.append(obj.toString());
break;
}
case Types.DOUBLE:
if (obj instanceof Double) {
sb.append(obj.toString());
break;
}
if (obj instanceof DoubleWritable) {
sb.append(((DoubleWritable) obj).get());
break;
}
case Types.FLOAT:
if (obj instanceof Float) {
sb.append(obj.toString());
break;
}
if (obj instanceof FloatWritable) {
sb.append(((FloatWritable) obj).get());
break;
}
case Types.BINARY:
case Types.LONGVARBINARY:
case Types.VARBINARY:
if(obj == null) sb.append("");
else sb.append(ByteBuffer.wrap((byte[]) obj).asCharBuffer());
break;
case Types.BIT:
case Types.BOOLEAN:
if (obj instanceof Boolean) {
if ((Boolean) obj)
sb.append("true");
else
sb.append("false");
break;
}
if (obj instanceof BooleanWritable) {
if (((BooleanWritable) obj).get())
sb.append("true");
else
sb.append("false");
break;
}
case Types.LONGNVARCHAR:
case Types.LONGVARCHAR:
case Types.NCHAR:
case Types.NVARCHAR:
case Types.VARCHAR:
if (obj instanceof String) {
sb.append((String) obj);
break;
}
if (obj instanceof byte[]) {
sb.append((byte[]) obj);
break;
}
if (obj instanceof BytesWritable) {
sb.append(((BytesWritable) obj).getBytes());
break;
}
case Types.CHAR:
if (obj instanceof Character) {
sb.append((Character) obj);
break;
}
if (obj instanceof ByteWritable) {
sb.append(((ByteWritable) obj).get());
break;
}
case Types.DATE:
case Types.TIME:
case Types.TIMESTAMP:
if (obj instanceof java.util.Date)
sb.append(sqlfmt.format((java.util.Date) obj));
else if (obj instanceof Date)
sb.append(sqlfmt.format((Date) obj));
else if (obj instanceof Time)
sb.append(sqlfmt.format((Time) obj));
else if (obj instanceof Timestamp)
sb.append(sqlfmt.format((Timestamp) obj));
break;
default:
if(obj == null) sb.append("");
else throw new RuntimeException("Unknown type value " + types.get(i));
}
if (i < columns - 1)
sb.append(delimiterArg);
else
sb.append(terminatorArg);
}
return sb.toString();
}
@Override
public void readFields(DataInput in) throws IOException {
columns = in.readInt();
if (types.size() > 0)
types.clear();
for (int i = 0; i < columns; i++)
types.add(in.readInt());
for (int i = 0; i < columns; i++) {
int type = types.get(i);
switch (type) {
case Types.NULL:
values.add(null);
break;
case Types.BIGINT:
values.add(in.readLong());
break;
case Types.INTEGER:
values.add(in.readInt());
break;
case Types.TINYINT:
case Types.SMALLINT:
values.add(in.readShort());
break;
case Types.REAL:
case Types.DECIMAL:
case Types.NUMERIC:
values.add(new BigDecimal(Text.readString(in)));
break;
case Types.DOUBLE:
values.add(in.readDouble());
break;
case Types.FLOAT:
values.add(in.readFloat());
break;
case Types.BINARY:
case Types.LONGVARBINARY:
case Types.VARBINARY:
values.add(StringUtils.hexStringToByte(Text.readString(in)));
break;
case Types.BIT:
case Types.BOOLEAN:
values.add(in.readBoolean());
break;
case Types.CHAR:
values.add(in.readChar());
break;
case Types.LONGNVARCHAR:
case Types.LONGVARCHAR:
case Types.NCHAR:
case Types.NVARCHAR:
case Types.VARCHAR:
values.add(Text.readString(in));
break;
case Types.DATE:
if (dateString)
try {
values.add(new Date(datefmt.parse(Text.readString(in)).getTime()));
} catch (ParseException e) {
throw new IOException(e);
}
else
values.add(new Date(in.readLong()));
break;
case Types.TIME:
if (dateString)
try {
values.add(new Time(timefmt.parse(Text.readString(in)).getTime()));
} catch (ParseException e) {
throw new IOException(e);
}
else
values.add(new Time(in.readLong()));
break;
case Types.TIMESTAMP:
if (dateString)
try {
values.add(new Timestamp(tmstmpfmt.parse(Text.readString(in))
.getTime()));
} catch (ParseException e) {
throw new IOException(e);
}
else
values.add(new Timestamp(in.readLong()));
break;
default:
throw new IOException("Unknown type value " + type);
}
}
}
@Override
public void write(DataOutput out) throws IOException {
out.writeInt(columns);
for (int i = 0; i < columns; i++) {
Object obj = values.get(i);
Integer type = types.get(i);
if(obj == null) out.writeInt(Types.NULL);
else out.writeInt(type);
}
for (int i = 0; i < columns; i++) {
Object obj = values.get(i);
Integer type = types.get(i);
if(obj == null) continue;
switch (type) {
case Types.BIGINT:
out.writeLong((Long) obj);
break;
case Types.INTEGER:
out.writeInt((Integer) obj);
break;
case Types.TINYINT:
case Types.SMALLINT:
out.writeShort((Short) obj);
break;
case Types.REAL:
case Types.DECIMAL:
case Types.NUMERIC:
Text.writeString(out, obj.toString());
break;
case Types.DOUBLE:
out.writeDouble((Double) obj);
break;
case Types.FLOAT:
out.writeFloat((Float) obj);
break;
case Types.BINARY:
case Types.LONGVARBINARY:
case Types.VARBINARY:
Text.writeString(out, StringUtils.byteToHexString((byte[]) obj));
break;
case Types.BIT:
case Types.BOOLEAN:
out.writeBoolean((Boolean) obj);
break;
case Types.CHAR:
out.writeChar((Character) obj);
break;
case Types.LONGNVARCHAR:
case Types.LONGVARCHAR:
case Types.NCHAR:
case Types.NVARCHAR:
case Types.VARCHAR:
Text.writeString(out, (String) obj);
break;
case Types.DATE:
if (obj instanceof java.util.Date) {
if (dateString)
Text.writeString(out, datefmt.format((java.util.Date) obj));
else
out.writeLong(((java.util.Date) obj).getTime());
} else {
if (dateString)
Text.writeString(out, datefmt.format((Date) obj));
else
out.writeLong(((Date) obj).getTime());
}
break;
case Types.TIME:
if (dateString)
Text.writeString(out, timefmt.format((Time) obj));
else
out.writeLong(((Time) obj).getTime());
break;
case Types.TIMESTAMP:
if (dateString)
Text.writeString(out, tmstmpfmt.format((Timestamp) obj));
else
out.writeLong(((Timestamp) obj).getTime());
break;
default:
throw new IOException("Unknown type value " + types.get(i));
}
}
}
}