Mercurial > hg > monetdb-java
changeset 109:e026fe73bb5e embedded
After a lot of suffering, finnaly passed all the tests in a MAPI connection! :) Now I will port some for the embedded connection, as some features are no available on it.
line wrap: on
line diff
--- a/src/main/java/nl/cwi/monetdb/jdbc/MonetConnection.java +++ b/src/main/java/nl/cwi/monetdb/jdbc/MonetConnection.java @@ -47,6 +47,11 @@ public abstract class MonetConnection ex /** The sequence counter */ private static int SeqCounter = 0; + /** + * Gets the current sequence counter. + * + * @return The current sequence counter + */ public static int GetSeqCounter() { return SeqCounter; } @@ -69,6 +74,7 @@ public abstract class MonetConnection ex private Map<String,Class<?>> typeMap = new HashMap<String,Class<?>>() { private static final long serialVersionUID = 1L; { put("inet", MonetINET.class); + put("url", MonetURL.class); } }; @@ -106,6 +112,15 @@ public abstract class MonetConnection ex } /** + * Checks if the conection is embedded or not + * + * @return If the connection is embedded + */ + public boolean isEmbedded() { + return isEmbedded; + } + + /** * Gets the connection's language data. * * @return The connection's language data
--- a/src/main/java/nl/cwi/monetdb/jdbc/MonetINET.java +++ b/src/main/java/nl/cwi/monetdb/jdbc/MonetINET.java @@ -29,6 +29,8 @@ import java.sql.SQLOutput; * This class allows to retrieve the value of this INET as InetAddress. * This is probably meaningful only and only if the netmask is 32. The * getNetmaskBits() method can be used to retrieve the subnet bits. + * + * @author Fabian Groffen */ public class MonetINET implements SQLData {
--- a/src/main/java/nl/cwi/monetdb/jdbc/MonetPreparedStatement.java +++ b/src/main/java/nl/cwi/monetdb/jdbc/MonetPreparedStatement.java @@ -44,23 +44,11 @@ import java.util.Map; * [ "int", 9, 0 ] * </pre> * - * @author Fabian Groffen, Martin van Dinther + * @author Fabian Groffen, Martin van Dinther, Pedro Ferreira * @version 0.4 */ public class MonetPreparedStatement extends MonetStatement implements PreparedStatement { - /* only parse the date patterns once, use multiple times */ - /** Format of a timestamp with RFC822 time zone */ - private static final SimpleDateFormat MTimestampZ = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSZ"); - /** Format of a timestamp */ - private static final SimpleDateFormat MTimestamp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); - /** Format of a time with RFC822 time zone */ - private static final SimpleDateFormat MTimeZ = new SimpleDateFormat("HH:mm:ss.SSSZ"); - /** Format of a time */ - private static final SimpleDateFormat MTime = new SimpleDateFormat("HH:mm:ss.SSS"); - /** Format of a date used by mserver */ - private static final SimpleDateFormat MDate = new SimpleDateFormat("yyyy-MM-dd"); - private final MonetConnection connection; private final String[] monetdbType; private final int[] javaType; @@ -74,6 +62,12 @@ public class MonetPreparedStatement exte private final int rscolcnt; private final String[] values; + private final SimpleDateFormat mTimestampZ; + private final SimpleDateFormat mTimestamp; + private final SimpleDateFormat mTimeZ; + private final SimpleDateFormat mTime; + private final SimpleDateFormat mDate; + /** * MonetPreparedStatement constructor which checks the arguments for validity. A MonetPreparedStatement is backed * by a {@link MonetStatement}, which deals with most of the required stuff of this class. @@ -126,6 +120,12 @@ public class MonetPreparedStatement exte // PreparedStatements are by default poolable poolable = true; + + mTimestampZ = connection.getProtocol().getMonetTimestampTz(); + mTimestamp = connection.getProtocol().getMonetTimestamp(); + mTimeZ = connection.getProtocol().getMonetTimeTz(); + mTime = connection.getProtocol().getMonetTime(); + mDate = connection.getProtocol().getMonetDate(); } //== methods interface PreparedStatement @@ -335,7 +335,7 @@ public class MonetPreparedStatement exte case Types.LONGVARCHAR: return true; default: - return true; + return false; } } @@ -1307,8 +1307,8 @@ public class MonetPreparedStatement exte if (cal == null) { setValue(parameterIndex, "date '" + x.toString() + "'"); } else { - MDate.setTimeZone(cal.getTimeZone()); - setValue(parameterIndex, "date '" + MDate.format(x) + "'"); + mDate.setTimeZone(cal.getTimeZone()); + setValue(parameterIndex, "date '" + mDate.format(x) + "'"); } } @@ -2152,7 +2152,7 @@ public class MonetPreparedStatement exte if (hasTimeZone) { // timezone shouldn't matter, since the server is timezone // aware in this case - String RFC822 = MTimeZ.format(x); + String RFC822 = mTimeZ.format(x); setValue(index, "timetz '" + RFC822.substring(0, 15) + ":" + RFC822.substring(15) + "'"); } else { // server is not timezone aware for this field, and no @@ -2162,8 +2162,8 @@ public class MonetPreparedStatement exte if (cal == null) { setValue(index, "time '" + x.toString() + "'"); } else { - MTime.setTimeZone(cal.getTimeZone()); - setValue(index, "time '" + MTime.format(x) + "'"); + mTime.setTimeZone(cal.getTimeZone()); + setValue(index, "time '" + mTime.format(x) + "'"); } } } @@ -2209,7 +2209,7 @@ public class MonetPreparedStatement exte if (hasTimeZone) { // timezone shouldn't matter, since the server is timezone // aware in this case - String RFC822 = MTimestampZ.format(x); + String RFC822 = mTimestampZ.format(x); setValue(index, "timestamptz '" + RFC822.substring(0, 26) + ":" + RFC822.substring(26) + "'"); } else { // server is not timezone aware for this field, and no @@ -2219,8 +2219,8 @@ public class MonetPreparedStatement exte if (cal == null) { setValue(index, "timestamp '" + x.toString() + "'"); } else { - MTimestamp.setTimeZone(cal.getTimeZone()); - setValue(index, "timestamp '" + MTimestamp.format(x) + "'"); + mTimestamp.setTimeZone(cal.getTimeZone()); + setValue(index, "timestamp '" + mTimestamp.format(x) + "'"); } } } @@ -2263,6 +2263,7 @@ public class MonetPreparedStatement exte @Override public void setURL(int parameterIndex, URL x) throws SQLException { setString(parameterIndex, x.toString()); + values[getParamIdx(parameterIndex)] = "url " + values[getParamIdx(parameterIndex)]; } /**
--- a/src/main/java/nl/cwi/monetdb/jdbc/MonetResultSet.java +++ b/src/main/java/nl/cwi/monetdb/jdbc/MonetResultSet.java @@ -8,8 +8,6 @@ package nl.cwi.monetdb.jdbc; -import nl.cwi.monetdb.mcl.connection.helpers.GregorianCalendarParser; -import nl.cwi.monetdb.mcl.protocol.ProtocolException; import nl.cwi.monetdb.mcl.responses.DataBlockResponse; import nl.cwi.monetdb.mcl.responses.ResultSetResponse; @@ -40,7 +38,6 @@ import java.sql.Statement; import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; -import java.text.ParsePosition; import java.util.*; /** @@ -61,7 +58,7 @@ import java.util.*; * for FORWARD_ONLY result sets the memory usage will be likely lower for large * result sets. * - * @author Fabian Groffen, Martin van Dinther + * @author Fabian Groffen, Martin van Dinther, Pedro Ferreira * @version 0.8 */ public class MonetResultSet extends MonetWrapper implements ResultSet { @@ -707,40 +704,36 @@ public class MonetResultSet extends Mone } // match type specific values switch (JdbcSQLTypes[columnIndex - 1]) { - case Types.BOOLEAN: - return currentBlock.getBooleanValue(columnIndex - 1); - case Types.TINYINT: - return currentBlock.getByteValue(columnIndex - 1) != 0; - case Types.SMALLINT: - return currentBlock.getShortValue(columnIndex - 1) != 0; - case Types.INTEGER: - return currentBlock.getIntValue(columnIndex - 1) != 0; - case Types.BIGINT: - return currentBlock.getLongValue(columnIndex - 1) != 0L; - case Types.REAL: - return currentBlock.getFloatValue(columnIndex - 1) != 0.0f; - case Types.DOUBLE: - return currentBlock.getDoubleValue(columnIndex - 1) != 0.0d; - case Types.CHAR: - case Types.VARCHAR: - case Types.LONGVARCHAR: - case Types.CLOB: - case Types.BLOB: - case Types.LONGVARBINARY: - String val = currentBlock.getValueAsString(columnIndex - 1); - if ("false".equalsIgnoreCase(val) || "0".equals(val)) - return false; - if ("true".equalsIgnoreCase(val) || "1".equals(val)) - return true; - throw newSQLInvalidColumnIndexException(columnIndex); - case Types.NUMERIC: - case Types.DECIMAL: - BigDecimal bigdec = (BigDecimal) currentBlock.getValueAsObject(columnIndex - 1); - return bigdec.compareTo(BigDecimal.ZERO) != 0; - default: //OTHERS, BLOB, LONGVARBINARY, TIME... - throw new SQLException("Conversion from " + types[columnIndex - 1] + - " to boolean type not supported", "M1M05"); - } + case Types.BOOLEAN: + return currentBlock.getBooleanValue(columnIndex - 1); + case Types.TINYINT: + return currentBlock.getByteValue(columnIndex - 1) != 0; + case Types.SMALLINT: + return currentBlock.getShortValue(columnIndex - 1) != 0; + case Types.INTEGER: + return currentBlock.getIntValue(columnIndex - 1) != 0; + case Types.BIGINT: + return currentBlock.getLongValue(columnIndex - 1) != 0L; + case Types.REAL: + return currentBlock.getFloatValue(columnIndex - 1) != 0.0f; + case Types.DOUBLE: + return currentBlock.getDoubleValue(columnIndex - 1) != 0.0d; + case Types.CHAR: + case Types.VARCHAR: + case Types.LONGVARCHAR: + case Types.CLOB: + case Types.BLOB: + case Types.LONGVARBINARY: + String val = currentBlock.getValueAsString(columnIndex - 1); + return !"0".equals(val) && ("1".equals(val) || Boolean.parseBoolean(val)); + case Types.NUMERIC: + case Types.DECIMAL: + BigDecimal bigdec = (BigDecimal) currentBlock.getValueAsObject(columnIndex - 1); + return bigdec.compareTo(BigDecimal.ZERO) != 0; + default: //OTHERS, BLOB, LONGVARBINARY, TIME... + throw new SQLException("Conversion from " + types[columnIndex - 1] + + " to boolean type not supported", "M1M05"); + } } catch (ClassCastException ex) { throw new SQLException(ex.getMessage()); } catch (IndexOutOfBoundsException e) { @@ -1828,7 +1821,7 @@ public class MonetResultSet extends Mone case 3: if ("url".equals(MonetDBType)) { try { - return new URL(val); + return new MonetURL(val); } catch (Exception exc) { // ignore exception and just return the val String object return val; @@ -2504,33 +2497,40 @@ public class MonetResultSet extends Mone if(setLastNullValue(columnIndex - 1)) { return null; } - Calendar res; + Calendar res; + long millis; switch (JdbcSQLTypes[columnIndex - 1]) { - case Types.DATE: + case Types.DATE: + case Types.TIME: + case Types.TIMESTAMP: res = (Calendar) currentBlock.getValueAsObject(columnIndex - 1); + millis = res.getTimeInMillis() - res.getTimeZone().getRawOffset() + cal.getTimeZone().getRawOffset(); break; + case Types.TIME_WITH_TIMEZONE: + case Types.TIMESTAMP_WITH_TIMEZONE: + res = (Calendar) currentBlock.getValueAsObject(columnIndex - 1); + millis = res.getTimeInMillis(); + break; case Types.CHAR: case Types.VARCHAR: case Types.LONGVARCHAR: case Types.CLOB: case Types.BLOB: case Types.LONGVARBINARY: - String value = currentBlock.getValueAsString(columnIndex - 1); - res = GregorianCalendarParser.ParseDate(value, new ParsePosition(0)); + res = currentBlock.getDateValueFromString(this, columnIndex - 1, Types.DATE); + millis = res.getTimeInMillis() - res.getTimeZone().getRawOffset() + cal.getTimeZone().getRawOffset(); break; default: - throw new SQLException("Conversion from " + types[columnIndex - 1] + - " to Date not supported", "M1M05"); + this.addWarning("unsupported data type", "01M03"); + cal.clear(); + millis = 0; } - res.setTimeZone(cal.getTimeZone()); - return new Date(res.getTimeInMillis()); + return new Date(millis); } catch (ClassCastException ex) { throw new SQLException(ex.getMessage()); } catch (IndexOutOfBoundsException e) { throw newSQLInvalidColumnIndexException(columnIndex); - } catch (ProtocolException ep) { - throw new SQLException(ep.getMessage(), "M1M05"); - } + } } /** @@ -2597,13 +2597,18 @@ public class MonetResultSet extends Mone return null; } Calendar res; + long millis; switch (JdbcSQLTypes[columnIndex - 1]) { - case Types.TIME: + case Types.DATE: + case Types.TIME: + case Types.TIMESTAMP: res = (Calendar) currentBlock.getValueAsObject(columnIndex - 1); - res.setTimeZone(cal.getTimeZone()); + millis = res.getTimeInMillis() - res.getTimeZone().getRawOffset() + cal.getTimeZone().getRawOffset(); break; - case Types.TIME_WITH_TIMEZONE: + case Types.TIME_WITH_TIMEZONE: + case Types.TIMESTAMP_WITH_TIMEZONE: res = (Calendar) currentBlock.getValueAsObject(columnIndex - 1); + millis = res.getTimeInMillis(); break; case Types.CHAR: case Types.VARCHAR: @@ -2611,21 +2616,19 @@ public class MonetResultSet extends Mone case Types.CLOB: case Types.BLOB: case Types.LONGVARBINARY: - String value = currentBlock.getValueAsString(columnIndex - 1); - res = GregorianCalendarParser.ParseTime(value, new ParsePosition(0), false); - res.setTimeZone(cal.getTimeZone()); + res = currentBlock.getDateValueFromString(this, columnIndex - 1, Types.TIME); + millis = res.getTimeInMillis() - res.getTimeZone().getRawOffset() + cal.getTimeZone().getRawOffset(); break; default: - throw new SQLException("Conversion from " + types[columnIndex - 1] + - " to Time not supported", "M1M05"); + this.addWarning("unsupported data type", "01M03"); + cal.clear(); + millis = 0; } - return new Time(res.getTimeInMillis()); + return new Time(millis); } catch (ClassCastException ex) { throw new SQLException(ex.getMessage()); } catch (IndexOutOfBoundsException e) { throw newSQLInvalidColumnIndexException(columnIndex); - } catch (ProtocolException ep) { - throw new SQLException(ep.getMessage(), "M1M05"); } } @@ -2693,13 +2696,27 @@ public class MonetResultSet extends Mone return null; } Calendar res; + long millis; + int nanos = 0; switch (JdbcSQLTypes[columnIndex - 1]) { + case Types.DATE: + case Types.TIME: + res = (Calendar) currentBlock.getValueAsObject(columnIndex - 1); + millis = res.getTimeInMillis() - res.getTimeZone().getRawOffset() + cal.getTimeZone().getRawOffset(); + break; + case Types.TIME_WITH_TIMEZONE: + res = (Calendar) currentBlock.getValueAsObject(columnIndex - 1); + millis = res.getTimeInMillis(); + break; case Types.TIMESTAMP: res = (Calendar) currentBlock.getValueAsObject(columnIndex - 1); - res.setTimeZone(cal.getTimeZone()); + nanos = currentBlock.getNanos(columnIndex - 1); + millis = res.getTimeInMillis() - res.getTimeZone().getRawOffset() + cal.getTimeZone().getRawOffset(); break; case Types.TIMESTAMP_WITH_TIMEZONE: res = (Calendar) currentBlock.getValueAsObject(columnIndex - 1); + nanos = currentBlock.getNanos(columnIndex - 1); + millis = res.getTimeInMillis(); break; case Types.CHAR: case Types.VARCHAR: @@ -2707,21 +2724,21 @@ public class MonetResultSet extends Mone case Types.CLOB: case Types.BLOB: case Types.LONGVARBINARY: - String value = currentBlock.getValueAsString(columnIndex - 1); - res = GregorianCalendarParser.ParseTimestamp(value, new ParsePosition(0), false); - res.setTimeZone(cal.getTimeZone()); + res = currentBlock.getDateValueFromString(this, columnIndex - 1, Types.TIMESTAMP); + millis = res.getTimeInMillis() - res.getTimeZone().getRawOffset() + cal.getTimeZone().getRawOffset(); break; default: - throw new SQLException("Conversion from " + types[columnIndex - 1] + - " to Timestamp not supported", "M1M05"); + this.addWarning("unsupported data type", "01M03"); + cal.clear(); + millis = 0; } - return new Timestamp(res.getTimeInMillis()); + Timestamp result = new Timestamp(millis); + result.setNanos(nanos); + return result; } catch (ClassCastException ex) { throw new SQLException(ex.getMessage()); } catch (IndexOutOfBoundsException e) { throw newSQLInvalidColumnIndexException(columnIndex); - } catch (ProtocolException ep) { - throw new SQLException(ep.getMessage(), "M1M05"); } } @@ -2787,6 +2804,7 @@ public class MonetResultSet extends Mone if("url".equals(types[columnIndex - 1])) { return new URL(currentBlock.getValueAsString(columnIndex - 1)); } + break; case Types.CHAR: case Types.VARCHAR: case Types.LONGVARCHAR: @@ -2794,9 +2812,8 @@ public class MonetResultSet extends Mone case Types.BLOB: case Types.LONGVARBINARY: return new URL(currentBlock.getValueAsString(columnIndex - 1)); - default: - throw new SQLException("Cannot convert " + types[columnIndex - 1] + " to an url", "M1M05"); } + throw new SQLException("Cannot convert " + types[columnIndex - 1] + " to an url", "M1M05"); } catch (IndexOutOfBoundsException e) { throw newSQLInvalidColumnIndexException(columnIndex); } catch (MalformedURLException e) { @@ -3016,6 +3033,22 @@ public class MonetResultSet extends Mone return false; } + /** + * Adds a warning to the pile of warnings this ResultSet object has. If + * there were no warnings (or clearWarnings was called) this warning will + * be the first, otherwise this warning will get appended to the current + * warning. + * + * @param reason the warning message + */ + public void addWarning(String reason, String sqlstate) { + if (warnings == null) { + warnings = new SQLWarning(reason, sqlstate); + } else { + warnings.setNextWarning(new SQLWarning(reason, sqlstate)); + } + } + /* the next methods are all related to updateable result sets, which we currently do not support */ @Override public void cancelRowUpdates() throws SQLException {
new file mode 100644 --- /dev/null +++ b/src/main/java/nl/cwi/monetdb/jdbc/MonetURL.java @@ -0,0 +1,77 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Copyright 1997 - July 2008 CWI, August 2008 - 2017 MonetDB B.V. + */ + +package nl.cwi.monetdb.jdbc; + +import java.net.MalformedURLException; +import java.net.URL; +import java.sql.SQLData; +import java.sql.SQLException; +import java.sql.SQLInput; +import java.sql.SQLOutput; + +/** + * The URL class represents the URL datatype in MonetDB. It represents an URL, that is, a well-formed string conforming + * to RFC2396. + * + * @author Fabian Groffen + */ +public class MonetURL implements SQLData { + + public static String FromString(String newurl) throws Exception { + if (newurl == null) { + return null; + } + // if above doesn't fail (throws an Exception), it is fine + new URL(newurl); + return newurl; + } + + private String url; + + public MonetURL(String inet) throws Exception { + this.url = FromString(inet); + } + + @Override + public String getSQLTypeName() { + return "url"; + } + + @Override + public void readSQL(SQLInput stream, String typeName) throws SQLException { + if (typeName.compareTo("url") != 0) + throw new SQLException("can only use this class with 'url' type", "M1M05"); + url = stream.readString(); + } + + @Override + public void writeSQL(SQLOutput stream) throws SQLException { + stream.writeString(url); + } + + @Override + public String toString() { + return url; + } + + public URL getURL() throws SQLException { + if (url == null) + return null; + + try { + return new URL(url); + } catch (MalformedURLException mue) { + throw new SQLException("data is not a valid URL", "M0M27"); + } + } + + public void setURL(URL nurl) throws Exception { + url = nurl.toString(); + } +}
--- a/src/main/java/nl/cwi/monetdb/mcl/connection/helpers/GregorianCalendarParser.java +++ b/src/main/java/nl/cwi/monetdb/mcl/connection/helpers/GregorianCalendarParser.java @@ -8,8 +8,10 @@ package nl.cwi.monetdb.mcl.connection.helpers; +import nl.cwi.monetdb.jdbc.MonetResultSet; import nl.cwi.monetdb.mcl.protocol.ProtocolException; +import java.sql.Types; import java.text.ParsePosition; import java.text.SimpleDateFormat; import java.util.Calendar; @@ -25,21 +27,6 @@ import java.util.TimeZone; public final class GregorianCalendarParser { /** - * The date parser, with intention for re-usage to save memory allocations. - */ - private static final SimpleDateFormat DateParser = new SimpleDateFormat("yyyy-MM-dd"); - - /** - * The time parser, with intention for re-usage to save memory allocations. - */ - private static final SimpleDateFormat TimeParser = new SimpleDateFormat("HH:mm:ss"); - - /** - * The timestamp parser, with intention for re-usage to save memory allocations. - */ - private static final SimpleDateFormat TimestampParser = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - - /** * Small helper method that returns the intrinsic value of a char if it represents a digit. If a non-digit character * is encountered an MCLParseException is thrown. * @@ -57,18 +44,90 @@ public final class GregorianCalendarPars } } + /** The time zone information, to be resused to avoid memory allocations */ + private static final TimeZone DefaultTimeZone = TimeZone.getDefault(); + + /** + * Parses a date or time or timestamp MAPI String into a Java {@link Calendar} instance. + * + * @param mrs A MonetResultSet instance where warning can be added + * @param toParse The date or time or timestamp String to parse + * @param pos The position of the String to start the parsing + * @param parser The parser to use + * @param jdbcType The JDBC type of the column + * @return A {@link Calendar} instance of the parsed date + */ + public static Calendar ParseDateString(MonetResultSet mrs, String toParse, ParsePosition pos, + SimpleDateFormat parser, int jdbcType) { + pos.setIndex(0); + Calendar res = new GregorianCalendar(); + if(jdbcType == Types.TIME || jdbcType == Types.TIMESTAMP || jdbcType == Types.DATE) { + parser.setTimeZone(TimeZone.getTimeZone("GMT" + toParse.substring(toParse.length() - 6))); + } else { + parser.setTimeZone(DefaultTimeZone); + } + + Date aux = parser.parse(toParse, pos); + if (aux == null) { + // parsing failed + int epos = pos.getErrorIndex(); + if (epos == -1) { + mrs.addWarning("parsing '" + toParse + "' failed", "01M10"); + } else if (epos < toParse.length()) { + mrs.addWarning("parsing failed, found: '" + toParse.charAt(epos) + "' in: \"" + toParse + + "\" at pos: " + pos.getErrorIndex(), "01M10"); + } else { + mrs.addWarning("parsing failed, expected more data after '" + toParse + "'", "01M10"); + } + res.clear(); + } else { + res.setTime(aux); + } + + if (jdbcType != Types.DATE) { + // parse additional nanos (if any) + int pos1 = pos.getIndex(), nanos; + char[] monDate = toParse.toCharArray(); + if (pos1 < monDate.length && monDate[pos1] == '.') { + pos1++; + int ctr; + try { + nanos = getIntrinsicValue(monDate[pos1], pos1++); + for (ctr = 1; pos1 < monDate.length && monDate[pos1] >= '0' && monDate[pos1] <= '9'; ctr++) { + if (ctr < 9) { + nanos *= 10; + nanos += (getIntrinsicValue(monDate[pos1], pos1)); + } + if (ctr == 2) // we have three at this point + res.set(Calendar.MILLISECOND, nanos); + pos1++; + } + while (ctr++ < 9) + nanos *= 10; + } catch(ProtocolException e) { + mrs.addWarning(e.getMessage() + " found: '" + monDate[e.getErrorOffset()] + "' in: \"" + + toParse + "\" at pos: " + e.getErrorOffset(), "01M10"); + res.clear(); + } + } + } + return res; + } + /** * Parses a date MAPI String into a Java {@link Calendar} instance. * * @param toParse The date String to parse * @param pos The position of the String to start the parsing + * @param parser The parser to use (date) * @return A {@link Calendar} instance of the parsed date * @throws ProtocolException If the String could not be parsed */ - public static Calendar ParseDate(String toParse, ParsePosition pos) throws ProtocolException { + public static Calendar ParseDate(String toParse, ParsePosition pos, SimpleDateFormat parser) + throws ProtocolException { pos.setIndex(0); Calendar res = new GregorianCalendar(); - Date util = DateParser.parse(toParse, pos); + Date util = parser.parse(toParse, pos); if(util == null) { res.clear(); } else { @@ -80,16 +139,22 @@ public final class GregorianCalendarPars /** * Parses a time or a timestamp MAPI String into a Java {@link Calendar} instance. * - * @param toParse The time/timestamp String to parse + * @param toParse The time String to parse * @param pos The position of the String to start the parsing - * @param hasTimeZone If the time/timestamp String has timezone information - * @param parser The parser to use (time or timestamp) - * @return A {@link Calendar} instance of the parsed time/timestamp + * @param hasTimeZone If the time String has timezone information + * @param parser The parser to use (time) + * @return A {@link Calendar} instance of the parsed time * @throws ProtocolException If the String could not be parsed */ - private static Calendar ParseTimeIn(String toParse, ParsePosition pos, boolean hasTimeZone, SimpleDateFormat parser) + public static Calendar ParseTime(String toParse, ParsePosition pos, SimpleDateFormat parser, boolean hasTimeZone) throws ProtocolException { pos.setIndex(0); + if(hasTimeZone) { // MonetDB/SQL99: Sign TwoDigitHours : Minutes + parser.setTimeZone(TimeZone.getTimeZone("GMT" + toParse.substring(toParse.length() - 6))); + } else { + parser.setTimeZone(DefaultTimeZone); + } + Calendar res = new GregorianCalendar(); Date util = parser.parse(toParse, pos); if(util == null) { @@ -97,9 +162,38 @@ public final class GregorianCalendarPars } else { res.setTime(util); } + return res; + } + + /** + * Parses a timestamp MAPI String into a {@link TimestampHelper} instance. + * + * @param toParse The timestamp String to parse + * @param pos The position of the String to start the parsing + * @param hasTimeZone If the timestamp String has timezone information + * @param parser The parser to use (timestamp) + * @return A {@link TimestampHelper} instance of the parsed timestamp with nanos information + * @throws ProtocolException If the String could not be parsed + */ + public static TimestampHelper ParseTimestamp(String toParse, ParsePosition pos, SimpleDateFormat parser, + boolean hasTimeZone) throws ProtocolException { + pos.setIndex(0); + if(hasTimeZone) { // MonetDB/SQL99: Sign TwoDigitHours : Minutes + parser.setTimeZone(TimeZone.getTimeZone("GMT" + toParse.substring(toParse.length() - 6))); + } else { + parser.setTimeZone(DefaultTimeZone); + } + + GregorianCalendar res = new GregorianCalendar(); + Date util = parser.parse(toParse, pos); + if(util != null) { + res.setTime(util); + } else { + res.clear(); + } // parse additional nanos (if any) - int pos1 = pos.getIndex(), nanos; + int pos1 = pos.getIndex(), nanos = 0; if (pos1 < toParse.length() && toParse.charAt(pos1) == '.') { pos1++; int ctr; @@ -110,47 +204,14 @@ public final class GregorianCalendarPars nanos *= 10; nanos += (getIntrinsicValue(toParse.charAt(pos1), pos1)); } - if (ctr == 2) // we have three at this point + if (ctr == 2) { // we have three at this point res.set(Calendar.MILLISECOND, nanos); + } pos1++; } while (ctr++ < 9) nanos *= 10; } - - if(hasTimeZone) { - int vallen = toParse.length(); - if (vallen >= 6) { // MonetDB/SQL99: Sign TwoDigitHours : Minutes - res.setTimeZone(TimeZone.getTimeZone("GMT" + toParse.substring(vallen - 6, vallen))); - } - } - return res; - } - - /** - * Parses a time MAPI String into a Java {@link Calendar} instance. - * - * @param toParse The time String to parse - * @param pos The position of the String to start the parsing - * @param hasTimeZone If the time String has timezone information - * @return A {@link Calendar} instance of the parsed time - * @throws ProtocolException If the String could not be parsed - */ - public static Calendar ParseTime(String toParse, ParsePosition pos, boolean hasTimeZone) throws ProtocolException { - return ParseTimeIn(toParse, pos, hasTimeZone, TimeParser); - } - - /** - * Parses a timestamp MAPI String into a Java {@link Calendar} instance. - * - * @param toParse The timestamp String to parse - * @param pos The position of the String to start the parsing - * @param hasTimeZone If the timestamp String has timezone information - * @return A {@link Calendar} instance of the parsed timestamp - * @throws ProtocolException If the String could not be parsed - */ - public static Calendar ParseTimestamp(String toParse, ParsePosition pos, boolean hasTimeZone) - throws ProtocolException { - return ParseTimeIn(toParse, pos, hasTimeZone, TimestampParser); + return new TimestampHelper(res, nanos); } }
new file mode 100644 --- /dev/null +++ b/src/main/java/nl/cwi/monetdb/mcl/connection/helpers/TimestampHelper.java @@ -0,0 +1,72 @@ +package nl.cwi.monetdb.mcl.connection.helpers; + +import java.sql.Timestamp; +import java.util.Calendar; + +/** + * Due to the poor design of the old Java date and time API, when retrieving timestamps from the MAPI connection, this + * class is used to store the calendar with timezone information and the nanoseconds information to generate a + * {@link Timestamp} instance, as there is no mapping in Java Classes to store both information. + * + * @author Pedro Ferreira + */ +public class TimestampHelper { + + /** The calendar instance */ + private Calendar calendar; + + /** The nanoseconds information */ + private int nanoseconds; + + TimestampHelper(Calendar calendar, int nanoseconds) { + this.calendar = calendar; + this.nanoseconds = nanoseconds; + } + + /** + * Gets the Calendar instance. + * + * @return The Calendar instance + */ + public Calendar getCalendar() { + return calendar; + } + + /** + * Sets the Calendar instance. + * + * @param calendar The Calendar instance + */ + public void setCalendar(Calendar calendar) { + this.calendar = calendar; + } + + /** + * Gets the nanoseconds information. + * + * @return The nanoseconds information + */ + public int getNanoseconds() { + return nanoseconds; + } + + /** + * Sets the nanoseconds information. + * + * @param nanoseconds The nanoseconds information + */ + public void setNanoseconds(int nanoseconds) { + this.nanoseconds = nanoseconds; + } + + /** + * Generates a {@link Timestamp} instance from the provided information. + * + * @return The generated {@link Timestamp} instance + */ + public Timestamp getTimestamp() { + Timestamp res = new Timestamp(calendar.getTimeInMillis()); + res.setNanos(nanoseconds); + return res; + } +}
--- a/src/main/java/nl/cwi/monetdb/mcl/connection/mapi/AbstractSocket.java +++ b/src/main/java/nl/cwi/monetdb/mcl/connection/mapi/AbstractSocket.java @@ -208,7 +208,7 @@ public abstract class AbstractSocket imp this.utf8Encoder.reset(); CoderResult res; int written = 0; - do { + do { //to avoid overflow in the UTF-16 to UTF-8 conversion, has to do this cycle res = this.utf8Encoder.encode(this.stringsEncoded, this.bufferOut, false); written += this.writeFromBufferOut(this.bufferOut); } while (res == CoderResult.OVERFLOW);
--- a/src/main/java/nl/cwi/monetdb/mcl/protocol/AbstractProtocol.java +++ b/src/main/java/nl/cwi/monetdb/mcl/protocol/AbstractProtocol.java @@ -16,6 +16,8 @@ import nl.cwi.monetdb.mcl.responses.Data import nl.cwi.monetdb.mcl.responses.ResultSetResponse; import java.io.IOException; +import java.text.ParsePosition; +import java.text.SimpleDateFormat; import java.util.Map; /** @@ -26,6 +28,121 @@ import java.util.Map; */ public abstract class AbstractProtocol { + /* only parse the date patterns once, use multiple times */ + /** Format of a date used by mserver */ + private final SimpleDateFormat monetDate = new SimpleDateFormat("yyyy-MM-dd"); + + /** Format of a time */ + private final SimpleDateFormat monetTime = new SimpleDateFormat("HH:mm:ss.SSS"); + /** Format of a time with RFC822 time zone */ + private final SimpleDateFormat monetTimeTz = new SimpleDateFormat("HH:mm:ss.SSSZ"); + /** Format to print a Time String */ + private final SimpleDateFormat monetTimePrinter = new SimpleDateFormat("HH:mm:ss"); + /** Format to print a TimeTz String */ + private final SimpleDateFormat monetTimeTzPrinter = new SimpleDateFormat("HH:mm:ssXXX"); + + /** Format of a timestamp */ + private final SimpleDateFormat monetTimestamp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + /** Format of a timestamp with RFC822 time zone */ + private final SimpleDateFormat monetTimestampTz = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSZ"); + /** Format to print a TimeStamp String */ + private final SimpleDateFormat monetTimestampPrinter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSSSS"); + /** Format to print a TimeStampTz String */ + private final SimpleDateFormat monetTimestampTzPrinter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSSSSXXX"); + + /** A helper to parse Dates, Times and Timestamps, to be reused during the connection to save memory allocations. */ + private final ParsePosition monetParserPosition = new ParsePosition(0); + + /** + * Gets the MonetDB Date formatter. + * + * @return The MonetDB Date formatter + */ + public SimpleDateFormat getMonetDate() { + return monetDate; + } + + /** + * Gets the MonetDB Time formatter. + * + * @return The MonetDB Time formatter + */ + public SimpleDateFormat getMonetTime() { + return monetTime; + } + + /** + * Gets the MonetDB Time with RFC822 time zone formatter. + * + * @return The MonetDB Time with RFC822 time zone formatter + */ + public SimpleDateFormat getMonetTimeTz() { + return monetTimeTz; + } + + /** + * Gets the MonetDB Time printer. + * + * @return The MonetDB Time printer + */ + public SimpleDateFormat getMonetTimePrinter() { + return monetTimePrinter; + } + + /** + * Gets the MonetDB Time with timezone printer. + * + * @return The MonetDB Time with timezone printer. + */ + public SimpleDateFormat getMonetTimeTzPrinter() { + return monetTimeTzPrinter; + } + + /** + * Gets the MonetDB Timestamp formatter. + * + * @return The MonetDB Timestamp formatter + */ + public SimpleDateFormat getMonetTimestamp() { + return monetTimestamp; + } + + /** + * Gets the MonetDB Timestamp with RFC822 time zone formatter. + * + * @return The MonetDB Timestamp with RFC822 time zone formatter + */ + public SimpleDateFormat getMonetTimestampTz() { + return monetTimestampTz; + } + + /** + * Gets the MonetDB Timestamp printer. + * + * @return The MonetDB Timestamp printer + */ + public SimpleDateFormat getMonetTimestampPrinter() { + return monetTimestampPrinter; + } + + /** + * Gets the MonetDB Timestamp with timezone printer. + * + * @return The MonetDB Timestamp with timezone printer. + */ + public SimpleDateFormat getMonetTimestampTzPrinter() { + return monetTimestampTzPrinter; + } + + /** + * Gets the Protocol parser position. + * + * @return The Protocol parser position + */ + public ParsePosition getMonetParserPosition() { + return monetParserPosition; + } + /** * Waits until the server sends the PROMPT message, meaning that the next response is ready for retrieval. *
--- a/src/main/java/nl/cwi/monetdb/mcl/protocol/oldmapi/OldMapiProtocol.java +++ b/src/main/java/nl/cwi/monetdb/mcl/protocol/oldmapi/OldMapiProtocol.java @@ -21,6 +21,7 @@ import nl.cwi.monetdb.mcl.responses.Resu import java.io.IOException; import java.nio.CharBuffer; +import java.text.SimpleDateFormat; import java.util.Map; /** @@ -37,24 +38,22 @@ public class OldMapiProtocol extends Abs */ private static final int TUPLE_LINE_BUFFER_DEFAULT_SIZE = 1024; - /** - * The current server response. - */ + /** Format of a time string from the old MAPI connection */ + final SimpleDateFormat timeParser = new SimpleDateFormat("HH:mm:ss"); + + /** Format of a timestamp string from the old MAPI connection */ + final SimpleDateFormat timestampParser = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + /**The current server response */ private int currentServerResponseHeader = ServerResponses.UNKNOWN; - /** - * The underlying MAPI socket connection. - */ + /** The underlying MAPI socket connection */ private final OldMapiSocket socket; - /** - * The buffer used to parse server's responses. - */ + /** The buffer used to parse server's responses */ CharBuffer lineBuffer; - /** - * A helper buffer used to parse tuple line responses. - */ + /** A helper buffer used to parse tuple line responses */ CharBuffer tupleLineBuffer; public OldMapiProtocol(OldMapiSocket socket) { @@ -205,7 +204,7 @@ public class OldMapiProtocol extends Abs if (rs == null) { return null; } - return rs.addDataBlockResponse(offset, rowcount, this); + return rs.addDataBlockResponse(offset, rowcount); } /** @@ -251,6 +250,9 @@ public class OldMapiProtocol extends Abs @Override public String getRemainingStringLine(int startIndex) { if(this.lineBuffer.limit() > startIndex) { + if(this.lineBuffer.array()[startIndex] == '!') { + startIndex++; + } return new String(this.lineBuffer.array(), startIndex, this.lineBuffer.limit() - startIndex); } else { return null;
--- a/src/main/java/nl/cwi/monetdb/mcl/protocol/oldmapi/OldMapiTupleLineParser.java +++ b/src/main/java/nl/cwi/monetdb/mcl/protocol/oldmapi/OldMapiTupleLineParser.java @@ -12,13 +12,12 @@ import nl.cwi.monetdb.jdbc.MonetBlob; import nl.cwi.monetdb.jdbc.MonetClob; import nl.cwi.monetdb.mcl.connection.helpers.BufferReallocator; import nl.cwi.monetdb.mcl.connection.helpers.GregorianCalendarParser; +import nl.cwi.monetdb.mcl.connection.helpers.TimestampHelper; import nl.cwi.monetdb.mcl.protocol.ProtocolException; import java.math.BigDecimal; -import java.math.BigInteger; import java.nio.CharBuffer; import java.sql.Types; -import java.text.ParsePosition; import java.util.Calendar; /** @@ -36,12 +35,6 @@ final class OldMapiTupleLineParser { private static final char[] NULL_STRING = new char[]{'N','U','L','L'}; /** - * A Java parser to parse Date, Time and Timestamp data, to be reused during the connection to save memory - * allocation. - */ - private static final ParsePosition Ppos = new ParsePosition(0); - - /** * Parses the given OldMapiProtocol's lineBuffer source as tuple line for a DataBlock. If source cannot be parsed, a * ProtocolException is thrown. The OldMapiProtocol's tupleLineBuffer is used to help parsing a column value. * @@ -66,7 +59,7 @@ final class OldMapiTupleLineParser { throw new ProtocolException(typesMap.length + " columns expected, but only single value found"); } // return the whole string but the leading = - OldMapiStringToJavaDataConversion(array, 1, len - 1, lineNumber, values[0], typesMap[0]); + OldMapiStringToJavaDataConversion(protocol, array, 1, len - 1, lineNumber, values[0], typesMap[0]); return 1; } @@ -147,18 +140,32 @@ final class OldMapiTupleLineParser { break; } } else if(array[pos] == '\\') { - + pos++; + switch (array[pos]) { + case '\\': + tupleLineBuffer.put('\\'); + break; + case 'n': + tupleLineBuffer.put('\n'); + break; + case 't': + tupleLineBuffer.put('\t'); + break; + case '"': + tupleLineBuffer.put('"'); + break; + } } else { tupleLineBuffer.put(array[pos]); } } // put the unescaped string in the right place tupleLineBuffer.flip(); - OldMapiStringToJavaDataConversion(tupleLineBuffer.array(), 0, tupleLineBuffer.limit(), lineNumber, values[column], typesMap[column]); + OldMapiStringToJavaDataConversion(protocol, tupleLineBuffer.array(), 0, tupleLineBuffer.limit(), lineNumber, values[column], typesMap[column]); } else if ((i - 1) - cursor == 4 && OldMapiTupleLineParserHelper.CharIndexOf(array, 0, array.length, NULL_STRING, 0,4, cursor) == cursor) { SetNullValue(lineNumber, values[column], typesMap[column]); } else { - OldMapiStringToJavaDataConversion(array, cursor, i - 1 - cursor, lineNumber, values[column], typesMap[column]); + OldMapiStringToJavaDataConversion(protocol, array, cursor, i - 1 - cursor, lineNumber, values[column], typesMap[column]); } column++; cursor = i + 1; @@ -204,8 +211,9 @@ final class OldMapiTupleLineParser { * @param jDBCMapping The JDBC mapping of the value * @throws ProtocolException If the JDBC Mapping is unknown */ - private static void OldMapiStringToJavaDataConversion(char[] toParse, int startPosition, int count, int lineNumber, - Object columnArray, int jDBCMapping) throws ProtocolException { + private static void OldMapiStringToJavaDataConversion(OldMapiProtocol protocol, char[] toParse, int startPosition, + int count, int lineNumber, Object columnArray, + int jDBCMapping) throws ProtocolException { switch (jDBCMapping) { case Types.BOOLEAN: ((byte[]) columnArray)[lineNumber] = OldMapiTupleLineParserHelper.CharArrayToBoolean(toParse, startPosition); @@ -239,19 +247,19 @@ final class OldMapiTupleLineParser { ((String[]) columnArray)[lineNumber] = new String(toParse, startPosition, count); break; case Types.DATE: - ((Calendar[]) columnArray)[lineNumber] = GregorianCalendarParser.ParseDate(new String(toParse, startPosition, count), Ppos); + ((Calendar[]) columnArray)[lineNumber] = GregorianCalendarParser.ParseDate(new String(toParse, startPosition, count), protocol.getMonetParserPosition(), protocol.getMonetDate()); break; case Types.TIME: - ((Calendar[]) columnArray)[lineNumber] = GregorianCalendarParser.ParseTime(new String(toParse, startPosition, count), Ppos, false); + ((Calendar[]) columnArray)[lineNumber] = GregorianCalendarParser.ParseTime(new String(toParse, startPosition, count), protocol.getMonetParserPosition(), protocol.timeParser, false); break; case Types.TIME_WITH_TIMEZONE: - ((Calendar[]) columnArray)[lineNumber] = GregorianCalendarParser.ParseTime(new String(toParse, startPosition, count), Ppos, true); + ((Calendar[]) columnArray)[lineNumber] = GregorianCalendarParser.ParseTime(new String(toParse, startPosition, count), protocol.getMonetParserPosition(), protocol.timeParser, true); break; case Types.TIMESTAMP: - ((Calendar[]) columnArray)[lineNumber] = GregorianCalendarParser.ParseTimestamp(new String(toParse, startPosition, count), Ppos, false); + ((TimestampHelper[]) columnArray)[lineNumber] = GregorianCalendarParser.ParseTimestamp(new String(toParse, startPosition, count), protocol.getMonetParserPosition(), protocol.timestampParser, false); break; case Types.TIMESTAMP_WITH_TIMEZONE: - ((Calendar[]) columnArray)[lineNumber] = GregorianCalendarParser.ParseTimestamp(new String(toParse, startPosition, count), Ppos, true); + ((TimestampHelper[]) columnArray)[lineNumber] = GregorianCalendarParser.ParseTimestamp(new String(toParse, startPosition, count), protocol.getMonetParserPosition(), protocol.timestampParser, true); break; case Types.CLOB: ((MonetClob[]) columnArray)[lineNumber] = new MonetClob(toParse, startPosition, count);
--- a/src/main/java/nl/cwi/monetdb/mcl/responses/DataBlockResponse.java +++ b/src/main/java/nl/cwi/monetdb/mcl/responses/DataBlockResponse.java @@ -10,12 +10,17 @@ package nl.cwi.monetdb.mcl.responses; import nl.cwi.monetdb.jdbc.MonetBlob; import nl.cwi.monetdb.jdbc.MonetClob; +import nl.cwi.monetdb.jdbc.MonetConnection; +import nl.cwi.monetdb.jdbc.MonetResultSet; +import nl.cwi.monetdb.mcl.connection.helpers.GregorianCalendarParser; +import nl.cwi.monetdb.mcl.connection.helpers.TimestampHelper; import nl.cwi.monetdb.mcl.protocol.AbstractProtocol; import nl.cwi.monetdb.mcl.protocol.ProtocolException; import nl.cwi.monetdb.mcl.protocol.ServerResponses; import java.math.BigDecimal; -import java.sql.Types; +import java.sql.*; +import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Calendar; @@ -41,6 +46,8 @@ public class DataBlockResponse implement private Object[] data; /** The counter which keeps the current position in the data array */ private int pos; + /** If the underlying connection is embedded or not */ + private final boolean isEmbedded; /** The connection protocol to parse the tuple lines */ private final AbstractProtocol protocol; /** The JdbcSQLTypes mapping */ @@ -58,10 +65,12 @@ public class DataBlockResponse implement * @param protocol the underlying protocol * @param JdbcSQLTypes an array of the JDBC mappings of the columns */ - DataBlockResponse(int rowcount, int columncount, AbstractProtocol protocol, int[] JdbcSQLTypes) { + DataBlockResponse(int rowcount, int columncount, MonetConnection connection, AbstractProtocol protocol, + int[] JdbcSQLTypes) { this.pos = -1; this.rowcount = rowcount; this.data = new Object[columncount]; + this.isEmbedded = connection.isEmbedded(); this.protocol = protocol; this.jdbcSQLTypes = JdbcSQLTypes; } @@ -84,6 +93,9 @@ public class DataBlockResponse implement int numberOfColumns = this.data.length; for (int i = 0 ; i < numberOfColumns ; i++) { switch (this.jdbcSQLTypes[i]) { + case Types.INTEGER: + this.data[i] = new int[this.rowcount]; + break; case Types.BOOLEAN: case Types.TINYINT: this.data[i] = new byte[this.rowcount]; @@ -91,18 +103,28 @@ public class DataBlockResponse implement case Types.SMALLINT: this.data[i] = new short[this.rowcount]; break; - case Types.INTEGER: - this.data[i] = new int[this.rowcount]; - break; - case Types.BIGINT: - this.data[i] = new long[this.rowcount]; - break; case Types.REAL: this.data[i] = new float[this.rowcount]; break; case Types.DOUBLE: this.data[i] = new double[this.rowcount]; break; + case Types.BIGINT: + this.data[i] = new long[this.rowcount]; + break; + case Types.DATE: + case Types.TIME: + case Types.TIME_WITH_TIMEZONE: + this.data[i] = new Calendar[this.rowcount]; + break; + case Types.TIMESTAMP: + case Types.TIMESTAMP_WITH_TIMEZONE: + if(this.isEmbedded) { + this.data[i] = new Calendar[this.rowcount]; + } else { + this.data[i] = new TimestampHelper[this.rowcount]; + } + break; case Types.NUMERIC: case Types.DECIMAL: this.data[i] = new BigDecimal[this.rowcount]; @@ -113,13 +135,6 @@ public class DataBlockResponse implement case Types.CLOB: this.data[i] = new MonetClob[this.rowcount]; break; - case Types.TIME: - case Types.TIME_WITH_TIMEZONE: - case Types.DATE: - case Types.TIMESTAMP: - case Types.TIMESTAMP_WITH_TIMEZONE: - this.data[i] = new Calendar[this.rowcount]; - break; case Types.LONGVARBINARY: this.data[i] = new byte[this.rowcount][]; break; @@ -276,6 +291,44 @@ public class DataBlockResponse implement } /** + * To parse a String column in a date, time or timestamp instance, this method is used. + + * @param mrs A MonetResultSet instance where warning can be added * + * @param column The column index starting from 0 + * @param jdbcType The JDBC type of the column desired to convert + * @return A {@link Calendar} instance of the parsed date + * @throws SQLException If the conversation cannot be performed + */ + public Calendar getDateValueFromString(MonetResultSet mrs, int column, int jdbcType) throws SQLException { + String value = ((Object[]) this.data[column])[this.blockLine].toString(); + SimpleDateFormat aux; + switch (jdbcType) { + case Types.DATE: + aux = protocol.getMonetDate(); + break; + case Types.TIME: + case Types.TIME_WITH_TIMEZONE: + aux = protocol.getMonetTimePrinter(); + break; + case Types.TIMESTAMP: + case Types.TIMESTAMP_WITH_TIMEZONE: + aux = protocol.getMonetTimestampPrinter(); + break; + default: + throw new SQLException("Internal error!", "M1M05"); + } + return GregorianCalendarParser.ParseDateString(mrs, value, protocol.getMonetParserPosition(), aux, jdbcType); + } + + public int getNanos(int column) { + if(isEmbedded) { + return 0; + } else { + return ((TimestampHelper[]) this.data[column])[this.blockLine].getTimestamp().getNanos(); + } + } + + /** * Gets the current row value as a Java String. * * @param column The column index starting from 0 @@ -304,7 +357,32 @@ public class DataBlockResponse implement return Float.toString(((float[]) this.data[column])[this.blockLine]); case Types.DOUBLE: return Double.toString(((double[]) this.data[column])[this.blockLine]); - default: //BLOB, CLOB, BigDecimal, Time, Timestamp and Date + case Types.DATE: + Date aux1 = new Date(((Calendar[]) this.data[column])[this.blockLine].getTimeInMillis()); + return protocol.getMonetDate().format(aux1); + case Types.TIME: + Time aux2 = new Time(((Calendar[]) this.data[column])[this.blockLine].getTimeInMillis()); + return protocol.getMonetTimePrinter().format(aux2); + case Types.TIME_WITH_TIMEZONE: + Time aux3 = new Time(((Calendar[]) this.data[column])[this.blockLine].getTimeInMillis()); + return protocol.getMonetTimeTzPrinter().format(aux3); + case Types.TIMESTAMP: + Timestamp aux4; + if(this.isEmbedded) { + aux4 = new Timestamp(((Calendar[]) this.data[column])[this.blockLine].getTimeInMillis()); + } else { + aux4 = ((TimestampHelper[]) this.data[column])[this.blockLine].getTimestamp(); + } + return protocol.getMonetTimestampPrinter().format(aux4); + case Types.TIMESTAMP_WITH_TIMEZONE: + Timestamp aux5; + if(this.isEmbedded) { + aux5 = new Timestamp(((Calendar[]) this.data[column])[this.blockLine].getTimeInMillis()); + } else { + aux5 = ((TimestampHelper[]) this.data[column])[this.blockLine].getTimestamp(); + } + return protocol.getMonetTimestampTzPrinter().format(aux5); + default: //BLOB, CLOB, BigDecimal return ((Object[]) this.data[column])[this.blockLine].toString(); } } @@ -331,6 +409,13 @@ public class DataBlockResponse implement return ((float[]) this.data[column])[this.blockLine]; case Types.DOUBLE: return ((double[]) this.data[column])[this.blockLine]; + case Types.TIMESTAMP: + case Types.TIMESTAMP_WITH_TIMEZONE: + if(isEmbedded) { + return ((Calendar[]) this.data[column])[this.blockLine]; + } else { + return ((TimestampHelper[]) this.data[column])[this.blockLine].getCalendar(); + } default: return ((Object[]) this.data[column])[this.blockLine]; }
--- a/src/main/java/nl/cwi/monetdb/mcl/responses/ResultSetResponse.java +++ b/src/main/java/nl/cwi/monetdb/mcl/responses/ResultSetResponse.java @@ -35,9 +35,7 @@ import java.sql.Types; */ public class ResultSetResponse implements IIncompleteResponse { - /** - * The expected final value after the table headers are set. - */ + /** The expected final value after the table headers are set. */ private static final byte IS_SET_FINAL_VALUE = 15; /** The number of rows in the current block */ @@ -124,7 +122,7 @@ public class ResultSetResponse implement this.JdbcSQLTypes = new int[columncount]; this.resultBlocks = new DataBlockResponse[(tuplecount / cacheSize) + 1]; - this.resultBlocks[0] = new DataBlockResponse(rowcount, columncount, con.getProtocol(), this.JdbcSQLTypes); + this.resultBlocks[0] = new DataBlockResponse(rowcount, columncount, con, con.getProtocol(), this.JdbcSQLTypes); } /** @@ -159,11 +157,10 @@ public class ResultSetResponse implement * * @param offset the offset number of rows for this block * @param rowcount the number of rows for this block - * @param proto The connection's protocol */ - public DataBlockResponse addDataBlockResponse(int offset, int rowcount, AbstractProtocol proto) { + public DataBlockResponse addDataBlockResponse(int offset, int rowcount) { int block = (offset - blockOffset) / cacheSize; - DataBlockResponse res = new DataBlockResponse(rowcount, this.name.length, proto, JdbcSQLTypes); + DataBlockResponse res = new DataBlockResponse(rowcount, this.name.length, this.con, this.con.getProtocol(), JdbcSQLTypes); resultBlocks[block] = res; return res; }