/** * 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)); } } } }