diff --git a/.gitignore b/.gitignore index 4bfa5506..1abfe0c2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ .idea/ *.iml target/ +*.project +.settings/ +*.classpath +test-output/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..5223b523 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +adbcj +===== + +Asynchronous Database Connectivity in Java ,like jdbc + +enhancement: + +done: +1. prepared statment has been supported +2. add mysql5.5 protocol support +3. make mysql data type adjuestment to the mysql manaul : http://dev.mysql.com/doc/refman/5.0/en/connector-j-reference-type-conversions.html +4. exception handling + + +TODO: + diff --git a/api/src/main/java/org/adbcj/ConnectionManagerProvider.java b/api/src/main/java/org/adbcj/ConnectionManagerProvider.java index 5138fba6..8b0c9937 100644 --- a/api/src/main/java/org/adbcj/ConnectionManagerProvider.java +++ b/api/src/main/java/org/adbcj/ConnectionManagerProvider.java @@ -23,38 +23,38 @@ public class ConnectionManagerProvider { - public static final String ADBCJ_PROTOCOL = "adbcj"; - - private ConnectionManagerProvider () {} - - public static ConnectionManager createConnectionManager(String url, String username, String password) throws DbException { - return createConnectionManager(url, username, password, null); - } - - public static ConnectionManager createConnectionManager(String url, String username, String password, Properties properties) throws DbException { - if (url == null) { - throw new IllegalArgumentException("Connection url can not be null"); - } - - try { - URI uri = new URI(url); - String adbcjProtocol = uri.getScheme(); - if (!ADBCJ_PROTOCOL.equals(adbcjProtocol)) { - throw new DbException("Invalid connection URL: " + url); - } - URI driverUri = new URI(uri.getSchemeSpecificPart()); - String protocol = driverUri.getScheme(); - - ServiceLoader serviceLoader = ServiceLoader.load(ConnectionManagerFactory.class); - for (ConnectionManagerFactory factory : serviceLoader) { - if (factory.canHandle(protocol)) { - return factory.createConnectionManager(url, username, password, properties); - } - } - throw new DbException("Could not find ConnectionManagerFactory for protocol '" + protocol + "'"); - } catch (URISyntaxException e) { - throw new DbException("Invalid connection URL: " + url); - } - } - -} + public static final String ADBCJ_PROTOCOL = "adbcj"; + + private ConnectionManagerProvider () {} + + public static ConnectionManager createConnectionManager(String url, String username, String password) throws DbException { + return createConnectionManager(url, username, password, null); + } + + public static ConnectionManager createConnectionManager(String url, String username, String password, Properties properties) throws DbException { + if (url == null) { + throw new IllegalArgumentException("Connection url can not be null"); + } + + try { + URI uri = new URI(url); + String adbcjProtocol = uri.getScheme(); + if (!ADBCJ_PROTOCOL.equals(adbcjProtocol)) { + throw new DbException("Invalid connection URL: " + url); + } + URI driverUri = new URI(uri.getSchemeSpecificPart()); + String protocol = driverUri.getScheme(); + + ServiceLoader serviceLoader = ServiceLoader.load(ConnectionManagerFactory.class); + for (ConnectionManagerFactory factory : serviceLoader) { + if (factory.canHandle(protocol)) { + return factory.createConnectionManager(url, username, password, properties); + } + } + throw new DbException("Could not find ConnectionManagerFactory for protocol '" + protocol + "'"); + } catch (URISyntaxException e) { + throw new DbException("Invalid connection URL: " + url); + } + } + +} \ No newline at end of file diff --git a/api/src/main/java/org/adbcj/ConnectionPool.java b/api/src/main/java/org/adbcj/ConnectionPool.java index 7a0f710e..4ad4e157 100644 --- a/api/src/main/java/org/adbcj/ConnectionPool.java +++ b/api/src/main/java/org/adbcj/ConnectionPool.java @@ -4,106 +4,106 @@ //********************************************************************* package org.adbcj; -import org.adbcj.support.DefaultDbFuture; -import org.adbcj.support.DefaultDbSessionFuture; - import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicInteger; +import org.adbcj.support.DefaultDbFuture; +import org.adbcj.support.DefaultDbSessionFuture; + /** * @author Mike Heath */ public class ConnectionPool implements DbSessionProvider { - private final Queue sessions = new ConcurrentLinkedQueue(); - private final ConnectionManager connectionManager; - private final AtomicInteger count = new AtomicInteger(0); - - public ConnectionPool(ConnectionManager connectionManager) { - this.connectionManager = connectionManager; - } - - public void addConnection() { - sessions.add(connectionManager.connect().getUninterruptably()); - count.incrementAndGet(); - System.out.println("Pool size at: " + count.get()); - } - - public void setPoolSize(int size) { - if (size < count.get()) { - throw new IllegalArgumentException("Can't decrease pool size."); - } - while (count.get() < size) { - addConnection(); - } - } - - @Override - public DbFuture connect() { - final DbSession session = sessions.poll(); - if (session == null) { - throw new IllegalStateException("No connections available in pool"); - } - DefaultDbFuture future = new DefaultDbFuture(); - future.setResult(new DbSession() { - - @Override - public void beginTransaction() { - session.beginTransaction(); - } - - @Override - public DbSessionFuture commit() { - return session.commit(); - } - - @Override - public DbSessionFuture rollback() { - return session.rollback(); - } - - @Override - public boolean isInTransaction() { - return session.isInTransaction(); - } - - @Override - public DbSessionFuture executeQuery(String sql) { - return session.executeQuery(sql); - } - - @Override - public DbSessionFuture executeQuery(String sql, ResultEventHandler eventHandler, T accumulator) { - return session.executeQuery(sql, eventHandler, accumulator); - } - - @Override - public DbSessionFuture executeUpdate(String sql) { - return session.executeUpdate(sql); - } - - @Override - public DbSessionFuture prepareStatement(String sql) { - return session.prepareStatement(sql); - } - - @Override - public DbSessionFuture prepareStatement(Object key, String sql) { - return session.prepareStatement(key, sql); - } - - @Override - public DbSessionFuture close(boolean immediate) throws DbException { - sessions.add(session); - return DefaultDbSessionFuture.createCompletedFuture(this, null); - } - - @Override - public boolean isClosed() throws DbException { - return false; - } - }); - return future; - } + private final Queue sessions = new ConcurrentLinkedQueue(); + private final ConnectionManager connectionManager; + private final AtomicInteger count = new AtomicInteger(0); + + public ConnectionPool(ConnectionManager connectionManager) { + this.connectionManager = connectionManager; + } + + public void addConnection() { + sessions.add(connectionManager.connect().getUninterruptably()); + count.incrementAndGet(); + System.out.println("Pool size at: " + count.get()); + } + + public void setPoolSize(int size) { + if (size < count.get()) { + throw new IllegalArgumentException("Can't decrease pool size."); + } + while (count.get() < size) { + addConnection(); + } + } + + @Override + public DbFuture connect() { + final DbSession session = sessions.poll(); + if (session == null) { + throw new IllegalStateException("No connections available in pool"); + } + DefaultDbFuture future = new DefaultDbFuture(); + future.setResult(new DbSession() { + + @Override + public void beginTransaction() { + session.beginTransaction(); + } + + @Override + public DbSessionFuture commit() { + return session.commit(); + } + + @Override + public DbSessionFuture rollback() { + return session.rollback(); + } + + @Override + public boolean isInTransaction() { + return session.isInTransaction(); + } + + @Override + public DbSessionFuture executeQuery(String sql) { + return session.executeQuery(sql); + } + + @Override + public DbSessionFuture executeQuery(String sql, ResultEventHandler eventHandler, T accumulator) { + return session.executeQuery(sql, eventHandler, accumulator); + } + + @Override + public DbSessionFuture executeUpdate(String sql) { + return session.executeUpdate(sql); + } + + @Override + public DbSessionFuture prepareStatement(String sql) { + return session.prepareStatement(sql); + } + + @Override + public DbSessionFuture prepareStatement(Object key, String sql) { + return session.prepareStatement(key, sql); + } + + @Override + public DbSessionFuture close(boolean immediate) throws DbException { + sessions.add(session); + return DefaultDbSessionFuture.createCompletedFuture(this, null); + } + + @Override + public boolean isClosed() throws DbException { + return false; + } + }); + return future; + } } \ No newline at end of file diff --git a/api/src/main/java/org/adbcj/Row.java b/api/src/main/java/org/adbcj/Row.java index 94c28346..e6ed46c3 100644 --- a/api/src/main/java/org/adbcj/Row.java +++ b/api/src/main/java/org/adbcj/Row.java @@ -21,5 +21,7 @@ public interface Row extends Map { ResultSet getResultSet(); - + + Value[] getValues(); + } diff --git a/api/src/main/java/org/adbcj/Type.java b/api/src/main/java/org/adbcj/Type.java index c1a73b96..01b94bb7 100644 --- a/api/src/main/java/org/adbcj/Type.java +++ b/api/src/main/java/org/adbcj/Type.java @@ -26,6 +26,7 @@ public enum Type { ARRAY(Types.ARRAY), BIGINT(Types.BIGINT), + LONG(Types.BIGINT), BINARY(Types.BINARY), BIT(Types.BIT), BLOB(Types.BLOB), diff --git a/api/src/main/java/org/adbcj/Value.java b/api/src/main/java/org/adbcj/Value.java index 24c8cdc0..8643ba6c 100644 --- a/api/src/main/java/org/adbcj/Value.java +++ b/api/src/main/java/org/adbcj/Value.java @@ -20,95 +20,125 @@ import java.util.Date; /** - * Holds a field value. The {@code Value} methods attempt to convert the field value to Java types. - * + * Holds a field value. The {@code Value} methods attempt to convert the field + * value to Java types. + * * @author Mike Heath */ public interface Value { - /** - * Return the field type for this value. - * - * @return the field type for this value. - */ - Field getField(); + /** + * Return the field type for this value. + * + * @return the field type for this value. + */ + Field getField(); + + /** + * The value as a {@code BigDecimal} with full precision . If the value is + * {@code null}, returns {@code null}. + * + * @return the value as a {@link BigDecimal} or {@code null} if the value is + * {@code null}. + * @throws NumberFormatException if the value is not a valid representation + * of a {@code BigDecimal}. + */ + BigDecimal getBigDecimal(); + + /** + * Returns the value as a {@code boolean}. If the value is {@code null}, + * returns false. If the value is a numeric type, return {@code true} if the + * the value is not 0. Otherwise, returns {@true} if the value as a + * {@code String} is {@code "true"}. + * + * @return the value as a boolean. + */ + boolean getBoolean(); + + /** + * Returns the value as a {@link Date}. If the value is {@code null}, + * returns {@code null}. + * + * @return the value as a {@link Date} or {@code null} if the value is + * {@code null}. + * @throws DbException if the value is not a date type + */ + Date getDate(); + + /** + * Returns the value as a {@code double}. If the value is {@code null}, + * returns 0. + * + * @return the value as a {@code double} or 0 if the value is {@code null}. + * @throws NumberFormatException if the value is not a valid representation + * of a {@code double}. + */ + double getDouble(); - /** - * The value as a {@code BigDecimal} with full precision . If the value is {@code null}, returns {@code null}. - * - * @return the value as a {@link BigDecimal} or {@code null} if the value is {@code null}. - * @throws NumberFormatException if the value is not a valid representation of a {@code BigDecimal}. - */ - BigDecimal getBigDecimal(); + /** + * Returns the value as a {@code float}. If the value is {@code null}, + * returns 0. + * + * @return the value as a {@code float} or 0 if the value is {@code null}. + * @throws NumberFormatException if the value is not a valid representation + * of a {@code float}. + */ + float getFloat(); - /** - * Returns the value as a {@code boolean}. If the value is {@code null}, returns false. If the value is a numeric - * type, return {@code true} if the the value is not 0. Otherwise, returns {@true} if the value as a - * {@code String} is {@code "true"}. - * - * @return the value as a boolean. - */ - boolean getBoolean(); + /** + * Returns the value as a {@code int}. If the value is {@code null}, returns + * 0. + * + * @return the value as a {@code double} or 0 if the value is {@code null}. + * @throws NumberFormatException if the value is not a valid representation + * of a {@code double}. + */ + int getInt(); - /** - * Returns the value as a {@link Date}. If the value is {@code null}, returns {@code null}. - * - * @return the value as a {@link Date} or {@code null} if the value is {@code null}. - * @throws DbException if the value is not a date type - */ - Date getDate(); + /** + * Returns the value as a {@code long}. If the value is {@code null}, + * returns 0. + * + * @return the value as a {@code long} or 0 if the value is {@code null}. + * @throws NumberFormatException if the value is not a valid representation + * of a {@code long}. + */ + long getLong(); - /** - * Returns the value as a {@code double}. If the value is {@code null}, returns 0. - * - * @return the value as a {@code double} or 0 if the value is {@code null}. - * @throws NumberFormatException if the value is not a valid representation of a {@code double}. - */ - double getDouble(); - - /** - * Returns the value as a {@code float}. If the value is {@code null}, returns 0. - * - * @return the value as a {@code float} or 0 if the value is {@code null}. - * @throws NumberFormatException if the value is not a valid representation of a {@code float}. - */ - float getFloat(); + /** + * Returns the value as a {@link String}. If the value is {@code null}, + * returns {@code null}. + * + * @return the value as a {@link String} or {@code null} if the value is + * {@code null}. + */ + String getString(); - /** - * Returns the value as a {@code int}. If the value is {@code null}, returns 0. - * - * @return the value as a {@code double} or 0 if the value is {@code null}. - * @throws NumberFormatException if the value is not a valid representation of a {@code double}. - */ - int getInt(); - - /** - * Returns the value as a {@code long}. If the value is {@code null}, returns 0. - * - * @return the value as a {@code long} or 0 if the value is {@code null}. - * @throws NumberFormatException if the value is not a valid representation of a {@code long}. - */ - long getLong(); + /** + * Returns the value in its native type. If the value is {@code null}, + * return {@code null}. + * + * @return the avleu in its native or {@code null} if the value is + * {@code null}. + */ + Object getValue(); - /** - * Returns the value as a {@link String}. If the value is {@code null}, returns {@code null}. - * - * @return the value as a {@link String} or {@code null} if the value is {@code null}. - */ - String getString(); + /** + * Returns the value in its native type. If the value is {@code null}, + * return {@code null}. + * + * @return the avleu in its native or {@code null} if the value is + * {@code null}. + */ + short getShort(); - /** - * Returns the value in its native type. If the value is {@code null}, return {@code null}. - * - * @return the avleu in its native or {@code null} if the value is {@code null}. - */ - Object getValue(); + /** + * Returns {@code true} if the value is {@code null}, {@code false} + * otherwise. + * + * @return {@code true} if the value is {@code null}, {@code false} + * otherwise. + */ + boolean isNull(); - /** - * Returns {@code true} if the value is {@code null}, {@code false} otherwise. - * - * @return {@code true} if the value is {@code null}, {@code false} otherwise. - */ - boolean isNull(); - } diff --git a/api/src/main/java/org/adbcj/support/DefaultValue.java b/api/src/main/java/org/adbcj/support/DefaultValue.java index 07aac75d..8b95551f 100644 --- a/api/src/main/java/org/adbcj/support/DefaultValue.java +++ b/api/src/main/java/org/adbcj/support/DefaultValue.java @@ -25,108 +25,118 @@ public class DefaultValue implements Value { - private final Field field; - private final Object value; - - public DefaultValue(Field field, Object value) { - this.field = field; - this.value = value; - } - - public Field getField() { - return field; - } - - public BigDecimal getBigDecimal() { - if (value == null) { - return null; - } - if (value instanceof BigDecimal) { - return (BigDecimal)value; - } - return new BigDecimal(value.toString()); - } - - public boolean getBoolean() { - if (value == null) { - return false; - } - if (value instanceof Boolean) { - return ((Boolean)value).booleanValue(); - } - if (value instanceof Number) { - return getInt() != 0; - } - return Boolean.valueOf(value.toString()); - } - - public Date getDate() { - if (value == null) { - return null; - } - if (value instanceof Date) { - return (Date)value; - } - throw new DbException(String.format("%s is not a date", value.toString())); - } - - public double getDouble() { - if (value == null) { - return 0d; - } - if (value instanceof Number) { - return ((Number)value).doubleValue(); - } - return Double.valueOf(value.toString()); - } - - public float getFloat() { - if (value == null) { - return 0f; - } - if (value instanceof Number) { - return ((Number)value).floatValue(); - } - return Float.valueOf(value.toString()); - } - - public int getInt() { - if (value == null) { - return 0; - } - if (value instanceof Number) { - return ((Number)value).intValue(); - } - return Integer.valueOf(value.toString()); - } - - public long getLong() { - if (value == null) { - return 0L; - } - if (value instanceof Number) { - return ((Number)value).longValue(); - } - return Long.valueOf(value.toString()); - } - - public String getString() { - return value == null ? null : value.toString(); - } - - public Object getValue() { - return value; - } - - public boolean isNull() { - return value == null; - } - - @Override - public String toString() { - // TODO Add padding if a display width is specified in the Field - String s = getString(); - return s == null ? "null" : s; - } + private final Field field; + private final Object value; + + public DefaultValue(Field field, Object value){ + this.field = field; + this.value = value; + } + + public Field getField() { + return field; + } + + public BigDecimal getBigDecimal() { + if (value == null) { + return null; + } + if (value instanceof BigDecimal) { + return (BigDecimal) value; + } + return new BigDecimal(value.toString()); + } + + public boolean getBoolean() { + if (value == null) { + return false; + } + if (value instanceof Boolean) { + return ((Boolean) value).booleanValue(); + } + if (value instanceof Number) { + return getInt() != 0; + } + return Boolean.valueOf(value.toString()); + } + + public Date getDate() { + if (value == null) { + return null; + } + if (value instanceof Date) { + return (Date) value; + } + throw new DbException(String.format("%s is not a date", value.toString())); + } + + public double getDouble() { + if (value == null) { + return 0d; + } + if (value instanceof Number) { + return ((Number) value).doubleValue(); + } + return Double.valueOf(value.toString()); + } + + public float getFloat() { + if (value == null) { + return 0f; + } + if (value instanceof Number) { + return ((Number) value).floatValue(); + } + return Float.valueOf(value.toString()); + } + + public int getInt() { + if (value == null) { + return 0; + } + if (value instanceof Number) { + return ((Number) value).intValue(); + } + return Integer.valueOf(value.toString()); + } + + public short getShort() { + if (value == null) { + return 0; + } + if (value instanceof Number) { + return ((Number) value).shortValue(); + } + return Short.valueOf(value.toString()); + } + + public long getLong() { + if (value == null) { + return 0L; + } + if (value instanceof Number) { + return ((Number) value).longValue(); + } + return Long.valueOf(value.toString()); + } + + public String getString() { + return value == null ? null : value.toString(); + } + + public Object getValue() { + return value; + } + + public boolean isNull() { + return value == null; + } + + @Override + public String toString() { + // TODO Add padding if a display width is specified in the Field + String s = getString(); + return s == null ? "null" : s; + } } diff --git a/jdbc/src/main/java/org/adbcj/jdbc/JdbcConnectionManager.java b/jdbc/src/main/java/org/adbcj/jdbc/JdbcConnectionManager.java index 6c5dbce4..66269414 100644 --- a/jdbc/src/main/java/org/adbcj/jdbc/JdbcConnectionManager.java +++ b/jdbc/src/main/java/org/adbcj/jdbc/JdbcConnectionManager.java @@ -40,14 +40,14 @@ public class JdbcConnectionManager implements ConnectionManager { private static final Object USER = "user"; private static final Object PASSWORD = "password"; - + private final String jdbcUrl; private final Properties properties; private final ExecutorService executorService; private final Object lock = this; private final Set connections = new HashSet(); // Access must be synchronized on lock - + private volatile DefaultDbFuture closeFuture; private volatile boolean pipeliningEnabled = false; @@ -116,7 +116,7 @@ public void onCompletion(DbFuture future) throws Exception { } final AtomicInteger countDown = new AtomicInteger(); final AtomicBoolean allClosed = new AtomicBoolean(false); - + DbListener listener = new DbListener() { @Override public void onCompletion(DbFuture future) { @@ -154,7 +154,7 @@ public boolean isClosed() { * Non API Method * */ - + public ExecutorService getExecutorService() { return executorService; } @@ -177,5 +177,5 @@ public void setPipeliningEnabled(boolean pipeliningEnabled) { public String toString() { return String.format("%s: %s (user: %s)", getClass().getName(), jdbcUrl, properties.get(USER)); } - -} + +} \ No newline at end of file diff --git a/jdbc/src/main/java/org/adbcj/jdbc/JdbcConnectionManagerFactory.java b/jdbc/src/main/java/org/adbcj/jdbc/JdbcConnectionManagerFactory.java index 12e2c992..0ec97854 100644 --- a/jdbc/src/main/java/org/adbcj/jdbc/JdbcConnectionManagerFactory.java +++ b/jdbc/src/main/java/org/adbcj/jdbc/JdbcConnectionManagerFactory.java @@ -43,7 +43,7 @@ public ConnectionManager createConnectionManager(String url, String username, St throw new DbException(e); } } - + @Override public boolean canHandle(String protocol) { return PROTOCOL.equals(protocol); diff --git a/mysql/codec/src/main/java/org/adbcj/mysql/codec/AbstractMySqlConnection.java b/mysql/codec/src/main/java/org/adbcj/mysql/codec/AbstractMySqlConnection.java index d7211d66..ecd3446c 100644 --- a/mysql/codec/src/main/java/org/adbcj/mysql/codec/AbstractMySqlConnection.java +++ b/mysql/codec/src/main/java/org/adbcj/mysql/codec/AbstractMySqlConnection.java @@ -14,6 +14,7 @@ import org.adbcj.ResultEventHandler; import org.adbcj.support.AbstractDbSession; import org.adbcj.support.DefaultDbFuture; +import org.adbcj.support.DefaultDbSessionFuture; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,6 +31,8 @@ public abstract class AbstractMySqlConnection extends AbstractDbSession implemen private Request closeRequest; // Access must by synchronized on 'this' private MysqlCharacterSet charset = MysqlCharacterSet.LATIN1_SWEDISH_CI; + + private final FastDateFormat fastDateFormat; protected AbstractMySqlConnection(AbstractMySqlConnectionManager connectionManager, LoginCredentials credentials) { super(connectionManager.isPipeliningEnabled()); @@ -37,6 +40,8 @@ protected AbstractMySqlConnection(AbstractMySqlConnectionManager connectionManag this.credentials = credentials; this.id = connectionManager.nextId(); connectionManager.addConnection(this); + String format = "yyyy-MM-dd HH:mm:ss"; + fastDateFormat = FastDateFormat.getInstance(format); } public abstract void write(ClientRequest request); @@ -156,13 +161,13 @@ public String toString() { public DbSessionFuture prepareStatement(String sql) { checkClosed(); - // TODO Implement MySQL prepareStatement(String sql) - throw new IllegalStateException("Not yet implemented"); + PreparedStatement myPs = new MysqlPreparedStatement(sql, this, fastDateFormat); + DbSessionFuture dbFuture = DefaultDbSessionFuture.createCompletedFuture(this, myPs); + return dbFuture; } public DbSessionFuture prepareStatement(Object key, String sql) { checkClosed(); - // TODO Implement MySQL prepareStatement(Object key, String sql) throw new IllegalStateException("Not yet implemented"); } diff --git a/mysql/codec/src/main/java/org/adbcj/mysql/codec/FastDateFormat.java b/mysql/codec/src/main/java/org/adbcj/mysql/codec/FastDateFormat.java new file mode 100644 index 00000000..b6653810 --- /dev/null +++ b/mysql/codec/src/main/java/org/adbcj/mysql/codec/FastDateFormat.java @@ -0,0 +1,1522 @@ +/* + * 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.adbcj.mysql.codec; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.text.DateFormat; +import java.text.DateFormatSymbols; +import java.text.FieldPosition; +import java.text.Format; +import java.text.ParsePosition; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.List; +import java.util.Locale; +import java.util.TimeZone; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + *

FastDateFormat is a fast and thread-safe version of + * {@link java.text.SimpleDateFormat}.

+ * + *

This class can be used as a direct replacement to + * {@code SimpleDateFormat} in most formatting situations. + * This class is especially useful in multi-threaded server environments. + * {@code SimpleDateFormat} is not thread-safe in any JDK version, + * nor will it be as Sun have closed the bug/RFE. + *

+ * + *

Only formatting is supported, but all patterns are compatible with + * SimpleDateFormat (except time zones and some year patterns - see below).

+ * + *

Java 1.4 introduced a new pattern letter, {@code 'Z'}, to represent + * time zones in RFC822 format (eg. {@code +0800} or {@code -1100}). + * This pattern letter can be used here (on all JDK versions).

+ * + *

In addition, the pattern {@code 'ZZ'} has been made to represent + * ISO8601 full format time zones (eg. {@code +08:00} or {@code -11:00}). + * This introduces a minor incompatibility with Java 1.4, but at a gain of + * useful functionality.

+ * + *

Javadoc cites for the year pattern: For formatting, if the number of + * pattern letters is 2, the year is truncated to 2 digits; otherwise it is + * interpreted as a number. Starting with Java 1.7 a pattern of 'Y' or + * 'YYY' will be formatted as '2003', while it was '03' in former Java + * versions. FastDateFormat implements the behavior of Java 7.

+ * + * @since 2.0 + * @version $Id: FastDateFormat.java 1146138 2011-07-13 17:01:37Z joehni $ + */ +public class FastDateFormat extends Format { + // A lot of the speed in this class comes from caching, but some comes + // from the special int to StringBuffer conversion. + // + // The following produces a padded 2 digit number: + // buffer.append((char)(value / 10 + '0')); + // buffer.append((char)(value % 10 + '0')); + // + // Note that the fastest append to StringBuffer is a single char (used here). + // Note that Integer.toString() is not called, the conversion is simply + // taking the value and adding (mathematically) the ASCII value for '0'. + // So, don't change this code! It works and is very fast. + + /** + * Required for serialization support. + * + * @see java.io.Serializable + */ + private static final long serialVersionUID = 1L; + + /** + * FULL locale dependent date or time style. + */ + public static final int FULL = DateFormat.FULL; + /** + * LONG locale dependent date or time style. + */ + public static final int LONG = DateFormat.LONG; + /** + * MEDIUM locale dependent date or time style. + */ + public static final int MEDIUM = DateFormat.MEDIUM; + /** + * SHORT locale dependent date or time style. + */ + public static final int SHORT = DateFormat.SHORT; + + private static final FormatCache cache= new FormatCache() { + @Override + protected FastDateFormat createInstance(String pattern, TimeZone timeZone, Locale locale) { + return new FastDateFormat(pattern, timeZone, locale); + } + }; + + private static ConcurrentMap cTimeZoneDisplayCache = + new ConcurrentHashMap(7); + + /** + * The pattern. + */ + private final String mPattern; + /** + * The time zone. + */ + private final TimeZone mTimeZone; + /** + * The locale. + */ + private final Locale mLocale; + /** + * The parsed rules. + */ + private transient Rule[] mRules; + /** + * The estimated maximum length. + */ + private transient int mMaxLengthEstimate; + + //----------------------------------------------------------------------- + /** + *

Gets a formatter instance using the default pattern in the + * default locale.

+ * + * @return a date/time formatter + */ + public static FastDateFormat getInstance() { + return cache.getDateTimeInstance(SHORT, SHORT, null, null); + } + + /** + *

Gets a formatter instance using the specified pattern in the + * default locale.

+ * + * @param pattern {@link java.text.SimpleDateFormat} compatible + * pattern + * @return a pattern based date/time formatter + * @throws IllegalArgumentException if pattern is invalid + */ + public static FastDateFormat getInstance(String pattern) { + return cache.getInstance(pattern, null, null); + } + + /** + *

Gets a formatter instance using the specified pattern and + * time zone.

+ * + * @param pattern {@link java.text.SimpleDateFormat} compatible + * pattern + * @param timeZone optional time zone, overrides time zone of + * formatted date + * @return a pattern based date/time formatter + * @throws IllegalArgumentException if pattern is invalid + */ + public static FastDateFormat getInstance(String pattern, TimeZone timeZone) { + return cache.getInstance(pattern, timeZone, null); + } + + /** + *

Gets a formatter instance using the specified pattern and + * locale.

+ * + * @param pattern {@link java.text.SimpleDateFormat} compatible + * pattern + * @param locale optional locale, overrides system locale + * @return a pattern based date/time formatter + * @throws IllegalArgumentException if pattern is invalid + */ + public static FastDateFormat getInstance(String pattern, Locale locale) { + return cache.getInstance(pattern, null, locale); + } + + /** + *

Gets a formatter instance using the specified pattern, time zone + * and locale.

+ * + * @param pattern {@link java.text.SimpleDateFormat} compatible + * pattern + * @param timeZone optional time zone, overrides time zone of + * formatted date + * @param locale optional locale, overrides system locale + * @return a pattern based date/time formatter + * @throws IllegalArgumentException if pattern is invalid + * or {@code null} + */ + public static FastDateFormat getInstance(String pattern, TimeZone timeZone, Locale locale) { + return cache.getInstance(pattern, timeZone, locale); + } + + //----------------------------------------------------------------------- + /** + *

Gets a date formatter instance using the specified style in the + * default time zone and locale.

+ * + * @param style date style: FULL, LONG, MEDIUM, or SHORT + * @return a localized standard date formatter + * @throws IllegalArgumentException if the Locale has no date + * pattern defined + * @since 2.1 + */ + public static FastDateFormat getDateInstance(int style) { + return cache.getDateTimeInstance(style, null, null, null); + } + + /** + *

Gets a date formatter instance using the specified style and + * locale in the default time zone.

+ * + * @param style date style: FULL, LONG, MEDIUM, or SHORT + * @param locale optional locale, overrides system locale + * @return a localized standard date formatter + * @throws IllegalArgumentException if the Locale has no date + * pattern defined + * @since 2.1 + */ + public static FastDateFormat getDateInstance(int style, Locale locale) { + return cache.getDateTimeInstance(style, null, null, locale); + } + + /** + *

Gets a date formatter instance using the specified style and + * time zone in the default locale.

+ * + * @param style date style: FULL, LONG, MEDIUM, or SHORT + * @param timeZone optional time zone, overrides time zone of + * formatted date + * @return a localized standard date formatter + * @throws IllegalArgumentException if the Locale has no date + * pattern defined + * @since 2.1 + */ + public static FastDateFormat getDateInstance(int style, TimeZone timeZone) { + return cache.getDateTimeInstance(style, null, timeZone, null); + } + + /** + *

Gets a date formatter instance using the specified style, time + * zone and locale.

+ * + * @param style date style: FULL, LONG, MEDIUM, or SHORT + * @param timeZone optional time zone, overrides time zone of + * formatted date + * @param locale optional locale, overrides system locale + * @return a localized standard date formatter + * @throws IllegalArgumentException if the Locale has no date + * pattern defined + */ + public static FastDateFormat getDateInstance(int style, TimeZone timeZone, Locale locale) { + return cache.getDateTimeInstance(style, null, timeZone, locale); + } + + //----------------------------------------------------------------------- + /** + *

Gets a time formatter instance using the specified style in the + * default time zone and locale.

+ * + * @param style time style: FULL, LONG, MEDIUM, or SHORT + * @return a localized standard time formatter + * @throws IllegalArgumentException if the Locale has no time + * pattern defined + * @since 2.1 + */ + public static FastDateFormat getTimeInstance(int style) { + return cache.getDateTimeInstance(null, style, null, null); + } + + /** + *

Gets a time formatter instance using the specified style and + * locale in the default time zone.

+ * + * @param style time style: FULL, LONG, MEDIUM, or SHORT + * @param locale optional locale, overrides system locale + * @return a localized standard time formatter + * @throws IllegalArgumentException if the Locale has no time + * pattern defined + * @since 2.1 + */ + public static FastDateFormat getTimeInstance(int style, Locale locale) { + return cache.getDateTimeInstance(null, style, null, locale); + } + + /** + *

Gets a time formatter instance using the specified style and + * time zone in the default locale.

+ * + * @param style time style: FULL, LONG, MEDIUM, or SHORT + * @param timeZone optional time zone, overrides time zone of + * formatted time + * @return a localized standard time formatter + * @throws IllegalArgumentException if the Locale has no time + * pattern defined + * @since 2.1 + */ + public static FastDateFormat getTimeInstance(int style, TimeZone timeZone) { + return cache.getDateTimeInstance(null, style, timeZone, null); + } + + /** + *

Gets a time formatter instance using the specified style, time + * zone and locale.

+ * + * @param style time style: FULL, LONG, MEDIUM, or SHORT + * @param timeZone optional time zone, overrides time zone of + * formatted time + * @param locale optional locale, overrides system locale + * @return a localized standard time formatter + * @throws IllegalArgumentException if the Locale has no time + * pattern defined + */ + public static FastDateFormat getTimeInstance(int style, TimeZone timeZone, Locale locale) { + return cache.getDateTimeInstance(null, style, timeZone, locale); + } + + //----------------------------------------------------------------------- + /** + *

Gets a date/time formatter instance using the specified style + * in the default time zone and locale.

+ * + * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT + * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT + * @return a localized standard date/time formatter + * @throws IllegalArgumentException if the Locale has no date/time + * pattern defined + * @since 2.1 + */ + public static FastDateFormat getDateTimeInstance(int dateStyle, int timeStyle) { + return cache.getDateTimeInstance(dateStyle, timeStyle, null, null); + } + + /** + *

Gets a date/time formatter instance using the specified style and + * locale in the default time zone.

+ * + * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT + * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT + * @param locale optional locale, overrides system locale + * @return a localized standard date/time formatter + * @throws IllegalArgumentException if the Locale has no date/time + * pattern defined + * @since 2.1 + */ + public static FastDateFormat getDateTimeInstance(int dateStyle, int timeStyle, Locale locale) { + return cache.getDateTimeInstance(dateStyle, timeStyle, null, locale); + } + + /** + *

Gets a date/time formatter instance using the specified style and + * time zone in the default locale.

+ * + * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT + * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT + * @param timeZone optional time zone, overrides time zone of + * formatted date + * @return a localized standard date/time formatter + * @throws IllegalArgumentException if the Locale has no date/time + * pattern defined + * @since 2.1 + */ + public static FastDateFormat getDateTimeInstance(int dateStyle, int timeStyle, TimeZone timeZone) { + return getDateTimeInstance(dateStyle, timeStyle, timeZone, null); + } + /** + *

Gets a date/time formatter instance using the specified style, + * time zone and locale.

+ * + * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT + * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT + * @param timeZone optional time zone, overrides time zone of + * formatted date + * @param locale optional locale, overrides system locale + * @return a localized standard date/time formatter + * @throws IllegalArgumentException if the Locale has no date/time + * pattern defined + */ + public static FastDateFormat getDateTimeInstance( + int dateStyle, int timeStyle, TimeZone timeZone, Locale locale) { + return cache.getDateTimeInstance(dateStyle, timeStyle, timeZone, locale); + } + + //----------------------------------------------------------------------- + /** + *

Gets the time zone display name, using a cache for performance.

+ * + * @param tz the zone to query + * @param daylight true if daylight savings + * @param style the style to use {@code TimeZone.LONG} or {@code TimeZone.SHORT} + * @param locale the locale to use + * @return the textual name of the time zone + */ + static String getTimeZoneDisplay(TimeZone tz, boolean daylight, int style, Locale locale) { + TimeZoneDisplayKey key = new TimeZoneDisplayKey(tz, daylight, style, locale); + String value = cTimeZoneDisplayCache.get(key); + if (value == null) { + // This is a very slow call, so cache the results. + value = tz.getDisplayName(daylight, style, locale); + String prior = cTimeZoneDisplayCache.putIfAbsent(key, value); + if (prior != null) { + value= prior; + } + } + return value; + } + + // Constructor + //----------------------------------------------------------------------- + /** + *

Constructs a new FastDateFormat.

+ * + * @param pattern {@link java.text.SimpleDateFormat} compatible pattern + * @param timeZone non-null time zone to use + * @param locale non-null locale to use + * @throws NullPointerException if pattern, timeZone, or locale is null. + */ + protected FastDateFormat(String pattern, TimeZone timeZone, Locale locale) { + mPattern = pattern; + mTimeZone = timeZone; + mLocale = locale; + + init(); + } + + /** + *

Initializes the instance for first use.

+ */ + private void init() { + List rulesList = parsePattern(); + mRules = rulesList.toArray(new Rule[rulesList.size()]); + + int len = 0; + for (int i=mRules.length; --i >= 0; ) { + len += mRules[i].estimateLength(); + } + + mMaxLengthEstimate = len; + } + + // Parse the pattern + //----------------------------------------------------------------------- + /** + *

Returns a list of Rules given a pattern.

+ * + * @return a {@code List} of Rule objects + * @throws IllegalArgumentException if pattern is invalid + */ + protected List parsePattern() { + DateFormatSymbols symbols = new DateFormatSymbols(mLocale); + List rules = new ArrayList(); + + String[] ERAs = symbols.getEras(); + String[] months = symbols.getMonths(); + String[] shortMonths = symbols.getShortMonths(); + String[] weekdays = symbols.getWeekdays(); + String[] shortWeekdays = symbols.getShortWeekdays(); + String[] AmPmStrings = symbols.getAmPmStrings(); + + int length = mPattern.length(); + int[] indexRef = new int[1]; + + for (int i = 0; i < length; i++) { + indexRef[0] = i; + String token = parseToken(mPattern, indexRef); + i = indexRef[0]; + + int tokenLen = token.length(); + if (tokenLen == 0) { + break; + } + + Rule rule; + char c = token.charAt(0); + + switch (c) { + case 'G': // era designator (text) + rule = new TextField(Calendar.ERA, ERAs); + break; + case 'y': // year (number) + if (tokenLen == 2) { + rule = TwoDigitYearField.INSTANCE; + } else { + rule = selectNumberRule(Calendar.YEAR, tokenLen < 4 ? 4 : tokenLen); + } + break; + case 'M': // month in year (text and number) + if (tokenLen >= 4) { + rule = new TextField(Calendar.MONTH, months); + } else if (tokenLen == 3) { + rule = new TextField(Calendar.MONTH, shortMonths); + } else if (tokenLen == 2) { + rule = TwoDigitMonthField.INSTANCE; + } else { + rule = UnpaddedMonthField.INSTANCE; + } + break; + case 'd': // day in month (number) + rule = selectNumberRule(Calendar.DAY_OF_MONTH, tokenLen); + break; + case 'h': // hour in am/pm (number, 1..12) + rule = new TwelveHourField(selectNumberRule(Calendar.HOUR, tokenLen)); + break; + case 'H': // hour in day (number, 0..23) + rule = selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen); + break; + case 'm': // minute in hour (number) + rule = selectNumberRule(Calendar.MINUTE, tokenLen); + break; + case 's': // second in minute (number) + rule = selectNumberRule(Calendar.SECOND, tokenLen); + break; + case 'S': // millisecond (number) + rule = selectNumberRule(Calendar.MILLISECOND, tokenLen); + break; + case 'E': // day in week (text) + rule = new TextField(Calendar.DAY_OF_WEEK, tokenLen < 4 ? shortWeekdays : weekdays); + break; + case 'D': // day in year (number) + rule = selectNumberRule(Calendar.DAY_OF_YEAR, tokenLen); + break; + case 'F': // day of week in month (number) + rule = selectNumberRule(Calendar.DAY_OF_WEEK_IN_MONTH, tokenLen); + break; + case 'w': // week in year (number) + rule = selectNumberRule(Calendar.WEEK_OF_YEAR, tokenLen); + break; + case 'W': // week in month (number) + rule = selectNumberRule(Calendar.WEEK_OF_MONTH, tokenLen); + break; + case 'a': // am/pm marker (text) + rule = new TextField(Calendar.AM_PM, AmPmStrings); + break; + case 'k': // hour in day (1..24) + rule = new TwentyFourHourField(selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen)); + break; + case 'K': // hour in am/pm (0..11) + rule = selectNumberRule(Calendar.HOUR, tokenLen); + break; + case 'z': // time zone (text) + if (tokenLen >= 4) { + rule = new TimeZoneNameRule(mTimeZone, mLocale, TimeZone.LONG); + } else { + rule = new TimeZoneNameRule(mTimeZone, mLocale, TimeZone.SHORT); + } + break; + case 'Z': // time zone (value) + if (tokenLen == 1) { + rule = TimeZoneNumberRule.INSTANCE_NO_COLON; + } else { + rule = TimeZoneNumberRule.INSTANCE_COLON; + } + break; + case '\'': // literal text + String sub = token.substring(1); + if (sub.length() == 1) { + rule = new CharacterLiteral(sub.charAt(0)); + } else { + rule = new StringLiteral(sub); + } + break; + default: + throw new IllegalArgumentException("Illegal pattern component: " + token); + } + + rules.add(rule); + } + + return rules; + } + + /** + *

Performs the parsing of tokens.

+ * + * @param pattern the pattern + * @param indexRef index references + * @return parsed token + */ + protected String parseToken(String pattern, int[] indexRef) { + StringBuilder buf = new StringBuilder(); + + int i = indexRef[0]; + int length = pattern.length(); + + char c = pattern.charAt(i); + if (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z') { + // Scan a run of the same character, which indicates a time + // pattern. + buf.append(c); + + while (i + 1 < length) { + char peek = pattern.charAt(i + 1); + if (peek == c) { + buf.append(c); + i++; + } else { + break; + } + } + } else { + // This will identify token as text. + buf.append('\''); + + boolean inLiteral = false; + + for (; i < length; i++) { + c = pattern.charAt(i); + + if (c == '\'') { + if (i + 1 < length && pattern.charAt(i + 1) == '\'') { + // '' is treated as escaped ' + i++; + buf.append(c); + } else { + inLiteral = !inLiteral; + } + } else if (!inLiteral && + (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z')) { + i--; + break; + } else { + buf.append(c); + } + } + } + + indexRef[0] = i; + return buf.toString(); + } + + /** + *

Gets an appropriate rule for the padding required.

+ * + * @param field the field to get a rule for + * @param padding the padding required + * @return a new rule with the correct padding + */ + protected NumberRule selectNumberRule(int field, int padding) { + switch (padding) { + case 1: + return new UnpaddedNumberField(field); + case 2: + return new TwoDigitNumberField(field); + default: + return new PaddedNumberField(field, padding); + } + } + + // Format methods + //----------------------------------------------------------------------- + /** + *

Formats a {@code Date}, {@code Calendar} or + * {@code Long} (milliseconds) object.

+ * + * @param obj the object to format + * @param toAppendTo the buffer to append to + * @param pos the position - ignored + * @return the buffer passed in + */ + @Override + public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { + if (obj instanceof Date) { + return format((Date) obj, toAppendTo); + } else if (obj instanceof Calendar) { + return format((Calendar) obj, toAppendTo); + } else if (obj instanceof Long) { + return format(((Long) obj).longValue(), toAppendTo); + } else { + throw new IllegalArgumentException("Unknown class: " + + (obj == null ? "" : obj.getClass().getName())); + } + } + + /** + *

Formats a millisecond {@code long} value.

+ * + * @param millis the millisecond value to format + * @return the formatted string + * @since 2.1 + */ + public String format(long millis) { + return format(new Date(millis)); + } + + /** + *

Formats a {@code Date} object using a {@code GregorianCalendar}.

+ * + * @param date the date to format + * @return the formatted string + */ + public String format(Date date) { + Calendar c = new GregorianCalendar(mTimeZone, mLocale); // hard code GregorianCalendar + c.setTime(date); + return applyRules(c, new StringBuffer(mMaxLengthEstimate)).toString(); + } + + /** + *

Formats a {@code Calendar} object.

+ * + * @param calendar the calendar to format + * @return the formatted string + */ + public String format(Calendar calendar) { + return format(calendar, new StringBuffer(mMaxLengthEstimate)).toString(); + } + + /** + *

Formats a milliseond {@code long} value into the + * supplied {@code StringBuffer}.

+ * + * @param millis the millisecond value to format + * @param buf the buffer to format into + * @return the specified string buffer + * @since 2.1 + */ + public StringBuffer format(long millis, StringBuffer buf) { + return format(new Date(millis), buf); + } + + /** + *

Formats a {@code Date} object into the + * supplied {@code StringBuffer} using a {@code GregorianCalendar}.

+ * + * @param date the date to format + * @param buf the buffer to format into + * @return the specified string buffer + */ + public StringBuffer format(Date date, StringBuffer buf) { + Calendar c = new GregorianCalendar(mTimeZone, mLocale); // hard code GregorianCalendar + c.setTime(date); + return applyRules(c, buf); + } + + /** + *

Formats a {@code Calendar} object into the + * supplied {@code StringBuffer}.

+ * + * @param calendar the calendar to format + * @param buf the buffer to format into + * @return the specified string buffer + */ + public StringBuffer format(Calendar calendar, StringBuffer buf) { + return applyRules(calendar, buf); + } + + /** + *

Performs the formatting by applying the rules to the + * specified calendar.

+ * + * @param calendar the calendar to format + * @param buf the buffer to format into + * @return the specified string buffer + */ + protected StringBuffer applyRules(Calendar calendar, StringBuffer buf) { + for (Rule rule : mRules) { + rule.appendTo(buf, calendar); + } + return buf; + } + + // Parsing + //----------------------------------------------------------------------- + /** + *

Parsing is not supported.

+ * + * @param source the string to parse + * @param pos the parsing position + * @return {@code null} as not supported + */ + @Override + public Object parseObject(String source, ParsePosition pos) { + pos.setIndex(0); + pos.setErrorIndex(0); + return null; + } + + // Accessors + //----------------------------------------------------------------------- + /** + *

Gets the pattern used by this formatter.

+ * + * @return the pattern, {@link java.text.SimpleDateFormat} compatible + */ + public String getPattern() { + return mPattern; + } + + /** + *

Gets the time zone used by this formatter.

+ * + *

This zone is always used for {@code Date} formatting.

+ * + * @return the time zone + */ + public TimeZone getTimeZone() { + return mTimeZone; + } + + /** + *

Gets the locale used by this formatter.

+ * + * @return the locale + */ + public Locale getLocale() { + return mLocale; + } + + /** + *

Gets an estimate for the maximum string length that the + * formatter will produce.

+ * + *

The actual formatted length will almost always be less than or + * equal to this amount.

+ * + * @return the maximum formatted length + */ + public int getMaxLengthEstimate() { + return mMaxLengthEstimate; + } + + // Basics + //----------------------------------------------------------------------- + /** + *

Compares two objects for equality.

+ * + * @param obj the object to compare to + * @return {@code true} if equal + */ + @Override + public boolean equals(Object obj) { + if (obj instanceof FastDateFormat == false) { + return false; + } + FastDateFormat other = (FastDateFormat) obj; + return mPattern.equals(other.mPattern) + && mTimeZone.equals(other.mTimeZone) + && mLocale.equals(other.mLocale); + } + + /** + *

Returns a hashcode compatible with equals.

+ * + * @return a hashcode compatible with equals + */ + @Override + public int hashCode() { + return mPattern.hashCode() + 13 * (mTimeZone.hashCode() + 13 * mLocale.hashCode()); + } + + /** + *

Gets a debugging string version of this formatter.

+ * + * @return a debugging string + */ + @Override + public String toString() { + return "FastDateFormat[" + mPattern + "]"; + } + + // Serializing + //----------------------------------------------------------------------- + /** + * Create the object after serialization. This implementation reinitializes the + * transient properties. + * + * @param in ObjectInputStream from which the object is being deserialized. + * @throws IOException if there is an IO issue. + * @throws ClassNotFoundException if a class cannot be found. + */ + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + init(); + } + + // Rules + //----------------------------------------------------------------------- + /** + *

Inner class defining a rule.

+ */ + private interface Rule { + /** + * Returns the estimated lentgh of the result. + * + * @return the estimated length + */ + int estimateLength(); + + /** + * Appends the value of the specified calendar to the output buffer based on the rule implementation. + * + * @param buffer the output buffer + * @param calendar calendar to be appended + */ + void appendTo(StringBuffer buffer, Calendar calendar); + } + + /** + *

Inner class defining a numeric rule.

+ */ + private interface NumberRule extends Rule { + /** + * Appends the specified value to the output buffer based on the rule implementation. + * + * @param buffer the output buffer + * @param value the value to be appended + */ + void appendTo(StringBuffer buffer, int value); + } + + /** + *

Inner class to output a constant single character.

+ */ + private static class CharacterLiteral implements Rule { + private final char mValue; + + /** + * Constructs a new instance of {@code CharacterLiteral} + * to hold the specified value. + * + * @param value the character literal + */ + CharacterLiteral(char value) { + mValue = value; + } + + /** + * {@inheritDoc} + */ + public int estimateLength() { + return 1; + } + + /** + * {@inheritDoc} + */ + public void appendTo(StringBuffer buffer, Calendar calendar) { + buffer.append(mValue); + } + } + + /** + *

Inner class to output a constant string.

+ */ + private static class StringLiteral implements Rule { + private final String mValue; + + /** + * Constructs a new instance of {@code StringLiteral} + * to hold the specified value. + * + * @param value the string literal + */ + StringLiteral(String value) { + mValue = value; + } + + /** + * {@inheritDoc} + */ + public int estimateLength() { + return mValue.length(); + } + + /** + * {@inheritDoc} + */ + public void appendTo(StringBuffer buffer, Calendar calendar) { + buffer.append(mValue); + } + } + + /** + *

Inner class to output one of a set of values.

+ */ + private static class TextField implements Rule { + private final int mField; + private final String[] mValues; + + /** + * Constructs an instance of {@code TextField} + * with the specified field and values. + * + * @param field the field + * @param values the field values + */ + TextField(int field, String[] values) { + mField = field; + mValues = values; + } + + /** + * {@inheritDoc} + */ + public int estimateLength() { + int max = 0; + for (int i=mValues.length; --i >= 0; ) { + int len = mValues[i].length(); + if (len > max) { + max = len; + } + } + return max; + } + + /** + * {@inheritDoc} + */ + public void appendTo(StringBuffer buffer, Calendar calendar) { + buffer.append(mValues[calendar.get(mField)]); + } + } + + /** + *

Inner class to output an unpadded number.

+ */ + private static class UnpaddedNumberField implements NumberRule { + private final int mField; + + /** + * Constructs an instance of {@code UnpadedNumberField} with the specified field. + * + * @param field the field + */ + UnpaddedNumberField(int field) { + mField = field; + } + + /** + * {@inheritDoc} + */ + public int estimateLength() { + return 4; + } + + /** + * {@inheritDoc} + */ + public void appendTo(StringBuffer buffer, Calendar calendar) { + appendTo(buffer, calendar.get(mField)); + } + + /** + * {@inheritDoc} + */ + public final void appendTo(StringBuffer buffer, int value) { + if (value < 10) { + buffer.append((char)(value + '0')); + } else if (value < 100) { + buffer.append((char)(value / 10 + '0')); + buffer.append((char)(value % 10 + '0')); + } else { + buffer.append(Integer.toString(value)); + } + } + } + + /** + *

Inner class to output an unpadded month.

+ */ + private static class UnpaddedMonthField implements NumberRule { + static final UnpaddedMonthField INSTANCE = new UnpaddedMonthField(); + + /** + * Constructs an instance of {@code UnpaddedMonthField}. + * + */ + UnpaddedMonthField() { + super(); + } + + /** + * {@inheritDoc} + */ + public int estimateLength() { + return 2; + } + + /** + * {@inheritDoc} + */ + public void appendTo(StringBuffer buffer, Calendar calendar) { + appendTo(buffer, calendar.get(Calendar.MONTH) + 1); + } + + /** + * {@inheritDoc} + */ + public final void appendTo(StringBuffer buffer, int value) { + if (value < 10) { + buffer.append((char)(value + '0')); + } else { + buffer.append((char)(value / 10 + '0')); + buffer.append((char)(value % 10 + '0')); + } + } + } + + /** + *

Inner class to output a padded number.

+ */ + private static class PaddedNumberField implements NumberRule { + private final int mField; + private final int mSize; + + /** + * Constructs an instance of {@code PaddedNumberField}. + * + * @param field the field + * @param size size of the output field + */ + PaddedNumberField(int field, int size) { + if (size < 3) { + // Should use UnpaddedNumberField or TwoDigitNumberField. + throw new IllegalArgumentException(); + } + mField = field; + mSize = size; + } + + /** + * {@inheritDoc} + */ + public int estimateLength() { + return 4; + } + + /** + * {@inheritDoc} + */ + public void appendTo(StringBuffer buffer, Calendar calendar) { + appendTo(buffer, calendar.get(mField)); + } + + /** + * {@inheritDoc} + */ + public final void appendTo(StringBuffer buffer, int value) { + if (value < 100) { + for (int i = mSize; --i >= 2; ) { + buffer.append('0'); + } + buffer.append((char)(value / 10 + '0')); + buffer.append((char)(value % 10 + '0')); + } else { + int digits; + if (value < 1000) { + digits = 3; + } else { + isTrue(value > -1, "Negative values should not be possible", value); + digits = Integer.toString(value).length(); + } + for (int i = mSize; --i >= digits; ) { + buffer.append('0'); + } + buffer.append(Integer.toString(value)); + } + } + } + + public static void isTrue(boolean expression, String message, Object... values) { + if (expression == false) { + throw new IllegalArgumentException(String.format(message, values)); + } + } + /** + *

Inner class to output a two digit number.

+ */ + private static class TwoDigitNumberField implements NumberRule { + private final int mField; + + /** + * Constructs an instance of {@code TwoDigitNumberField} with the specified field. + * + * @param field the field + */ + TwoDigitNumberField(int field) { + mField = field; + } + + /** + * {@inheritDoc} + */ + public int estimateLength() { + return 2; + } + + /** + * {@inheritDoc} + */ + public void appendTo(StringBuffer buffer, Calendar calendar) { + appendTo(buffer, calendar.get(mField)); + } + + /** + * {@inheritDoc} + */ + public final void appendTo(StringBuffer buffer, int value) { + if (value < 100) { + buffer.append((char)(value / 10 + '0')); + buffer.append((char)(value % 10 + '0')); + } else { + buffer.append(Integer.toString(value)); + } + } + } + + /** + *

Inner class to output a two digit year.

+ */ + private static class TwoDigitYearField implements NumberRule { + static final TwoDigitYearField INSTANCE = new TwoDigitYearField(); + + /** + * Constructs an instance of {@code TwoDigitYearField}. + */ + TwoDigitYearField() { + super(); + } + + /** + * {@inheritDoc} + */ + public int estimateLength() { + return 2; + } + + /** + * {@inheritDoc} + */ + public void appendTo(StringBuffer buffer, Calendar calendar) { + appendTo(buffer, calendar.get(Calendar.YEAR) % 100); + } + + /** + * {@inheritDoc} + */ + public final void appendTo(StringBuffer buffer, int value) { + buffer.append((char)(value / 10 + '0')); + buffer.append((char)(value % 10 + '0')); + } + } + + /** + *

Inner class to output a two digit month.

+ */ + private static class TwoDigitMonthField implements NumberRule { + static final TwoDigitMonthField INSTANCE = new TwoDigitMonthField(); + + /** + * Constructs an instance of {@code TwoDigitMonthField}. + */ + TwoDigitMonthField() { + super(); + } + + /** + * {@inheritDoc} + */ + public int estimateLength() { + return 2; + } + + /** + * {@inheritDoc} + */ + public void appendTo(StringBuffer buffer, Calendar calendar) { + appendTo(buffer, calendar.get(Calendar.MONTH) + 1); + } + + /** + * {@inheritDoc} + */ + public final void appendTo(StringBuffer buffer, int value) { + buffer.append((char)(value / 10 + '0')); + buffer.append((char)(value % 10 + '0')); + } + } + + /** + *

Inner class to output the twelve hour field.

+ */ + private static class TwelveHourField implements NumberRule { + private final NumberRule mRule; + + /** + * Constructs an instance of {@code TwelveHourField} with the specified + * {@code NumberRule}. + * + * @param rule the rule + */ + TwelveHourField(NumberRule rule) { + mRule = rule; + } + + /** + * {@inheritDoc} + */ + public int estimateLength() { + return mRule.estimateLength(); + } + + /** + * {@inheritDoc} + */ + public void appendTo(StringBuffer buffer, Calendar calendar) { + int value = calendar.get(Calendar.HOUR); + if (value == 0) { + value = calendar.getLeastMaximum(Calendar.HOUR) + 1; + } + mRule.appendTo(buffer, value); + } + + /** + * {@inheritDoc} + */ + public void appendTo(StringBuffer buffer, int value) { + mRule.appendTo(buffer, value); + } + } + + /** + *

Inner class to output the twenty four hour field.

+ */ + private static class TwentyFourHourField implements NumberRule { + private final NumberRule mRule; + + /** + * Constructs an instance of {@code TwentyFourHourField} with the specified + * {@code NumberRule}. + * + * @param rule the rule + */ + TwentyFourHourField(NumberRule rule) { + mRule = rule; + } + + /** + * {@inheritDoc} + */ + public int estimateLength() { + return mRule.estimateLength(); + } + + /** + * {@inheritDoc} + */ + public void appendTo(StringBuffer buffer, Calendar calendar) { + int value = calendar.get(Calendar.HOUR_OF_DAY); + if (value == 0) { + value = calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1; + } + mRule.appendTo(buffer, value); + } + + /** + * {@inheritDoc} + */ + public void appendTo(StringBuffer buffer, int value) { + mRule.appendTo(buffer, value); + } + } + + /** + *

Inner class to output a time zone name.

+ */ + private static class TimeZoneNameRule implements Rule { + private final TimeZone mTimeZone; + private final String mStandard; + private final String mDaylight; + + /** + * Constructs an instance of {@code TimeZoneNameRule} with the specified properties. + * + * @param timeZone the time zone + * @param locale the locale + * @param style the style + */ + TimeZoneNameRule(TimeZone timeZone, Locale locale, int style) { + mTimeZone = timeZone; + + mStandard = getTimeZoneDisplay(timeZone, false, style, locale); + mDaylight = getTimeZoneDisplay(timeZone, true, style, locale); + } + + /** + * {@inheritDoc} + */ + public int estimateLength() { + return Math.max(mStandard.length(), mDaylight.length()); + } + + /** + * {@inheritDoc} + */ + public void appendTo(StringBuffer buffer, Calendar calendar) { + if (mTimeZone.useDaylightTime() && calendar.get(Calendar.DST_OFFSET) != 0) { + buffer.append(mDaylight); + } else { + buffer.append(mStandard); + } + } + } + + /** + *

Inner class to output a time zone as a number {@code +/-HHMM} + * or {@code +/-HH:MM}.

+ */ + private static class TimeZoneNumberRule implements Rule { + static final TimeZoneNumberRule INSTANCE_COLON = new TimeZoneNumberRule(true); + static final TimeZoneNumberRule INSTANCE_NO_COLON = new TimeZoneNumberRule(false); + + final boolean mColon; + + /** + * Constructs an instance of {@code TimeZoneNumberRule} with the specified properties. + * + * @param colon add colon between HH and MM in the output if {@code true} + */ + TimeZoneNumberRule(boolean colon) { + mColon = colon; + } + + /** + * {@inheritDoc} + */ + public int estimateLength() { + return 5; + } + + /** + * {@inheritDoc} + */ + public void appendTo(StringBuffer buffer, Calendar calendar) { + int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET); + + if (offset < 0) { + buffer.append('-'); + offset = -offset; + } else { + buffer.append('+'); + } + + int hours = offset / (60 * 60 * 1000); + buffer.append((char)(hours / 10 + '0')); + buffer.append((char)(hours % 10 + '0')); + + if (mColon) { + buffer.append(':'); + } + + int minutes = offset / (60 * 1000) - 60 * hours; + buffer.append((char)(minutes / 10 + '0')); + buffer.append((char)(minutes % 10 + '0')); + } + } + + // ---------------------------------------------------------------------- + /** + *

Inner class that acts as a compound key for time zone names.

+ */ + private static class TimeZoneDisplayKey { + private final TimeZone mTimeZone; + private final int mStyle; + private final Locale mLocale; + + /** + * Constructs an instance of {@code TimeZoneDisplayKey} with the specified properties. + * + * @param timeZone the time zone + * @param daylight adjust the style for daylight saving time if {@code true} + * @param style the timezone style + * @param locale the timezone locale + */ + TimeZoneDisplayKey(TimeZone timeZone, + boolean daylight, int style, Locale locale) { + mTimeZone = timeZone; + if (daylight) { + style |= 0x80000000; + } + mStyle = style; + mLocale = locale; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return (mStyle * 31 + mLocale.hashCode() ) * 31 + mTimeZone.hashCode(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof TimeZoneDisplayKey) { + TimeZoneDisplayKey other = (TimeZoneDisplayKey)obj; + return + mTimeZone.equals(other.mTimeZone) && + mStyle == other.mStyle && + mLocale.equals(other.mLocale); + } + return false; + } + } +} diff --git a/mysql/codec/src/main/java/org/adbcj/mysql/codec/FormatCache.java b/mysql/codec/src/main/java/org/adbcj/mysql/codec/FormatCache.java new file mode 100644 index 00000000..6d202838 --- /dev/null +++ b/mysql/codec/src/main/java/org/adbcj/mysql/codec/FormatCache.java @@ -0,0 +1,202 @@ +/* + * 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.adbcj.mysql.codec; + +import java.text.DateFormat; +import java.text.Format; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Locale; +import java.util.TimeZone; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + *

FormatCache is a cache and factory for {@link Format}s.

+ * + * @since 3.0 + * @version $Id: FormatCache 892161 2009-12-18 07:21:10Z $ + */ +// TODO: Before making public move from getDateTimeInstance(Integer,...) to int; or some other approach. +abstract class FormatCache { + /** + * No date or no time. Used in same parameters as DateFormat.SHORT or DateFormat.LONG + */ + static final int NONE= -1; + + private final ConcurrentMap cInstanceCache + = new ConcurrentHashMap(7); + + private final ConcurrentMap cDateTimeInstanceCache + = new ConcurrentHashMap(7); + + /** + *

Gets a formatter instance using the default pattern in the + * default timezone and locale.

+ * + * @return a date/time formatter + */ + public F getInstance() { + return getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, TimeZone.getDefault(), Locale.getDefault()); + } + + /** + *

Gets a formatter instance using the specified pattern, time zone + * and locale.

+ * + * @param pattern {@link java.text.SimpleDateFormat} compatible + * pattern + * @param timeZone the non-null time zone + * @param locale the non-null locale + * @return a pattern based date/time formatter + * @throws IllegalArgumentException if pattern is invalid + * or null + */ + public F getInstance(String pattern, TimeZone timeZone, Locale locale) { + if (pattern == null) { + throw new NullPointerException("pattern must not be null"); + } + if (timeZone == null) { + timeZone = TimeZone.getDefault(); + } + if (locale == null) { + locale = Locale.getDefault(); + } + MultipartKey key = new MultipartKey(pattern, timeZone, locale); + F format = cInstanceCache.get(key); + if (format == null) { + format = createInstance(pattern, timeZone, locale); + F previousValue= cInstanceCache.putIfAbsent(key, format); + if (previousValue != null) { + // another thread snuck in and did the same work + // we should return the instance that is in ConcurrentMap + format= previousValue; + } + } + return format; + } + + /** + *

Create a format instance using the specified pattern, time zone + * and locale.

+ * + * @param pattern {@link java.text.SimpleDateFormat} compatible pattern, this will not be null. + * @param timeZone time zone, this will not be null. + * @param locale locale, this will not be null. + * @return a pattern based date/time formatter + * @throws IllegalArgumentException if pattern is invalid + * or null + */ + abstract protected F createInstance(String pattern, TimeZone timeZone, Locale locale); + + /** + *

Gets a date/time formatter instance using the specified style, + * time zone and locale.

+ * + * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT + * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT + * @param timeZone optional time zone, overrides time zone of + * formatted date + * @param locale optional locale, overrides system locale + * @return a localized standard date/time formatter + * @throws IllegalArgumentException if the Locale has no date/time + * pattern defined + */ + public F getDateTimeInstance(Integer dateStyle, Integer timeStyle, TimeZone timeZone, Locale locale) { + if (locale == null) { + locale = Locale.getDefault(); + } + MultipartKey key = new MultipartKey(dateStyle, timeStyle, locale); + + String pattern = cDateTimeInstanceCache.get(key); + if (pattern == null) { + try { + DateFormat formatter; + if (dateStyle == null) { + formatter = DateFormat.getTimeInstance(timeStyle, locale); + } + else if (timeStyle == null) { + formatter = DateFormat.getDateInstance(dateStyle, locale); + } + else { + formatter = DateFormat.getDateTimeInstance(dateStyle, timeStyle, locale); + } + pattern = ((SimpleDateFormat)formatter).toPattern(); + String previous = cDateTimeInstanceCache.putIfAbsent(key, pattern); + if (previous != null) { + // even though it doesn't matter if another thread put the pattern + // it's still good practice to return the String instance that is + // actually in the ConcurrentMap + pattern= previous; + } + } catch (ClassCastException ex) { + throw new IllegalArgumentException("No date time pattern for locale: " + locale); + } + } + + return getInstance(pattern, timeZone, locale); + } + + // ---------------------------------------------------------------------- + /** + *

Helper class to hold multi-part Map keys

+ */ + private static class MultipartKey { + private final Object[] keys; + private int hashCode; + + /** + * Constructs an instance of MultipartKey to hold the specified objects. + * @param keys the set of objects that make up the key. Each key may be null. + */ + public MultipartKey(Object... keys) { + this.keys = keys; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if ( obj instanceof MultipartKey == false ) { + return false; + } + return Arrays.equals(keys, ((MultipartKey)obj).keys); + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + if(hashCode==0) { + int rc= 0; + for(Object key : keys) { + if(key!=null) { + rc= rc*7 + key.hashCode(); + } + } + hashCode= rc; + } + return hashCode; + } + } + +} diff --git a/mysql/codec/src/main/java/org/adbcj/mysql/codec/MySqlClientDecoder.java b/mysql/codec/src/main/java/org/adbcj/mysql/codec/MySqlClientDecoder.java index 468dee54..21204b7b 100644 --- a/mysql/codec/src/main/java/org/adbcj/mysql/codec/MySqlClientDecoder.java +++ b/mysql/codec/src/main/java/org/adbcj/mysql/codec/MySqlClientDecoder.java @@ -17,8 +17,14 @@ Copyright 2008 Mike Heath */ package org.adbcj.mysql.codec; + import java.io.IOException; import java.io.InputStream; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.Date; +import java.sql.Time; +import java.sql.Timestamp; import java.util.Set; import org.adbcj.Value; @@ -27,354 +33,446 @@ import org.slf4j.LoggerFactory; /** - * Client stateful decoder. Being stateful, each client connection must have its own decoder instance to function - * properly. - * + * Client stateful decoder. Being stateful, each client connection must have its + * own decoder instance to function properly. + * * @author Mike Heath - * */ public class MySqlClientDecoder { - private static final Logger logger = LoggerFactory.getLogger(MySqlClientDecoder.class); - - /** - * The salt size in a server greeting - */ - public static final int SALT_SIZE = 8; - - /** - * The size of the second salt in a server greeting - */ - public static final int SALT2_SIZE = 12; - - /** - * Number of unused bytes in server greeting - */ - public static final int GREETING_UNUSED_SIZE = 13; - - public static final int SQL_STATE_LENGTH = 5; - - public static final int RESPONSE_OK = 0x00; - public static final int RESPONSE_EOF = 0xfe; - public static final int RESPONSE_ERROR = 0xff; - /** - * The state of the decoder. - */ - enum State { - /** - * The client is connecting - */ - CONNECTING, - RESPONSE, - FIELD, - FIELD_EOF, - ROW - } - - private State state = State.CONNECTING; - - private String charset = "ISO8859_1"; - - /** - * Holds the remaining number of field packets expected to build the result set - */ - private int expectedFieldPackets = 0; - - private int fieldIndex = 0; - - /** - * The field definitions for the current result set - */ - private MysqlField[] fields; - - /** - * Decodes a message from a MySql server. - * - * @param input the {@code InputStream} from which to decode the message - * @param block true if the decoder can block, false otherwise - * @return the decode message, null if the {@code block} is {@code} false and there is not enough data available - * to decode the message without blocking - * @throws IOException thrown if an error occurs reading data from the inputstream - */ - public ServerPacket decode(InputStream input, boolean block) throws IOException { - // If mark is not support and we can't block, throw an exception - if (!input.markSupported() && !block) { - throw new IllegalArgumentException("Non-blocking decoding requires an InputStream that supports marking"); - } - // TODO This should be the max packet size - make this configurable - input.mark(Integer.MAX_VALUE); - ServerPacket message = null; - try { - message = doDecode(input, block); - } finally { - if (message == null) { - input.reset(); - } - } - return message; - } - - protected ServerPacket doDecode(InputStream input, boolean block) throws IOException { - // If we can't block, make sure there's enough data available to read - if (!block) { - if (input.available() < 3) { - return null; - } - } - // Read the packet length - final int length = IoUtils.readUnsignedMediumInt(input); - - // If we can't block, make sure the stream has enough data - if (!block) { - // Make sure we have enough data for the packet length and the packet number - if (input.available() < length + 1) { - return null; - } - } - final int packetNumber = IoUtils.safeRead(input); - BoundedInputStream in = new BoundedInputStream(input, length); - boolean threwException = false; - try { - - logger.trace("Decoding in state {}", state); - switch (state) { - case CONNECTING: - ServerGreeting serverGreeting = decodeServerGreeting(in, length, packetNumber); - state = State.RESPONSE; - return serverGreeting; - case RESPONSE: - int fieldCount = in.read(); - if (fieldCount == RESPONSE_OK) { - // Create Ok response - return decodeOkResponse(in, length, packetNumber); - } - if (fieldCount == RESPONSE_ERROR) { - // Create error response - ErrorResponse response = decodeErrorResponse(in, length, packetNumber); - return response; - } - if (fieldCount == RESPONSE_EOF) { - throw new IllegalStateException("Did not expect an EOF response from the server"); - } - // Must be receiving result set header - - // Get the number of fields. The largest this can be is a 24-bit - // integer so cast to int is ok - expectedFieldPackets = (int)IoUtils.readBinaryLengthEncoding(in, fieldCount); - fields = new MysqlField[expectedFieldPackets]; - logger.trace("Field count {}", expectedFieldPackets); - - Long extra = null; - if (in.getRemaining() > 0) { - extra = IoUtils.readBinaryLengthEncoding(in); - } - - state = State.FIELD; - - return new ResultSetResponse(length, packetNumber, expectedFieldPackets, extra); - case FIELD: - ResultSetFieldResponse resultSetFieldResponse = decodeFieldResponse(in, length, packetNumber); - - expectedFieldPackets--; - logger.trace("fieldPacketCount: {}", expectedFieldPackets); - if (expectedFieldPackets == 0) { - state = State.FIELD_EOF; - } - return resultSetFieldResponse; - case FIELD_EOF: - fieldCount = in.read(); - - if (fieldCount != RESPONSE_EOF) { - throw new IllegalStateException("Expected an EOF response from the server"); - } - EofResponse fieldEof = decodeEofResponse(in, length, packetNumber, EofResponse.Type.FIELD); - state = State.ROW; - fieldIndex = 0; - return fieldEof; - case ROW: - fieldCount = in.read(); // This is only for checking for EOF - if (fieldCount == RESPONSE_EOF) { - EofResponse rowEof = decodeEofResponse(in, length, packetNumber, EofResponse.Type.ROW); - - state = State.RESPONSE; - - return rowEof; - } - - Value[] values = new Value[fields.length]; - for (int i = 0; i < fields.length; ) { - MysqlField field = fields[i++]; - Object value = null; - if (fieldCount != IoUtils.NULL_VALUE) { - // We will have to move this as some datatypes will not be sent across the wire as strings - String strVal = IoUtils.readLengthCodedString(in, fieldCount, charset); - - // TODO add decoding for all column types - switch (field.getColumnType()) { - case TINYINT: - value = Byte.valueOf(strVal); - break; - case INTEGER: - case BIGINT: - value = Long.valueOf(strVal); - break; - case VARCHAR: - value = strVal; - break; - default: - throw new IllegalStateException("Don't know how to handle column type of " - + field.getColumnType()); - } - } - values[field.getIndex()] = new DefaultValue(field, value); - if (i < fields.length) { - fieldCount = in.read(); - } - - } - return new ResultSetRowResponse(length, packetNumber, values); - default: - throw new IllegalStateException("Unkown decoder state " + state); - } - } catch (IOException e) { - threwException = true; - throw e; - } catch (RuntimeException e) { - threwException = true; - throw e; - } finally { - if (!threwException && in.getRemaining() > 0) { - throw new IllegalStateException("Buffer underrun occured; remaining bytes: " + in.getRemaining()); - } - } - } - - protected ServerGreeting decodeServerGreeting(InputStream in, int length, int packetNumber) throws IOException { - int protocol = IoUtils.safeRead(in); - String version = IoUtils.readString(in, "ASCII"); - int threadId = IoUtils.readInt(in); - - byte[] salt = new byte[SALT_SIZE + SALT2_SIZE]; - in.read(salt, 0, SALT_SIZE); - in.read(); // Throw away 0 byte - - Set serverCapabilities = IoUtils.readEnumSetShort(in, ClientCapabilities.class); - MysqlCharacterSet charSet = MysqlCharacterSet.findById(in.read()); - Set serverStatus = IoUtils.readEnumSetShort(in, ServerStatus.class); - in.skip(GREETING_UNUSED_SIZE); - - in.read(salt, SALT_SIZE, SALT2_SIZE); - in.read(); // Throw away 0 byte - - return new ServerGreeting(length, packetNumber, protocol, version, threadId, salt, serverCapabilities, charSet, - serverStatus); - } - - protected OkResponse decodeOkResponse(BoundedInputStream in, int length, int packetNumber) throws IOException { - long affectedRows = IoUtils.readBinaryLengthEncoding(in); - long insertId = IoUtils.readBinaryLengthEncoding(in); - Set serverStatus = IoUtils.readEnumSetShort(in, ServerStatus.class); - int warningCount = IoUtils.readUnsignedShort(in); - String message = IoUtils.readFixedLengthString(in, in.getRemaining(), charset); - - return new OkResponse(length, packetNumber, affectedRows, insertId, serverStatus, - warningCount, message); - } - - protected ErrorResponse decodeErrorResponse(InputStream in, int length, int packetNumber) throws IOException { - int errorNumber = IoUtils.readUnsignedShort(in); - in.read(); // Throw away sqlstate marker - String sqlState = IoUtils.readString(in, "ASCII"); - String message = IoUtils.readString(in, charset); - return new ErrorResponse(length, packetNumber, errorNumber, sqlState, message); - } - - protected EofResponse decodeEofResponse(InputStream in, int length, int packetNumber, EofResponse.Type type) throws IOException { - int warnings = IoUtils.readUnsignedShort(in); - Set serverStatus = IoUtils.readEnumSetShort(in, ServerStatus.class); - - return new EofResponse(length, packetNumber, warnings, serverStatus, type); - } - - protected ResultSetFieldResponse decodeFieldResponse(InputStream in, - int packetLength, int packetNumber) throws IOException { - String catalogName = IoUtils.readLengthCodedString(in, charset); - String schemaName = IoUtils.readLengthCodedString(in, charset); - String tableLabel = IoUtils.readLengthCodedString(in, charset); - String tableName = IoUtils.readLengthCodedString(in, charset); - String columnLabel = IoUtils.readLengthCodedString(in, charset); - String columnName = IoUtils.readLengthCodedString(in, charset); - in.read(); // Skip filler - int characterSetNumber = IoUtils.readUnsignedShort(in); - MysqlCharacterSet charSet = MysqlCharacterSet.findById(characterSetNumber); - long length = IoUtils.readUnsignedInt(in); - int fieldTypeId = in.read(); - MysqlType fieldType = MysqlType.findById(fieldTypeId); - Set flags = IoUtils.readEnumSet(in, FieldFlag.class); - int decimals = in.read(); - in.skip(2); // Skip filler - long fieldDefault = IoUtils.readBinaryLengthEncoding(in); - MysqlField field = new MysqlField(fieldIndex, catalogName, schemaName, tableLabel, tableName, fieldType, columnLabel, - columnName, 0, // Figure out precision - decimals, charSet, length, flags, fieldDefault); - fields[fieldIndex++] = field; - return new ResultSetFieldResponse(packetLength, packetNumber, field); - } - - // TODO: This stream implementation doesn't even work b ecause it doesn't delegate all InputStream methods - private static class BoundedInputStream extends InputStream { - - private final InputStream in; - private int remaining; - - public BoundedInputStream(InputStream in, int length) { - this.in = in; - this.remaining = length; - } - - @Override - public int read() throws IOException { - int i = in.read(); - if (i >= 0) { - remaining --; - } - if (remaining < 0) { - throw new IllegalStateException("Buffer overrun"); - } - return i; - } - - @Override - public int read(byte[] b, int off, int len) throws IOException { - int i = in.read(b, off, len); - remaining -= i; - if (remaining < 0) { - throw new IllegalStateException("Read too many bytes"); - } - return i; - } - - @Override - public long skip(long n) throws IOException { - long i = in.skip(n); - remaining -= i; - if (remaining < 0) { - throw new IllegalStateException("Read too many bytes"); - } - return i; - } - - public int getRemaining() { - return remaining; - } - - } - - /** - * Sets the state, used for testing. - * - * @param state - */ - void setState(State state) { - this.state = state; - } + + private static final Logger logger = LoggerFactory.getLogger(MySqlClientDecoder.class); + + /** + * The salt size in a server greeting + */ + public static final int SALT_SIZE = 8; + + /** + * The size of the second salt in a server greeting + */ + public static final int SALT2_SIZE = 12; + + /** + * Number of unused bytes in server greeting + */ + public static final int GREETING_UNUSED_SIZE = 13; + + public static final int SQL_STATE_LENGTH = 5; + + public static final int RESPONSE_OK = 0x00; + public static final int RESPONSE_EOF = 0xfe; + public static final int RESPONSE_ERROR = 0xff; + + /** + * The state of the decoder. + */ + enum State { + /** + * The client is connecting + */ + CONNECTING, RESPONSE, FIELD, FIELD_EOF, ROW + } + + private State state = State.CONNECTING; + + private String charset = "ISO8859_1"; + + /** + * Holds the remaining number of field packets expected to build the result + * set + */ + private int expectedFieldPackets = 0; + + private int fieldIndex = 0; + + /** + * The field definitions for the current result set + */ + private MysqlField[] fields; + + /** + * Decodes a message from a MySql server. + * + * @param input the {@code InputStream} from which to decode the message + * @param block true if the decoder can block, false otherwise + * @return the decode message, null if the {@code block} is {@code} false + * and there is not enough data available to decode the message without + * blocking + * @throws IOException thrown if an error occurs reading data from the + * inputstream + */ + public ServerPacket decode(InputStream input, boolean block) throws IOException { + // If mark is not support and we can't block, throw an exception + if (!input.markSupported() && !block) { + throw new IllegalArgumentException("Non-blocking decoding requires an InputStream that supports marking"); + } + // TODO This should be the max packet size - make this configurable + input.mark(Integer.MAX_VALUE); + ServerPacket message = null; + try { + message = doDecode(input, block); + } finally { + if (message == null) { + input.reset(); + } + } + return message; + } + + protected ServerPacket doDecode(InputStream input, boolean block) throws IOException { + // If we can't block, make sure there's enough data available to read + if (!block) { + if (input.available() < 3) { + return null; + } + } + // Read the packet length + final int length = IoUtils.readUnsignedMediumInt(input); + + // If we can't block, make sure the stream has enough data + if (!block) { + // Make sure we have enough data for the packet length and the + // packet number + if (input.available() < length + 1) { + return null; + } + } + final int packetNumber = IoUtils.safeRead(input); + BoundedInputStream in = new BoundedInputStream(input, length); + boolean threwException = false; + try { + + logger.trace("Decoding in state {}", state); + switch (state) { + case CONNECTING: + ServerGreeting serverGreeting = decodeServerGreeting(in, length, packetNumber); + state = State.RESPONSE; + return serverGreeting; + case RESPONSE: + int fieldCount = in.read(); + if (fieldCount == RESPONSE_OK) { + // Create Ok response + return decodeOkResponse(in, length, packetNumber); + } + if (fieldCount == RESPONSE_ERROR) { + // Create error response + ErrorResponse response = decodeErrorResponse(in, length, packetNumber); + return response; + } + if (fieldCount == RESPONSE_EOF) { + throw new IllegalStateException("Did not expect an EOF response from the server"); + } + // Must be receiving result set header + + // Get the number of fields. The largest this can be is a + // 24-bit + // integer so cast to int is ok + expectedFieldPackets = (int) IoUtils.readBinaryLengthEncoding(in, fieldCount); + fields = new MysqlField[expectedFieldPackets]; + logger.trace("Field count {}", expectedFieldPackets); + + Long extra = null; + if (in.getRemaining() > 0) { + extra = IoUtils.readBinaryLengthEncoding(in); + } + + state = State.FIELD; + + return new ResultSetResponse(length, packetNumber, expectedFieldPackets, extra); + case FIELD: + ResultSetFieldResponse resultSetFieldResponse = decodeFieldResponse(in, length, packetNumber); + + expectedFieldPackets--; + logger.trace("fieldPacketCount: {}", expectedFieldPackets); + if (expectedFieldPackets == 0) { + state = State.FIELD_EOF; + } + return resultSetFieldResponse; + case FIELD_EOF: + fieldCount = in.read(); + + if (fieldCount != RESPONSE_EOF) { + throw new IllegalStateException("Expected an EOF response from the server"); + } + EofResponse fieldEof = decodeEofResponse(in, length, packetNumber, EofResponse.Type.FIELD); + state = State.ROW; + fieldIndex = 0; + return fieldEof; + case ROW: + fieldCount = in.read(); // This is only for checking for EOF + if (fieldCount == RESPONSE_EOF) { + EofResponse rowEof = decodeEofResponse(in, length, packetNumber, EofResponse.Type.ROW); + + state = State.RESPONSE; + + return rowEof; + } + + Value[] values = new Value[fields.length]; + for (int i = 0; i < fields.length;) { + MysqlField field = fields[i++]; + Object value = null; + if (fieldCount != IoUtils.NULL_VALUE) { + // We will have to move this as some datatypes will + // not be sent across the wire as strings + String strVal = IoUtils.readLengthCodedString(in, fieldCount, charset); + + // TODO add decoding for all column types + switch (field.getColumnType()) { + case TINYINT: + //in java Boolean is the same with Integer + value = Integer.valueOf(strVal); + break; + case INTEGER: + value = Integer.valueOf(strVal); + break; + case BIGINT: + value = BigInteger.valueOf(Long.valueOf(strVal)); + break; + case LONG: + value = Long.valueOf(strVal); + break; + case VARCHAR: + value = strVal; + break; + case DATE: + value = Date.valueOf(strVal); + break; + case TIMESTAMP: + value = Timestamp.valueOf(strVal); + break; + case FLOAT: + value = Float.valueOf(strVal); + break; + case TIME: + value = Time.valueOf(strVal); + break; + case BOOLEAN: + value = Boolean.valueOf(strVal); + break; + case CHAR: + value = strVal; + break; + case DOUBLE: + value = Double.valueOf(strVal); + break; + case SMALLINT: + value = Integer.valueOf(strVal); + break; + case LONGVARCHAR: + value = String.valueOf(strVal); + break; + case BINARY: + // UTF-8?GBK? + value = strVal.getBytes(); + break; + case BLOB: + value = new SimpleBlob(strVal.getBytes()); + break; + case BIT: + value = strVal.getBytes(); + break; + case DECIMAL: + if(strVal == null) + { + break; + } + if(strVal.contains(".")) + { + //double + value = BigDecimal.valueOf(Double.valueOf(strVal)); + } + else + { + value = BigDecimal.valueOf(Long.valueOf(strVal)); + } + + break; + default: + throw new IllegalStateException("Don't know how to handle column type of " + + field.getColumnType()); + } + } + values[field.getIndex()] = new DefaultValue(field, value); + if (i < fields.length) { + fieldCount = in.read(); + } + + } + return new ResultSetRowResponse(length, packetNumber, values); + default: + throw new IllegalStateException("Unkown decoder state " + state); + } + } catch (IOException e) { + threwException = true; + throw e; + } catch (RuntimeException e) { + threwException = true; + throw e; + } finally { + if (!threwException && in.getRemaining() > 0) { + throw new IllegalStateException("Buffer underrun occured; remaining bytes: " + in.getRemaining()); + } + } + } + + protected ServerGreeting decodeServerGreeting(InputStream in, int length, int packetNumber) throws IOException { + int protocol = IoUtils.safeRead(in); + String version = IoUtils.readString(in, "ASCII"); + int threadId = IoUtils.readInt(in); + + byte[] salt = new byte[SALT_SIZE + SALT2_SIZE]; + in.read(salt, 0, SALT_SIZE); + in.read(); // Throw away 0 byte + + Set serverCapabilities = IoUtils.readEnumSetShort(in, ClientCapabilities.class); + MysqlCharacterSet charSet = MysqlCharacterSet.findById(in.read()); + Set serverStatus = IoUtils.readEnumSetShort(in, ServerStatus.class); + in.skip(GREETING_UNUSED_SIZE); + + in.read(salt, SALT_SIZE, SALT2_SIZE); + in.read(); // Throw away 0 byte + String str = IoUtils.readString(in, "ASCII"); + if (!"mysql_native_password".equals(str)) { + // in 5.5, the authentication plugin name is specifical tolded by + // default + throw new IllegalStateException("not supported yet : " + str); + } + return new ServerGreeting(length, + packetNumber, + protocol, + version, + threadId, + salt, + serverCapabilities, + charSet, + serverStatus); + } + + protected OkResponse decodeOkResponse(BoundedInputStream in, int length, int packetNumber) throws IOException { + long affectedRows = IoUtils.readBinaryLengthEncoding(in); + long insertId = IoUtils.readBinaryLengthEncoding(in); + Set serverStatus = IoUtils.readEnumSetShort(in, ServerStatus.class); + int warningCount = IoUtils.readUnsignedShort(in); + String message = IoUtils.readFixedLengthString(in, in.getRemaining(), charset); + + return new OkResponse(length, packetNumber, affectedRows, insertId, serverStatus, warningCount, message); + } + + protected ErrorResponse decodeErrorResponse(InputStream in, int length, int packetNumber) throws IOException { + int errorNumber = IoUtils.readUnsignedShort(in); + in.read(); // Throw away sqlstate marker + String sqlState = IoUtils.readString(in, "ASCII"); + String message = IoUtils.readString(in, charset); + return new ErrorResponse(length, packetNumber, errorNumber, sqlState, message); + } + + protected EofResponse decodeEofResponse(InputStream in, int length, int packetNumber, EofResponse.Type type) + throws IOException { + int warnings = IoUtils.readUnsignedShort(in); + Set serverStatus = IoUtils.readEnumSetShort(in, ServerStatus.class); + + return new EofResponse(length, packetNumber, warnings, serverStatus, type); + } + + protected ResultSetFieldResponse decodeFieldResponse(InputStream in, int packetLength, int packetNumber) + throws IOException { + String catalogName = IoUtils.readLengthCodedString(in, charset); + String schemaName = IoUtils.readLengthCodedString(in, charset); + String tableLabel = IoUtils.readLengthCodedString(in, charset); + String tableName = IoUtils.readLengthCodedString(in, charset); + String columnLabel = IoUtils.readLengthCodedString(in, charset); + String columnName = IoUtils.readLengthCodedString(in, charset); + in.read(); // Skip filler + int characterSetNumber = IoUtils.readUnsignedShort(in); + MysqlCharacterSet charSet = MysqlCharacterSet.findById(characterSetNumber); + long length = IoUtils.readUnsignedInt(in); + int fieldTypeId = in.read(); + Set flags = IoUtils.readEnumSet(in, FieldFlag.class); + boolean unsigned = false; + if(flags.contains(FieldFlag.UNSIGNED)) + { + unsigned = true; + } + MysqlType fieldType = MysqlType.findById(fieldTypeId,unsigned); + + int decimals = in.read(); + in.skip(2); // Skip filler + long fieldDefault = IoUtils.readBinaryLengthEncoding(in); + MysqlField field = new MysqlField(fieldIndex, + catalogName, + schemaName, + tableLabel, + tableName, + fieldType, + columnLabel, + columnName, + 0, // Figure out precision + decimals, + charSet, + length, + flags, + fieldDefault); + fields[fieldIndex++] = field; + return new ResultSetFieldResponse(packetLength, packetNumber, field); + } + + // TODO: This stream implementation doesn't even work b ecause it doesn't + // delegate all InputStream methods + private static class BoundedInputStream extends InputStream { + + private final InputStream in; + private int remaining; + + public BoundedInputStream(InputStream in, int length){ + this.in = in; + this.remaining = length; + } + + @Override + public int read() throws IOException { + int i = in.read(); + if (i >= 0) { + remaining--; + } + if (remaining < 0) { + throw new IllegalStateException("Buffer overrun"); + } + return i; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + int i = in.read(b, off, len); + remaining -= i; + if (remaining < 0) { + throw new IllegalStateException("Read too many bytes"); + } + return i; + } + + @Override + public long skip(long n) throws IOException { + long i = in.skip(n); + remaining -= i; + if (remaining < 0) { + throw new IllegalStateException("Read too many bytes"); + } + return i; + } + + public int getRemaining() { + return remaining; + } + + } + + /** + * Sets the state, used for testing. + * + * @param state + */ + void setState(State state) { + this.state = state; + } } diff --git a/mysql/codec/src/main/java/org/adbcj/mysql/codec/MysqlDefs.java b/mysql/codec/src/main/java/org/adbcj/mysql/codec/MysqlDefs.java new file mode 100644 index 00000000..7053b923 --- /dev/null +++ b/mysql/codec/src/main/java/org/adbcj/mysql/codec/MysqlDefs.java @@ -0,0 +1,562 @@ +/* + Copyright (c) 2002, 2011, Oracle and/or its affiliates. All rights reserved. + + + The MySQL Connector/J is licensed under the terms of the GPLv2 + , like most MySQL Connectors. + There are special exceptions to the terms and conditions of the GPLv2 as it is applied to + this software, see the FLOSS License Exception + . + + This program is free software; you can redistribute it and/or modify it under the terms + of the GNU General Public License as published by the Free Software Foundation; version 2 + of the License. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with this + program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth + Floor, Boston, MA 02110-1301 USA + + + + */ +package org.adbcj.mysql.codec; + +import java.sql.Types; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +/** + * MysqlDefs contains many values that are needed for communication with the + * MySQL server. + * + * @author Mark Matthews + * @version $Id$ + */ +public final class MysqlDefs { + + // ~ Static fields/initializers + // --------------------------------------------- + + static final int COM_BINLOG_DUMP = 18; + + static final int COM_CHANGE_USER = 17; + + static final int COM_CLOSE_STATEMENT = 25; + + static final int COM_CONNECT_OUT = 20; + + static final int COM_END = 29; + + static final int COM_EXECUTE = 23; + + static final int COM_FETCH = 28; + + static final int COM_LONG_DATA = 24; + + static final int COM_PREPARE = 22; + + static final int COM_REGISTER_SLAVE = 21; + + static final int COM_RESET_STMT = 26; + + static final int COM_SET_OPTION = 27; + + static final int COM_TABLE_DUMP = 19; + + static final int CONNECT = 11; + + static final int CREATE_DB = 5; + + static final int DEBUG = 13; + + static final int DELAYED_INSERT = 16; + + static final int DROP_DB = 6; + + static final int FIELD_LIST = 4; + + static final int FIELD_TYPE_BIT = 16; + + public static final int FIELD_TYPE_BLOB = 252; + + static final int FIELD_TYPE_DATE = 10; + + static final int FIELD_TYPE_DATETIME = 12; + + // Data Types + static final int FIELD_TYPE_DECIMAL = 0; + + static final int FIELD_TYPE_DOUBLE = 5; + + static final int FIELD_TYPE_ENUM = 247; + + static final int FIELD_TYPE_FLOAT = 4; + + static final int FIELD_TYPE_GEOMETRY = 255; + + static final int FIELD_TYPE_INT24 = 9; + + static final int FIELD_TYPE_LONG = 3; + + static final int FIELD_TYPE_LONG_BLOB = 251; + + static final int FIELD_TYPE_LONGLONG = 8; + + static final int FIELD_TYPE_MEDIUM_BLOB = 250; + + static final int FIELD_TYPE_NEW_DECIMAL = 246; + + static final int FIELD_TYPE_NEWDATE = 14; + + static final int FIELD_TYPE_NULL = 6; + + static final int FIELD_TYPE_SET = 248; + + static final int FIELD_TYPE_SHORT = 2; + + static final int FIELD_TYPE_STRING = 254; + + static final int FIELD_TYPE_TIME = 11; + + static final int FIELD_TYPE_TIMESTAMP = 7; + + static final int FIELD_TYPE_TINY = 1; + + // Older data types + static final int FIELD_TYPE_TINY_BLOB = 249; + + static final int FIELD_TYPE_VAR_STRING = 253; + + static final int FIELD_TYPE_VARCHAR = 15; + + // Newer data types + static final int FIELD_TYPE_YEAR = 13; + + static final int INIT_DB = 2; + + static final long LENGTH_BLOB = 65535; + + static final long LENGTH_LONGBLOB = 4294967295L; + + static final long LENGTH_MEDIUMBLOB = 16777215; + + static final long LENGTH_TINYBLOB = 255; + + // Limitations + static final int MAX_ROWS = 50000000; // From the + // MySQL FAQ + + /** + * Used to indicate that the server sent no field-level character set + * information, so the driver should use the connection-level character + * encoding instead. + */ + public static final int NO_CHARSET_INFO = -1; + + static final byte OPEN_CURSOR_FLAG = 1; + + static final int PING = 14; + + static final int PROCESS_INFO = 10; + + static final int PROCESS_KILL = 12; + + static final int QUERY = 3; + + static final int QUIT = 1; + + // ~ Methods + // ---------------------------------------------------------------- + + static final int RELOAD = 7; + + static final int SHUTDOWN = 8; + + // + // Constants defined from mysql + // + // DB Operations + static final int SLEEP = 0; + + static final int STATISTICS = 9; + + static final int TIME = 15; + + /** + * Maps the given MySQL type to the correct JDBC type. + */ + static int mysqlToJavaType(int mysqlType) { + int jdbcType; + + switch (mysqlType) { + case MysqlDefs.FIELD_TYPE_NEW_DECIMAL: + case MysqlDefs.FIELD_TYPE_DECIMAL: + jdbcType = Types.DECIMAL; + + break; + + case MysqlDefs.FIELD_TYPE_TINY: + jdbcType = Types.TINYINT; + + break; + + case MysqlDefs.FIELD_TYPE_SHORT: + jdbcType = Types.SMALLINT; + + break; + + case MysqlDefs.FIELD_TYPE_LONG: + jdbcType = Types.INTEGER; + + break; + + case MysqlDefs.FIELD_TYPE_FLOAT: + jdbcType = Types.REAL; + + break; + + case MysqlDefs.FIELD_TYPE_DOUBLE: + jdbcType = Types.DOUBLE; + + break; + + case MysqlDefs.FIELD_TYPE_NULL: + jdbcType = Types.NULL; + + break; + + case MysqlDefs.FIELD_TYPE_TIMESTAMP: + jdbcType = Types.TIMESTAMP; + + break; + + case MysqlDefs.FIELD_TYPE_LONGLONG: + jdbcType = Types.BIGINT; + + break; + + case MysqlDefs.FIELD_TYPE_INT24: + jdbcType = Types.INTEGER; + + break; + + case MysqlDefs.FIELD_TYPE_DATE: + jdbcType = Types.DATE; + + break; + + case MysqlDefs.FIELD_TYPE_TIME: + jdbcType = Types.TIME; + + break; + + case MysqlDefs.FIELD_TYPE_DATETIME: + jdbcType = Types.TIMESTAMP; + + break; + + case MysqlDefs.FIELD_TYPE_YEAR: + jdbcType = Types.DATE; + + break; + + case MysqlDefs.FIELD_TYPE_NEWDATE: + jdbcType = Types.DATE; + + break; + + case MysqlDefs.FIELD_TYPE_ENUM: + jdbcType = Types.CHAR; + + break; + + case MysqlDefs.FIELD_TYPE_SET: + jdbcType = Types.CHAR; + + break; + + case MysqlDefs.FIELD_TYPE_TINY_BLOB: + jdbcType = Types.VARBINARY; + + break; + + case MysqlDefs.FIELD_TYPE_MEDIUM_BLOB: + jdbcType = Types.LONGVARBINARY; + + break; + + case MysqlDefs.FIELD_TYPE_LONG_BLOB: + jdbcType = Types.LONGVARBINARY; + + break; + + case MysqlDefs.FIELD_TYPE_BLOB: + jdbcType = Types.LONGVARBINARY; + + break; + + case MysqlDefs.FIELD_TYPE_VAR_STRING: + case MysqlDefs.FIELD_TYPE_VARCHAR: + jdbcType = Types.VARCHAR; + + break; + + case MysqlDefs.FIELD_TYPE_STRING: + jdbcType = Types.CHAR; + + break; + case MysqlDefs.FIELD_TYPE_GEOMETRY: + jdbcType = Types.BINARY; + + break; + case MysqlDefs.FIELD_TYPE_BIT: + jdbcType = Types.BIT; + + break; + default: + jdbcType = Types.VARCHAR; + } + + return jdbcType; + } + + /** + * Maps the given MySQL type to the correct JDBC type. + */ + static int mysqlToJavaType(String mysqlType) { + if (mysqlType.equalsIgnoreCase("BIT")) { + return mysqlToJavaType(FIELD_TYPE_BIT); + } else if (mysqlType.equalsIgnoreCase("TINYINT")) { //$NON-NLS-1$ + return mysqlToJavaType(FIELD_TYPE_TINY); + } else if (mysqlType.equalsIgnoreCase("SMALLINT")) { //$NON-NLS-1$ + return mysqlToJavaType(FIELD_TYPE_SHORT); + } else if (mysqlType.equalsIgnoreCase("MEDIUMINT")) { //$NON-NLS-1$ + return mysqlToJavaType(FIELD_TYPE_INT24); + } else if (mysqlType.equalsIgnoreCase("INT") || mysqlType.equalsIgnoreCase("INTEGER")) { //$NON-NLS-1$ //$NON-NLS-2$ + return mysqlToJavaType(FIELD_TYPE_LONG); + } else if (mysqlType.equalsIgnoreCase("BIGINT")) { //$NON-NLS-1$ + return mysqlToJavaType(FIELD_TYPE_LONGLONG); + } else if (mysqlType.equalsIgnoreCase("INT24")) { //$NON-NLS-1$ + return mysqlToJavaType(FIELD_TYPE_INT24); + } else if (mysqlType.equalsIgnoreCase("REAL")) { //$NON-NLS-1$ + return mysqlToJavaType(FIELD_TYPE_DOUBLE); + } else if (mysqlType.equalsIgnoreCase("FLOAT")) { //$NON-NLS-1$ + return mysqlToJavaType(FIELD_TYPE_FLOAT); + } else if (mysqlType.equalsIgnoreCase("DECIMAL")) { //$NON-NLS-1$ + return mysqlToJavaType(FIELD_TYPE_DECIMAL); + } else if (mysqlType.equalsIgnoreCase("NUMERIC")) { //$NON-NLS-1$ + return mysqlToJavaType(FIELD_TYPE_DECIMAL); + } else if (mysqlType.equalsIgnoreCase("DOUBLE")) { //$NON-NLS-1$ + return mysqlToJavaType(FIELD_TYPE_DOUBLE); + } else if (mysqlType.equalsIgnoreCase("CHAR")) { //$NON-NLS-1$ + return mysqlToJavaType(FIELD_TYPE_STRING); + } else if (mysqlType.equalsIgnoreCase("VARCHAR")) { //$NON-NLS-1$ + return mysqlToJavaType(FIELD_TYPE_VAR_STRING); + } else if (mysqlType.equalsIgnoreCase("DATE")) { //$NON-NLS-1$ + return mysqlToJavaType(FIELD_TYPE_DATE); + } else if (mysqlType.equalsIgnoreCase("TIME")) { //$NON-NLS-1$ + return mysqlToJavaType(FIELD_TYPE_TIME); + } else if (mysqlType.equalsIgnoreCase("YEAR")) { //$NON-NLS-1$ + return mysqlToJavaType(FIELD_TYPE_YEAR); + } else if (mysqlType.equalsIgnoreCase("TIMESTAMP")) { //$NON-NLS-1$ + return mysqlToJavaType(FIELD_TYPE_TIMESTAMP); + } else if (mysqlType.equalsIgnoreCase("DATETIME")) { //$NON-NLS-1$ + return mysqlToJavaType(FIELD_TYPE_DATETIME); + } else if (mysqlType.equalsIgnoreCase("TINYBLOB")) { //$NON-NLS-1$ + return java.sql.Types.BINARY; + } else if (mysqlType.equalsIgnoreCase("BLOB")) { //$NON-NLS-1$ + return java.sql.Types.LONGVARBINARY; + } else if (mysqlType.equalsIgnoreCase("MEDIUMBLOB")) { //$NON-NLS-1$ + return java.sql.Types.LONGVARBINARY; + } else if (mysqlType.equalsIgnoreCase("LONGBLOB")) { //$NON-NLS-1$ + return java.sql.Types.LONGVARBINARY; + } else if (mysqlType.equalsIgnoreCase("TINYTEXT")) { //$NON-NLS-1$ + return java.sql.Types.VARCHAR; + } else if (mysqlType.equalsIgnoreCase("TEXT")) { //$NON-NLS-1$ + return java.sql.Types.LONGVARCHAR; + } else if (mysqlType.equalsIgnoreCase("MEDIUMTEXT")) { //$NON-NLS-1$ + return java.sql.Types.LONGVARCHAR; + } else if (mysqlType.equalsIgnoreCase("LONGTEXT")) { //$NON-NLS-1$ + return java.sql.Types.LONGVARCHAR; + } else if (mysqlType.equalsIgnoreCase("ENUM")) { //$NON-NLS-1$ + return mysqlToJavaType(FIELD_TYPE_ENUM); + } else if (mysqlType.equalsIgnoreCase("SET")) { //$NON-NLS-1$ + return mysqlToJavaType(FIELD_TYPE_SET); + } else if (mysqlType.equalsIgnoreCase("GEOMETRY")) { + return mysqlToJavaType(FIELD_TYPE_GEOMETRY); + } else if (mysqlType.equalsIgnoreCase("BINARY")) { + return Types.BINARY; // no concrete type on the wire + } else if (mysqlType.equalsIgnoreCase("VARBINARY")) { + return Types.VARBINARY; // no concrete type on the wire + } else if (mysqlType.equalsIgnoreCase("BIT")) { + return mysqlToJavaType(FIELD_TYPE_BIT); + } + + // Punt + return java.sql.Types.OTHER; + } + + /** + * @param mysqlType + * @return + */ + public static String typeToName(int mysqlType) { + switch (mysqlType) { + case MysqlDefs.FIELD_TYPE_DECIMAL: + return "FIELD_TYPE_DECIMAL"; + + case MysqlDefs.FIELD_TYPE_TINY: + return "FIELD_TYPE_TINY"; + + case MysqlDefs.FIELD_TYPE_SHORT: + return "FIELD_TYPE_SHORT"; + + case MysqlDefs.FIELD_TYPE_LONG: + return "FIELD_TYPE_LONG"; + + case MysqlDefs.FIELD_TYPE_FLOAT: + return "FIELD_TYPE_FLOAT"; + + case MysqlDefs.FIELD_TYPE_DOUBLE: + return "FIELD_TYPE_DOUBLE"; + + case MysqlDefs.FIELD_TYPE_NULL: + return "FIELD_TYPE_NULL"; + + case MysqlDefs.FIELD_TYPE_TIMESTAMP: + return "FIELD_TYPE_TIMESTAMP"; + + case MysqlDefs.FIELD_TYPE_LONGLONG: + return "FIELD_TYPE_LONGLONG"; + + case MysqlDefs.FIELD_TYPE_INT24: + return "FIELD_TYPE_INT24"; + + case MysqlDefs.FIELD_TYPE_DATE: + return "FIELD_TYPE_DATE"; + + case MysqlDefs.FIELD_TYPE_TIME: + return "FIELD_TYPE_TIME"; + + case MysqlDefs.FIELD_TYPE_DATETIME: + return "FIELD_TYPE_DATETIME"; + + case MysqlDefs.FIELD_TYPE_YEAR: + return "FIELD_TYPE_YEAR"; + + case MysqlDefs.FIELD_TYPE_NEWDATE: + return "FIELD_TYPE_NEWDATE"; + + case MysqlDefs.FIELD_TYPE_ENUM: + return "FIELD_TYPE_ENUM"; + + case MysqlDefs.FIELD_TYPE_SET: + return "FIELD_TYPE_SET"; + + case MysqlDefs.FIELD_TYPE_TINY_BLOB: + return "FIELD_TYPE_TINY_BLOB"; + + case MysqlDefs.FIELD_TYPE_MEDIUM_BLOB: + return "FIELD_TYPE_MEDIUM_BLOB"; + + case MysqlDefs.FIELD_TYPE_LONG_BLOB: + return "FIELD_TYPE_LONG_BLOB"; + + case MysqlDefs.FIELD_TYPE_BLOB: + return "FIELD_TYPE_BLOB"; + + case MysqlDefs.FIELD_TYPE_VAR_STRING: + return "FIELD_TYPE_VAR_STRING"; + + case MysqlDefs.FIELD_TYPE_STRING: + return "FIELD_TYPE_STRING"; + + case MysqlDefs.FIELD_TYPE_VARCHAR: + return "FIELD_TYPE_VARCHAR"; + + case MysqlDefs.FIELD_TYPE_GEOMETRY: + return "FIELD_TYPE_GEOMETRY"; + + default: + return " Unknown MySQL Type # " + mysqlType; + } + } + + private static Map mysqlToJdbcTypesMap = new HashMap(); + + static { + mysqlToJdbcTypesMap.put("BIT", Integer.valueOf(mysqlToJavaType(FIELD_TYPE_BIT))); + + mysqlToJdbcTypesMap.put("TINYINT", Integer.valueOf(mysqlToJavaType(FIELD_TYPE_TINY))); + mysqlToJdbcTypesMap.put("SMALLINT", Integer.valueOf(mysqlToJavaType(FIELD_TYPE_SHORT))); + mysqlToJdbcTypesMap.put("MEDIUMINT", Integer.valueOf(mysqlToJavaType(FIELD_TYPE_INT24))); + mysqlToJdbcTypesMap.put("INT", Integer.valueOf(mysqlToJavaType(FIELD_TYPE_LONG))); + mysqlToJdbcTypesMap.put("INTEGER", Integer.valueOf(mysqlToJavaType(FIELD_TYPE_LONG))); + mysqlToJdbcTypesMap.put("BIGINT", Integer.valueOf(mysqlToJavaType(FIELD_TYPE_LONGLONG))); + mysqlToJdbcTypesMap.put("INT24", Integer.valueOf(mysqlToJavaType(FIELD_TYPE_INT24))); + mysqlToJdbcTypesMap.put("REAL", Integer.valueOf(mysqlToJavaType(FIELD_TYPE_DOUBLE))); + mysqlToJdbcTypesMap.put("FLOAT", Integer.valueOf(mysqlToJavaType(FIELD_TYPE_FLOAT))); + mysqlToJdbcTypesMap.put("DECIMAL", Integer.valueOf(mysqlToJavaType(FIELD_TYPE_DECIMAL))); + mysqlToJdbcTypesMap.put("NUMERIC", Integer.valueOf(mysqlToJavaType(FIELD_TYPE_DECIMAL))); + mysqlToJdbcTypesMap.put("DOUBLE", Integer.valueOf(mysqlToJavaType(FIELD_TYPE_DOUBLE))); + mysqlToJdbcTypesMap.put("CHAR", Integer.valueOf(mysqlToJavaType(FIELD_TYPE_STRING))); + mysqlToJdbcTypesMap.put("VARCHAR", Integer.valueOf(mysqlToJavaType(FIELD_TYPE_VAR_STRING))); + mysqlToJdbcTypesMap.put("DATE", Integer.valueOf(mysqlToJavaType(FIELD_TYPE_DATE))); + mysqlToJdbcTypesMap.put("TIME", Integer.valueOf(mysqlToJavaType(FIELD_TYPE_TIME))); + mysqlToJdbcTypesMap.put("YEAR", Integer.valueOf(mysqlToJavaType(FIELD_TYPE_YEAR))); + mysqlToJdbcTypesMap.put("TIMESTAMP", Integer.valueOf(mysqlToJavaType(FIELD_TYPE_TIMESTAMP))); + mysqlToJdbcTypesMap.put("DATETIME", Integer.valueOf(mysqlToJavaType(FIELD_TYPE_DATETIME))); + mysqlToJdbcTypesMap.put("TINYBLOB", Integer.valueOf(java.sql.Types.BINARY)); + mysqlToJdbcTypesMap.put("BLOB", Integer.valueOf(java.sql.Types.LONGVARBINARY)); + mysqlToJdbcTypesMap.put("MEDIUMBLOB", Integer.valueOf(java.sql.Types.LONGVARBINARY)); + mysqlToJdbcTypesMap.put("LONGBLOB", Integer.valueOf(java.sql.Types.LONGVARBINARY)); + mysqlToJdbcTypesMap.put("TINYTEXT", Integer.valueOf(java.sql.Types.VARCHAR)); + mysqlToJdbcTypesMap.put("TEXT", Integer.valueOf(java.sql.Types.LONGVARCHAR)); + mysqlToJdbcTypesMap.put("MEDIUMTEXT", Integer.valueOf(java.sql.Types.LONGVARCHAR)); + mysqlToJdbcTypesMap.put("LONGTEXT", Integer.valueOf(java.sql.Types.LONGVARCHAR)); + mysqlToJdbcTypesMap.put("ENUM", Integer.valueOf(mysqlToJavaType(FIELD_TYPE_ENUM))); + mysqlToJdbcTypesMap.put("SET", Integer.valueOf(mysqlToJavaType(FIELD_TYPE_SET))); + mysqlToJdbcTypesMap.put("GEOMETRY", Integer.valueOf(mysqlToJavaType(FIELD_TYPE_GEOMETRY))); + } + + static final void appendJdbcTypeMappingQuery(StringBuffer buf, String mysqlTypeColumnName) { + + buf.append("CASE "); + Map typesMap = new HashMap(); + typesMap.putAll(mysqlToJdbcTypesMap); + typesMap.put("BINARY", Integer.valueOf(Types.BINARY)); + typesMap.put("VARBINARY", Integer.valueOf(Types.VARBINARY)); + + Iterator mysqlTypes = typesMap.keySet().iterator(); + + while (mysqlTypes.hasNext()) { + String mysqlTypeName = mysqlTypes.next(); + buf.append(" WHEN "); + buf.append(mysqlTypeColumnName); + buf.append("='"); + buf.append(mysqlTypeName); + buf.append("' THEN "); + buf.append(typesMap.get(mysqlTypeName)); + + if (mysqlTypeName.equalsIgnoreCase("DOUBLE") || mysqlTypeName.equalsIgnoreCase("FLOAT") + || mysqlTypeName.equalsIgnoreCase("DECIMAL") || mysqlTypeName.equalsIgnoreCase("NUMERIC")) { + buf.append(" WHEN "); + buf.append(mysqlTypeColumnName); + buf.append("='"); + buf.append(mysqlTypeName); + buf.append(" unsigned' THEN "); + buf.append(typesMap.get(mysqlTypeName)); + } + } + + buf.append(" ELSE "); + buf.append(Types.OTHER); + buf.append(" END "); + + } +} diff --git a/mysql/codec/src/main/java/org/adbcj/mysql/codec/MysqlPreparedStatement.java b/mysql/codec/src/main/java/org/adbcj/mysql/codec/MysqlPreparedStatement.java new file mode 100644 index 00000000..9ed853d2 --- /dev/null +++ b/mysql/codec/src/main/java/org/adbcj/mysql/codec/MysqlPreparedStatement.java @@ -0,0 +1,133 @@ +package org.adbcj.mysql.codec; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +import org.adbcj.DbFuture; +import org.adbcj.DbSession; +import org.adbcj.PreparedStatement; +import org.adbcj.Result; +import org.adbcj.ResultSet; + + +/** + * mysql doesn't have the 'real' prepared statement . + * so here I just replace all question mark by params + * @author Whisper 2013Äê7ÔÂ4ÈÕ ÏÂÎç1:39:54 + * @since 3.0.1 + */ +public class MysqlPreparedStatement implements PreparedStatement{ + protected final String originalSql ; + protected final DbSession session; + protected final FastDateFormat fdf; + public MysqlPreparedStatement(String originalSql,DbSession session,FastDateFormat fdf){ + super(); + this.originalSql = originalSql; + this.session = session; + this.fdf = fdf; + } + + @Override + public List getParameterKeys() { + return null; + } + + @Override + public String getNativeSQL() { + return originalSql; + } + + @Override + public DbFuture executeQuery(Object... params) { + String newSql = replacePattern(originalSql,fdf, params); + return session.executeQuery(newSql); + } + public static final String replacePattern(String sql,FastDateFormat fdf,Object...params) { + StringBuilder sb = new StringBuilder(); + int quotationCount = 0; + int questionMarkCount = 0; + for(int i = 0; i DbFuture executeQuery(Map params) { + throw new UnsupportedOperationException("not supported yet !"); + } + + @Override + public DbFuture executeUpdate(Object... params) { + String newSql = replacePattern(originalSql,fdf, params); + return session.executeUpdate(newSql); + } + + @Override + public DbFuture executeUpdate(Map params) { + throw new UnsupportedOperationException("not supported yet !"); + } + +} diff --git a/mysql/codec/src/main/java/org/adbcj/mysql/codec/MysqlType.java b/mysql/codec/src/main/java/org/adbcj/mysql/codec/MysqlType.java index 1a62058b..0000114c 100644 --- a/mysql/codec/src/main/java/org/adbcj/mysql/codec/MysqlType.java +++ b/mysql/codec/src/main/java/org/adbcj/mysql/codec/MysqlType.java @@ -22,33 +22,37 @@ // TODO Make sure all the types are mapped up properly - Most of these are guesses public enum MysqlType { - DECIMAL(0x00, Type.DECIMAL), - TINY(0x01, Type.TINYINT), - SHORT(0x02, Type.SMALLINT), - LONG(0x03, Type.BIGINT), - FLOAT(0x04, Type.FLOAT), - DOUBLE(0x05, Type.DOUBLE), - NULL(0x06, Type.NULL), - TIMESTAMP(0x07, Type.TIMESTAMP), - LONGLONG(0x08, Type.BIGINT), - INT24(0x09, Type.INTEGER), - DATE(0x0a, Type.DATE), - TIME(0x0b, Type.TIME), - DATETIME(0x0c, Type.TIMESTAMP), - YEAR(0x0d, Type.INTEGER), - NEWDATE(0x0e, Type.DATE), - VARCHAR(0x0f, Type.VARCHAR), - BIT(0x10, Type.BIT), - NEWDECIMAL(0xf6, Type.DECIMAL), - ENUM(0xf7, Type.INTEGER), - SET(0xf8, Type.ARRAY), - TINY_BLOB(0xf9, Type.BLOB), - MEDIUM_BLOB(0xfa, Type.BLOB), - LONG_BLOB(0xfb, Type.BLOB), - BLOB(0xfc, Type.BLOB), - VAR_STRING(0xfd, Type.VARCHAR), - STRING(0xfe, Type.VARCHAR), - GEOMETRY(0xff, Type.STRUCT); + + SIGNED_DECIMAL(MysqlDefs.FIELD_TYPE_DECIMAL, Type.DECIMAL), + SIGNED_NEW_DECIMAL(MysqlDefs.FIELD_TYPE_NEW_DECIMAL,Type.DECIMAL), + SIGNED_TINY(MysqlDefs.FIELD_TYPE_TINY, Type.TINYINT), + SIGNED_SHORT(MysqlDefs.FIELD_TYPE_SHORT, Type.SMALLINT), + SIGNED_LONG(MysqlDefs.FIELD_TYPE_LONG, Type.INTEGER), + SIGNED_FLOAT(MysqlDefs.FIELD_TYPE_FLOAT, Type.FLOAT), + SIGNED_DOUBLE(MysqlDefs.FIELD_TYPE_DOUBLE, Type.DOUBLE), + SIGNED_NULL(MysqlDefs.FIELD_TYPE_NULL, Type.NULL), + SIGNED_TIMESTAMP(MysqlDefs.FIELD_TYPE_TIMESTAMP, Type.TIMESTAMP), + SIGNED_LONGLONG(MysqlDefs.FIELD_TYPE_LONGLONG, Type.LONG), + SIGNED_INT24(MysqlDefs.FIELD_TYPE_INT24, Type.INTEGER), + SIGNED_DATE(MysqlDefs.FIELD_TYPE_DATE, Type.DATE), + SIGNED_TIME(MysqlDefs.FIELD_TYPE_TIME, Type.TIME), + SIGNED_DATETIME(MysqlDefs.FIELD_TYPE_DATETIME, Type.TIMESTAMP), + SIGNED_YEAR(MysqlDefs.FIELD_TYPE_YEAR, Type.INTEGER), + SIGNED_NEWDATE(MysqlDefs.FIELD_TYPE_NEWDATE, Type.DATE), + SIGNED_VARCHAR(MysqlDefs.FIELD_TYPE_VARCHAR, Type.VARCHAR), + SIGNED_BIT(MysqlDefs.FIELD_TYPE_BIT, Type.BIT), + SIGNED_NEWDECIMAL(MysqlDefs.FIELD_TYPE_NEW_DECIMAL, Type.DECIMAL), + SIGNED_ENUM(MysqlDefs.FIELD_TYPE_ENUM, Type.INTEGER), + SIGNED_SET(MysqlDefs.FIELD_TYPE_SET, Type.ARRAY), + SIGNED_TINY_BLOB(MysqlDefs.FIELD_TYPE_TINY_BLOB, Type.BLOB), + SIGNED_MEDIUM_BLOB(MysqlDefs.FIELD_TYPE_MEDIUM_BLOB, Type.BLOB), + SIGNED_LONG_BLOB(MysqlDefs.FIELD_TYPE_LONG_BLOB, Type.BLOB), + SIGNED_BLOB(MysqlDefs.FIELD_TYPE_BLOB, Type.BLOB), + SIGNED_VAR_STRING(MysqlDefs.FIELD_TYPE_VAR_STRING, Type.VARCHAR), + SIGNED_STRING(MysqlDefs.FIELD_TYPE_STRING, Type.VARCHAR), + SIGNED_GEOMETRY(MysqlDefs.FIELD_TYPE_GEOMETRY, Type.STRUCT), + UNSIGNED_LONGLONG(MysqlDefs.FIELD_TYPE_LONGLONG,Type.BIGINT), + UNSIGNED_LONG(MysqlDefs.FIELD_TYPE_LONG,Type.LONG); private final int id; private final Type type; @@ -66,10 +70,21 @@ public Type getType() { return type; } - public static MysqlType findById(int id) { + public static MysqlType findById(int id,boolean unsign) { for (MysqlType type : values()) { if (id == type.id) { - return type; + if(unsign) + { + if(type == SIGNED_LONG) + { + return UNSIGNED_LONG; + } + else if(type == SIGNED_LONGLONG) + { + return UNSIGNED_LONGLONG; + } + } + return type; } } return null; diff --git a/mysql/codec/src/main/java/org/adbcj/mysql/codec/ProtocolHandler.java b/mysql/codec/src/main/java/org/adbcj/mysql/codec/ProtocolHandler.java index 0e78c494..d014427e 100644 --- a/mysql/codec/src/main/java/org/adbcj/mysql/codec/ProtocolHandler.java +++ b/mysql/codec/src/main/java/org/adbcj/mysql/codec/ProtocolHandler.java @@ -8,170 +8,186 @@ import org.adbcj.Result; import org.adbcj.ResultSet; import org.adbcj.Value; +import org.adbcj.support.AbstractDbSession.Request; import org.adbcj.support.DefaultDbFuture; import org.adbcj.support.DefaultResult; -import org.adbcj.support.AbstractDbSession.Request; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Asynchronous protocol handler suitable for use with frameworks like MINA or Netty. - * + * Asynchronous protocol handler suitable for use with frameworks like MINA or + * Netty. + * * @author Mike Heath */ public class ProtocolHandler { - private final Logger logger = LoggerFactory.getLogger(ProtocolHandler.class); - - public void connectionClosed(AbstractMySqlConnection connection) throws Exception { - logger.trace("IoSession closed"); - connection.doClose(); - } - - /** - * Handles an exception - * - * @param connection - * @param cause - * @return any exception that couldn't be handled, null if the exception was succesfully handled - * @throws Exception - */ - public Throwable handleException(AbstractMySqlConnection connection, Throwable cause) throws Exception { - logger.debug("Caught exception: ", cause); - - DbException dbException = DbException.wrap(connection, cause); - if (connection != null) { - DefaultDbFuture connectFuture = connection.getConnectFuture(); - if (!connectFuture.isDone()) { - connectFuture.setException(dbException); - return null; - } - Request activeRequest = connection.getActiveRequest(); - if (activeRequest != null) { - if (!activeRequest.isDone()) { - try { - activeRequest.error(dbException); - - return null; - } catch (Throwable e) { - return e; - } - } - } - } - return dbException; - } - - public void messageReceived(AbstractMySqlConnection connection, Object message) throws Exception { - logger.trace("Received message: {}", message); - if (message instanceof ServerGreeting) { - handleServerGreeting(connection, (ServerGreeting)message); - } else if (message instanceof OkResponse) { - handleOkResponse(connection, (OkResponse)message); - } else if (message instanceof ErrorResponse) { - handleErrorResponse(connection, (ErrorResponse)message); - } else if (message instanceof ResultSetResponse) { - handleResultSetResponse(connection, (ResultSetResponse)message); - } else if (message instanceof ResultSetFieldResponse) { - handleResultSetFieldResponse(connection, (ResultSetFieldResponse)message); - } else if (message instanceof ResultSetRowResponse) { - handleResultSetRowResponse(connection, (ResultSetRowResponse)message); - } else if (message instanceof EofResponse) { - handleEofResponse(connection, (EofResponse)message); - } else { - throw new IllegalStateException("Unable to handle message of type: " + message.getClass().getName()); - } - } - - private void handleServerGreeting(AbstractMySqlConnection connection, ServerGreeting serverGreeting) { - // TODO save the parts of the greeting that we might need (like the protocol version, etc.) - // Send Login request - LoginRequest request = new LoginRequest(connection.getCredentials(), connection.getClientCapabilities(), connection.getExtendedClientCapabilities(), connection.getCharacterSet(), serverGreeting.getSalt()); - connection.write(request); - } - - private void handleOkResponse(AbstractMySqlConnection connection, OkResponse response) { - logger.trace("Response '{}' on connection {}", response, connection); - - List warnings = null; - if (response.getWarningCount() > 0) { - warnings = new LinkedList(); - for (int i = 0; i < response.getWarningCount(); i++) { - warnings.add(response.getMessage()); - } - } - - logger.warn("Warnings: {}", warnings); - - Request activeRequest = connection.getActiveRequest(); - if (activeRequest == null) { - // TODO Do we need to pass the warnings on to the connection? - DefaultDbFuture connectFuture = connection.getConnectFuture(); - if (!connectFuture.isDone() ) { - connectFuture.setResult(connection); - - return; - } else { - throw new IllegalStateException("Received an OkResponse with no activeRequest " + response); - } - } - Result result = new DefaultResult(response.getAffectedRows(), warnings); - activeRequest.complete(result); - } - - private void handleErrorResponse(AbstractMySqlConnection connection, ErrorResponse message) { - throw new MysqlException(connection, message.getMessage()); - } - - private void handleResultSetResponse(AbstractMySqlConnection connection, ResultSetResponse message) { - Request activeRequest = connection.getActiveRequest(); - - if (activeRequest == null) { - throw new IllegalStateException("No active request for response: " + message); - } - - logger.debug("Start field definitions"); - activeRequest.getEventHandler().startFields(activeRequest.getAccumulator()); - } - - private void handleResultSetFieldResponse(AbstractMySqlConnection connection, ResultSetFieldResponse message) { - Request activeRequest = connection.getActiveRequest(); - - ResultSetFieldResponse fieldResponse = (ResultSetFieldResponse)message; - activeRequest.getEventHandler().field(fieldResponse.getField(), activeRequest.getAccumulator()); - } - - private void handleResultSetRowResponse(AbstractMySqlConnection connection, ResultSetRowResponse message) { - Request activeRequest = connection.getActiveRequest(); - - ResultSetRowResponse rowResponse = (ResultSetRowResponse)message; - - activeRequest.getEventHandler().startRow(activeRequest.getAccumulator()); - for (Value value : rowResponse.getValues()) { - activeRequest.getEventHandler().value(value, activeRequest.getAccumulator()); - } - activeRequest.getEventHandler().endRow(activeRequest.getAccumulator()); - } - - private void handleEofResponse(AbstractMySqlConnection connection, EofResponse message) { - logger.trace("Fetching active request in handleEofResponse()"); - Request activeRequest = connection.getActiveRequest(); - - if (activeRequest == null) { - throw new IllegalStateException("No active request for response: " + message); - } - - EofResponse eof = (EofResponse)message; - switch (eof.getType()) { - case FIELD: - activeRequest.getEventHandler().endFields(activeRequest.getAccumulator()); - break; - case ROW: - activeRequest.getEventHandler().endResults(activeRequest.getAccumulator()); - activeRequest.complete(activeRequest.getAccumulator()); - break; - default: - throw new MysqlException(connection, "Unkown eof response type"); - } - } + + private final Logger logger = LoggerFactory.getLogger(ProtocolHandler.class); + + public void connectionClosed(AbstractMySqlConnection connection) throws Exception { + logger.trace("IoSession closed"); + connection.doClose(); + } + + /** + * Handles an exception + * + * @param connection + * @param cause + * @return any exception that couldn't be handled, null if the exception was + * succesfully handled + * @throws Exception + */ + public Throwable handleException(AbstractMySqlConnection connection, Throwable cause) throws Exception { + logger.debug("Caught exception: ", cause); + + DbException dbException = DbException.wrap(connection, cause); + if (connection != null) { + DefaultDbFuture connectFuture = connection.getConnectFuture(); + if (!connectFuture.isDone()) { + connectFuture.setException(dbException); + return null; + } + Request activeRequest = connection.getActiveRequest(); + if (activeRequest != null) { + if (!activeRequest.isDone()) { + try { + activeRequest.error(dbException); + + return null; + } catch (Throwable e) { + return e; + } + } + } + } + return dbException; + } + + public void messageReceived(AbstractMySqlConnection connection, Object message) throws Exception { + logger.trace("Received message: {}", message); + if (message instanceof ServerGreeting) { + handleServerGreeting(connection, (ServerGreeting) message); + } else if (message instanceof OkResponse) { + handleOkResponse(connection, (OkResponse) message); + } else if (message instanceof ErrorResponse) { + handleErrorResponse(connection, (ErrorResponse) message); + } else if (message instanceof ResultSetResponse) { + handleResultSetResponse(connection, (ResultSetResponse) message); + } else if (message instanceof ResultSetFieldResponse) { + handleResultSetFieldResponse(connection, (ResultSetFieldResponse) message); + } else if (message instanceof ResultSetRowResponse) { + handleResultSetRowResponse(connection, (ResultSetRowResponse) message); + } else if (message instanceof EofResponse) { + handleEofResponse(connection, (EofResponse) message); + } else { + throw new IllegalStateException("Unable to handle message of type: " + message.getClass().getName()); + } + } + + private void handleServerGreeting(AbstractMySqlConnection connection, ServerGreeting serverGreeting) { + // TODO save the parts of the greeting that we might need (like the + // protocol version, etc.) + // Send Login request + LoginRequest request = new LoginRequest(connection.getCredentials(), + connection.getClientCapabilities(), + connection.getExtendedClientCapabilities(), + connection.getCharacterSet(), + serverGreeting.getSalt()); + connection.write(request); + } + + private void handleOkResponse(AbstractMySqlConnection connection, OkResponse response) { + logger.trace("Response '{}' on connection {}", response, connection); + + List warnings = null; + if (response.getWarningCount() > 0) { + warnings = new LinkedList(); + for (int i = 0; i < response.getWarningCount(); i++) { + warnings.add(response.getMessage()); + } + } + + logger.warn("Warnings: {}", warnings); + + Request activeRequest = connection.getActiveRequest(); + if (activeRequest == null) { + // TODO Do we need to pass the warnings on to the connection? + DefaultDbFuture connectFuture = connection.getConnectFuture(); + if (!connectFuture.isDone()) { + connectFuture.setResult(connection); + + return; + } else { + throw new IllegalStateException("Received an OkResponse with no activeRequest " + response); + } + } + Result result = new DefaultResult(response.getAffectedRows(), warnings); + activeRequest.complete(result); + } + + private void handleErrorResponse(AbstractMySqlConnection connection, ErrorResponse message) { + StringBuilder sb = new StringBuilder(); + if (!message.getMessage().isEmpty()) { + sb.append(message.getMessage()).append("\n"); + } + if (!message.getSqlState().isEmpty()) { + sb.append(message.getSqlState()).append("\n"); + } + + throw new MysqlException(connection, sb.toString()); + } + + private void handleResultSetResponse(AbstractMySqlConnection connection, ResultSetResponse message) { + Request activeRequest = connection.getActiveRequest(); + + if (activeRequest == null) { + throw new IllegalStateException("No active request for response: " + message); + } + + logger.debug("Start field definitions"); + activeRequest.getEventHandler().startFields(activeRequest.getAccumulator()); + } + + private void handleResultSetFieldResponse(AbstractMySqlConnection connection, ResultSetFieldResponse message) { + Request activeRequest = connection.getActiveRequest(); + + ResultSetFieldResponse fieldResponse = (ResultSetFieldResponse) message; + activeRequest.getEventHandler().field(fieldResponse.getField(), activeRequest.getAccumulator()); + } + + private void handleResultSetRowResponse(AbstractMySqlConnection connection, ResultSetRowResponse message) { + Request activeRequest = connection.getActiveRequest(); + + ResultSetRowResponse rowResponse = (ResultSetRowResponse) message; + + activeRequest.getEventHandler().startRow(activeRequest.getAccumulator()); + for (Value value : rowResponse.getValues()) { + activeRequest.getEventHandler().value(value, activeRequest.getAccumulator()); + } + activeRequest.getEventHandler().endRow(activeRequest.getAccumulator()); + } + + private void handleEofResponse(AbstractMySqlConnection connection, EofResponse message) { + logger.trace("Fetching active request in handleEofResponse()"); + Request activeRequest = connection.getActiveRequest(); + + if (activeRequest == null) { + throw new IllegalStateException("No active request for response: " + message); + } + + EofResponse eof = (EofResponse) message; + switch (eof.getType()) { + case FIELD: + activeRequest.getEventHandler().endFields(activeRequest.getAccumulator()); + break; + case ROW: + activeRequest.getEventHandler().endResults(activeRequest.getAccumulator()); + activeRequest.complete(activeRequest.getAccumulator()); + break; + default: + throw new MysqlException(connection, "Unkown eof response type"); + } + } } diff --git a/mysql/codec/src/main/java/org/adbcj/mysql/codec/SimpleBlob.java b/mysql/codec/src/main/java/org/adbcj/mysql/codec/SimpleBlob.java new file mode 100644 index 00000000..78be24be --- /dev/null +++ b/mysql/codec/src/main/java/org/adbcj/mysql/codec/SimpleBlob.java @@ -0,0 +1,162 @@ +package org.adbcj.mysql.codec; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.sql.Blob; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; + +/** + * @author + */ +public class SimpleBlob implements Blob { + + private final byte[] data; + + /** + * Constructor TDHSBlob creates a new TDHSBlob instance. + * + * @param data of type byte[] + */ + public SimpleBlob(byte[] data){ + this.data = data; + } + + /** + * Method length ... + * + * @return long + * @throws SQLException when + */ + public long length() throws SQLException { + return data != null ? data.length : 0; + } + + /** + * Method getBytes ... + * + * @param pos of type long + * @param length of type int + * @return byte[] + * @throws SQLException when + */ + public byte[] getBytes(long pos, int length) throws SQLException { + pos--; + if (data == null || data.length < pos + length) { + throw new SQLException("getBytes out of range! " + "the byte length[" + length() + "]," + + "request is pos [" + pos + "] length [" + length + "]"); + } + byte[] r = new byte[length]; + System.arraycopy(data, (int) pos, r, 0, length); + return r; + } + + /** + * Method getBinaryStream returns the binaryStream of this TDHSBlob object. + * + * @return the binaryStream (type InputStream) of this TDHSBlob object. + * @throws SQLException when + */ + public InputStream getBinaryStream() throws SQLException { + return data == null ? null : new ByteArrayInputStream(data); + } + + /** + * Method position ... + * + * @param pattern of type byte[] + * @param start of type long + * @return long + * @throws SQLException when + */ + public long position(byte[] pattern, long start) throws SQLException { + if (start < 1 || start > length()) { + throw new SQLException("start [" + start + "] is out of range!"); + } + throw new SQLFeatureNotSupportedException(); + } + + /** + * Method position ... + * + * @param pattern of type Blob + * @param start of type long + * @return long + * @throws SQLException when + */ + public long position(Blob pattern, long start) throws SQLException { + if (start < 1 || start > length()) { + throw new SQLException("start [" + start + "] is out of range!"); + } + throw new SQLFeatureNotSupportedException(); + } + + /** + * Method setBytes ... + * + * @param pos of type long + * @param bytes of type byte[] + * @return int + * @throws SQLException when + */ + public int setBytes(long pos, byte[] bytes) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + /** + * Method setBytes ... + * + * @param pos of type long + * @param bytes of type byte[] + * @param offset of type int + * @param len of type int + * @return int + * @throws SQLException when + */ + public int setBytes(long pos, byte[] bytes, int offset, int len) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + /** + * Method setBinaryStream ... + * + * @param pos of type long + * @return OutputStream + * @throws SQLException when + */ + public OutputStream setBinaryStream(long pos) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + /** + * Method truncate ... + * + * @param len of type long + * @throws SQLException when + */ + public void truncate(long len) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + /** + * Method free ... + * + * @throws SQLException when + */ + public void free() throws SQLException { + } + + /** + * Method getBinaryStream ... + * + * @param pos of type long + * @param length of type long + * @return InputStream + * @throws SQLException when + */ + public InputStream getBinaryStream(long pos, long length) throws SQLException { + byte[] bytes = getBytes(pos, (int) length); + return bytes == null ? null : new ByteArrayInputStream(bytes); + } +} diff --git a/mysql/codec/src/test/java/org/adbcj/mysql/codec/MysqlPreparedStatementUnitTest.java b/mysql/codec/src/test/java/org/adbcj/mysql/codec/MysqlPreparedStatementUnitTest.java new file mode 100644 index 00000000..fd8d58ce --- /dev/null +++ b/mysql/codec/src/test/java/org/adbcj/mysql/codec/MysqlPreparedStatementUnitTest.java @@ -0,0 +1,61 @@ +package org.adbcj.mysql.codec; + +import java.math.BigInteger; +import java.util.Date; + +import junit.framework.Assert; +import junit.framework.TestCase; + +import org.testng.annotations.Test; + + +public class MysqlPreparedStatementUnitTest extends TestCase { + @Test + public void test_normal() throws Exception + { + String format = "yyyy-MM-dd HH:mm:ss"; + FastDateFormat fdf = FastDateFormat.getInstance(format); + + String sql = "select * from table where id = ?"; + String after = null; + after = MysqlPreparedStatement.replacePattern(sql,fdf, 1); + Assert.assertEquals("select * from table where id = 1", after); + + after = MysqlPreparedStatement.replacePattern(sql,fdf, BigInteger.valueOf(1l)); + Assert.assertEquals("select * from table where id = 1", after); + + after = MysqlPreparedStatement.replacePattern(sql,fdf, 1.1f); + Assert.assertEquals("select * from table where id = 1.1", after); + + after = MysqlPreparedStatement.replacePattern(sql,fdf, 2.2d); + Assert.assertEquals("select * from table where id = 2.2", after); + + sql = "select * from table where id = ? and b = ?"; + after = MysqlPreparedStatement.replacePattern(sql,fdf, 1,"'asdf?'"); + Assert.assertEquals("select * from table where id = 1 and b = 'asdf?'", after); + + after = MysqlPreparedStatement.replacePattern(sql,fdf, 1,new Date(10000000l)); + Assert.assertEquals("select * from table where id = 1 and b = '1970-01-01 10:46:40'", after); + + after = MysqlPreparedStatement.replacePattern(sql,fdf, 1,"asdf?"); + Assert.assertEquals("select * from table where id = 1 and b = 'asdf?'", after); + + } + + @Test + public void test_exception() throws Exception + { + String format = "yyyy-MM-dd HH:mm:ss"; + FastDateFormat fdf = FastDateFormat.getInstance(format); + + String sql = "select * from table where id = ? and b = ?"; +// String after = null; + + try { + String after = MysqlPreparedStatement.replacePattern(sql, fdf, 1); + Assert.fail(); + } catch (Exception e) { + Assert.assertEquals("params' size is less than question mark in sql", e.getMessage()); + } + } +} diff --git a/mysql/mina/src/main/java/org/adbcj/mysql/mina/MysqlConnectionManagerFactory.java b/mysql/mina/src/main/java/org/adbcj/mysql/mina/MysqlConnectionManagerFactory.java index 7a5522f5..d86f1ac9 100644 --- a/mysql/mina/src/main/java/org/adbcj/mysql/mina/MysqlConnectionManagerFactory.java +++ b/mysql/mina/src/main/java/org/adbcj/mysql/mina/MysqlConnectionManagerFactory.java @@ -50,7 +50,6 @@ public ConnectionManager createConnectionManager(String url, String username, St } } - @Override public boolean canHandle(String protocol) { return PROTOCOL.equals(protocol) | PROTOCOL_MINA.equals(protocol); } diff --git a/mysql/mina/src/main/java/org/adbcj/mysql/mina/MysqlMessageEncoder.java b/mysql/mina/src/main/java/org/adbcj/mysql/mina/MysqlMessageEncoder.java index 8621aae7..cea5b8ad 100644 --- a/mysql/mina/src/main/java/org/adbcj/mysql/mina/MysqlMessageEncoder.java +++ b/mysql/mina/src/main/java/org/adbcj/mysql/mina/MysqlMessageEncoder.java @@ -31,6 +31,7 @@ public class MysqlMessageEncoder implements ProtocolEncoder { private final MySqlClientEncoder encoder = new MySqlClientEncoder(); + @Override public void dispose(IoSession session) throws Exception { // Nothing to dispose } diff --git a/mysql/netty/pom.xml b/mysql/netty/pom.xml index 6934f3dd..12c1d509 100644 --- a/mysql/netty/pom.xml +++ b/mysql/netty/pom.xml @@ -1,12 +1,10 @@ - + 4.0.0 - org.adbcj + org.adbcj mysql-build 0.2-SNAPSHOT @@ -21,10 +19,25 @@ netty ${netty.version} + + + org.slf4j + slf4j-api + + + + org.slf4j + slf4j-log4j12 + + ${project.groupId} mysql-codec + + org.slf4j + slf4j-log4j12 + diff --git a/mysql/netty/src/main/java/org/adbcj/mysql/netty/MySqlConnectionManagerFactory.java b/mysql/netty/src/main/java/org/adbcj/mysql/netty/MySqlConnectionManagerFactory.java index 0841c4d3..dfbbc68b 100644 --- a/mysql/netty/src/main/java/org/adbcj/mysql/netty/MySqlConnectionManagerFactory.java +++ b/mysql/netty/src/main/java/org/adbcj/mysql/netty/MySqlConnectionManagerFactory.java @@ -15,6 +15,10 @@ public class MySqlConnectionManagerFactory implements ConnectionManagerFactory { @Override public ConnectionManager createConnectionManager(String url, String username, String password, Properties properties) throws DbException { + String host = null; + int port = 0; + String schema = null; + try { /* * Parse URL @@ -23,8 +27,8 @@ public ConnectionManager createConnectionManager(String url, String username, St // Throw away the 'adbcj' protocol part of the URL uri = new URI(uri.getSchemeSpecificPart()); - String host = uri.getHost(); - int port = uri.getPort(); + host = uri.getHost(); + port = uri.getPort(); if (port < 0) { port = DEFAULT_PORT; } @@ -32,12 +36,13 @@ public ConnectionManager createConnectionManager(String url, String username, St if (path.length() == 0 || "/".equals(path)) { throw new DbException("You must specific a database in the URL path"); } - String schema = path.substring(1); + schema = path.substring(1); - return new MysqlConnectionManager(host, port, username, password, schema, properties); - } catch (URISyntaxException e) { + } catch (URISyntaxException e) { throw new DbException(e); } + return new MysqlConnectionManager(host, port, username, password, schema, properties); + } @Override diff --git a/mysql/netty/src/main/java/org/adbcj/mysql/netty/MysqlConnectionManager.java b/mysql/netty/src/main/java/org/adbcj/mysql/netty/MysqlConnectionManager.java index c2dad332..f63ae559 100644 --- a/mysql/netty/src/main/java/org/adbcj/mysql/netty/MysqlConnectionManager.java +++ b/mysql/netty/src/main/java/org/adbcj/mysql/netty/MysqlConnectionManager.java @@ -5,6 +5,7 @@ import java.util.Properties; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; import org.adbcj.Connection; import org.adbcj.mysql.codec.AbstractMySqlConnectionManager; @@ -40,116 +41,125 @@ public class MysqlConnectionManager extends AbstractMySqlConnectionManager { - private static final Logger logger = LoggerFactory.getLogger(MysqlConnectionManager.class); - - private static final String QUEUE_HANDLER = MysqlConnectionManager.class.getName() + ".queueHandler"; - private static final String ENCODER = MysqlConnectionManager.class.getName() + ".encoder"; - private static final String DECODER = MysqlConnectionManager.class.getName() + ".decoder"; - - private final ExecutorService executorService; - private final ClientBootstrap bootstrap; - - public MysqlConnectionManager(String host, int port, String username, String password, String schema, Properties properties) { - super(username, password, schema, properties); - executorService = Executors.newCachedThreadPool(); - - ChannelFactory factory = new NioClientSocketChannelFactory(executorService, executorService); - bootstrap = new ClientBootstrap(factory); - init(host, port); - } - - public MysqlConnectionManager(String host, int port, String username, String password, String schema, Properties properties, ChannelFactory factory) { - super(username, password, schema, properties); - executorService = null; - bootstrap = new ClientBootstrap(factory); - init(host, port); - } - - private void init(String host, int port) { - bootstrap.setPipelineFactory(new ChannelPipelineFactory() { - @Override - public ChannelPipeline getPipeline() throws Exception { - ChannelPipeline pipeline = Channels.pipeline(); - - pipeline.addFirst(QUEUE_HANDLER, new MessageQueuingHandler()); - - pipeline.addLast(DECODER, new Decoder()); - pipeline.addLast(ENCODER, new Encoder()); - - return pipeline; - } - }); - bootstrap.setOption("tcpNoDelay", true); - bootstrap.setOption("keepAlive", true); - bootstrap.setOption("remoteAddress", new InetSocketAddress(host, port)); - } - - @Override - protected void dispose() { - if (executorService != null) { - executorService.shutdownNow(); - } - } - - @Override - protected DefaultDbFuture createConnectionFuture() { - final ChannelFuture channelFuture = bootstrap.connect(); - return new MysqlConnectFuture(channelFuture); - } - - class MysqlConnectFuture extends DefaultDbFuture { - private final ChannelFuture channelFuture; - - public MysqlConnectFuture(ChannelFuture channelFuture) { - this.channelFuture = channelFuture; - channelFuture.addListener(new ChannelFutureListener() { - @Override - public void operationComplete(ChannelFuture future) throws Exception { - logger.debug("Connect completed"); - - Channel channel = future.getChannel(); - MysqlConnection connection = new MysqlConnection(MysqlConnectionManager.this, getCredentials(), channel, MysqlConnectFuture.this); - channel.getPipeline().addLast("handler", new Handler(connection)); - MessageQueuingHandler queuingHandler = channel.getPipeline().get(MessageQueuingHandler.class); - synchronized (queuingHandler) { - queuingHandler.flush(); - channel.getPipeline().remove(queuingHandler); - } - - } - }); - } - - @Override - protected boolean doCancel(boolean mayInterruptIfRunning) { - return channelFuture.cancel(); - } - } + private static final Logger logger = LoggerFactory.getLogger(MysqlConnectionManager.class); + + private static final String QUEUE_HANDLER = MysqlConnectionManager.class.getName() + ".queueHandler"; + private static final String ENCODER = MysqlConnectionManager.class.getName() + ".encoder"; + private static final String DECODER = MysqlConnectionManager.class.getName() + ".decoder"; + + private final ExecutorService executorService; + private final ClientBootstrap bootstrap; + + public MysqlConnectionManager(String host, int port, String username, String password, String schema, + Properties properties){ + super(username, password, schema, properties); + ThreadFactory threadFactory = new ThreadFactory() { + + public Thread newThread(Runnable r) { + Thread thd = new Thread(r); + thd.setDaemon(true); + return thd; + } + }; + executorService = Executors.newCachedThreadPool(threadFactory); + + ChannelFactory factory = new NioClientSocketChannelFactory(executorService, executorService); + bootstrap = new ClientBootstrap(factory); + init(host, port); + } + + public MysqlConnectionManager(String host, int port, String username, String password, String schema, + Properties properties, ChannelFactory factory){ + super(username, password, schema, properties); + executorService = null; + bootstrap = new ClientBootstrap(factory); + init(host, port); + } + + private void init(String host, int port) { + bootstrap.setPipelineFactory(new ChannelPipelineFactory() { + + public ChannelPipeline getPipeline() throws Exception { + ChannelPipeline pipeline = Channels.pipeline(); + + pipeline.addFirst(QUEUE_HANDLER, new MessageQueuingHandler()); + + pipeline.addLast(DECODER, new Decoder()); + pipeline.addLast(ENCODER, new Encoder()); + + return pipeline; + } + }); + bootstrap.setOption("tcpNoDelay", true); + bootstrap.setOption("keepAlive", true); + bootstrap.setOption("remoteAddress", new InetSocketAddress(host, port)); + } + + protected void dispose() { + if (executorService != null) { + executorService.shutdownNow(); + } + } + + protected DefaultDbFuture createConnectionFuture() { + final ChannelFuture channelFuture = bootstrap.connect(); + return new MysqlConnectFuture(channelFuture); + } + + class MysqlConnectFuture extends DefaultDbFuture { + + private final ChannelFuture channelFuture; + + public MysqlConnectFuture(ChannelFuture channelFuture){ + this.channelFuture = channelFuture; + channelFuture.addListener(new ChannelFutureListener() { + + public void operationComplete(ChannelFuture future) throws Exception { + logger.debug("Connect completed"); + + Channel channel = future.getChannel(); + MysqlConnection connection = new MysqlConnection(MysqlConnectionManager.this, + getCredentials(), + channel, + MysqlConnectFuture.this); + channel.getPipeline().addLast("handler", new Handler(connection)); + MessageQueuingHandler queuingHandler = channel.getPipeline().get(MessageQueuingHandler.class); + synchronized (queuingHandler) { + queuingHandler.flush(); + channel.getPipeline().remove(queuingHandler); + } + + } + }); + } + + protected boolean doCancel(boolean mayInterruptIfRunning) { + return channelFuture.cancel(); + } + } } @ChannelPipelineCoverage("one") class Decoder extends FrameDecoder { - private final MySqlClientDecoder decoder = new MySqlClientDecoder(); + private final MySqlClientDecoder decoder = new MySqlClientDecoder(); - @Override - protected Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) throws Exception { - InputStream in = new ChannelBufferInputStream(buffer); - try { - return decoder.decode(in, false); - } finally { - in.close(); - } - } + protected Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) throws Exception { + InputStream in = new ChannelBufferInputStream(buffer); + try { + return decoder.decode(in, false); + } finally { + in.close(); + } + } } -@ChannelPipelineCoverage("all") class Encoder implements ChannelDownstreamHandler { - private final MySqlClientEncoder encoder = new MySqlClientEncoder(); + private final MySqlClientEncoder encoder = new MySqlClientEncoder(); - public void handleDownstream(ChannelHandlerContext context, ChannelEvent event) throws Exception { + public void handleDownstream(ChannelHandlerContext context, ChannelEvent event) throws Exception { if (!(event instanceof MessageEvent)) { context.sendDownstream(event); return; @@ -163,38 +173,36 @@ public void handleDownstream(ChannelHandlerContext context, ChannelEvent event) ChannelBuffer buffer = ChannelBuffers.buffer(1024); ChannelBufferOutputStream out = new ChannelBufferOutputStream(buffer); - encoder.encode((ClientRequest) e.getMessage(), out); - Channels.write(context, e.getFuture(), buffer); - } + encoder.encode((ClientRequest) e.getMessage(), out); + Channels.write(context, e.getFuture(), buffer); + } } @ChannelPipelineCoverage("one") class Handler extends SimpleChannelHandler { - private final MysqlConnection connection; - private final ProtocolHandler handler = new ProtocolHandler(); - - public Handler(MysqlConnection connection) { - this.connection = connection; - } - - @Override - public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { - handler.messageReceived(connection, e.getMessage()); - } - - @Override - public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { - Throwable t = handler.handleException(connection, e.getCause()); - if (t != null) { - // TODO: Pass exception on to connectionManager - t.printStackTrace(); - } - } - - @Override - public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { - handler.connectionClosed(connection); - } - -} \ No newline at end of file + private final MysqlConnection connection; + private final ProtocolHandler handler = new ProtocolHandler(); + + public Handler(MysqlConnection connection){ + this.connection = connection; + } + + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { + handler.messageReceived(connection, e.getMessage()); + } + + public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { + Throwable t = handler.handleException(connection, e.getCause()); + if (t != null) { +// t.printStackTrace(); + e.getChannel().close(); + super.exceptionCaught(ctx, e); + } + } + + public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { + handler.connectionClosed(connection); + } + +} diff --git a/mysql/netty/src/test/java/org/adbcj/mysql/netty/ExceptionTest.java b/mysql/netty/src/test/java/org/adbcj/mysql/netty/ExceptionTest.java new file mode 100644 index 00000000..6ec3e800 --- /dev/null +++ b/mysql/netty/src/test/java/org/adbcj/mysql/netty/ExceptionTest.java @@ -0,0 +1,54 @@ +package org.adbcj.mysql.netty; + +import junit.framework.Assert; + +import org.adbcj.Connection; +import org.adbcj.ConnectionManager; +import org.adbcj.ConnectionManagerProvider; +import org.adbcj.DbSessionFuture; +import org.adbcj.Result; +import org.adbcj.ResultSet; +import org.testng.annotations.Test; + + +public class ExceptionTest { + + public static ConnectionManager cm = ConnectionManagerProvider.createConnectionManager("adbcj:mysqlnetty://localhost/unit_test", + "root", + ""); + public static final String DATE = "'1986-03-22 05:33:12'"; + + @Test + public void test_duplicate_exception() throws Exception { + Connection connection = cm.connect().get(); + DbSessionFuture result = connection.executeUpdate("delete from type_test"); + Assert.assertTrue(result.get().getAffectedRows() > -1); + String sql = "insert into type_test (" + "pk,varcharr,charr,blobr,integerr,integerr_unsigned,tinyintr,tinyintr_unsigned," + + "smallintr,smallintr_unsigned,mediumintr,mediumintr_unsigned,bitr,bigintr,bigintr_unsigned,floatr,doubler," + + "decimalr,dater,timer,datetimer,timestampr,yearr) values (" + "0,'varch','char','lob',100,100,4,4," + + "1,1,100,100,b'0',1000000,1000000,1.1,2.2,1000.1," + DATE + "," + DATE + "," + DATE + "," + DATE + "," + DATE + + ")"; + result = connection.executeUpdate(sql); + Assert.assertTrue(result.get().getAffectedRows() > -1); + + sql = "insert into type_test (" + "pk,varcharr,charr,blobr,integerr,integerr_unsigned,tinyintr,tinyintr_unsigned," + + "smallintr,smallintr_unsigned,mediumintr,mediumintr_unsigned,bitr,bigintr,bigintr_unsigned,floatr,doubler," + + "decimalr,dater,timer,datetimer,timestampr,yearr) values (" + "0,'varch','char','lob',100,100,4,4," + + "1,1,100,100,b'0',1000000,1000000,1.1,2.2,1000.1," + DATE + "," + DATE + "," + DATE + "," + DATE + "," + DATE + + ")"; + result = connection.executeUpdate(sql); + try { + Assert.assertTrue(result.get().getAffectedRows() > -1); + Assert.fail(); + } catch (Exception e) { + Assert.assertEquals("org.adbcj.mysql.codec.MysqlException: 23000Duplicate entry '0' for key 'PRIMARY'\n", e.getMessage()); + + } + + //make sure the connection can execute other request + sql = "select * from type_test"; + DbSessionFuture queryResult = connection.executeQuery(sql); + Assert.assertTrue(queryResult.get().size()>0); + connection.close(true); + } +} diff --git a/mysql/netty/src/test/java/org/adbcj/mysql/netty/TestType.java b/mysql/netty/src/test/java/org/adbcj/mysql/netty/TestType.java new file mode 100644 index 00000000..88794342 --- /dev/null +++ b/mysql/netty/src/test/java/org/adbcj/mysql/netty/TestType.java @@ -0,0 +1,200 @@ +package org.adbcj.mysql.netty; + +import java.io.InputStream; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.Blob; +import java.sql.Date; +import java.sql.Time; +import java.sql.Timestamp; + +import junit.framework.Assert; + +import org.adbcj.Connection; +import org.adbcj.ConnectionManager; +import org.adbcj.ConnectionManagerProvider; +import org.adbcj.DbFuture; +import org.adbcj.DbSessionFuture; +import org.adbcj.PreparedStatement; +import org.adbcj.Result; +import org.adbcj.ResultSet; +import org.adbcj.Row; +import org.adbcj.Value; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.Test; + +public class TestType { + + // public static ConnectionManager cm = + // ConnectionManagerProvider.createConnectionManager("adbcj:mysqlnetty://10.232.31.25:3309/unitTest", + // "test", + // "test"); + + public static ConnectionManager cm = ConnectionManagerProvider.createConnectionManager("adbcj:mysqlnetty://localhost/unit_test", + "root", + ""); + public static final String DATE = "'1986-03-22 05:33:12'"; + + public void prepare_notNull() throws Exception { + Connection connection = cm.connect().get(); + DbSessionFuture result = connection.executeUpdate("delete from type_test"); + Assert.assertTrue(result.get().getAffectedRows() > -1); + String sql = "insert into type_test (" + "pk,varcharr,charr,blobr,integerr,integerr_unsigned,tinyintr,tinyintr_unsigned," + + "smallintr,smallintr_unsigned,mediumintr,mediumintr_unsigned,bitr,bigintr,bigintr_unsigned,floatr,doubler," + + "decimalr,dater,timer,datetimer,timestampr,yearr) values (" + "0,'varch','char','lob',100,100,4,4," + + "1,1,100,100,b'0',1000000,1000000,1.1,2.2,1000.1," + DATE + "," + DATE + "," + DATE + "," + DATE + "," + DATE + + ")"; + result = connection.executeUpdate(sql); + Assert.assertTrue(result.get().getAffectedRows() > -1); + connection.close(true); + } + + public void prepare_Null() throws Exception { + Connection connection = cm.connect().get(); + DbSessionFuture result = connection.executeUpdate("delete from type_test"); + Assert.assertTrue(result.get().getAffectedRows() > -1); + String sql = "insert into type_test (" + "pk,varcharr,charr,blobr,integerr,integerr_unsigned,tinyintr,tinyintr_unsigned," + + "smallintr,smallintr_unsigned,mediumintr,mediumintr_unsigned,bitr,bigintr,bigintr_unsigned,floatr,doubler," + + "decimalr,dater,timer,datetimer,timestampr,yearr) values (" + "0,NULL,NULL,NULL,NULL,NULL,NULL,NULL," + + "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL" + + ")"; + result = connection.executeUpdate(sql); + Assert.assertTrue(result.get().getAffectedRows() > -1); + connection.close(true); + } + + @AfterMethod + public void tear() throws Exception { + Connection connection = cm.connect().get(); + DbSessionFuture result = connection.executeUpdate("delete from type_test"); + Assert.assertTrue(result.get().getAffectedRows() > -1); + connection.close(true); + } + + + + @Test + public void testNull() throws Exception { + prepare_Null(); + Connection connection = cm.connect().get(); + DbSessionFuture result = connection.executeQuery("select pk,varcharr,charr,blobr,integerr,integerr_unsigned,tinyintr,tinyintr_unsigned," + + "smallintr,smallintr_unsigned,mediumintr,mediumintr_unsigned,bitr,bigintr,bigintr_unsigned,floatr,doubler," + + "decimalr,dater,timer,datetimer,timestampr,yearr from type_test"); + ResultSet r = result.get(); + Row row = r.iterator().next(); + Value[] values = row.getValues(); + // pk + Assert.assertEquals(0, values[0].getValue()); + // varcharr varch + Assert.assertEquals(null, values[1].getValue()); + Assert.assertTrue(values[1].isNull()); + // charr 'char' + Assert.assertEquals(null, values[2].getValue()); + // blobr 'lob' + Assert.assertEquals(null, values[3].getValue()); + // ,integerr 100 + Assert.assertEquals(null, values[4].getValue()); + // ,integerr_unsign 100 + Assert.assertEquals(null, values[5].getValue()); + + // ,tinyintr, 4 + Assert.assertEquals(null, values[6].getValue()); + // ,tinyintr_unsign, 4 + Assert.assertEquals(null, values[7].getValue()); + + // "smallintr + Assert.assertEquals(null, values[8].getValue()); + // "smallintr_unsign + Assert.assertEquals(null, values[9].getValue()); + // ,mediumintr 100 + Assert.assertEquals(null, values[10].getValue()); + // ,mediumintr_unsign 100 + Assert.assertEquals(null, values[11].getValue()); + // ,bitr, 0 + Assert.assertEquals(null, ((byte[])values[12].getValue())); + // bigintr 1000000 + Assert.assertEquals(null, values[13].getValue()); + // bigintr 1000000 + Assert.assertEquals(null, values[14].getValue()); + // ,floatr + Assert.assertEquals(null, values[15].getValue()); + // ,doubler," 2.2 + Assert.assertEquals(null, values[16].getValue()); + // + "decimalr, + Assert.assertEquals(null, values[17].getValue()); + // dater + Assert.assertEquals(null, values[18].getValue()); + //timer + Assert.assertEquals(null, values[19].getValue()); + //datetimer + Assert.assertEquals(null, values[20].getValue()); + //,timestampr + Assert.assertEquals(null, values[21].getValue()); + //yearr + Assert.assertEquals(null, values[22].getValue()); + } + @Test + public void testType() throws Exception { + prepare_notNull(); + Connection connection = cm.connect().get(); + DbSessionFuture ps = connection.prepareStatement("select pk,varcharr,charr,blobr,integerr,integerr_unsigned,tinyintr,tinyintr_unsigned," + + "smallintr,smallintr_unsigned,mediumintr,mediumintr_unsigned,bitr,bigintr,bigintr_unsigned,floatr,doubler," + + "decimalr,dater,timer,datetimer,timestampr,yearr from type_test where pk = ?"); + DbFuture result = ps.get().executeQuery(0); + ResultSet r = result.get(); + Row row = r.iterator().next(); + Value[] values = row.getValues(); + // pk + Assert.assertEquals(0, values[0].getValue()); + // varcharr varch + Assert.assertEquals("varch", values[1].getValue()); + // charr 'char' + Assert.assertEquals("char", values[2].getValue()); + // blobr 'lob' + InputStream instream = ((Blob) values[3].getValue()).getBinaryStream(); + byte[] b = new byte[instream.available()]; + instream.read(b); + String str = new String(b); + Assert.assertEquals("lob", str); + // ,integerr 100 + Assert.assertEquals(100, values[4].getValue()); + // ,integerr_unsign 100 + Assert.assertEquals(100l, values[5].getValue()); + + // ,tinyintr, 4 + Assert.assertEquals(Integer.valueOf("4"), values[6].getValue()); + // ,tinyintr_unsign, 4 + Assert.assertEquals(Integer.valueOf("4"), values[7].getValue()); + + // "smallintr + Assert.assertEquals(1, values[8].getValue()); + // "smallintr_unsign + Assert.assertEquals(1, values[9].getValue()); + // ,mediumintr 100 + Assert.assertEquals(100, values[10].getValue()); + // ,mediumintr_unsign 100 + Assert.assertEquals(100, values[11].getValue()); + // ,bitr, 0 + Assert.assertEquals(Byte.valueOf("0").byteValue(), (byte)((byte[])values[12].getValue())[0]); + // bigintr 1000000 + Assert.assertEquals(1000000l, values[13].getValue()); + // bigintr 1000000 + Assert.assertEquals(BigInteger.valueOf(1000000l), values[14].getValue()); + // ,floatr + Assert.assertEquals(1.1f, values[15].getValue()); + // ,doubler," 2.2 + Assert.assertEquals(2.2d, values[16].getValue()); + // + "decimalr, + Assert.assertEquals(BigDecimal.valueOf(1000l), values[17].getValue()); + // dater + Assert.assertEquals(Date.valueOf("1986-03-22"), values[18].getValue()); + //timer + Assert.assertEquals(Time.valueOf("05:33:12"), values[19].getValue()); + //datetimer + Assert.assertEquals(Timestamp.valueOf("1986-03-22 05:33:12"), values[20].getValue()); + //,timestampr + Assert.assertEquals(Timestamp.valueOf("1986-03-22 05:33:12"), values[21].getValue()); + //yearr + Assert.assertEquals(1986, values[22].getValue()); + } +} diff --git a/mysql/netty/src/test/java/org/adbcj/mysql/netty/createTable.sql b/mysql/netty/src/test/java/org/adbcj/mysql/netty/createTable.sql new file mode 100644 index 00000000..a796a5eb --- /dev/null +++ b/mysql/netty/src/test/java/org/adbcj/mysql/netty/createTable.sql @@ -0,0 +1,28 @@ +create schema unit_test; +use unit_test; +CREATE TABLE `type_test` ( + `varcharr` varchar(255) DEFAULT NULL, + `charr` char(255) DEFAULT NULL, + `blobr` blob, + `integerr` int(11) DEFAULT NULL, + `integerr_unsigned` int(11) UNSIGNED DEFAULT NULL, + `tinyintr` tinyint(4) DEFAULT NULL, + `tinyintr_unsigned` tinyint(4) UNSIGNED DEFAULT NULL, + `smallintr` smallint(6) DEFAULT NULL, + `smallintr_unsigned` smallint(6) UNSIGNED DEFAULT NULL, + `mediumintr` mediumint(9) DEFAULT NULL, + `mediumintr_unsigned` mediumint(9) UNSIGNED DEFAULT NULL, + `bitr` bit(1) DEFAULT NULL, + `bigintr` bigint(20) DEFAULT NULL, + `bigintr_unsigned` bigint(20) UNSIGNED DEFAULT NULL, + `floatr` float DEFAULT NULL, + `doubler` double DEFAULT NULL, + `decimalr` decimal(10,0) DEFAULT NULL, + `dater` date DEFAULT NULL, + `timer` time DEFAULT NULL, + `datetimer` datetime DEFAULT NULL, + `timestampr` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, + `yearr` year(4) DEFAULT NULL, + `pk` int(11) NOT NULL, + PRIMARY KEY (`pk`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; \ No newline at end of file diff --git a/pom.xml b/pom.xml index b790fd03..df7c5ef0 100644 --- a/pom.xml +++ b/pom.xml @@ -79,7 +79,8 @@ 1.6 true - + 2.0.1 + org.apache.maven.plugins maven-surefire-plugin diff --git a/postgresql/mina/src/main/java/org/adbcj/postgresql/mina/MinaConnectionManager.java b/postgresql/mina/src/main/java/org/adbcj/postgresql/mina/MinaConnectionManager.java index 676f9487..0088c558 100644 --- a/postgresql/mina/src/main/java/org/adbcj/postgresql/mina/MinaConnectionManager.java +++ b/postgresql/mina/src/main/java/org/adbcj/postgresql/mina/MinaConnectionManager.java @@ -1,204 +1,202 @@ package org.adbcj.postgresql.mina; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.util.Properties; + +import org.adbcj.Connection; +import org.adbcj.DbException; +import org.adbcj.DbFuture; import org.adbcj.postgresql.codec.AbstractConnectionManager; -import org.adbcj.postgresql.codec.backend.BackendMessageDecoder; import org.adbcj.postgresql.codec.backend.AbstractBackendMessage; -import org.adbcj.postgresql.codec.frontend.FrontendMessageEncoder; +import org.adbcj.postgresql.codec.backend.BackendMessageDecoder; import org.adbcj.postgresql.codec.frontend.AbstractFrontendMessage; -import org.adbcj.postgresql.mina.IoHandler; -import org.adbcj.postgresql.mina.IoSessionUtil; -import org.adbcj.support.DefaultDbFuture; +import org.adbcj.postgresql.codec.frontend.FrontendMessageEncoder; import org.adbcj.support.DecoderInputStream; -import org.adbcj.Connection; -import org.adbcj.DbFuture; -import org.adbcj.DbException; -import org.apache.mina.core.session.IoSession; -import org.apache.mina.core.session.IoSessionInitializer; +import org.adbcj.support.DefaultDbFuture; +import org.apache.mina.core.buffer.IoBuffer; import org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder; import org.apache.mina.core.future.ConnectFuture; -import org.apache.mina.core.buffer.IoBuffer; -import org.apache.mina.filter.codec.ProtocolDecoder; +import org.apache.mina.core.session.IoSession; +import org.apache.mina.core.session.IoSessionInitializer; +import org.apache.mina.filter.codec.CumulativeProtocolDecoder; import org.apache.mina.filter.codec.ProtocolCodecFactory; -import org.apache.mina.filter.codec.ProtocolEncoder; import org.apache.mina.filter.codec.ProtocolCodecFilter; -import org.apache.mina.filter.codec.ProtocolEncoderOutput; +import org.apache.mina.filter.codec.ProtocolDecoder; import org.apache.mina.filter.codec.ProtocolDecoderOutput; -import org.apache.mina.filter.codec.CumulativeProtocolDecoder; +import org.apache.mina.filter.codec.ProtocolEncoder; +import org.apache.mina.filter.codec.ProtocolEncoderOutput; import org.apache.mina.transport.socket.nio.NioSocketConnector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Properties; -import java.net.InetSocketAddress; -import java.io.OutputStream; - /** * @author Mike Heath */ public class MinaConnectionManager extends AbstractConnectionManager { - public static final String CODEC_NAME = AbstractConnectionManager.class.getName() + ".codec"; - - private final Logger logger = LoggerFactory.getLogger(AbstractConnectionManager.class); - - private final NioSocketConnector socketConnector; - - // Access must be synchronized on 'this' - private DefaultDbFuture closeFuture = null; - - private volatile boolean pipeliningEnabled = true; - - private static final ProtocolCodecFactory CODEC_FACTORY = new ProtocolCodecFactory() { - public ProtocolDecoder getDecoder(IoSession session) throws Exception { - final MinaConnection connection = IoSessionUtil.getConnection(session); - return new CumulativeProtocolDecoder() { - - private final BackendMessageDecoder decoder = new BackendMessageDecoder(connection.getConnectionState()); - - @Override - protected boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception { - DecoderInputStream inputStream = new DecoderInputStream(in.asInputStream()); - while (true) { - inputStream.setLimit(Integer.MAX_VALUE); - AbstractBackendMessage message = decoder.decode(inputStream, false); - if (message == null) { - return false; - } - out.write(message); - } - } - - }; - } - public ProtocolEncoder getEncoder(IoSession session) throws Exception { - final MinaConnection connection = IoSessionUtil.getConnection(session); - return new ProtocolEncoder() { - - private final FrontendMessageEncoder encoder = new FrontendMessageEncoder(connection.getConnectionState()); - - @Override - public void dispose(IoSession ioSession) throws Exception { - // Do nothing. - } - - @Override - public void encode(IoSession ioSession, Object o, ProtocolEncoderOutput protocolEncoderOutput) throws Exception { - IoBuffer buffer = IoBuffer.allocate(4096); - OutputStream out = buffer.asOutputStream(); - if (o instanceof AbstractFrontendMessage) { - encoder.encode(out, (AbstractFrontendMessage)o); - } else if (o instanceof AbstractFrontendMessage[]) { - encoder.encode(out, (AbstractFrontendMessage[])o); - } else { - throw new IllegalStateException("Unkown message type for: " + o); - } - out.close(); - buffer.flip(); - protocolEncoderOutput.write(buffer); - protocolEncoderOutput.flush(); - } - }; - } - }; - - public MinaConnectionManager(String host, int port, String username, String password, String database, - Properties properties) { - super(username, password, database); - logger.debug("Creating new Postgresql ConnectionManager"); - - socketConnector = new NioSocketConnector(); - - socketConnector.getSessionConfig().setTcpNoDelay(true); - DefaultIoFilterChainBuilder filterChain = socketConnector.getFilterChain(); - - filterChain.addLast(CODEC_NAME, new ProtocolCodecFilter(CODEC_FACTORY)); - - socketConnector.setHandler(new IoHandler(this)); - - InetSocketAddress address = new InetSocketAddress(host, port); - socketConnector.setDefaultRemoteAddress(address); - } - - public DbFuture connect() { - if (isClosed()) { - throw new DbException("Connection manager closed"); - } - logger.debug("Starting connection"); - PgConnectFuture future = new PgConnectFuture(); - socketConnector.connect(future); - - logger.debug("Started connection"); - - return future; - } - - class PgConnectFuture extends DefaultDbFuture implements IoSessionInitializer { - - private boolean cancelled = false; - private boolean started = false; - - @Override - public synchronized void initializeSession(IoSession session, ConnectFuture future) { - if (cancelled) { - session.close(true); - return; - } - logger.debug("Creating AbstractConnection"); - - MinaConnection connection = new MinaConnection(MinaConnectionManager.this, this, session); - IoSessionUtil.setConnection(session, connection); - } - - @Override - protected synchronized boolean doCancel(boolean mayInterruptIfRunning) { - if (started) { - logger.debug("Can't cancel, connection already started"); - return false; - } - logger.debug("Cancelled connect"); - cancelled = true; - return true; - } - - } - - public synchronized DbFuture close(boolean immediate) throws DbException { - // TODO Put test in TCK to make sure all ConnectionManager connections get closed - if (isClosed()) { - return closeFuture; - } - closeFuture = new DefaultDbFuture(); - if (immediate) { - socketConnector.dispose(); - closeFuture.setResult(null); - } else { - // TODO Implement MinaConnectionManager.close(boolean) - throw new IllegalStateException("Non immediate finalizeClose not yet implemented"); - } - return closeFuture; - } - - public synchronized boolean isClosed() { - return closeFuture != null; - } - - public boolean isPipeliningEnabled() { - return pipeliningEnabled; - } - - public void setPipeliningEnabled(boolean pipeliningEnabled) { - this.pipeliningEnabled = pipeliningEnabled; - } - - // ================================================================================================================ - // - // Non-API methods - // - // ================================================================================================================ - - @Override - public String toString() { - return String.format("Postgresql (MINA) Connection Manager (Db: '%s', User: '%s')", getDatabase(), getUsername()); - } - - -} + public static final String CODEC_NAME = AbstractConnectionManager.class.getName() + ".codec"; + + private final Logger logger = LoggerFactory.getLogger(AbstractConnectionManager.class); + + private final NioSocketConnector socketConnector; + + // Access must be synchronized on 'this' + private DefaultDbFuture closeFuture = null; + + private volatile boolean pipeliningEnabled = true; + + private static final ProtocolCodecFactory CODEC_FACTORY = new ProtocolCodecFactory() { + public ProtocolDecoder getDecoder(IoSession session) throws Exception { + final MinaConnection connection = IoSessionUtil.getConnection(session); + return new CumulativeProtocolDecoder() { + + private final BackendMessageDecoder decoder = new BackendMessageDecoder(connection.getConnectionState()); + + @Override + protected boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception { + DecoderInputStream inputStream = new DecoderInputStream(in.asInputStream()); + while (true) { + inputStream.setLimit(Integer.MAX_VALUE); + AbstractBackendMessage message = decoder.decode(inputStream, false); + if (message == null) { + return false; + } + out.write(message); + } + } + + }; + } + public ProtocolEncoder getEncoder(IoSession session) throws Exception { + final MinaConnection connection = IoSessionUtil.getConnection(session); + return new ProtocolEncoder() { + + private final FrontendMessageEncoder encoder = new FrontendMessageEncoder(connection.getConnectionState()); + + @Override + public void dispose(IoSession ioSession) throws Exception { + // Do nothing. + } + + @Override + public void encode(IoSession ioSession, Object o, ProtocolEncoderOutput protocolEncoderOutput) throws Exception { + IoBuffer buffer = IoBuffer.allocate(4096); + OutputStream out = buffer.asOutputStream(); + if (o instanceof AbstractFrontendMessage) { + encoder.encode(out, (AbstractFrontendMessage)o); + } else if (o instanceof AbstractFrontendMessage[]) { + encoder.encode(out, (AbstractFrontendMessage[])o); + } else { + throw new IllegalStateException("Unkown message type for: " + o); + } + out.close(); + buffer.flip(); + protocolEncoderOutput.write(buffer); + protocolEncoderOutput.flush(); + } + }; + } + }; + + public MinaConnectionManager(String host, int port, String username, String password, String database, + Properties properties) { + super(username, password, database); + logger.debug("Creating new Postgresql ConnectionManager"); + + socketConnector = new NioSocketConnector(); + + socketConnector.getSessionConfig().setTcpNoDelay(true); + DefaultIoFilterChainBuilder filterChain = socketConnector.getFilterChain(); + + filterChain.addLast(CODEC_NAME, new ProtocolCodecFilter(CODEC_FACTORY)); + + socketConnector.setHandler(new IoHandler(this)); + + InetSocketAddress address = new InetSocketAddress(host, port); + socketConnector.setDefaultRemoteAddress(address); + } + + public DbFuture connect() { + if (isClosed()) { + throw new DbException("Connection manager closed"); + } + logger.debug("Starting connection"); + PgConnectFuture future = new PgConnectFuture(); + socketConnector.connect(future); + + logger.debug("Started connection"); + + return future; + } + + class PgConnectFuture extends DefaultDbFuture implements IoSessionInitializer { + + private boolean cancelled = false; + private boolean started = false; + + @Override + public synchronized void initializeSession(IoSession session, ConnectFuture future) { + if (cancelled) { + session.close(true); + return; + } + logger.debug("Creating AbstractConnection"); + + MinaConnection connection = new MinaConnection(MinaConnectionManager.this, this, session); + IoSessionUtil.setConnection(session, connection); + } + + @Override + protected synchronized boolean doCancel(boolean mayInterruptIfRunning) { + if (started) { + logger.debug("Can't cancel, connection already started"); + return false; + } + logger.debug("Cancelled connect"); + cancelled = true; + return true; + } + + } + + public synchronized DbFuture close(boolean immediate) throws DbException { + // TODO Put test in TCK to make sure all ConnectionManager connections get closed + if (isClosed()) { + return closeFuture; + } + closeFuture = new DefaultDbFuture(); + if (immediate) { + socketConnector.dispose(); + closeFuture.setResult(null); + } else { + // TODO Implement MinaConnectionManager.close(boolean) + throw new IllegalStateException("Non immediate finalizeClose not yet implemented"); + } + return closeFuture; + } + + public synchronized boolean isClosed() { + return closeFuture != null; + } + + public boolean isPipeliningEnabled() { + return pipeliningEnabled; + } + + public void setPipeliningEnabled(boolean pipeliningEnabled) { + this.pipeliningEnabled = pipeliningEnabled; + } + + // ================================================================================================================ + // + // Non-API methods + // + // ================================================================================================================ + + @Override + public String toString() { + return String.format("Postgresql (MINA) Connection Manager (Db: '%s', User: '%s')", getDatabase(), getUsername()); + } + + +} \ No newline at end of file diff --git a/postgresql/mina/src/main/java/org/adbcj/postgresql/mina/MinaConnectionManagerFactory.java b/postgresql/mina/src/main/java/org/adbcj/postgresql/mina/MinaConnectionManagerFactory.java index b9078481..5796733b 100644 --- a/postgresql/mina/src/main/java/org/adbcj/postgresql/mina/MinaConnectionManagerFactory.java +++ b/postgresql/mina/src/main/java/org/adbcj/postgresql/mina/MinaConnectionManagerFactory.java @@ -51,7 +51,6 @@ public ConnectionManager createConnectionManager(String url, String username, St } } - @Override public boolean canHandle(String protocol) { return PROTOCOL.equals(protocol); } diff --git a/postgresql/netty/src/main/java/org/adbcj/postgresql/netty/NettyConnectionManager.java b/postgresql/netty/src/main/java/org/adbcj/postgresql/netty/NettyConnectionManager.java index 8126aad3..f32c98ba 100644 --- a/postgresql/netty/src/main/java/org/adbcj/postgresql/netty/NettyConnectionManager.java +++ b/postgresql/netty/src/main/java/org/adbcj/postgresql/netty/NettyConnectionManager.java @@ -1,47 +1,47 @@ package org.adbcj.postgresql.netty; -import org.adbcj.DbFuture; +import java.io.InputStream; +import java.net.InetSocketAddress; +import java.util.Properties; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + import org.adbcj.DbException; -import org.adbcj.support.DecoderInputStream; -import org.adbcj.support.DefaultDbFuture; +import org.adbcj.DbFuture; +import org.adbcj.postgresql.codec.AbstractConnection; import org.adbcj.postgresql.codec.AbstractConnectionManager; import org.adbcj.postgresql.codec.ConnectionState; -import org.adbcj.postgresql.codec.AbstractConnection; import org.adbcj.postgresql.codec.ProtocolHandler; -import org.adbcj.postgresql.codec.backend.BackendMessageDecoder; import org.adbcj.postgresql.codec.backend.AbstractBackendMessage; -import org.adbcj.postgresql.codec.frontend.FrontendMessageEncoder; +import org.adbcj.postgresql.codec.backend.BackendMessageDecoder; import org.adbcj.postgresql.codec.frontend.AbstractFrontendMessage; -import org.jboss.netty.channel.ChannelFactory; -import org.jboss.netty.channel.Channels; -import org.jboss.netty.channel.ChannelPipelineCoverage; -import org.jboss.netty.channel.ChannelHandlerContext; +import org.adbcj.postgresql.codec.frontend.FrontendMessageEncoder; +import org.adbcj.support.DecoderInputStream; +import org.adbcj.support.DefaultDbFuture; +import org.jboss.netty.bootstrap.ClientBootstrap; +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBufferInputStream; +import org.jboss.netty.buffer.ChannelBufferOutputStream; +import org.jboss.netty.buffer.ChannelBuffers; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelDownstreamHandler; import org.jboss.netty.channel.ChannelEvent; -import org.jboss.netty.channel.MessageEvent; +import org.jboss.netty.channel.ChannelFactory; import org.jboss.netty.channel.ChannelFuture; import org.jboss.netty.channel.ChannelFutureListener; +import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.channel.ChannelPipeline; -import org.jboss.netty.channel.SimpleChannelHandler; -import org.jboss.netty.channel.ExceptionEvent; +import org.jboss.netty.channel.ChannelPipelineCoverage; import org.jboss.netty.channel.ChannelStateEvent; +import org.jboss.netty.channel.Channels; +import org.jboss.netty.channel.ExceptionEvent; +import org.jboss.netty.channel.MessageEvent; +import org.jboss.netty.channel.SimpleChannelHandler; import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory; -import org.jboss.netty.bootstrap.ClientBootstrap; import org.jboss.netty.handler.codec.frame.FrameDecoder; -import org.jboss.netty.buffer.ChannelBuffer; -import org.jboss.netty.buffer.ChannelBufferInputStream; -import org.jboss.netty.buffer.ChannelBuffers; -import org.jboss.netty.buffer.ChannelBufferOutputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Properties; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.net.InetSocketAddress; -import java.io.InputStream; - /** * @author Mike Heath */ @@ -283,4 +283,3 @@ public void handleDownstream(ChannelHandlerContext context, ChannelEvent event) Channels.write(context, e.getFuture(), buffer); } } - diff --git a/postgresql/netty/src/main/java/org/adbcj/postgresql/netty/NettyConnectionManagerFactory.java b/postgresql/netty/src/main/java/org/adbcj/postgresql/netty/NettyConnectionManagerFactory.java index c66420e2..58f43300 100644 --- a/postgresql/netty/src/main/java/org/adbcj/postgresql/netty/NettyConnectionManagerFactory.java +++ b/postgresql/netty/src/main/java/org/adbcj/postgresql/netty/NettyConnectionManagerFactory.java @@ -38,7 +38,6 @@ public ConnectionManager createConnectionManager(String url, String username, St } } - @Override public boolean canHandle(String protocol) { return PROTOCOL.equals(protocol); }