// jTDS JDBC Driver for Microsoft SQL Server and Sybase // Copyright (C) 2004 The jTDS Project // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library 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 // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA package net.sourceforge.jtds.jdbc; import java.io.*; import java.math.BigInteger; import java.math.BigDecimal; import java.sql.SQLException; import java.sql.Types; import net.sourceforge.jtds.util.BlobBuffer; /** * Implement TDS data types and related I/O logic. * <p> * Implementation notes: * <bl> * <li>This class encapsulates all the knowledge about reading and writing * TDS data descriptors and related application data. * <li>There are four key methods supplied here: * <ol> * <li>readType() - Reads the column and parameter meta data. * <li>readData() - Reads actual data values. * <li>writeParam() - Write parameter descriptors and data. * <li>getNativeType() - knows how to map JDBC data types to the equivalent TDS type. * </ol> * </bl> * * @author Mike Hutchinson * @author Alin Sinpalean * @author freeTDS project * @version $Id: TdsData.java,v 1.60.2.3 2009-11-05 10:42:18 ickzon Exp $ */ public class TdsData { /** * This class implements a descriptor for TDS data types; * * @author Mike Hutchinson. */ private static class TypeInfo { /** The SQL type name. */ public final String sqlType; /** * The size of this type or < 0 for variable sizes. * <p> Special values as follows: * <ol> * <li> -5 sql_variant type. * <li> -4 text, image or ntext types. * <li> -2 SQL Server 7+ long char and var binary types. * <li> -1 varchar, varbinary, null types. * </ol> */ public final int size; /** * The precision of the type. * <p>If this is -1 precision must be calculated from buffer size * eg for varchar fields. */ public final int precision; /** * The display size of the type. * <p>-1 If the display size must be calculated from the buffer size. */ public final int displaySize; /** true if type is a signed numeric. */ public final boolean isSigned; /** true if type requires TDS80 collation. */ public final boolean isCollation; /** The java.sql.Types constant for this data type. */ public final int jdbcType; /** * Construct a new TDS data type descriptor. * * @param sqlType SQL type name. * @param size Byte size for this type or < 0 for variable length types. * @param precision Decimal precision or -1 * @param displaySize Printout size for this type or special values -1,-2. * @param isSigned True if signed numeric type. * @param isCollation True if type has TDS 8 collation information. * @param jdbcType The java.sql.Type constant for this type. */ TypeInfo(String sqlType, int size, int precision, int displaySize, boolean isSigned, boolean isCollation, int jdbcType) { this.sqlType = sqlType; this.size = size; this.precision = precision; this.displaySize = displaySize; this.isSigned = isSigned; this.isCollation = isCollation; this.jdbcType = jdbcType; } } /* * Constants for TDS data types */ private static final int SYBCHAR = 47; // 0x2F private static final int SYBVARCHAR = 39; // 0x27 private static final int SYBINTN = 38; // 0x26 (MS: INTN) private static final int SYBINT1 = 48; // 0x30 (MS: INT1) private static final int SYBDATE = 49; // 0x31 Sybase 12 private static final int SYBTIME = 51; // 0x33 Sybase 12 private static final int SYBINT2 = 52; // 0x34 (MS: INT2) private static final int SYBINT4 = 56; // 0x38 (MS: INT4) private static final int SYBINT8 = 127;// 0x7F (MS: INT8) private static final int SYBFLT8 = 62; // 0x3E (MS: FLOAT8) private static final int SYBDATETIME = 61; // 0x3D (MS: DATETIME8) private static final int SYBBIT = 50; // 0x32 (MS: BIT1) private static final int SYBTEXT = 35; // 0x23 (MS: TEXT) private static final int SYBNTEXT = 99; // 0x63 (MS: NTEXT) private static final int SYBIMAGE = 34; // 0x22 (MS: IMAGE) private static final int SYBMONEY4 = 122;// 0x7A (MS: MONEY4) private static final int SYBMONEY = 60; // 0x3C (MS: MONEY8) private static final int SYBDATETIME4 = 58; // 0x3A (MS: DATETIME4) private static final int SYBREAL = 59; // 0x3B (MS: FLOAT4) private static final int SYBBINARY = 45; // 0x2D private static final int SYBVOID = 31; // 0x1F private static final int SYBVARBINARY = 37; // 0x25 private static final int SYBNVARCHAR = 103;// 0x67 private static final int SYBBITN = 104;// 0x68 (MS: BITN) private static final int SYBNUMERIC = 108;// 0x6C (MS: NUMERICN) private static final int SYBDECIMAL = 106;// 0x6A (MS: DECIMALN) private static final int SYBFLTN = 109;// 0x6D (MS: FLOATN) private static final int SYBMONEYN = 110;// 0x6E (MS: MONEYN) private static final int SYBDATETIMN = 111;// 0x6F (MS: DATETIMEN) private static final int SYBDATEN = 123;// 0x7B SYBASE 12 private static final int SYBTIMEN = 147;// 0x93 SYBASE 12 private static final int XSYBCHAR = 175;// 0xAF (MS: BIGCHAR) private static final int XSYBVARCHAR = 167;// 0xA7 (MS: BIGVARCHAR) private static final int XSYBNVARCHAR = 231;// 0xE7 (MS: NVARCHAR) private static final int XSYBNCHAR = 239;// 0xEF (MS: NCHAR) private static final int XSYBVARBINARY = 165;// 0xA5 (MS: BIGVARBINARY) private static final int XSYBBINARY = 173;// 0xAD (MS: BIGBINARY) private static final int SYBUNITEXT = 174;// 0xAE SYBASE 15 private static final int SYBLONGBINARY = 225;// 0xE1 SYBASE 12 private static final int SYBSINT1 = 64; // 0x40 private static final int SYBUINT2 = 65; // 0x41 SYBASE 15 private static final int SYBUINT4 = 66; // 0x42 SYBASE 15 private static final int SYBUINT8 = 67; // 0x43 SYBASE 15 private static final int SYBUINTN = 68; // 0x44 SYBASE 15 private static final int SYBUNIQUE = 36; // 0x24 (MS: GUID) private static final int SYBVARIANT = 98; // 0x62 (MS: SQL_VARIANT) private static final int SYBSINT8 = 191;// 0xBF SYBASE 15 // SQL Server user defined data type // private static final int UDT = 240; // XML data type introduced in SQL Server 2005 private static final int XML = 241; // time and date data types introduced in SQL Server 2008 private static final int DATEN = 40; // 0x28 MSSQL 2008 private static final int TIMEN = 41; // 0x29 MSSQL 2008 private static final int DATETIME2N = 42; // 0x2A MSSQL 2008 private static final int DATETIMEOFFSETN = 43; // 0x2B MSSQL 2008 /* * Special case for Sybase 12.5+ * This long data type is used to send text and image * data as statement parameters as a replacement for * writetext. * As far as I can tell this data type is only sent not * received. */ static final int SYBLONGDATA = 36; // 0x24 SYBASE 12 /* * Constants for Sybase User Defined data types used to * qualify the new longchar and longbinary types. */ // Common to Sybase and SQL Server private static final int UDT_CHAR = 1; // 0x01 private static final int UDT_VARCHAR = 2; // 0x02 private static final int UDT_BINARY = 3; // 0x03 private static final int UDT_VARBINARY = 4; // 0x04 private static final int UDT_SYSNAME = 18; // 0x12 // Sybase only private static final int UDT_NCHAR = 24; // 0x18 private static final int UDT_NVARCHAR = 25; // 0x19 private static final int UDT_UNICHAR = 34; // 0x22 private static final int UDT_UNIVARCHAR = 35; // 0x23 private static final int UDT_UNITEXT = 36; // 0x24 private static final int UDT_LONGSYSNAME = 42; // 0x2A private static final int UDT_TIMESTAMP = 80; // 0x50 // SQL Server 7+ private static final int UDT_NEWSYSNAME =256; // 0x100 /* * Constants for variable length data types */ private static final int VAR_MAX = 255; private static final int SYB_LONGVAR_MAX = 16384; private static final int MS_LONGVAR_MAX = 8000; private static final int SYB_CHUNK_SIZE = 8192; /** * Array of TDS data type descriptors. */ private final static TypeInfo types[] = new TypeInfo[256]; /** * Static block to initialize TDS data type descriptors. */ static { // SQL type precision signed Java type // | | | | // | | display | TDS8 | // | size | size | collation | // v v v v v v v types[SYBCHAR] = new TypeInfo( "char" , -1, -1, 1, false, false, Types.CHAR ); types[SYBVARCHAR] = new TypeInfo( "varchar" , -1, -1, 1, false, false, Types.VARCHAR ); types[SYBINTN] = new TypeInfo( "int" , -1, 10, 11, true , false, Types.INTEGER ); types[SYBINT1] = new TypeInfo( "tinyint" , 1, 3, 4, false, false, Types.TINYINT ); types[SYBINT2] = new TypeInfo( "smallint" , 2, 5, 6, true , false, Types.SMALLINT ); types[SYBINT4] = new TypeInfo( "int" , 4, 10, 11, true , false, Types.INTEGER ); types[SYBINT8] = new TypeInfo( "bigint" , 8, 19, 20, true , false, Types.BIGINT ); types[SYBFLT8] = new TypeInfo( "float" , 8, 15, 24, true , false, Types.DOUBLE ); types[SYBDATETIME] = new TypeInfo( "datetime" , 8, 23, 23, false, false, Types.TIMESTAMP ); types[SYBBIT] = new TypeInfo( "bit" , 1, 1, 1, false, false, Types.BIT ); types[SYBTEXT] = new TypeInfo( "text" , -4, -1, -1, false, true , Types.CLOB ); types[SYBNTEXT] = new TypeInfo( "ntext" , -4, -1, -1, false, true , Types.CLOB ); types[SYBUNITEXT] = new TypeInfo( "unitext" , -4, -1, -1, false, true , Types.CLOB ); types[SYBIMAGE] = new TypeInfo( "image" , -4, -1, -1, false, false, Types.BLOB ); types[SYBMONEY4] = new TypeInfo( "smallmoney" , 4, 10, 12, true , false, Types.DECIMAL ); types[SYBMONEY] = new TypeInfo( "money" , 8, 19, 21, true , false, Types.DECIMAL ); types[SYBDATETIME4] = new TypeInfo( "smalldatetime" , 4, 16, 19, false, false, Types.TIMESTAMP ); types[SYBREAL] = new TypeInfo( "real" , 4, 7, 14, true , false, Types.REAL ); types[SYBBINARY] = new TypeInfo( "binary" , -1, -1, 2, false, false, Types.BINARY ); types[SYBVOID] = new TypeInfo( "void" , -1, 1, 1, false, false, Types.NULL ); types[SYBVARBINARY] = new TypeInfo( "varbinary" , -1, -1, -1, false, false, Types.VARBINARY ); types[SYBNVARCHAR] = new TypeInfo( "nvarchar" , -1, -1, -1, false, false, Types.VARCHAR ); types[SYBBITN] = new TypeInfo( "bit" , -1, 1, 1, false, false, Types.BIT ); types[SYBNUMERIC] = new TypeInfo( "numeric" , -1, -1, -1, true , false, Types.NUMERIC ); types[SYBDECIMAL] = new TypeInfo( "decimal" , -1, -1, -1, true , false, Types.DECIMAL ); types[SYBFLTN] = new TypeInfo( "float" , -1, 15, 24, true , false, Types.DOUBLE ); types[SYBMONEYN] = new TypeInfo( "money" , -1, 19, 21, true , false, Types.DECIMAL ); types[SYBDATETIMN] = new TypeInfo( "datetime" , -1, 23, 23, false, false, Types.TIMESTAMP ); types[SYBDATE] = new TypeInfo( "date" , 4, 10, 10, false, false, Types.DATE ); types[SYBTIME] = new TypeInfo( "time" , 4, 8, 8, false, false, Types.TIME ); types[SYBDATEN] = new TypeInfo( "date" , -1, 10, 10, false, false, Types.DATE ); types[SYBTIMEN] = new TypeInfo( "time" , -1, 8, 8, false, false, Types.TIME ); types[XSYBCHAR] = new TypeInfo( "char" , -2, -1, -1, false, true , Types.CHAR ); types[XSYBVARCHAR] = new TypeInfo( "varchar" , -2, -1, -1, false, true , Types.VARCHAR ); types[XSYBNVARCHAR] = new TypeInfo( "nvarchar" , -2, -1, -1, false, true , Types.VARCHAR ); types[XSYBNCHAR] = new TypeInfo( "nchar" , -2, -1, -1, false, true , Types.CHAR ); types[XSYBVARBINARY] = new TypeInfo( "varbinary" , -2, -1, -1, false, false, Types.VARBINARY ); types[XSYBBINARY] = new TypeInfo( "binary" , -2, -1, -1, false, false, Types.BINARY ); types[SYBLONGBINARY] = new TypeInfo( "varbinary" , -5, -1, 2, false, false, Types.BINARY ); types[SYBSINT1] = new TypeInfo( "tinyint" , 1, 2, 3, false, false, Types.TINYINT ); types[SYBUINT2] = new TypeInfo( "unsigned smallint", 2, 5, 6, false, false, Types.INTEGER ); types[SYBUINT4] = new TypeInfo( "unsigned int" , 4, 10, 11, false, false, Types.BIGINT ); types[SYBUINT8] = new TypeInfo( "unsigned bigint" , 8, 20, 20, false, false, Types.DECIMAL ); types[SYBUINTN] = new TypeInfo( "unsigned int" , -1, 10, 11, true , false, Types.BIGINT ); types[SYBUNIQUE] = new TypeInfo( "uniqueidentifier" , -1, 36, 36, false, false, Types.CHAR ); types[SYBVARIANT] = new TypeInfo( "sql_variant" , -5, 0, 8000, false, false, Types.VARCHAR ); types[SYBSINT8] = new TypeInfo( "bigint" , 8, 19, 20, true , false, Types.BIGINT ); // XML data type introduced in SQL Server 2005 types[XML] = new TypeInfo( "xml" , -4, -1, -1, false, true , Types.SQLXML ); // time and date data types introduced in SQL Server 2008 types[DATEN] = new TypeInfo( "date" , 3, 10, 10, false, false, Types.DATE ); types[TIMEN] = new TypeInfo( "time" , -1, -1, -1, false, false, Types.TIME ); types[DATETIME2N] = new TypeInfo( "datetime2" , -1, -1, -1, false, false, Types.TIMESTAMP ); types[DATETIMEOFFSETN] = new TypeInfo( "datetimeoffset" , -1, -1, -1, false, false, Types.TIMESTAMP ); } /** Default Decimal Scale. */ static final int DEFAULT_SCALE = 10; /** Default precision for SQL Server 6.5 and 7. */ static final int DEFAULT_PRECISION_28 = 28; /** Default precision for Sybase and SQL Server 2000 and newer. */ static final int DEFAULT_PRECISION_38 = 38; /** * TDS 8 supplies collation information for character data types. * * @param in the server response stream * @param ci the column descriptor * @return the number of bytes read from the stream as an <code>int</code> */ static int getCollation(ResponseStream in, ColInfo ci) throws IOException { if (TdsData.isCollation(ci)) { // Read TDS8 collation info ci.collation = new byte[5]; in.read(ci.collation); return 5; } return 0; } /** * Set the <code>charsetInfo</code> field of <code>ci</code> according to * the value of its <code>collation</code> field. * <p> * The <code>Connection</code> is used to find out whether a specific * charset was requested. In this case, the column charset will be ignored. * * @param ci the <code>ColInfo</code> instance to update * @param connection a <code>Connection</code> instance to check whether it * has a fixed charset or not * @throws SQLException if a <code>CharsetInfo</code> is not found for this * particular column collation */ static void setColumnCharset(ColInfo ci, JtdsConnection connection) throws SQLException { if (connection.isCharsetSpecified()) { // If a charset was requested on connection creation, ignore the // column collation and use default ci.charsetInfo = connection.getCharsetInfo(); } else if (ci.collation != null) { // TDS version will be 8.0 or higher in this case and connection // collation will be non-null byte[] collation = ci.collation; byte[] defaultCollation = connection.getCollation(); int i; for (i = 0; i < 5; ++i) { if (collation[i] != defaultCollation[i]) { break; } } if (i == 5) { ci.charsetInfo = connection.getCharsetInfo(); } else { ci.charsetInfo = CharsetInfo.getCharset(collation); } } } /** * Read the TDS datastream and populate the ColInfo parameter with * data type and related information. * <p>The type infomation conforms to one of the following formats: * <ol> * <li> [int1 type] - eg SYBINT4. * <li> [int1 type] [int1 buffersize] - eg VARCHAR < 256 * <li> [int1 type] [int2 buffersize] - eg VARCHAR > 255. * <li> [int1 type] [int4 buffersize] [int1 tabnamelen] [int1*n tabname] - eg text. * <li> [int1 type] [int4 buffersize] - eg sql_variant. * <li> [int1 type] [int1 buffersize] [int1 precision] [int1 scale] - eg decimal. * </ol> * For TDS 8 large character types include a 5 byte collation field after the buffer size. * * @param in The server response stream. * @param ci The ColInfo column descriptor object. * @return The number of bytes read from the input stream. * @throws IOException * @throws ProtocolException */ static int readType(ResponseStream in, ColInfo ci) throws IOException, ProtocolException { int tdsVersion = in.getTdsVersion(); boolean isTds8 = tdsVersion >= Driver.TDS80; boolean isTds7 = tdsVersion >= Driver.TDS70; boolean isTds5 = tdsVersion == Driver.TDS50; boolean isTds42 = tdsVersion == Driver.TDS42; int bytesRead = 1; // Get the TDS data type code int type = in.read(); if (types[type] == null || (isTds5 && type == SYBLONGDATA)) { // Trap invalid type or 0x24 received from a Sybase server! throw new ProtocolException("Invalid TDS data type 0x" + Integer.toHexString(type & 0xFF)); } ci.tdsType = type; ci.jdbcType = types[type].jdbcType; ci.bufferSize = types[type].size; // Now get the buffersize if required if (ci.bufferSize == -5) { // sql_variant // Sybase long binary ci.bufferSize = in.readInt(); bytesRead += 4; } else if (ci.bufferSize == -4) { // text or image ci.bufferSize = in.readInt(); if (isTds8) { bytesRead += getCollation(in, ci); } int lenName = in.readShort(); ci.tableName = in.readString(lenName); bytesRead += 6 + ((in.getTdsVersion() >= Driver.TDS70) ? lenName * 2 : lenName); } else if (ci.bufferSize == -2) { // longvarchar longvarbinary if (isTds5 && ci.tdsType == XSYBCHAR) { ci.bufferSize = in.readInt(); bytesRead += 4; } else { ci.bufferSize = in.readShort(); bytesRead += 2; } if (isTds8) { bytesRead += getCollation(in, ci); } } else if (ci.bufferSize == -1) { // varchar varbinary decimal etc bytesRead += 1; ci.bufferSize = in.read(); } // Set default displaySize and precision ci.displaySize = types[type].displaySize; ci.precision = types[type].precision; ci.sqlType = types[type].sqlType; // Now fine tune sizes for specific types switch (type) { // // long datetime has scale of 3 smalldatetime has scale of 0 // case SYBDATETIME: ci.scale = 3; break; // Establish actual size of nullable datetime case SYBDATETIMN: if (ci.bufferSize == 8) { ci.displaySize = types[SYBDATETIME].displaySize; ci.precision = types[SYBDATETIME].precision; ci.scale = 3; } else { ci.displaySize = types[SYBDATETIME4].displaySize; ci.precision = types[SYBDATETIME4].precision; ci.sqlType = types[SYBDATETIME4].sqlType; ci.scale = 0; } break; // Establish actual size of nullable float case SYBFLTN: if (ci.bufferSize == 8) { ci.displaySize = types[SYBFLT8].displaySize; ci.precision = types[SYBFLT8].precision; } else { ci.displaySize = types[SYBREAL].displaySize; ci.precision = types[SYBREAL].precision; ci.jdbcType = Types.REAL; ci.sqlType = types[SYBREAL].sqlType; } break; // Establish actual size of nullable int case SYBINTN: if (ci.bufferSize == 8) { ci.displaySize = types[SYBINT8].displaySize; ci.precision = types[SYBINT8].precision; ci.jdbcType = Types.BIGINT; ci.sqlType = types[SYBINT8].sqlType; } else if (ci.bufferSize == 4) { ci.displaySize = types[SYBINT4].displaySize; ci.precision = types[SYBINT4].precision; } else if (ci.bufferSize == 2) { ci.displaySize = types[SYBINT2].displaySize; ci.precision = types[SYBINT2].precision; ci.jdbcType = Types.SMALLINT; ci.sqlType = types[SYBINT2].sqlType; } else { ci.displaySize = types[SYBINT1].displaySize; ci.precision = types[SYBINT1].precision; ci.jdbcType = Types.TINYINT; ci.sqlType = types[SYBINT1].sqlType; } break; // Establish actual size of nullable unsigned int case SYBUINTN: if (ci.bufferSize == 8) { ci.displaySize = types[SYBUINT8].displaySize; ci.precision = types[SYBUINT8].precision; ci.jdbcType = types[SYBUINT8].jdbcType; ci.sqlType = types[SYBUINT8].sqlType; } else if (ci.bufferSize == 4) { ci.displaySize = types[SYBUINT4].displaySize; ci.precision = types[SYBUINT4].precision; } else if (ci.bufferSize == 2) { ci.displaySize = types[SYBUINT2].displaySize; ci.precision = types[SYBUINT2].precision; ci.jdbcType = types[SYBUINT2].jdbcType; ci.sqlType = types[SYBUINT2].sqlType; } else { throw new ProtocolException("unsigned int null (size 1) not supported"); } break; // // Money types have a scale of 4 // case SYBMONEY: case SYBMONEY4: ci.scale = 4; break; // Establish actual size of nullable money case SYBMONEYN: if (ci.bufferSize == 8) { ci.displaySize = types[SYBMONEY].displaySize; ci.precision = types[SYBMONEY].precision; } else { ci.displaySize = types[SYBMONEY4].displaySize; ci.precision = types[SYBMONEY4].precision; ci.sqlType = types[SYBMONEY4].sqlType; } ci.scale = 4; break; // Read in scale and precision for decimal types case SYBDECIMAL: case SYBNUMERIC: ci.precision = in.read(); ci.scale = in.read(); ci.displaySize = ((ci.scale > 0) ? 2 : 1) + ci.precision; bytesRead += 2; ci.sqlType = types[type].sqlType; break; // Although a binary type force displaysize to MAXINT case SYBIMAGE: ci.precision = Integer.MAX_VALUE; ci.displaySize = Integer.MAX_VALUE; break; // Normal binaries have a display size of 2 * precision 0x0A0B etc case SYBLONGBINARY: case SYBVARBINARY: case SYBBINARY: case XSYBBINARY: case XSYBVARBINARY: ci.precision = ci.bufferSize; ci.displaySize = ci.precision * 2; break; // SQL Server unicode text can only display half as many chars case SYBNTEXT: ci.precision = Integer.MAX_VALUE / 2; ci.displaySize = Integer.MAX_VALUE / 2; break; // ASE 15+ unicode text can only display half as many chars case SYBUNITEXT: ci.precision = Integer.MAX_VALUE / 2; ci.displaySize = Integer.MAX_VALUE / 2; break; // SQL Server unicode chars can only display half as many chars case XSYBNCHAR: case XSYBNVARCHAR: ci.displaySize = ci.bufferSize / 2; ci.precision = ci.displaySize; break; // Normal characters display size = precision = buffer size. case SYBTEXT: case SYBCHAR: case XSYBCHAR: case XSYBVARCHAR: case SYBVARCHAR: case SYBNVARCHAR: ci.precision = ci.bufferSize; ci.displaySize = ci.precision; break; } // For numeric types add 'identity' for auto inc data type if (ci.isIdentity) { ci.sqlType += " identity"; } // Fine tune Sybase or SQL 6.5 data types if (isTds42 || isTds5) { switch (ci.userType) { case UDT_CHAR: ci.sqlType = "char"; ci.displaySize = ci.bufferSize; ci.jdbcType = Types.CHAR; break; case UDT_VARCHAR: ci.sqlType = "varchar"; ci.displaySize = ci.bufferSize; ci.jdbcType = Types.VARCHAR; break; case UDT_BINARY: ci.sqlType = "binary"; ci.displaySize = ci.bufferSize * 2; ci.jdbcType = Types.BINARY; break; case UDT_VARBINARY: ci.sqlType = "varbinary"; ci.displaySize = ci.bufferSize * 2; ci.jdbcType = Types.VARBINARY; break; case UDT_SYSNAME: ci.sqlType = "sysname"; ci.displaySize = ci.bufferSize; ci.jdbcType = Types.VARCHAR; break; case UDT_TIMESTAMP: ci.sqlType = "timestamp"; ci.displaySize = ci.bufferSize * 2; ci.jdbcType = Types.VARBINARY; break; } } // Fine tune Sybase data types if (isTds5) { switch (ci.userType) { case UDT_NCHAR: ci.sqlType = "nchar"; ci.displaySize = ci.bufferSize; ci.jdbcType = Types.CHAR; break; case UDT_NVARCHAR: ci.sqlType = "nvarchar"; ci.displaySize = ci.bufferSize; ci.jdbcType = Types.VARCHAR; break; case UDT_UNICHAR: ci.sqlType = "unichar"; ci.displaySize = ci.bufferSize / 2; ci.precision = ci.displaySize; ci.jdbcType = Types.CHAR; break; case UDT_UNIVARCHAR: ci.sqlType = "univarchar"; ci.displaySize = ci.bufferSize / 2; ci.precision = ci.displaySize; ci.jdbcType = Types.VARCHAR; break; case UDT_LONGSYSNAME: ci.sqlType = "longsysname"; ci.jdbcType = Types.VARCHAR; ci.displaySize = ci.bufferSize; break; } } // Fine tune SQL Server 7+ datatypes if (isTds7) { switch (ci.userType) { case UDT_TIMESTAMP: ci.sqlType = "timestamp"; ci.jdbcType = Types.BINARY; break; case UDT_NEWSYSNAME: ci.sqlType = "sysname"; ci.jdbcType = Types.VARCHAR; break; } } return bytesRead; } /** * Read the TDS data item from the Response Stream. * <p> The data size is either implicit in the type for example * fixed size integers, or a count field precedes the actual data. * The size of the count field varies with the data type. * * @param connection an object reference to the caller of this method; * must be a <code>Connection</code>, <code>Statement</code> or * <code>ResultSet</code> * @param in The server ResponseStream. * @param ci The ColInfo column descriptor object. * @return The data item Object or null. * @throws IOException * @throws ProtocolException */ static Object readData(JtdsConnection connection, ResponseStream in, ColInfo ci) throws IOException, ProtocolException { int len; switch (ci.tdsType) { case SYBINTN: switch (in.read()) { case 1: return new Integer(in.read() & 0xFF); case 2: return new Integer(in.readShort()); case 4: return new Integer(in.readInt()); case 8: return new Long(in.readLong()); } break; // Sybase ASE 15+ supports unsigned null smallint, int and bigint case SYBUINTN: switch (in.read()) { case 1: return new Integer(in.read() & 0xFF); case 2: return new Integer(in.readShort() & 0xFFFF); case 4: return new Long(in.readInt() & 0xFFFFFFFFL ); case 8: return in.readUnsignedLong(); } break; case SYBINT1: return new Integer(in.read() & 0xFF); case SYBINT2: return new Integer(in.readShort()); case SYBINT4: return new Integer(in.readInt()); // SQL Server bigint case SYBINT8: return new Long(in.readLong()); // Sybase ASE 15+ bigint case SYBSINT8: return new Long(in.readLong()); // Sybase ASE 15+ unsigned smallint case SYBUINT2: return new Integer(in.readShort() & 0xFFFF); // Sybase ASE 15+ unsigned int case SYBUINT4: return new Long(in.readInt() & 0xFFFFFFFFL); // Sybase ASE 15+ unsigned bigint case SYBUINT8: return in.readUnsignedLong(); case SYBIMAGE: len = in.read(); if (len > 0) { in.skip(24); // Skip textptr and timestamp int dataLen = in.readInt(); BlobImpl blob; if (dataLen == 0 && in.getTdsVersion() <= Driver.TDS50) { // Length of zero may indicate an initialized image // column that has been updated to null. break; } if (dataLen <= connection.getLobBuffer()) { // // OK Small enough to load into memory // byte[] data = new byte[dataLen]; in.read(data); blob = new BlobImpl(connection, data); } else { // Too big, need to write straight to disk try { blob = new BlobImpl(connection); OutputStream out = blob.setBinaryStream(1); byte[] buffer = new byte[1024]; int result; while ((result = in.read(buffer, 0, Math.min(dataLen, buffer.length))) != -1 && dataLen != 0) { out.write(buffer, 0, result); dataLen -= result; } out.close(); } catch (SQLException e) { // Transform setBinaryStream SQLException throw new IOException(e.getMessage()); } } return blob; } break; case SYBTEXT: len = in.read(); if (len > 0) { String charset; if (ci.charsetInfo != null) { charset = ci.charsetInfo.getCharset(); } else { charset = connection.getCharset(); } in.skip(24); // Skip textptr and timestamp int dataLen = in.readInt(); if (dataLen == 0 && in.getTdsVersion() <= Driver.TDS50) { // Length of zero may indicate an initialized text // column that has been updated to null. break; } ClobImpl clob = new ClobImpl(connection); BlobBuffer blobBuffer = clob.getBlobBuffer(); if (dataLen <= connection.getLobBuffer()) { // // OK Small enough to load into memory // BufferedReader rdr = new BufferedReader( new InputStreamReader(in.getInputStream(dataLen), charset), 1024); byte[] data = new byte[dataLen * 2]; int p = 0; int c; while ((c = rdr.read()) >= 0) { data[p++] = (byte)c; data[p++] = (byte)(c >> 8); } rdr.close(); blobBuffer.setBuffer(data, false); if (p == 2 && data[0] == 0x20 && data[1] == 0 && in.getTdsVersion() < Driver.TDS70) { // Single space with Sybase equates to empty string p = 0; } // Explicitly set length as multi byte character sets // may not fill array completely. blobBuffer.setLength(p); } else { // Too big, need to write straight to disk BufferedReader rdr = new BufferedReader( new InputStreamReader(in.getInputStream(dataLen), charset), 1024); try { OutputStream out = blobBuffer.setBinaryStream(1, false); int c; while ((c = rdr.read()) >= 0) { out.write(c); out.write(c >> 8); } out.close(); rdr.close(); } catch (SQLException e) { // Turn back into an IOException throw new IOException(e.getMessage()); } } return clob; } break; case SYBUNITEXT: // ASE 15+ unicode text type case SYBNTEXT: len = in.read(); if (len > 0) { in.skip(24); // Skip textptr and timestamp int dataLen = in.readInt(); if (dataLen == 0 && in.getTdsVersion() <= Driver.TDS50) { // Length of zero may indicate an initialized unitext // column that has been updated to null. break; } ClobImpl clob = new ClobImpl(connection); BlobBuffer blobBuffer = clob.getBlobBuffer(); if (dataLen <= connection.getLobBuffer()) { // // OK Small enough to load into memory // byte[] data = new byte[dataLen]; in.read(data); blobBuffer.setBuffer(data, false); if (dataLen == 2 && data[0] == 0x20 && data[1] == 0 && in.getTdsVersion() == Driver.TDS50) { // Single space with Sybase equates to empty string dataLen = 0; } // Explicitly set length as multi byte character sets // may not fill array completely. blobBuffer.setLength(dataLen); } else { // Too big, need to write straight to disk try { OutputStream out = blobBuffer.setBinaryStream(1, false); byte[] buffer = new byte[1024]; int result; while ((result = in.read(buffer, 0, Math.min(dataLen, buffer.length))) != -1 && dataLen != 0) { out.write(buffer, 0, result); dataLen -= result; } out.close(); } catch (SQLException e) { // Transform setBinaryStream SQLException throw new IOException(e.getMessage()); } } return clob; } break; case SYBCHAR: case SYBVARCHAR: len = in.read(); if (len > 0) { String value = in.readNonUnicodeString(len, ci.charsetInfo == null ? connection.getCharsetInfo() : ci.charsetInfo); if (len == 1 && ci.tdsType == SYBVARCHAR && in.getTdsVersion() < Driver.TDS70) { // In TDS 4/5 zero length varchars are stored as a // single space to distinguish them from nulls. return (" ".equals(value)) ? "" : value; } return value; } break; case SYBNVARCHAR: len = in.read(); if (len > 0) { return in.readUnicodeString(len / 2); } break; case XSYBCHAR: case XSYBVARCHAR: if (in.getTdsVersion() == Driver.TDS50) { // This is a Sybase wide table String len = in.readInt(); if (len > 0) { String tmp = in.readNonUnicodeString(len); if (" ".equals(tmp) && !"char".equals(ci.sqlType)) { tmp = ""; } return tmp; } } else { // This is a TDS 7+ long string len = in.readShort(); if (len != -1) { return in.readNonUnicodeString(len, ci.charsetInfo == null ? connection.getCharsetInfo() : ci.charsetInfo); } } break; case XSYBNCHAR: case XSYBNVARCHAR: len = in.readShort(); if (len != -1) { return in.readUnicodeString(len / 2); } break; case SYBVARBINARY: case SYBBINARY: len = in.read(); if (len > 0) { byte[] bytes = new byte[len]; in.read(bytes); return bytes; } break; case XSYBVARBINARY: case XSYBBINARY: len = in.readShort(); if (len != -1) { byte[] bytes = new byte[len]; in.read(bytes); return bytes; } break; case SYBLONGBINARY: len = in.readInt(); if (len != 0) { if ("unichar".equals(ci.sqlType) || "univarchar".equals(ci.sqlType)) { char[] buf = new char[len / 2]; in.read(buf); if ((len & 1) != 0) { // Bad length should be divisible by 2 in.skip(1); // Deal with it anyway. } if (len == 2 && buf[0] == ' ') { return ""; } else { return new String(buf); } } else { byte[] bytes = new byte[len]; in.read(bytes); return bytes; } } break; case SYBMONEY4: case SYBMONEY: case SYBMONEYN: return getMoneyValue(in, ci.tdsType); case SYBDATETIME4: case SYBDATETIMN: case SYBDATETIME: return getDatetimeValue(in, ci.tdsType); case SYBDATEN: case SYBDATE: len = (ci.tdsType == SYBDATEN)? in.read(): 4; if (len == 4) { return new DateTime(in.readInt(), DateTime.TIME_NOT_USED); } else { // Invalid length or 0 for null in.skip(len); } break; case SYBTIMEN: case SYBTIME: len = (ci.tdsType == SYBTIMEN)? in.read(): 4; if (len == 4) { return new DateTime(DateTime.DATE_NOT_USED, in.readInt()); } else { // Invalid length or 0 for null in.skip(len); } break; case SYBBIT: return (in.read() != 0) ? Boolean.TRUE : Boolean.FALSE; case SYBBITN: len = in.read(); if (len > 0) { return (in.read() != 0) ? Boolean.TRUE : Boolean.FALSE; } break; case SYBREAL: return new Float(Float.intBitsToFloat(in.readInt())); case SYBFLT8: return new Double(Double.longBitsToDouble(in.readLong())); case SYBFLTN: len = in.read(); if (len == 4) { return new Float(Float.intBitsToFloat(in.readInt())); } else if (len == 8) { return new Double(Double.longBitsToDouble(in.readLong())); } break; case SYBUNIQUE: len = in.read(); if (len > 0) { byte[] bytes = new byte[len]; in.read(bytes); return new UniqueIdentifier(bytes); } break; case SYBNUMERIC: case SYBDECIMAL: len = in.read(); if (len > 0) { int sign = in.read(); len--; byte[] bytes = new byte[len]; BigInteger bi; if (in.getServerType() == Driver.SYBASE) { // Sybase order is MSB first! for (int i = 0; i < len; i++) { bytes[i] = (byte) in.read(); } bi = new BigInteger((sign == 0) ? 1 : -1, bytes); } else { while (len-- > 0) { bytes[len] = (byte)in.read(); } bi = new BigInteger((sign == 0) ? -1 : 1, bytes); } return new BigDecimal(bi, ci.scale); } break; case SYBVARIANT: return getVariant(connection, in); default: throw new ProtocolException("Unsupported TDS data type 0x" + Integer.toHexString(ci.tdsType & 0xFF)); } return null; } /** * Retrieve the signed status of the column. * * @param ci the column meta data * @return <code>true</code> if the column is a signed numeric. */ static boolean isSigned(ColInfo ci) { int type = ci.tdsType; if (type < 0 || type > 255 || types[type] == null) { throw new IllegalArgumentException("TDS data type " + type + " invalid"); } if (type == TdsData.SYBINTN && ci.bufferSize == 1) { type = TdsData.SYBINT1; // Tiny int is not signed! } return types[type].isSigned; } /** * Retrieve the collation status of the column. * <p/> * TDS 8.0 character columns include collation information. * * @param ci the column meta data * @return <code>true</code> if the column requires collation data. */ static boolean isCollation(ColInfo ci) { int type = ci.tdsType; if (type < 0 || type > 255 || types[type] == null) { throw new IllegalArgumentException("TDS data type " + type + " invalid"); } return types[type].isCollation; } /** * Retrieve the currency status of the column. * * @param ci The column meta data. * @return <code>boolean</code> true if the column is a currency type. */ static boolean isCurrency(ColInfo ci) { int type = ci.tdsType; if (type < 0 || type > 255 || types[type] == null) { throw new IllegalArgumentException("TDS data type " + type + " invalid"); } return type == SYBMONEY || type == SYBMONEY4 || type == SYBMONEYN; } /** * Retrieve the searchable status of the column. * * @param ci the column meta data * @return <code>true</code> if the column is not a text or image type. */ static boolean isSearchable(ColInfo ci) { int type = ci.tdsType; if (type < 0 || type > 255 || types[type] == null) { throw new IllegalArgumentException("TDS data type " + type + " invalid"); } return types[type].size != -4; } /** * Determines whether the column is Unicode encoded. * * @param ci the column meta data * @return <code>true</code> if the column is Unicode encoded */ static boolean isUnicode(ColInfo ci) { int type = ci.tdsType; if (type < 0 || type > 255 || types[type] == null) { throw new IllegalArgumentException("TDS data type " + type + " invalid"); } switch (type) { case SYBNVARCHAR: case SYBNTEXT: case XSYBNCHAR: case XSYBNVARCHAR: case XSYBCHAR: // Not always case SYBVARIANT: // Not always return true; default: return false; } } /** * Fill in the TDS native type code and all other fields for a * <code>ColInfo</code> instance with the JDBC type set. * * @param ci the <code>ColInfo</code> instance */ static void fillInType(ColInfo ci) throws SQLException { switch (ci.jdbcType) { case Types.VARCHAR: ci.tdsType = SYBVARCHAR; ci.bufferSize = MS_LONGVAR_MAX; ci.displaySize = MS_LONGVAR_MAX; ci.precision = MS_LONGVAR_MAX; break; case Types.INTEGER: ci.tdsType = SYBINT4; ci.bufferSize = 4; ci.displaySize = 11; ci.precision = 10; break; case Types.SMALLINT: ci.tdsType = SYBINT2; ci.bufferSize = 2; ci.displaySize = 6; ci.precision = 5; break; case Types.BIT: ci.tdsType = SYBBIT; ci.bufferSize = 1; ci.displaySize = 1; ci.precision = 1; break; default: throw new SQLException(Messages.get( "error.baddatatype", Integer.toString(ci.jdbcType)), "HY000"); } ci.sqlType = types[ci.tdsType].sqlType; ci.scale = 0; } /** * Retrieve the TDS native type code for the parameter. * * @param connection the connectionJDBC object * @param pi the parameter descriptor */ static void getNativeType(JtdsConnection connection, ParamInfo pi) throws SQLException { int len; int jdbcType = pi.jdbcType; if (jdbcType == Types.OTHER) { jdbcType = Support.getJdbcType(pi.value); } switch (jdbcType) { case Types.CHAR: case Types.VARCHAR: case Types.LONGVARCHAR: case Types.CLOB: if (pi.value == null) { len = 0; } else { len = pi.length; } if (connection.getTdsVersion() < Driver.TDS70) { String charset = connection.getCharset(); if (len > 0 && (len <= SYB_LONGVAR_MAX / 2 || connection.getSybaseInfo(TdsCore.SYB_UNITEXT)) && connection.getSybaseInfo(TdsCore.SYB_UNICODE) && connection.getUseUnicode() && !"UTF-8".equals(charset)) { // Sybase can send values as unicode if conversion to the // server charset fails. // One option to determine if conversion will fail is to use // the CharSetEncoder class but this is only available from // JDK 1.4. // For now we will call a local method to see if the string // should be sent as unicode. // This behaviour can be disabled by setting the connection // property sendParametersAsUnicode=false. // TODO: Find a better way of testing for convertable charset. // With ASE 15 this code will read a CLOB into memory just to // check for unicode characters. This is wasteful if no unicode // data is present and we are targetting a text column. The option // of always sending unicode does not work as the server will // complain about image to text conversions unless the target // column actually is unitext. try { String tmp = pi.getString(charset); if (!canEncode(tmp, charset)) { // Conversion fails need to send as unicode. pi.length = tmp.length(); if (pi.length > SYB_LONGVAR_MAX / 2) { pi.sqlType = "unitext"; pi.tdsType = SYBLONGDATA; } else { pi.sqlType = "univarchar("+pi.length+')'; pi.tdsType = SYBLONGBINARY; } break; } } catch (IOException e) { throw new SQLException( Messages.get("error.generic.ioerror", e.getMessage()), "HY000"); } } // // If the client character set is wide then we need to ensure that the size // is within bounds even after conversion from Unicode // if (connection.isWideChar() && len <= SYB_LONGVAR_MAX) { try { byte tmp[] = pi.getBytes(charset); len = (tmp == null) ? 0 : tmp.length; } catch (IOException e) { throw new SQLException( Messages.get("error.generic.ioerror", e.getMessage()), "HY000"); } } if (len <= VAR_MAX) { pi.tdsType = SYBVARCHAR; pi.sqlType = "varchar(255)"; } else { if (connection.getSybaseInfo(TdsCore.SYB_LONGDATA)) { if (len > SYB_LONGVAR_MAX) { // Use special Sybase long data type which // allows text data to be sent as a statement parameter // (although not as a SP parameter). pi.tdsType = SYBLONGDATA; pi.sqlType = "text"; } else { // Use Sybase 12.5+ long varchar type which // is limited to 16384 bytes. pi.tdsType = XSYBCHAR; pi.sqlType = "varchar(" + len + ')'; } } else { pi.tdsType = SYBTEXT; pi.sqlType = "text"; } } } else { if (pi.isUnicode && len <= MS_LONGVAR_MAX / 2) { pi.tdsType = XSYBNVARCHAR; pi.sqlType = "nvarchar(4000)"; } else if (!pi.isUnicode && len <= MS_LONGVAR_MAX) { CharsetInfo csi = connection.getCharsetInfo(); try { if (len > 0 && csi.isWideChars() && pi.getBytes(csi.getCharset()).length > MS_LONGVAR_MAX) { pi.tdsType = SYBTEXT; pi.sqlType = "text"; } else { pi.tdsType = XSYBVARCHAR; pi.sqlType = "varchar(8000)"; } } catch (IOException e) { throw new SQLException( Messages.get("error.generic.ioerror", e.getMessage()), "HY000"); } } else { if (pi.isOutput) { throw new SQLException( Messages.get("error.textoutparam"), "HY000"); } if (pi.isUnicode) { pi.tdsType = SYBNTEXT; pi.sqlType = "ntext"; } else { pi.tdsType = SYBTEXT; pi.sqlType = "text"; } } } break; case Types.TINYINT: case Types.SMALLINT: case Types.INTEGER: pi.tdsType = SYBINTN; pi.sqlType = "int"; break; case JtdsStatement.BOOLEAN: case Types.BIT: if (connection.getTdsVersion() >= Driver.TDS70 || connection.getSybaseInfo(TdsCore.SYB_BITNULL)) { pi.tdsType = SYBBITN; } else { pi.tdsType = SYBBIT; } pi.sqlType = "bit"; break; case Types.REAL: pi.tdsType = SYBFLTN; pi.sqlType = "real"; break; case Types.FLOAT: case Types.DOUBLE: pi.tdsType = SYBFLTN; pi.sqlType = "float"; break; case Types.DATE: if (connection.getSybaseInfo(TdsCore.SYB_DATETIME)) { pi.tdsType = SYBDATEN; pi.sqlType = "date"; } else { pi.tdsType = SYBDATETIMN; pi.sqlType = "datetime"; } break; case Types.TIME: if (connection.getSybaseInfo(TdsCore.SYB_DATETIME)) { pi.tdsType = SYBTIMEN; pi.sqlType = "time"; } else { pi.tdsType = SYBDATETIMN; pi.sqlType = "datetime"; } break; case Types.TIMESTAMP: pi.tdsType = SYBDATETIMN; pi.sqlType = "datetime"; break; case Types.BINARY: case Types.VARBINARY: case Types.BLOB: case Types.LONGVARBINARY: if (pi.value == null) { len = 0; } else { len = pi.length; } if (connection.getTdsVersion() < Driver.TDS70) { if (len <= VAR_MAX) { pi.tdsType = SYBVARBINARY; pi.sqlType = "varbinary(255)"; } else { if (connection.getSybaseInfo(TdsCore.SYB_LONGDATA)) { if (len > SYB_LONGVAR_MAX) { // Need to use special Sybase long binary type pi.tdsType = SYBLONGDATA; pi.sqlType = "image"; } else { // Sybase long binary that can be used as a SP parameter pi.tdsType = SYBLONGBINARY; pi.sqlType = "varbinary(" + len + ")"; } } else { // Sybase < 12.5 or SQL Server 6.5 pi.tdsType = SYBIMAGE; pi.sqlType = "image"; } } } else { if (len <= MS_LONGVAR_MAX) { pi.tdsType = XSYBVARBINARY; pi.sqlType = "varbinary(8000)"; } else { if (pi.isOutput) { throw new SQLException( Messages.get("error.textoutparam"), "HY000"); } pi.tdsType = SYBIMAGE; pi.sqlType = isMSSQL2005Plus(connection) ? "varbinary(max)" : "image"; } } break; case Types.BIGINT: if (connection.getTdsVersion() >= Driver.TDS80 || connection.getSybaseInfo(TdsCore.SYB_BIGINT)) { pi.tdsType = SYBINTN; pi.sqlType = "bigint"; } else { // int8 not supported send as a decimal field pi.tdsType = SYBDECIMAL; pi.sqlType = "decimal(" + connection.getMaxPrecision() + ')'; pi.scale = 0; } break; case Types.DECIMAL: case Types.NUMERIC: pi.tdsType = SYBDECIMAL; int prec = connection.getMaxPrecision(); int scale = DEFAULT_SCALE; if (pi.value instanceof BigDecimal) { scale = ((BigDecimal)pi.value).scale(); } else if (pi.scale >= 0 && pi.scale <= prec) { scale = pi.scale; } pi.sqlType = "decimal(" + prec + ',' + scale + ')'; break; case Types.OTHER: case Types.NULL: // Send a null String in the absence of anything better pi.tdsType = SYBVARCHAR; pi.sqlType = "varchar(255)"; break; case Types.SQLXML: len = pi.value == null ? 0 : pi.length; if (connection.getTdsVersion() >= Driver.TDS80 ) { pi.tdsType = XML; pi.sqlType = "xml"; } else if( connection.getTdsVersion() < Driver.TDS70 ) { if( len <= VAR_MAX ) { pi.tdsType = SYBVARBINARY; pi.sqlType = "varbinary(255)"; } else { if( connection.getSybaseInfo( TdsCore.SYB_LONGDATA ) ) { if( len > SYB_LONGVAR_MAX ) { // Need to use special Sybase long binary type pi.tdsType = SYBLONGDATA; pi.sqlType = "image"; } else { // Sybase long binary that can be used as a SP parameter pi.tdsType = SYBLONGBINARY; pi.sqlType = "varbinary(" + len + ')'; } } else { // Sybase < 12.5 or SQL Server 6.5 pi.tdsType = SYBIMAGE; pi.sqlType = "image"; } } } else { if( len <= MS_LONGVAR_MAX ) { pi.tdsType = XSYBVARBINARY; pi.sqlType = "varbinary(8000)"; } else { if( pi.isOutput ) throw new SQLException( Messages.get( "error.textoutparam" ), "HY000" ); pi.tdsType = SYBIMAGE; pi.sqlType = isMSSQL2005Plus(connection) ? "varbinary(max)" : "image"; } } break; default: throw new SQLException(Messages.get( "error.baddatatype", Integer.toString(pi.jdbcType)), "HY000"); } } /** * Calculate the size of the parameter descriptor array for TDS 5 packets. * * @param charset The encoding character set. * @param isWideChar True if multi byte encoding. * @param pi The parameter to describe. * @param useParamNames True if named parameters should be used. * @return The size of the parameter descriptor as an <code>int</code>. */ static int getTds5ParamSize(String charset, boolean isWideChar, ParamInfo pi, boolean useParamNames) { int size = 8; if (pi.name != null && useParamNames) { // Size of parameter name if (isWideChar) { byte[] buf = Support.encodeString(charset, pi.name); size += buf.length; } else { size += pi.name.length(); } } switch (pi.tdsType) { case SYBVARCHAR: case SYBVARBINARY: case SYBINTN: case SYBFLTN: case SYBDATETIMN: case SYBDATEN: case SYBTIMEN: size += 1; break; case SYBDECIMAL: case SYBLONGDATA: size += 3; break; case XSYBCHAR: case SYBLONGBINARY: size += 4; break; case SYBBIT: break; default: throw new IllegalStateException("Unsupported output TDS type 0x" + Integer.toHexString(pi.tdsType)); } return size; } /** * Write a TDS 5 parameter format descriptor. * * @param out The server RequestStream. * @param charset The encoding character set. * @param isWideChar True if multi byte encoding. * @param pi The parameter to describe. * @param useParamNames True if named parameters should be used. * @throws IOException */ static void writeTds5ParamFmt(RequestStream out, String charset, boolean isWideChar, ParamInfo pi, boolean useParamNames) throws IOException { if (pi.name != null && useParamNames) { // Output parameter name. if (isWideChar) { byte[] buf = Support.encodeString(charset, pi.name); out.write((byte) buf.length); out.write(buf); } else { out.write((byte) pi.name.length()); out.write(pi.name); } } else { out.write((byte)0); } out.write((byte) (pi.isOutput ? 1 : 0)); // Output param if (pi.sqlType.startsWith("univarchar")) { out.write(UDT_UNIVARCHAR); } else if ("unitext".equals(pi.sqlType)) { out.write(UDT_UNITEXT); } else { out.write(0); // user type } out.write((byte) pi.tdsType); // TDS data type token // Output length fields switch (pi.tdsType) { case SYBVARCHAR: case SYBVARBINARY: out.write((byte) VAR_MAX); break; case XSYBCHAR: out.write(0x7FFFFFFF); break; case SYBLONGDATA: // It appears that type 3 = send text data // and type 4 = send image or unitext data // No idea if there is a type 1/2 or what they are. out.write("text".equals(pi.sqlType) ? (byte) 3 : (byte) 4); out.write((byte)0); out.write((byte)0); break; case SYBLONGBINARY: out.write(0x7FFFFFFF); break; case SYBBIT: break; case SYBINTN: out.write("bigint".equals(pi.sqlType) ? (byte) 8: (byte) 4); break; case SYBFLTN: if (pi.value instanceof Float) { out.write((byte) 4); } else { out.write((byte) 8); } break; case SYBDATETIMN: out.write((byte) 8); break; case SYBDATEN: case SYBTIMEN: out.write((byte)4); break; case SYBDECIMAL: out.write((byte) 17); out.write((byte) 38); if (pi.jdbcType == Types.BIGINT) { out.write((byte) 0); } else { if (pi.value instanceof BigDecimal) { out.write((byte) ((BigDecimal) pi.value).scale()); } else { if (pi.scale >= 0 && pi.scale <= TdsData.DEFAULT_PRECISION_38) { out.write((byte) pi.scale); } else { out.write((byte) DEFAULT_SCALE); } } } break; default: throw new IllegalStateException( "Unsupported output TDS type " + Integer.toHexString(pi.tdsType)); } out.write((byte) 0); // Locale information } /** * Write the actual TDS 5 parameter data. * * @param out the server RequestStream * @param charsetInfo the encoding character set * @param pi the parameter to output * @throws IOException * @throws SQLException */ static void writeTds5Param(RequestStream out, CharsetInfo charsetInfo, ParamInfo pi) throws IOException, SQLException { if (pi.charsetInfo == null) { pi.charsetInfo = charsetInfo; } switch (pi.tdsType) { case SYBVARCHAR: if (pi.value == null) { out.write((byte) 0); } else { byte buf[] = pi.getBytes(pi.charsetInfo.getCharset()); if (buf.length == 0) { buf = new byte[1]; buf[0] = ' '; } if (buf.length > VAR_MAX) { throw new SQLException( Messages.get("error.generic.truncmbcs"), "HY000"); } out.write((byte) buf.length); out.write(buf); } break; case SYBVARBINARY: if (pi.value == null) { out.write((byte) 0); } else { byte buf[] = pi.getBytes(pi.charsetInfo.getCharset()); if (out.getTdsVersion() < Driver.TDS70 && buf.length == 0) { // Sybase and SQL 6.5 do not allow zero length binary out.write((byte) 1); out.write((byte) 0); } else { out.write((byte) buf.length); out.write(buf); } } break; case XSYBCHAR: if (pi.value == null) { out.write((byte) 0); } else { byte buf[] = pi.getBytes(pi.charsetInfo.getCharset()); if (buf.length == 0) { buf = new byte[1]; buf[0] = ' '; } out.write(buf.length); out.write(buf); } break; case SYBLONGDATA: // // Write a three byte prefix usage unknown // out.write((byte)0); out.write((byte)0); out.write((byte)0); // // Write BLOB direct from input stream // if (pi.value instanceof InputStream) { byte buffer[] = new byte[SYB_CHUNK_SIZE]; int len = ((InputStream) pi.value).read(buffer); while (len > 0) { out.write((byte) len); out.write((byte) (len >> 8)); out.write((byte) (len >> 16)); out.write((byte) ((len >> 24) | 0x80)); // 0x80 means more to come out.write(buffer, 0, len); len = ((InputStream) pi.value).read(buffer); } } else // // Write CLOB direct from input Reader // if (pi.value instanceof Reader && !pi.charsetInfo.isWideChars()) { // For ASE 15+ the getNativeType() routine will already have // read the data from the reader so this code will not be // reached unless sendStringParametersAsUnicode=false. char buffer[] = new char[SYB_CHUNK_SIZE]; int len = ((Reader) pi.value).read(buffer); while (len > 0) { out.write((byte) len); out.write((byte) (len >> 8)); out.write((byte) (len >> 16)); out.write((byte) ((len >> 24) | 0x80)); // 0x80 means more to come out.write(Support.encodeString( pi.charsetInfo.getCharset(), new String(buffer, 0, len))); len = ((Reader) pi.value).read(buffer); } } else // // Write data from memory buffer // if (pi.value != null) { // // Actual data needs to be written out in chunks of // 8192 bytes. // if ("unitext".equals(pi.sqlType)) { // Write out String as unicode bytes String buf = pi.getString(pi.charsetInfo.getCharset()); int pos = 0; while (pos < buf.length()) { int clen = (buf.length() - pos >= SYB_CHUNK_SIZE / 2)? SYB_CHUNK_SIZE / 2: buf.length() - pos; int len = clen * 2; out.write((byte) len); out.write((byte) (len >> 8)); out.write((byte) (len >> 16)); out.write((byte) ((len >> 24) | 0x80)); // 0x80 means more to come // Write data out.write(buf.substring(pos, pos+clen).toCharArray(), 0, clen); pos += clen; } } else { // Write text as bytes byte buf[] = pi.getBytes(pi.charsetInfo.getCharset()); int pos = 0; while (pos < buf.length) { int len = (buf.length - pos >= SYB_CHUNK_SIZE) ? SYB_CHUNK_SIZE : buf.length - pos; out.write((byte) len); out.write((byte) (len >> 8)); out.write((byte) (len >> 16)); out.write((byte) ((len >> 24) | 0x80)); // 0x80 means more to come // Write data for (int i = 0; i < len; i++) { out.write(buf[pos++]); } } } } // Write terminator out.write(0); break; case SYBLONGBINARY: // Sybase data <= 16284 bytes long if (pi.value == null) { out.write(0); } else { if (pi.sqlType.startsWith("univarchar")){ String tmp = pi.getString(pi.charsetInfo.getCharset()); if (tmp.length() == 0) { tmp = " "; } out.write(tmp.length() * 2); out.write(tmp.toCharArray(), 0, tmp.length()); } else { byte buf[] = pi.getBytes(pi.charsetInfo.getCharset()); if (buf.length > 0) { out.write(buf.length); out.write(buf); } else { out.write(1); out.write((byte) 0); } } } break; case SYBINTN: if (pi.value == null) { out.write((byte) 0); } else { if ("bigint".equals(pi.sqlType)) { out.write((byte) 8); out.write(((Number) pi.value).longValue()); } else { out.write((byte) 4); out.write(((Number) pi.value).intValue()); } } break; case SYBFLTN: if (pi.value == null) { out.write((byte) 0); } else { if (pi.value instanceof Float) { out.write((byte) 4); out.write(((Number) pi.value).floatValue()); } else { out.write((byte) 8); out.write(((Number) pi.value).doubleValue()); } } break; case SYBDATETIMN: putDateTimeValue(out, (DateTime) pi.value); break; case SYBDATEN: if (pi.value == null) { out.write((byte)0); } else { out.write((byte)4); out.write(((DateTime) pi.value).getDate()); } break; case SYBTIMEN: if (pi.value == null) { out.write((byte)0); } else { out.write((byte)4); out.write(((DateTime) pi.value).getTime()); } break; case SYBBIT: if (pi.value == null) { out.write((byte) 0); } else { out.write((byte) (((Boolean) pi.value).booleanValue() ? 1 : 0)); } break; case SYBNUMERIC: case SYBDECIMAL: BigDecimal value = null; if (pi.value != null) { if (pi.value instanceof Long) { // Long to BigDecimal conversion is buggy. It's actually // long to double to BigDecimal. value = new BigDecimal(pi.value.toString()); } else { value = (BigDecimal) pi.value; } } out.write(value); break; default: throw new IllegalStateException( "Unsupported output TDS type " + Integer.toHexString(pi.tdsType)); } } /** * TDS 8 requires collation information for char data descriptors. * * @param out The Server request stream. * @param pi The parameter descriptor. * @throws IOException */ static void putCollation(RequestStream out, ParamInfo pi) throws IOException { // // For TDS 8 write a collation string // I am assuming this can be all zero for now if none is known // if (types[pi.tdsType].isCollation) { if (pi.collation != null) { out.write(pi.collation); } else { byte collation[] = {0x00, 0x00, 0x00, 0x00, 0x00}; out.write(collation); } } } /** * Write a parameter to the server request stream. * * @param out the server request stream * @param charsetInfo the default character set * @param collation the default SQL Server 2000 collation * @param pi the parameter descriptor */ static void writeParam(RequestStream out, CharsetInfo charsetInfo, byte[] collation, ParamInfo pi) throws IOException { int len; String tmp; byte[] buf; boolean isTds8 = out.getTdsVersion() >= Driver.TDS80; if (isTds8) { if (pi.collation == null) { pi.collation = collation; } } if (pi.charsetInfo == null) { pi.charsetInfo = charsetInfo; } switch (pi.tdsType) { case XSYBVARCHAR: if (pi.value == null) { out.write((byte) pi.tdsType); out.write((short) MS_LONGVAR_MAX); if (isTds8) { putCollation(out, pi); } out.write((short) 0xFFFF); } else { buf = pi.getBytes(pi.charsetInfo.getCharset()); if (buf.length > MS_LONGVAR_MAX) { out.write((byte) SYBTEXT); out.write(buf.length); if (isTds8) { putCollation(out, pi); } out.write(buf.length); out.write(buf); } else { out.write((byte) pi.tdsType); out.write((short) MS_LONGVAR_MAX); if (isTds8) { putCollation(out, pi); } out.write((short) buf.length); out.write(buf); } } break; case SYBVARCHAR: if (pi.value == null) { out.write((byte) pi.tdsType); out.write((byte) VAR_MAX); out.write((byte) 0); } else { buf = pi.getBytes(pi.charsetInfo.getCharset()); if (buf.length > VAR_MAX) { if (buf.length <= MS_LONGVAR_MAX && out.getTdsVersion() >= Driver.TDS70) { out.write((byte) XSYBVARCHAR); out.write((short) MS_LONGVAR_MAX); if (isTds8) { putCollation(out, pi); } out.write((short) buf.length); out.write(buf); } else { out.write((byte) SYBTEXT); out.write(buf.length); if (isTds8) { putCollation(out, pi); } out.write(buf.length); out.write(buf); } } else { if (buf.length == 0) { buf = new byte[1]; buf[0] = ' '; } out.write((byte) pi.tdsType); out.write((byte) VAR_MAX); out.write((byte) buf.length); out.write(buf); } } break; case XSYBNVARCHAR: out.write((byte) pi.tdsType); out.write((short) MS_LONGVAR_MAX); if (isTds8) { putCollation(out, pi); } if (pi.value == null) { out.write((short) 0xFFFF); } else { tmp = pi.getString(pi.charsetInfo.getCharset()); out.write((short) (tmp.length() * 2)); out.write(tmp); } break; case SYBTEXT: if (pi.value == null) { len = 0; } else { len = pi.length; if (len == 0 && out.getTdsVersion() < Driver.TDS70) { pi.value = " "; len = 1; } } out.write((byte) pi.tdsType); if (len > 0) { if (pi.value instanceof InputStream) { // Write output directly from stream out.write(len); if (isTds8) { putCollation(out, pi); } out.write(len); out.writeStreamBytes((InputStream) pi.value, len); } else if (pi.value instanceof Reader && !pi.charsetInfo.isWideChars()) { // Write output directly from stream with character translation out.write(len); if (isTds8) { putCollation(out, pi); } out.write(len); out.writeReaderBytes((Reader) pi.value, len); } else { buf = pi.getBytes(pi.charsetInfo.getCharset()); out.write(buf.length); if (isTds8) { putCollation(out, pi); } out.write(buf.length); out.write(buf); } } else { out.write(len); // Zero length if (isTds8) { putCollation(out, pi); } out.write(len); } break; case SYBNTEXT: if (pi.value == null) { len = 0; } else { len = pi.length; } out.write((byte)pi.tdsType); if (len > 0) { if (pi.value instanceof Reader) { out.write(len); if (isTds8) { putCollation(out, pi); } out.write(len * 2); out.writeReaderChars((Reader) pi.value, len); } else if (pi.value instanceof InputStream && !pi.charsetInfo.isWideChars()) { out.write(len); if (isTds8) { putCollation(out, pi); } out.write(len * 2); out.writeReaderChars(new InputStreamReader( (InputStream) pi.value, pi.charsetInfo.getCharset()), len); } else { tmp = pi.getString(pi.charsetInfo.getCharset()); len = tmp.length(); out.write(len); if (isTds8) { putCollation(out, pi); } out.write(len * 2); out.write(tmp); } } else { out.write(len); if (isTds8) { putCollation(out, pi); } out.write(len); } break; case XSYBVARBINARY: out.write((byte) pi.tdsType); out.write((short) MS_LONGVAR_MAX); if (pi.value == null) { out.write((short)0xFFFF); } else { buf = pi.getBytes(pi.charsetInfo.getCharset()); out.write((short) buf.length); out.write(buf); } break; case SYBVARBINARY: out.write((byte) pi.tdsType); out.write((byte) VAR_MAX); if (pi.value == null) { out.write((byte) 0); } else { buf = pi.getBytes(pi.charsetInfo.getCharset()); if (out.getTdsVersion() < Driver.TDS70 && buf.length == 0) { // Sybase and SQL 6.5 do not allow zero length binary out.write((byte) 1); out.write((byte) 0); } else { out.write((byte) buf.length); out.write(buf); } } break; case SYBIMAGE: if (pi.value == null) { len = 0; } else { len = pi.length; } out.write((byte) pi.tdsType); if (len > 0) { if (pi.value instanceof InputStream) { out.write(len); out.write(len); out.writeStreamBytes((InputStream) pi.value, len); } else { buf = pi.getBytes(pi.charsetInfo.getCharset()); out.write(buf.length); out.write(buf.length); out.write(buf); } } else { if (out.getTdsVersion() < Driver.TDS70) { // Sybase and SQL 6.5 do not allow zero length binary out.write(1); out.write(1); out.write((byte) 0); } else { out.write(len); out.write(len); } } break; case SYBINTN: out.write((byte) pi.tdsType); if (pi.value == null) { out.write(("bigint".equals(pi.sqlType))? (byte)8: (byte)4); out.write((byte) 0); } else { if ("bigint".equals(pi.sqlType)) { out.write((byte) 8); out.write((byte) 8); out.write(((Number) pi.value).longValue()); } else { out.write((byte) 4); out.write((byte) 4); out.write(((Number) pi.value).intValue()); } } break; case SYBFLTN: out.write((byte) pi.tdsType); if (pi.value instanceof Float) { out.write((byte) 4); out.write((byte) 4); out.write(((Number) pi.value).floatValue()); } else { out.write((byte) 8); if (pi.value == null) { out.write((byte) 0); } else { out.write((byte) 8); out.write(((Number) pi.value).doubleValue()); } } break; case SYBDATETIMN: out.write((byte) SYBDATETIMN); out.write((byte) 8); putDateTimeValue(out, (DateTime) pi.value); break; case SYBBIT: out.write((byte) pi.tdsType); if (pi.value == null) { out.write((byte) 0); } else { out.write((byte) (((Boolean) pi.value).booleanValue() ? 1 : 0)); } break; case SYBBITN: out.write((byte) SYBBITN); out.write((byte) 1); if (pi.value == null) { out.write((byte) 0); } else { out.write((byte) 1); out.write((byte) (((Boolean) pi.value).booleanValue() ? 1 : 0)); } break; case SYBNUMERIC: case SYBDECIMAL: out.write((byte) pi.tdsType); BigDecimal value = null; int prec = out.getMaxPrecision(); int scale; if (pi.value == null) { if (pi.jdbcType == Types.BIGINT) { scale = 0; } else { if (pi.scale >= 0 && pi.scale <= prec) { scale = pi.scale; } else { scale = DEFAULT_SCALE; } } } else { if (pi.value instanceof Long) { value = new BigDecimal(((Long) pi.value).toString()); scale = 0; } else { value = (BigDecimal) pi.value; scale = value.scale(); } } out.write(out.getMaxDecimalBytes()); out.write((byte) prec); out.write((byte) scale); out.write(value); break; case XML: len = pi.length; out.write( (byte) pi.tdsType ); out.write( (byte) 0 ); if( pi.value == null ) { out.write( (long) -1 ); } else { out.write( (long) len ); out.write( len ); if( pi.value instanceof byte[] ) { out.write( (byte[]) pi.value ); } else if( pi.value instanceof InputStream ) { byte buffer[] = new byte[1024]; while( len > 0 ) { int res = ( (InputStream) pi.value ).read( buffer ); if (res < 0) throw new java.io.IOException( Messages.get( "error.io.outofdata" ) ); // FIXME: out.write( buffer, 0, res ); len -= res; } // FIXME: JDBC defines both to be an error, but we ignore it for now // if( len < 0 ) // throw new java.io.IOException( Messages.get( "error.io.toomuchdata" ) ); // // if( ( (InputStream) pi.value ).read() >= 0 ) // throw new java.io.IOException( Messages.get( "error.io.toofewdata" ) ); } out.write( 0 ); } break; default: throw new IllegalStateException("Unsupported output TDS type " + Integer.toHexString(pi.tdsType)); } } // // ---------------------- Private methods from here ----------------------- // /** * Private constructor to prevent users creating an * actual instance of this class. */ private TdsData() { } /** * Get a DATETIME value from the server response stream. * * @param in The server response stream. * @param type The TDS data type. * @return The java.sql.Timestamp value or null. * @throws java.io.IOException */ private static Object getDatetimeValue(ResponseStream in, final int type) throws IOException, ProtocolException { int len; int daysSince1900; int time; int minutes; if (type == SYBDATETIMN) { len = in.read(); // No need to & with 0xff } else if (type == SYBDATETIME4) { len = 4; } else { len = 8; } switch (len) { case 0: return null; case 8: // A datetime is made of of two 32 bit integers // The first one is the number of days since 1900 // The second integer is the number of seconds*300 // Negative days indicate dates earlier than 1900. // The full range is 1753-01-01 to 9999-12-31. daysSince1900 = in.readInt(); time = in.readInt(); return new DateTime(daysSince1900, time); case 4: // A smalldatetime is two 16 bit integers. // The first is the number of days past January 1, 1900, // the second smallint is the number of minutes past // midnight. // The full range is 1900-01-01 to 2079-06-06. daysSince1900 = in.readShort() & 0xFFFF; minutes = in.readShort(); return new DateTime((short) daysSince1900, (short) minutes); default: throw new ProtocolException("Invalid DATETIME value with size of " + len + " bytes."); } } /** * Output a java.sql.Date/Time/Timestamp value to the server * as a Sybase datetime value. * * @param out the server request stream * @param value the date value to write */ private static void putDateTimeValue(RequestStream out, DateTime value) throws IOException { if (value == null) { out.write((byte) 0); return; } out.write((byte) 8); out.write(value.getDate()); out.write(value.getTime()); } /** * Read a MONEY value from the server response stream. * * @param in The server response stream. * @param type The TDS data type. * @return The java.math.BigDecimal value or null. * @throws IOException * @throws ProtocolException */ private static Object getMoneyValue(ResponseStream in, final int type) throws IOException, ProtocolException { final int len; if (type == SYBMONEY) { len = 8; } else if (type == SYBMONEYN) { len = in.read(); } else { len = 4; } BigInteger x = null; if (len == 4) { x = BigInteger.valueOf(in.readInt()); } else if (len == 8) { final byte b4 = (byte) in.read(); final byte b5 = (byte) in.read(); final byte b6 = (byte) in.read(); final byte b7 = (byte) in.read(); final byte b0 = (byte) in.read(); final byte b1 = (byte) in.read(); final byte b2 = (byte) in.read(); final byte b3 = (byte) in.read(); final long l = (b0 & 0xff) + ((long) (b1 & 0xff) << 8) + ((long) (b2 & 0xff) << 16) + ((long) (b3 & 0xff) << 24) + ((long) (b4 & 0xff) << 32) + ((long) (b5 & 0xff) << 40) + ((long) (b6 & 0xff) << 48) + ((long) (b7 & 0xff) << 56); x = BigInteger.valueOf(l); } else if (len != 0) { throw new ProtocolException("Invalid money value."); } return (x == null) ? null : new BigDecimal(x, 4); } /** * Read a MSQL 2000 sql_variant data value from the input stream. * <p>SQL_VARIANT has the following structure: * <ol> * <li>INT4 total size of data * <li>INT1 TDS data type (text/image/ntext/sql_variant not allowed) * <li>INT1 Length of extra type descriptor information * <li>Optional additional type info required by some types * <li>byte[0...n] the actual data * </ol> * * @param connection used to obtain collation/charset information * @param in the server response stream * @return the SQL_VARIANT data */ private static Object getVariant(JtdsConnection connection, ResponseStream in) throws IOException, ProtocolException { byte[] bytes; int len = in.readInt(); if (len == 0) { // Length of zero means item is null return null; } ColInfo ci = new ColInfo(); len -= 2; ci.tdsType = in.read(); // TDS Type len -= in.read(); // Size of descriptor switch (ci.tdsType) { case SYBINT1: return new Integer(in.read() & 0xFF); case SYBINT2: return new Integer(in.readShort()); case SYBINT4: return new Integer(in.readInt()); case SYBINT8: return new Long(in.readLong()); case XSYBCHAR: case XSYBVARCHAR: // FIXME Use collation for reading getCollation(in, ci); try { setColumnCharset(ci, connection); } catch (SQLException ex) { // Skip the buffer size and value in.skip(2 + len); throw new ProtocolException(ex.toString() + " [SQLState: " + ex.getSQLState() + ']'); } in.skip(2); // Skip buffer size return in.readNonUnicodeString(len); case XSYBNCHAR: case XSYBNVARCHAR: // XXX Why do we need collation for Unicode strings? in.skip(7); // Skip collation and buffer size return in.readUnicodeString(len / 2); case XSYBVARBINARY: case XSYBBINARY: in.skip(2); // Skip buffer size bytes = new byte[len]; in.read(bytes); return bytes; case SYBMONEY4: case SYBMONEY: return getMoneyValue(in, ci.tdsType); case SYBDATETIME4: case SYBDATETIME: return getDatetimeValue(in, ci.tdsType); case SYBBIT: return (in.read() != 0) ? Boolean.TRUE : Boolean.FALSE; case SYBREAL: return new Float(Float.intBitsToFloat(in.readInt())); case SYBFLT8: return new Double(Double.longBitsToDouble(in.readLong())); case SYBUNIQUE: bytes = new byte[len]; in.read(bytes); return new UniqueIdentifier(bytes); case SYBNUMERIC: case SYBDECIMAL: ci.precision = in.read(); ci.scale = in.read(); int sign = in.read(); len--; bytes = new byte[len]; BigInteger bi; while (len-- > 0) { bytes[len] = (byte)in.read(); } bi = new BigInteger((sign == 0) ? -1 : 1, bytes); return new BigDecimal(bi, ci.scale); default: throw new ProtocolException("Unsupported TDS data type 0x" + Integer.toHexString(ci.tdsType) + " in sql_variant"); } // // For compatibility with the MS driver convert to String. // Change the data type for sql_variant from OTHER to VARCHAR // Without this code the actual Object type can be retrieved // by using getObject(n). // // try { // value = Support.convert(value, Types.VARCHAR, in.getCharset()); // } catch (SQLException e) { // // Conversion failed just try toString(); // value = value.toString(); // } } /** * For SQL 2005 This routine will modify the meta data to allow the * caller to distinguish between varchar(max) and text or varbinary(max) * and image or nvarchar(max) and ntext. * * @param typeName the SQL type returned by sp_columns * @param tdsType the TDS type returned by sp_columns * @return the (possibly) modified SQL type name as a <code>String</code> */ public static String getMSTypeName(String typeName, int tdsType) { if (typeName.equalsIgnoreCase("text") && tdsType != SYBTEXT) { return "varchar"; } else if (typeName.equalsIgnoreCase("ntext") && tdsType != SYBTEXT) { return "nvarchar"; } else if (typeName.equalsIgnoreCase("image") && tdsType != SYBIMAGE) { return "varbinary"; } else { return typeName; } } /** * Extract the TDS protocol version from the value returned by the server in the LOGINACK * packet. * * @param rawTdsVersion the TDS protocol version as returned by the server * @return the jTDS internal value for the protocol version (i.e one of the * <code>Driver.TDS<i>XX</i></code> values) */ public static int getTdsVersion(int rawTdsVersion) { if (rawTdsVersion >= 0x71000001) { return Driver.TDS81; } else if (rawTdsVersion >= 0x07010000) { return Driver.TDS80; } else if (rawTdsVersion >= 0x07000000) { return Driver.TDS70; } else if (rawTdsVersion >= 0x05000000) { return Driver.TDS50; } else { return Driver.TDS42; } } /** * Establish if a String can be converted to a byte based character set. * * @param value The String to test. * @param charset The server character set in force. * @return <code>boolean</code> true if string can be converted. */ private static boolean canEncode(String value, String charset) { if (value == null) { return true; } if ("UTF-8".equals(charset)) { // Should be no problem with UTF-8 return true; } if ("ISO-8859-1".equals(charset)) { // ISO_1 = lower byte of unicode for (int i = value.length() - 1; i >= 0; i--) { if (value.charAt(i) > 255) { return false; // Outside range } } return true; } if ("ISO-8859-15".equals(charset) || "Cp1252".equals(charset)) { // These will accept euro symbol for (int i = value.length() - 1; i >= 0; i--) { // FIXME This is not correct! Cp1252 also contains other characters. // No: I think it is OK the point is to ensure that all characters are either // < 256 in which case the sets are the same or the euro which is convertable. // Any other combination will cause the string to be sent as unicode. char c = value.charAt(i); if (c > 255 && c != 0x20AC) { return false; // Outside range } } return true; } if ("US-ASCII".equals(charset)) { for (int i = value.length() - 1; i >= 0; i--) { if (value.charAt(i) > 127) { return false; // Outside range } } return true; } // OK need to do an expensive check try { return new String(value.getBytes(charset), charset).equals(value); } catch (UnsupportedEncodingException e) { return false; } } static boolean isMSSQL2005Plus( JtdsConnection connection ) { return connection.getServerType() == Driver.SQLSERVER && connection.getDatabaseMajorVersion() > 8; } }