1 33 34 35 36 package com.internetcds.jdbc.tds; 37 38 import java.net.Socket ; 39 import java.util.Vector ; 40 import java.lang.Thread ; 41 import java.util.StringTokenizer ; 42 import java.sql.*; 43 import com.internetcds.jdbc.tds.TdsComm; 44 import com.internetcds.util.Logger; 45 import java.math.BigInteger ; 46 import java.math.BigDecimal ; 47 import java.util.Calendar ; 48 import java.util.Properties ; 49 import java.util.TimeZone ; 50 import java.util.Locale ; 51 52 53 54 60 class TimeoutHandler extends Thread 61 { 62 public static final String cvsVersion = "$Id: Tds.java,v 1.1 2006/06/23 10:39:30 sinisa Exp $"; 63 64 java.sql.Statement stmt; 65 int timeout; 66 67 68 public TimeoutHandler( 69 java.sql.Statement stmt_, 70 int timeout_) 71 { 72 stmt = stmt_; 73 timeout = timeout_; 74 } 75 76 77 public void run() 78 { 79 try 80 { 81 sleep(timeout * 1000); 82 stmt.cancel(); 83 } 84 catch(SQLException e) 85 { 86 } 88 catch(java.lang.InterruptedException e) 89 { 90 } 92 } 93 } 94 95 96 97 105 public class Tds implements TdsDefinitions 106 { 107 public static final String cvsVersion = "$Id: Tds.java,v 1.1 2006/06/23 10:39:30 sinisa Exp $"; 108 109 110 static boolean ignoreNotImplemented = false; 118 119 120 Socket sock = null; 121 TdsComm comm = null; 122 123 String databaseProductName; 124 String databaseProductVersion; 125 126 java.sql.Connection connection; 127 String host; 128 int serverType = -1; int port; String database; 131 String user; 132 String password; 133 String appName; 134 String serverName; 135 String progName; 136 byte progMajorVersion; 137 byte progMinorVersion; 138 139 boolean haveProcNameTable = false; 140 String procNameGeneratorName = null; 141 String procNameTableName = null; 142 143 String initialSettings = null; 144 145 private Properties initialProps = null; 146 private EncodingHelper encoder = null; 147 private String charset = null; 148 149 private boolean moreResults = false; 152 private boolean moreResults2 = true; 156 157 CancelController cancelController = null; 158 159 SqlMessage lastServerMessage = null; 160 161 private int tdsVer = Tds.TDS42; 163 164 private boolean showWarnings = false; 166 167 private int zoneOffset = Calendar.getInstance().get(Calendar.ZONE_OFFSET); 169 170 public Tds( 171 java.sql.Connection connection_, 172 Properties props_, 173 String initialSettings) 174 throws java.io.IOException , java.net.UnknownHostException , 175 java.sql.SQLException , com.internetcds.jdbc.tds.TdsException 176 { 177 connection = (java.sql.Connection )connection_; 178 initialProps = props_; 179 180 host = props_.getProperty("HOST"); 181 serverType = Integer.parseInt(props_.getProperty("SERVERTYPE")); 182 port = Integer.parseInt(props_.getProperty("PORT")); 183 database = props_.getProperty("DBNAME"); 184 user = props_.getProperty("user"); 185 password = props_.getProperty("password"); 186 appName = props_.getProperty("APPNAME", "jdbclib"); 187 serverName = props_.getProperty("SERVERNAME", host); 188 progName = props_.getProperty("PROGNAME", "java_app"); 189 progMajorVersion = (byte)DriverVersion.getDriverMajorVersion(); 190 progMinorVersion = (byte)DriverVersion.getDriverMinorVersion(); 191 String verString = props_.getProperty("TDS", "7.0"); 193 194 197 198 tdsVer = TDS42; 199 if (verString.equals("5.0")) 200 { 201 tdsVer = Tds.TDS50; 202 } 203 else if (verString.equals("7.0")) 204 { 205 tdsVer = Tds.TDS70; 206 } 207 if (System.getProperty("TDS_SHOW_WARNINGS") != null 209 || 210 props_.getProperty("TDS_SHOW_WARNINGS") != null) 211 { 212 showWarnings = true; 213 } 214 215 cancelController = new CancelController(); 216 217 sock = new Socket (host, port); 219 sock.setTcpNoDelay(true); 220 comm = new TdsComm(sock, tdsVer); 221 222 setCharset(props_.getProperty("CHARSET")); 223 224 if (logon()) 225 { 226 } 228 else 229 { 230 throw new SQLException("Logon failed. " + lastServerMessage); 231 } 232 } 233 234 private void setCharset(String charset) 235 { 236 try 237 { 238 Logger.println("Trying to change charset to " + charset); 239 } 240 catch(java.io.IOException e) 241 { 242 } 244 245 if (charset == null || charset.length() > 30) 246 { 247 charset = "iso_1"; 248 } 249 250 if (!charset.equals(this.charset)) 251 { 252 encoder = EncodingHelper.getHelper(charset); 253 if (encoder == null) 254 { 255 charset = "iso_1"; 256 encoder = EncodingHelper.getHelper(charset); 257 } 258 this.charset = charset; 259 } 260 } 261 262 EncodingHelper getEncoder() { 263 return encoder; 264 } 265 266 public void close() 267 { 268 comm.close(); 269 try 270 { 271 sock.close(); 272 } 273 catch(java.io.IOException e) 274 { 275 } 277 } 278 279 static private int toUInt(byte b) 280 { 281 int result = ((int)b) & 0x00ff; 282 return result; 283 } 284 285 public String toString() 286 { 287 return "" 288 + database + ", " 289 + sock.getLocalAddress() + ":" + sock.getLocalPort() 290 + " -> " + sock.getInetAddress() + ":" + sock.getPort(); 291 } 292 293 294 301 static public String toNativeSql(String input, int serverType) 302 throws SQLException 303 { 304 EscapeProcessor escape; 305 if (serverType==TdsDefinitions.SYBASE) 306 { 307 escape = new SybaseEscapeProcessor(input); 308 } 309 else 310 { 311 escape = new MSSqlServerEscapeProcessor(input); 312 } 313 314 return escape.nativeString(); 315 } 316 317 318 328 public static byte cvtJdbcTypeToNativeType(int jdbcType) 329 throws TdsNotImplemented 330 { 331 byte result = 0; 333 switch(jdbcType) 334 { 335 case java.sql.Types.CHAR: 336 { 339 result = SYBCHAR; 340 break; 341 } 342 case java.sql.Types.VARCHAR: 345 case java.sql.Types.LONGVARCHAR: 346 { 347 result = SYBVARCHAR; 348 break; 349 } 350 case java.sql.Types.DECIMAL: 353 { 354 result = SYBFLT8; 355 break; 356 } 357 case java.sql.Types.INTEGER: 358 case java.sql.Types.SMALLINT: 359 case java.sql.Types.BIGINT: 360 { 361 result = SYBINT4; 362 break; 363 } 364 case java.sql.Types.REAL: 365 case java.sql.Types.DOUBLE: 366 { 367 result = SYBFLT8; 368 break; 369 } 370 case java.sql.Types.DATE: 371 case java.sql.Types.TIMESTAMP: 372 case java.sql.Types.TIME: 373 { 374 result = SYBDATETIMN; 375 break; 376 } 377 case java.sql.Types.VARBINARY: 378 case java.sql.Types.LONGVARBINARY: 379 { 380 result = SYBIMAGE; 381 break; 382 } 383 case java.sql.Types.BIT: 385 { 386 result = SYBBIT; 387 break; 388 } 389 default: 390 { 391 throw new TdsNotImplemented("cvtJdbcTypeToNativeType (" 392 + TdsUtil.javaSqlTypeToString(jdbcType) + ")"); 393 } 394 } 395 396 return result; 397 } 398 399 400 411 public static int cvtNativeTypeToJdbcType(int nativeType, 412 int size) 413 throws TdsException 414 { 415 416 418 int result = java.sql.Types.OTHER; 419 switch(nativeType) 420 { 421 case SYBBINARY: result = java.sql.Types.BINARY; break; 423 case SYBBIT: result = java.sql.Types.BIT; break; 424 case SYBBITN: result = java.sql.Types.BIT; break; 425 case SYBCHAR: result = java.sql.Types.CHAR; break; 426 case SYBNCHAR: result = java.sql.Types.CHAR; break; 427 case SYBDATETIME4: result = java.sql.Types.TIMESTAMP; break; 428 case SYBDATETIME: result = java.sql.Types.TIMESTAMP; break; 429 case SYBDATETIMN: result = java.sql.Types.TIMESTAMP; break; 430 case SYBDECIMAL: result = java.sql.Types.DECIMAL; break; 431 case SYBNUMERIC: result = java.sql.Types.NUMERIC; break; 432 case SYBFLT8: result = java.sql.Types.DOUBLE; break; 433 case SYBFLTN: result = java.sql.Types.DOUBLE; break; 434 case SYBINT1: result = java.sql.Types.TINYINT; break; 435 case SYBINT2: result = java.sql.Types.SMALLINT; break; 436 case SYBINT4: result = java.sql.Types.INTEGER; break; 437 case SYBINTN: 438 { 439 switch (size) 440 { 441 case 1: result = java.sql.Types.TINYINT; break; 442 case 2: result = java.sql.Types.SMALLINT; break; 443 case 4: result = java.sql.Types.INTEGER; break; 444 default: throw new TdsException("Bad size of SYBINTN"); 445 } 446 break; 447 } 448 case SYBSMALLMONEY: result = java.sql.Types.NUMERIC; break; 450 case SYBMONEY4: result = java.sql.Types.NUMERIC; break; 451 case SYBMONEY: result = java.sql.Types.NUMERIC; break; 452 case SYBMONEYN: result = java.sql.Types.NUMERIC; break; 453 case SYBREAL: result = java.sql.Types.REAL; break; 455 case SYBTEXT: result = java.sql.Types.LONGVARCHAR; break; 456 case SYBNTEXT: result = java.sql.Types.LONGVARCHAR; break; 457 case SYBIMAGE: result = java.sql.Types.VARBINARY; break; 458 case SYBVARBINARY: result = java.sql.Types.VARBINARY; break; 459 case SYBVARCHAR: result = java.sql.Types.VARCHAR; break; 460 case SYBNVARCHAR: result = java.sql.Types.VARCHAR; break; 461 default: throw new TdsException("Unknown native data type " 463 + Integer.toHexString( 464 nativeType&0xff)); 465 } 466 return result; 467 } 468 469 470 475 public int getServerType() 476 { 477 return serverType; 478 } 479 480 481 487 private String getClientName() 488 { 489 String tmp; 491 try 492 { 493 tmp = java.net.InetAddress.getLocalHost().getHostName(); 494 } 495 catch(java.net.UnknownHostException e) 496 { 497 tmp = ""; 498 } 499 StringTokenizer st = new StringTokenizer (tmp, "."); 500 501 502 503 if (!st.hasMoreTokens()) 504 { 505 return "JOHNDOE"; 507 } 508 509 tmp = st.nextToken(); 511 if (tmp.length()==0) 512 { 513 return "JANEDOE"; 516 } 517 else if (Character.isDigit(tmp.charAt(0))) 518 { 519 return "BABYDOE"; 523 } 524 else 525 { 526 return tmp.toUpperCase(); 530 } 531 } 532 533 534 608 private boolean logon() 609 throws java.sql.SQLException , 610 TdsUnknownPacketSubType, java.io.IOException , 611 com.internetcds.jdbc.tds.TdsException 612 { 613 boolean isOkay = true; 614 byte pad = (byte) 0; 615 byte[] empty = new byte[0]; 616 617 if (tdsVer == Tds.TDS70) 619 send70Login(); 620 else { 621 622 comm.startPacket(TdsComm.LOGON); 623 624 byte[] tmp = encoder.getBytes(getClientName()); 627 comm.appendBytes(tmp, 30, pad); 628 comm.appendByte((byte)(tmp.length < 30 ? tmp.length : 30)); 629 630 tmp = encoder.getBytes(user); 632 comm.appendBytes(tmp, 30, pad); 633 comm.appendByte((byte)(tmp.length < 30 ? tmp.length : 30)); 634 635 tmp = encoder.getBytes(password); 637 comm.appendBytes(tmp, 30, pad); 638 comm.appendByte((byte)(tmp.length < 30 ? tmp.length : 30)); 639 640 tmp = encoder.getBytes("00000116"); 642 comm.appendBytes(tmp, 8, pad); 643 644 comm.appendBytes(empty, (30-14), pad); 646 647 comm.appendByte((byte)0x0); 649 comm.appendByte((byte)0xA0); 650 comm.appendByte((byte)0x24); 651 comm.appendByte((byte)0xCC); 652 comm.appendByte((byte)0x50); 653 comm.appendByte((byte)0x12); 654 655 comm.appendByte((byte)8); 657 658 comm.appendByte((byte)3); 660 661 comm.appendByte((byte)1); 663 664 comm.appendByte((byte)6); 666 667 comm.appendByte((byte)10); 669 670 comm.appendByte((byte)9); 672 673 comm.appendByte((byte)1); 675 676 comm.appendByte((byte)1); 678 679 comm.appendByte((byte)0); 681 682 comm.appendByte((byte)0); 684 685 comm.appendBytes(empty, 7, pad); 687 688 tmp = encoder.getBytes(appName); 690 comm.appendBytes(tmp, 30, pad); 691 comm.appendByte((byte)(tmp.length < 30 ? tmp.length : 30)); 692 693 tmp = encoder.getBytes(serverName); 695 comm.appendBytes(tmp, 30, pad); 696 comm.appendByte((byte)(tmp.length < 30 ? tmp.length : 30)); 697 698 comm.appendBytes(empty, 2, pad); 700 tmp = encoder.getBytes(password); 701 comm.appendBytes(tmp, 253, pad); 702 comm.appendByte((byte)(tmp.length < 253 ? tmp.length+2 : 253+2)); 703 704 comm.appendByte((byte)4); 706 comm.appendByte((byte)2); 707 comm.appendByte((byte)0); 708 comm.appendByte((byte)0); 709 710 tmp = encoder.getBytes(progName); 712 comm.appendBytes(tmp, 10, pad); 713 comm.appendByte((byte)(tmp.length < 10 ? tmp.length : 10)); 714 715 comm.appendByte((byte) 6); comm.appendByte((byte) 0); comm.appendByte((byte) 0); 719 comm.appendByte((byte) 0); 720 721 comm.appendByte((byte)0); 723 724 comm.appendByte((byte)0x0D); 726 727 comm.appendByte((byte)0x11); 729 730 tmp = encoder.getBytes("us_english"); 732 comm.appendBytes(tmp, 30, pad); 733 comm.appendByte((byte)(tmp.length < 30 ? tmp.length : 30)); 734 735 comm.appendByte((byte)1); 737 738 comm.appendShort((short)0); 740 741 comm.appendBytes(empty, 8, pad); 743 744 comm.appendShort((short)0); 746 747 comm.appendByte((byte)0); 749 750 tmp = encoder.getBytes(charset); 752 comm.appendBytes(tmp, 30, pad); 753 comm.appendByte((byte)(tmp.length < 30 ? tmp.length : 30)); 754 755 comm.appendByte((byte)1); 757 758 tmp = encoder.getBytes("512"); 760 comm.appendBytes(tmp, 6, pad); 761 comm.appendByte((byte)3); 762 763 comm.appendBytes(empty, 8, pad); 765 766 moreResults2=true; } 768 769 comm.sendPacket(); 770 771 PacketResult result; 773 774 while (! ((result = processSubPacket()) instanceof PacketEndTokenResult)) 775 { 776 if (result instanceof PacketErrorResult) 777 { 778 isOkay = false; 779 } 780 } 782 783 784 if (isOkay) 785 { 786 isOkay = changeSettings(database, initialSettings); 788 } 789 790 return isOkay; 793 } 794 795 796 804 private void send70Login() throws java.io.IOException { 805 806 byte[] magic1 = {(byte)0006, (byte)0203, (byte)0362, (byte)0370, 807 (byte)0377, (byte)0000, (byte)0000, (byte)0000, 808 (byte)0000, (byte)0340, (byte)0003, (byte)0000, 809 (byte)0000, (byte)0210, (byte)0377, (byte)0377, 810 (byte)0377, (byte)0066, (byte)0004, (byte)0000, 811 (byte)0000}; 812 byte[] magic2 = {(byte)0000, (byte)0100, (byte)0063, (byte)0232, 813 (byte)0153, (byte)0120}; 814 byte[] magic3 = encoder.getBytes("NTLMSSP"); 815 String libName = "DB-Library"; 816 byte pad = (byte)0; 817 byte[] empty = new byte[0]; 818 String appName = "CDR"; 819 short len = (short)(86 + 2 * (user.length() + 820 password.length() + 821 appName.length() + 822 serverName.length() + 823 libName.length())); 824 short packSize = (short)(len + 48); 825 comm.startPacket(TdsComm.LOGON70); 826 comm.appendTdsShort(packSize); 827 comm.appendBytes(empty, 5, pad); 828 comm.appendByte((byte)0x70); 829 comm.appendBytes(empty, 7, pad); 830 comm.appendBytes(magic1, 21, pad); 831 832 short curPos = 86; 834 835 comm.appendTdsShort(curPos); 837 comm.appendTdsShort((short)0); 838 839 comm.appendTdsShort(curPos); 841 comm.appendTdsShort((short)user.length()); 842 curPos += user.length() * 2; 843 844 comm.appendTdsShort(curPos); 846 comm.appendTdsShort((short)password.length()); 847 curPos += password.length() * 2; 848 849 comm.appendTdsShort(curPos); 851 comm.appendTdsShort((short)appName.length()); 852 curPos += appName.length() * 2; 853 854 comm.appendTdsShort(curPos); 856 comm.appendTdsShort((short)serverName.length()); 857 curPos += serverName.length() * 2; 858 859 comm.appendTdsShort((short)0); 861 comm.appendTdsShort((short)0); 862 863 comm.appendTdsShort(curPos); 865 comm.appendTdsShort((short)libName.length()); 866 curPos += libName.length() * 2; 867 868 comm.appendTdsShort(curPos); 870 comm.appendTdsShort((short)0); 871 comm.appendTdsShort(curPos); 872 comm.appendTdsShort((short)0); 873 874 comm.appendBytes(magic2, 6, pad); 876 comm.appendTdsShort(len); 877 comm.appendTdsShort((short)0x30); 878 comm.appendTdsShort(packSize); 879 comm.appendTdsShort((short)0); 880 881 String scrambledPw = tds7CryptPass(password); 883 comm.appendChars(user); 884 comm.appendChars(scrambledPw); 885 comm.appendChars(appName); 886 comm.appendChars(serverName); 887 comm.appendChars(libName); 888 889 comm.appendBytes(magic3, 7, pad); 891 comm.appendByte((byte)0); 892 comm.appendByte((byte)1); 893 comm.appendBytes(empty, 3, pad); 894 comm.appendByte((byte)6); 895 comm.appendByte((byte)130); 896 comm.appendBytes(empty, 22, pad); 897 comm.appendByte((byte)48); 898 comm.appendBytes(empty, 7, pad); 899 comm.appendByte((byte)48); 900 comm.appendBytes(empty, 3, pad); 901 } 902 903 906 private static String tds7CryptPass(String pw) { 907 int xormask = 0x5A5A; 908 int len = pw.length(); 909 char[] chars = new char[len]; 910 for (int i = 0; i < len; ++i) { 911 int c = (int)(pw.charAt(i)) ^ xormask; 912 int m1 = (c >> 4) & 0x0F0F; 913 int m2 = (c << 4) & 0xF0F0; 914 chars[i] = (char)(m1 | m2); 915 } 916 return new String (chars); 917 } 918 919 925 synchronized public boolean changeSettings( 926 String database, 927 String settings) 928 throws java.sql.SQLException 929 { 930 boolean isOkay = true; 931 try 932 { 933 PacketResult result; 934 935 if (database != null) 936 { 937 isOkay = changeDB(database); 938 } 939 940 if (isOkay && (settings!=null && settings.length()>0)) 941 { 942 String query = settings; 943 comm.startPacket(TdsComm.QUERY); 944 if (tdsVer == Tds.TDS70) 945 comm.appendChars(query); 946 else 947 { 948 byte[] queryBytes = encoder.getBytes(query); 949 comm.appendBytes(queryBytes, queryBytes.length, (byte)0); 950 } 951 moreResults2=true; comm.sendPacket(); 953 954 boolean done = false; 955 while (! done) 956 { 957 result = processSubPacket(); 958 done = ( result instanceof PacketEndTokenResult ) && 959 ! ((PacketEndTokenResult)result).moreResults() ; 960 if (result instanceof PacketErrorResult) 961 { 962 isOkay = false; 963 } 964 } 966 } 967 } 968 catch (com.internetcds.jdbc.tds.TdsUnknownPacketSubType e) 969 { 970 throw new SQLException("Unknown response. " + e.getMessage()); 971 } 972 catch (java.io.IOException e) 973 { 974 throw new SQLException("Network problem. " + e.getMessage()); 975 } 976 catch (com.internetcds.jdbc.tds.TdsException e) 977 { 978 throw new SQLException(e.getMessage()); 979 } 980 return isOkay; 981 } 983 990 synchronized private boolean changeDB(String database) 991 throws java.sql.SQLException 992 { 993 boolean isOkay = true;; 994 995 try 996 { 997 PacketResult result; 998 int i; 999 1000 1003 1004 if (database.length()>32) 1006 { 1007 throw new SQLException("Name too long " + database); 1008 } 1009 1010 for(i=0; i<database.length(); i++) 1011 { 1012 char ch; 1013 ch = database.charAt(i); 1014 if (! 1015 ((ch=='_' && i!=0) 1016 || (ch >= 'a' && ch<='z') 1017 || (ch >= 'A' && ch<='Z') 1018 || (ch >='0' && ch<='9'))) 1019 { 1020 throw new SQLException("Bad database name- " 1021 + database); 1022 } 1023 } 1024 1025 String query = "use " + database; 1026 comm.startPacket(TdsComm.QUERY); 1027 if (tdsVer == Tds.TDS70) 1028 comm.appendChars(query); 1029 else 1030 { 1031 byte[] queryBytes = encoder.getBytes(query); 1032 comm.appendBytes(queryBytes, queryBytes.length, (byte)0); 1033 } 1034 moreResults2=true; comm.sendPacket(); 1036 1037 while (! ((result = processSubPacket()) 1041 instanceof PacketEndTokenResult)) 1042 { 1043 if (result instanceof PacketErrorResult) 1044 { 1045 isOkay = false; 1046 } 1047 } 1049 } 1050 catch (com.internetcds.jdbc.tds.TdsUnknownPacketSubType e) 1051 { 1052 throw new SQLException("Unknown response. " + e.getMessage()); 1053 } 1054 catch (java.io.IOException e) 1055 { 1056 throw new SQLException("Network problem. " + e.getMessage()); 1057 } 1058 catch (com.internetcds.jdbc.tds.TdsException e) 1059 { 1060 throw new SQLException(e.getMessage()); 1061 } 1062 1063 return isOkay; 1064 } 1066 1067 public void cancel() 1068 throws java.io.IOException , com.internetcds.jdbc.tds.TdsException 1069 { 1070 1073 cancelController.doCancel(comm); 1074 } 1075 1076 1077 public boolean moreResults() 1078 { 1079 return moreResults2; 1080 } 1081 1082 1083 1090 private int getSubPacketLength() 1091 throws java.io.IOException , com.internetcds.jdbc.tds.TdsException 1092 { 1093 return comm.getTdsShort(); 1094 } 1095 1096 1097 1109 private PacketMsgResult processMsg(byte packetSubType) 1110 throws java.io.IOException , com.internetcds.jdbc.tds.TdsException 1111 { 1112 SqlMessage msg = new SqlMessage(); 1113 1114 int len = getSubPacketLength(); 1115 1116 msg.number = comm.getTdsInt(); 1117 1118 msg.state = comm.getByte(); 1119 1120 msg.level = comm.getByte(); 1122 int msgLen = comm.getTdsShort(); 1123 msg.message = comm.getString(msgLen); 1124 1125 if (showWarnings && msg.message != null) { 1128 String warn = msg.message.trim(); 1129 if (warn.length() > 0) 1130 System.err.println("Server message: " + warn); 1131 } 1132 1133 int srvNameLen = comm.getByte() & 0xFF; 1134 msg.server = comm.getString(srvNameLen); 1135 1136 if (packetSubType == TDS_MSG_TOKEN || packetSubType==TDS_ERR_TOKEN) 1137 { 1138 int procNameLen = comm.getByte() & 0xFF; 1140 msg.procName = comm.getString(procNameLen); 1141 } 1142 else 1143 { 1144 throw new TdsConfused("Was expecting a msg or error token. " + 1145 "Found 0x" + 1146 Integer.toHexString(packetSubType & 0xff)); 1147 } 1148 1149 msg.line = comm.getByte(); 1150 1151 comm.getByte(); 1153 1154 lastServerMessage = msg; 1155 1156 if (packetSubType == TDS_ERR_TOKEN) 1157 { 1158 return new PacketErrorResult(packetSubType, msg); 1159 } 1160 else 1161 { 1162 return new PacketMsgResult(packetSubType, msg); 1163 } 1164 } 1165 1166 1167 1176 private PacketResult processEnvChange() 1177 throws java.io.IOException , com.internetcds.jdbc.tds.TdsException 1178 { 1179 final byte CHARSET_CHANGE = (byte)3; 1180 1181 int len = getSubPacketLength(); 1182 int type = comm.getByte(); 1183 switch (type) 1184 { 1185 case CHARSET_CHANGE: 1186 { 1187 int clen = comm.getByte()&0xFF; 1188 String charset; 1189 if (tdsVer == TDS70) 1190 { 1191 charset = comm.getString(clen); 1192 comm.skip(len-2-clen*2); 1193 } 1194 else 1195 { 1196 charset = encoder.getString(comm.getBytes(clen)); 1197 comm.skip(len-2-clen); 1198 } 1199 setCharset(charset); 1200 break; 1201 } 1202 default: 1203 { 1204 comm.skip(len-1); 1207 break; 1208 } 1209 } 1210 1211 return new PacketResult(TDS_ENV_CHG_TOKEN); 1212 } 1213 1214 1222 private PacketColumnNamesResult processColumnNames() 1223 throws java.io.IOException , com.internetcds.jdbc.tds.TdsException 1224 { 1225 Columns columns = new Columns(); 1226 1227 int totalLen = comm.getTdsShort(); 1228 1229 int bytesRead = 0; 1230 int i = 0; 1231 while (bytesRead < totalLen) 1232 { 1233 int colNameLen = comm.getByte(); 1234 String colName = encoder.getString(comm.getBytes(colNameLen)); 1235 bytesRead = bytesRead + 1 + colNameLen; 1236 i++; 1237 columns.setName(i, colName); 1238 columns.setLabel(i, colName); 1239 } 1240 1241 return new PacketColumnNamesResult(columns); 1242 } 1244 1245 1252 private PacketColumnInfoResult processColumnInfo() 1253 throws java.io.IOException , com.internetcds.jdbc.tds.TdsException 1254 { 1255 Columns columns = new Columns(); 1256 int precision; 1257 int scale; 1258 1259 int totalLen = comm.getTdsShort(); 1260 1261 1262 int bytesRead = 0; 1263 int numColumns = 0; 1264 while (bytesRead < totalLen) 1265 { 1266 scale = -1; 1267 precision = -1; 1268 1269 int sizeOfColumn = -1; 1270 1271 byte flagData[] = new byte[4]; 1272 for (int i = 0; i < 4; i++) 1273 { 1274 flagData[i] = comm.getByte (); 1275 bytesRead++; 1276 } 1277 boolean nullable = (flagData[2] & 0x01) > 0; 1278 boolean writeable = (flagData[2] & 0x08) > 0; 1279 boolean autoIncrement = (flagData[2] & 0x10) > 0; 1280 1281 1282 byte columnType = comm.getByte(); 1284 bytesRead++; 1285 1286 if (columnType == SYBTEXT 1287 || columnType == SYBIMAGE) 1288 { 1289 int i; 1290 int tmpByte; 1291 1292 comm.skip(4); 1295 bytesRead += 4; 1296 1297 int tableNameLen = comm.getTdsShort(); 1298 bytesRead += 2; 1299 String tableName = encoder.getString(comm.getBytes(tableNameLen)); 1300 bytesRead += tableNameLen; 1301 1302 sizeOfColumn = 2<<31 - 1; 1303 } 1304 else if (columnType == SYBDECIMAL 1305 || columnType == SYBNUMERIC) 1306 { 1307 int tmp; 1308 sizeOfColumn = comm.getByte(); 1309 bytesRead++; 1310 precision = comm.getByte(); bytesRead++; 1312 scale = comm.getByte(); bytesRead++; 1314 } 1315 else if (isFixedSizeColumn(columnType)) 1316 { 1317 sizeOfColumn = lookupColumnSize(columnType); 1318 } 1319 else 1320 { 1321 sizeOfColumn = ((int) comm.getByte() & 0xff); 1322 bytesRead++; 1323 } 1324 numColumns++; 1325 1326 if (scale != -1) 1327 { 1328 columns.setScale(numColumns, scale); 1329 } 1330 if (precision != -1) 1331 { 1332 columns.setPrecision(numColumns, precision); 1333 } 1334 columns.setType(numColumns, columnType); 1335 columns.setDisplaySize(numColumns, sizeOfColumn); 1336 columns.setNullable(numColumns, (nullable 1337 ? ResultSetMetaData.columnNullable 1338 : ResultSetMetaData.columnNoNulls)); 1339 columns.setAutoIncrement(numColumns, autoIncrement); 1340 columns.setReadOnly(numColumns, !writeable); 1341 } 1342 1343 int skipLen = totalLen - bytesRead; 1345 if (skipLen != 0) 1346 { 1347 throw new TdsException( 1348 "skipping " + skipLen + " bytes"); 1349 } 1350 1351 return new PacketColumnInfoResult(columns); 1352 } 1354 1355 1356 private PacketTabNameResult processTabName() 1357 throws java.io.IOException , com.internetcds.jdbc.tds.TdsException 1358 { 1359 int totalLen = comm.getTdsShort(); 1360 1361 1373 1381 comm.skip(totalLen); 1382 1383 return new PacketTabNameResult(); 1384 } 1386 1387 1388 1401 private PacketEndTokenResult processEndToken( 1402 byte packetType) 1403 throws com.internetcds.jdbc.tds.TdsException, java.io.IOException 1404 { 1405 byte status = comm.getByte(); 1406 comm.skip(3); 1407 int rowCount = comm.getTdsInt(); 1408 1409 if (packetType==TdsDefinitions.TDS_DONEINPROC) 1410 { 1411 throw new TdsException("Internal error. TDS_DONEINPROC " 1412 + " is no longer considered an end token"); 1413 } 1414 1415 PacketEndTokenResult result = new PacketEndTokenResult(packetType, 1416 status, rowCount); 1417 1418 moreResults = result.moreResults(); 1419 1420 1421 cancelController.finishQuery(result.wasCanceled(), 1425 result.moreResults()); 1426 1427 1428 return result; 1431 } 1432 1433 1434 private PacketDoneInProcResult processDoneInProc( 1435 byte packetType) 1436 throws TdsException, java.io.IOException 1437 { 1438 byte status = comm.getByte(); 1439 comm.skip(3); 1440 int rowCount = comm.getTdsInt(); 1441 PacketDoneInProcResult result = new PacketDoneInProcResult(packetType, 1442 status, 1443 rowCount); 1444 if (!result.moreResults()) 1445 { 1446 throw new TdsException("What? No more results with a DONEINPROC!"); 1447 } 1448 1449 if (result.moreResults() && peek()==TdsDefinitions.TDS_DONEINPROC) 1450 { 1451 result = (PacketDoneInProcResult)processSubPacket(); 1452 } 1453 1454 while (result.moreResults() && 1455 (peek()==TdsDefinitions.TDS_PROCID 1456 || peek()==TdsDefinitions.TDS_RET_STAT_TOKEN)) 1457 { 1458 if (peek()==TDS_PROCID) 1459 { 1460 PacketResult tmp = processSubPacket(); 1461 } 1462 else if (peek()==TDS_RET_STAT_TOKEN) 1463 { 1464 PacketRetStatResult tmp = (PacketRetStatResult)processSubPacket(); 1465 result.setRetStat(tmp.getRetStat()); 1466 } 1467 } 1468 cancelController.finishQuery(result.wasCanceled(), 1472 result.moreResults()); 1473 return result; 1474 } 1475 1476 1477 1478 1479 1489 PacketResult processSubPacket() 1490 throws TdsUnknownPacketSubType, 1491 java.io.IOException , 1492 com.internetcds.jdbc.tds.TdsException 1493 { 1494 return processSubPacket(null); 1495 } 1496 1497 1498 1509 synchronized PacketResult processSubPacket(Context context) 1510 throws TdsUnknownPacketSubType, 1511 java.io.IOException , 1512 com.internetcds.jdbc.tds.TdsException 1513 { 1514 1523 PacketResult result = null; 1524 moreResults = false; 1525 1526 byte packetSubType = comm.getByte(); 1527 Logger.println("processSubPacket: " + 1528 Integer.toHexString(packetSubType & 0xFF) + " " + 1529 "moreResults: " + moreResults()); 1530 1531 switch(packetSubType) 1532 { 1533 case TDS_ENV_CHG_TOKEN: 1534 { 1535 result = processEnvChange(); 1536 break; 1537 } 1538 case TDS_ERR_TOKEN: 1539 case TDS_MSG_TOKEN: 1540 case TDS_MSG50_TOKEN: 1541 { 1542 result = processMsg(packetSubType); 1543 break; 1544 } 1545 case TDS_TEXT_UPD_TOKEN: 1546 { 1547 int len = getSubPacketLength(); 1548 comm.skip(len); 1549 result = new PacketResult(TDS_TEXT_UPD_TOKEN); 1550 break; 1551 } 1552 case TDS_LOGIN_ACK_TOKEN: 1553 { 1554 result = processLoginAck(); 1555 break; 1556 } 1557 case TDS_RET_STAT_TOKEN: 1558 { 1559 result = processRetStat(); 1560 break; 1561 } 1562 case TDS_PROCID: 1563 { 1564 result = processProcId(); 1565 break; 1566 } 1567 case TDS_DONEINPROC: 1568 { 1569 result = processDoneInProc(packetSubType); 1570 break; 1571 } 1572 case TDS_DONEPROC: 1573 case TDS_END_TOKEN: 1574 { 1575 result = processEndToken(packetSubType); 1576 moreResults2 = ((PacketEndTokenResult)result).moreResults(); 1577 break; 1578 } 1579 case TDS_COL_NAME_TOKEN: 1580 { 1581 result = processColumnNames(); 1582 break; 1583 } 1584 case TDS_COL_INFO_TOKEN: 1585 { 1586 result = processColumnInfo(); 1587 break; 1588 } 1589 case TDS_UNKNOWN_0xA5: 1590 case TDS_UNKNOWN_0xA7: 1591 case TDS_UNKNOWN_0xA8: 1592 { 1593 comm.skip(comm.getTdsShort()); 1595 result = new PacketUnknown(packetSubType); 1596 break; 1597 } 1598 case TDS_TABNAME: 1599 { 1600 result = processTabName(); 1601 break; 1602 } 1603 case TDS_ORDER: 1604 { 1605 int len = comm.getTdsShort(); 1606 comm.skip(len); 1607 1608 result = new PacketColumnOrderResult(); 1609 break; 1610 } 1611 case TDS_CONTROL: 1612 { 1613 int len = comm.getTdsShort(); 1614 comm.skip(len); 1615 result = new PacketControlResult(); 1617 break; 1618 } 1619 case TDS_ROW_TOKEN: 1620 { 1621 result = getRow(context.getColumnInfo()); 1622 break; 1623 } 1624 case TDS7_RESULT_TOKEN: 1625 { 1626 1627 result = processTds7Result(); 1628 break; 1629 } 1630 default: 1631 { 1632 throw new TdsUnknownPacketSubType(packetSubType); 1633 } 1634 } 1635 return result; 1636 } 1637 1638 1639 1650 private int lookupColumnSize(byte nativeColumnType) 1651 throws com.internetcds.jdbc.tds.TdsException 1652 { 1653 switch(nativeColumnType) 1654 { 1655 case SYBINT1: 1656 { 1657 return 1; 1658 } 1659 case SYBINT2: 1660 { 1661 return 2; 1662 } 1663 case SYBINT4: 1664 { 1665 return 4; 1666 } 1667 case SYBREAL: 1668 { 1669 return 4; 1670 } 1671 case SYBFLT8: 1672 { 1673 return 8; 1674 } 1675 case SYBDATETIME: 1676 { 1677 return 8; 1678 } 1679 case SYBDATETIME4: 1680 { 1681 return 8; 1682 } 1683 case SYBBIT: 1684 { 1685 return 1; 1686 } 1687 case SYBMONEY: 1688 { 1689 return 8; 1690 } 1691 case SYBMONEY4: 1692 case SYBSMALLMONEY: 1693 { 1694 return 4; 1695 } 1696 default: 1697 { 1698 throw new TdsException("Not fixed size column " 1699 + nativeColumnType); 1700 } 1701 } 1702 } 1704 1705 1706 1718 private boolean isFixedSizeColumn(byte nativeColumnType) 1719 throws com.internetcds.jdbc.tds.TdsException 1720 { 1721 switch (nativeColumnType) 1722 { 1723 case SYBINT1: 1724 case SYBINT2: 1725 case SYBINT4: 1726 case SYBFLT8: 1727 case SYBDATETIME: 1728 case SYBBIT: 1729 case SYBMONEY: 1730 case SYBMONEY4: 1731 case SYBSMALLMONEY: 1732 case SYBREAL: 1733 case SYBDATETIME4: 1734 { 1735 return true; 1736 } 1737 case SYBINTN: 1738 case SYBMONEYN: 1739 case SYBVARCHAR: 1740 case SYBNVARCHAR: 1741 case SYBDATETIMN: 1742 case SYBFLTN: 1743 case SYBCHAR: 1744 case SYBNCHAR: 1745 case SYBNTEXT: 1746 case SYBIMAGE: 1747 case SYBVARBINARY: 1748 case SYBBINARY: 1749 case SYBDECIMAL: 1750 case SYBNUMERIC: 1751 case SYBBITN: 1752 { 1753 return false; 1754 } 1755 default: 1756 { 1757 throw new TdsException("Unrecognized column type 0x" 1758 + Integer.toHexString(nativeColumnType)); 1759 } 1760 } 1761 } 1762 1763 1764 private Object readFloatN(int len) 1765 throws TdsException, java.io.IOException 1766 { 1767 Object tmp; 1768 1769 switch (len) 1770 { 1771 case 8: 1772 { 1773 long l = comm.getTdsInt64(); 1774 tmp = new Double (Double.longBitsToDouble(l)); 1775 break; 1776 } 1777 case 4: 1778 { 1779 int i = comm.getTdsInt(); 1780 tmp = new Float (Float.intBitsToFloat(i)); 1781 break; 1782 } 1783 case 0: 1784 { 1785 tmp = null; 1786 break; 1787 } 1788 default: 1789 { 1790 throw new TdsNotImplemented("Don't now how to handle " 1791 + "float with size of " 1792 + len 1793 + "(0x" 1794 + Integer.toHexString(len & 0xff) 1795 + ")"); 1796 } 1797 } 1798 return tmp; 1799 } 1800 1801 1802 private Object getMoneyValue( 1803 int type) 1804 throws java.io.IOException , TdsException 1805 { 1806 int len; 1807 Object result; 1808 1809 if (type == SYBMONEYN) 1810 { 1811 len = comm.getByte(); 1812 } 1813 else 1814 { 1815 len = lookupColumnSize((byte)type); 1816 } 1817 1818 if (len == 0) 1819 { 1820 result = null; 1821 } 1822 else 1823 { 1824 BigInteger x = null; 1825 1826 if (len == 4) 1827 { 1828 x = BigInteger.valueOf(comm.getTdsInt()); 1829 } 1830 else if (len == 8) 1831 { 1832 byte b4 = comm.getByte(); 1833 byte b5 = comm.getByte(); 1834 byte b6 = comm.getByte(); 1835 byte b7 = comm.getByte(); 1836 byte b0 = comm.getByte(); 1837 byte b1 = comm.getByte(); 1838 byte b2 = comm.getByte(); 1839 byte b3 = comm.getByte(); 1840 long l = 1841 (long)(b0&0xff) + ((long)(b1&0xff)<<8) + 1842 ((long)(b2&0xff)<<16) + ((long)(b3&0xff)<<24) + 1843 ((long)(b4&0xff)<<32) + ((long)(b5&0xff)<<40) + 1844 ((long)(b6&0xff)<<48) + ((long)(b7&0xff)<<56); 1845 x = BigInteger.valueOf(l); 1846 } 1847 else 1848 { 1849 throw new TdsConfused("Don't know what to do with len of " 1850 + len); 1851 } 1852 x = x.divide(BigInteger.valueOf(100)); 1853 result = new BigDecimal (x, 2); 1854 } 1855 return result; 1856 } 1858 1859 1875 private Object getDecimalValue(int scale) 1876 throws TdsException, java.io.IOException , NumberFormatException 1877 { 1878 int len = comm.getByte() & 0xff; 1879 if (--len < 1) 1880 return null; 1881 1882 byte[] bytes = new byte[len]; 1884 int signum = comm.getByte() == 0 ? -1 : 1; 1885 while (len > 0) 1886 bytes[--len] = comm.getByte(); 1887 BigInteger bigInt = new BigInteger (signum, bytes); 1888 return new BigDecimal (bigInt, scale); 1889 } 1890 1891 1892 private Object getDatetimeValue( 1893 int type) 1894 throws java.io.IOException , TdsException 1895 { 1896 final long SECONDS_PER_DAY = 24L * 60L * 60L; 1898 final long DAYS_BETWEEN_1900_AND_1970 = 25567L; 1899 1900 int len; 1901 Object result; 1902 1903 if (type == SYBDATETIMN) 1904 { 1905 len = comm.getByte(); 1906 } 1907 else if (type == SYBDATETIME4) 1908 { 1909 len = 4; 1910 } 1911 else 1912 { 1913 len = 8; } 1915 1916 1917 switch (len) 1918 { 1919 case 0: 1920 { 1921 result = null; 1922 break; 1923 } 1924 case 8: 1925 { 1926 long tdsDays = (long)comm.getTdsInt(); 1933 long tdsTime = (long)comm.getTdsInt(); 1934 long sqlDays = tdsDays - DAYS_BETWEEN_1900_AND_1970; 1935 long seconds = sqlDays * SECONDS_PER_DAY + tdsTime / 300L; 1936 long micros = ((tdsTime % 300L) * 1000000L) / 300L; 1937 long millis = seconds * 1000L + micros / 1000L - zoneOffset; 1938 1939 if (micros % 1000L >= 500L) 1941 millis++; 1942 1943 result = new Timestamp(millis - getDstOffset(millis)); 1944 break; 1945 } 1946 case 4: 1947 { 1948 1954 long tdsDays = (long)comm.getTdsShort(); 1955 long minutes = (long)comm.getTdsShort(); 1956 long sqlDays = tdsDays - DAYS_BETWEEN_1900_AND_1970; 1957 long seconds = sqlDays * SECONDS_PER_DAY + minutes * 60L; 1958 long millis = seconds * 1000L - zoneOffset; 1959 1960 result = new Timestamp(millis - getDstOffset(millis)); 1961 break; 1962 1963 } 1964 default: 1965 { 1966 result = null; 1967 throw new TdsNotImplemented("Don't now how to handle " 1968 + "date with size of " 1969 + len); 1970 } 1971 } 1972 return result; 1973 } 1975 1976 1984 private long getDstOffset(long time) { 1985 Calendar cal = Calendar.getInstance(); 1986 cal.setTime(new java.util.Date (time)); 1987 return cal.get(Calendar.DST_OFFSET); 1988 } 1989 1990 1991 private Object getIntValue(int type) 1992 throws java.io.IOException , TdsException 1993 { 1994 Object result; 1995 int len; 1996 1997 switch (type) 1998 { 1999 case SYBINTN: 2000 { 2001 len = comm.getByte(); 2002 break; 2003 } 2004 case SYBINT4: 2005 { 2006 len = 4; 2007 break; 2008 } 2009 case SYBINT2: 2010 { 2011 len = 2; 2012 break; 2013 } 2014 case SYBINT1: 2015 { 2016 len = 1; 2017 break; 2018 } 2019 default: 2020 { 2021 result = null; 2022 throw new TdsNotImplemented( 2023 "can't handle integer of type " 2024 + Integer.toHexString(type)); 2025 } 2026 } 2027 2028 switch (len) 2029 { 2030 case 4: {result = new Long (comm.getTdsInt()); break;} 2031 case 2: {result = new Long (comm.getTdsShort()); break;} 2032 case 1: 2033 { 2034 int tmp = toUInt(comm.getByte()); result = new Long (tmp); 2036 break; 2037 } 2038 case 0: 2039 { 2040 result = null; 2041 break; 2042 } 2043 default: 2044 { 2045 result = null; 2046 throw new TdsConfused("Bad SYBINTN length of " + 2047 len); 2048 } 2049 } 2050 return result; 2051 } 2053 2054 private Object getCharValue(boolean wideChars) 2055 throws TdsException, java.io.IOException 2056 { 2057 Object result; 2058 int len = tdsVer == Tds.TDS70 ? comm.getTdsShort() : comm.getByte() & 0xFF; 2059 2060 if (len == 0 || tdsVer == Tds.TDS70 && len == 0xFFFF) 2061 { 2062 result = null; 2063 } 2064 else if (len > 0) 2065 { 2066 if (wideChars) 2067 result = comm.getString(len / 2); 2068 else 2069 result = encoder.getString(comm.getBytes(len)); 2070 2071 if (result.equals(" ")) 2072 { 2073 result = ""; 2077 } 2078 } 2079 else 2080 { 2081 throw new TdsConfused("String with length<0"); 2082 } 2083 return result; 2084 } 2086 private Object getTextValue(boolean wideChars) 2087 throws TdsException, java.io.IOException 2088 { 2089 String result; 2090 2091 byte hasValue = comm.getByte(); 2092 2093 if (hasValue==0) 2094 { 2095 result = null; 2096 } 2097 else 2098 { 2099 comm.skip(24); 2102 2103 int len = comm.getTdsInt(); 2104 2105 if (len >= 0) 2116 { 2117 if (wideChars) { 2118 result = comm.getString(len / 2); 2119 } else { 2120 result = encoder.getString(comm.getBytes(len)); 2121 } 2122 2123 if (" ".equals(result)) 2124 { 2125 result = ""; 2129 } 2130 } 2131 else 2132 { 2133 throw new TdsConfused("String with length<0"); 2134 } 2135 } 2136 return result; 2137 } 2139 private Object getImageValue() throws TdsException, java.io.IOException 2140 { 2141 byte[] result; 2142 2143 byte hasValue = comm.getByte(); 2144 2145 if (hasValue==0) 2146 { 2147 result = null; 2148 } 2149 else 2150 { 2151 comm.skip(24); 2154 2155 int len = comm.getTdsInt(); 2156 2157 if (len >= 0) 2168 { 2169 result = comm.getBytes(len); 2170 } 2171 else 2172 { 2173 throw new TdsConfused("String with length<0"); 2174 } 2175 } 2176 return result; 2177 } 2179 2186 synchronized private PacketRowResult getRow(Columns columnsInfo) 2187 throws TdsException, java.io.IOException 2188 { 2189 PacketRowResult result = null; 2190 2191 int i; 2192 2193 result = new PacketRowResult(columnsInfo.getColumnCount()); 2194 2195 for(i=1; i<=columnsInfo.getColumnCount(); i++) 2196 { 2197 Object element; 2198 int colType = columnsInfo.getType(i); 2199 2200 Logger.println("colno=" + i + 2201 " type=" + colType + 2202 " offset=" + Integer.toHexString(comm.inBufferIndex)); 2203 switch (colType) 2204 { 2205 case SYBINTN: 2206 case SYBINT1: 2207 case SYBINT2: 2208 case SYBINT4: 2209 { 2210 element = getIntValue(colType); 2211 break; 2212 } 2213 case SYBIMAGE: 2214 { 2215 element = getImageValue(); 2216 break; 2217 } 2218 case SYBTEXT: 2219 { 2220 element = getTextValue(false); 2221 break; 2222 } 2223 case SYBNTEXT: 2224 { 2225 element = getTextValue(true); 2226 break; 2227 } 2228 case SYBCHAR: 2229 case SYBVARCHAR: 2230 { 2231 element = getCharValue(false); 2232 break; 2233 } 2234 case SYBNCHAR: 2235 case SYBNVARCHAR: 2236 { 2237 element = getCharValue(true); 2238 break; 2239 } 2240 case SYBREAL: 2241 { 2242 element = readFloatN(4); 2243 break; 2244 } 2245 case SYBFLT8: 2246 { 2247 element = readFloatN(8); 2248 break; 2249 } 2250 case SYBFLTN: 2251 { 2252 int len; 2253 2254 len = comm.getByte(); 2255 2256 element = readFloatN(len); 2257 break; 2258 } 2259 case SYBSMALLMONEY: 2260 case SYBMONEY: 2261 case SYBMONEYN: 2262 { 2263 element = getMoneyValue(colType); 2264 break; 2265 } 2266 case SYBNUMERIC: 2267 case SYBDECIMAL: 2268 { 2269 element = getDecimalValue(columnsInfo.getScale(i)); 2270 break; 2271 } 2272 case SYBDATETIME4: 2273 case SYBDATETIMN: 2274 case SYBDATETIME: 2275 { 2276 element = getDatetimeValue(colType); 2277 break; 2278 } 2279 case SYBVARBINARY: 2280 case SYBBINARY: 2281 { 2282 int len = tdsVer == Tds.TDS70 2283 ? comm.getTdsShort() 2284 : (comm.getByte() & 0xff); 2285 if (tdsVer == Tds.TDS70 && len == 0xffff) 2286 element = null; 2287 else 2288 element = comm.getBytes(len); 2289 break; 2290 } 2291 case SYBBITN: 2292 case SYBBIT: 2293 { 2294 if (colType == SYBBITN && comm.getByte() == 0) 2295 element = null; 2296 else 2297 element = new Boolean ((comm.getByte()!=0) ? true : false); 2298 break; 2299 } 2300 default: 2301 { 2302 element = null; 2303 throw new TdsNotImplemented("Don't now how to handle " + 2304 "column type 0x" + 2305 Integer.toHexString(colType)); 2306 } 2307 } 2308 result.setElementAt(element, i); 2309 } 2310 2311 return result; 2312 } 2314 2315 private boolean createStoredProcedureNameTable() 2316 { 2317 boolean result = false; 2318 String sql = null; 2319 2320 try 2321 { 2322 java.sql.Statement stmt = connection.createStatement(); 2323 2324 2325 try 2329 { 2330 sql = "" 2331 + "create table " + procNameTableName 2332 + "( " 2333 + " id NUMERIC(10, 0) IDENTITY, " 2334 + " session int not null, " 2335 + " name char(29) not null " 2336 + ") "; 2337 stmt.executeUpdate(sql); 2338 } 2339 catch(java.sql.SQLException e) 2340 { 2341 } 2343 2344 try 2345 { 2346 sql = "" 2347 + "create procedure " + procNameGeneratorName + " " 2348 + "as " 2349 + "begin tran " 2350 + "insert into " + procNameTableName + " " 2351 + " (session, name) " 2352 + " values " 2353 + " (@@spid, '') " 2354 + " " 2355 + "update " + procNameTableName + " " 2356 + " set name=('" + user + ".jdbctmpsp' + " 2357 + " convert(varchar, @@IDENTITY)) " 2358 + " where id = @@IDENTITY " 2359 + " " 2360 + "select name from " + procNameTableName + " " 2361 + " where id=@@IDENTITY " 2362 + " " 2363 + "commit tran " 2364 + ""; 2365 2366 stmt.execute(sql); 2367 stmt.execute("sp_procxmode " + 2368 procNameGeneratorName + 2369 ", 'anymode' "); 2370 } 2371 catch(java.sql.SQLException e) 2372 { 2373 } 2375 2376 stmt = null; 2377 } 2378 catch(java.sql.SQLException e) 2379 { 2380 } 2382 return result; 2383 } 2384 2385 private String generateUniqueProcName() 2386 throws java.sql.SQLException 2387 { 2388 java.sql.Statement stmt = connection.createStatement(); 2389 2390 boolean wasRs; 2391 2392 wasRs = stmt.execute("exec " + procNameGeneratorName); 2393 if (!wasRs) 2394 { 2395 throw new java.sql.SQLException ( 2396 "Confused. Was expecting a result set."); 2397 } 2398 2399 java.sql.ResultSet rs; 2400 rs = stmt.getResultSet(); 2401 if (!rs.next()) 2402 { 2403 throw new java.sql.SQLException ("Couldn't get stored proc name"); 2404 } 2405 return rs.getString(1); 2406 } 2407 2408 2409 2434 public String getUniqueProcedureName() 2435 throws java.sql.SQLException 2436 { 2437 String result = null; 2438 2439 if (serverType == SYBASE) 2440 { 2441 if (null == procNameTableName) 2442 { 2443 procNameTableName = database + "." + user 2444 + ".jdbc_temp_stored_proc_names"; 2445 procNameGeneratorName = user + ".jdbc_gen_temp_sp_names"; 2446 } 2447 2448 haveProcNameTable = createStoredProcedureNameTable(); 2455 2456 result = generateUniqueProcName(); 2457 } 2458 else 2459 { 2460 result = "#jdbc#" + UniqueId.getUniqueId(); 2461 } 2462 return result; 2463 } 2465 2466 2469 synchronized public PacketResult submitProcedure(String sql, 2470 SQLWarningChain chain) 2471 throws SQLException 2472 { 2473 2474 PacketResult result = null; 2475 PacketResult tmp = null; 2476 boolean okay = true; 2477 byte tmpByte; 2478 SQLException exception = null; 2479 2480 try 2481 { 2482 executeQuery(sql, null, 0); 2483 2484 tmpByte = (byte)(comm.peek() & 0xff); 2485 2486 while (! ((tmp = processSubPacket()) 2487 instanceof PacketEndTokenResult)) 2488 { 2489 if (tmp instanceof PacketErrorResult) 2490 { 2491 result = tmp; 2492 okay = false; 2493 2495 exception = ((PacketErrorResult)tmp).getMsg().toSQLException(); 2497 } 2498 else if (tmp instanceof PacketMsgResult) 2499 { 2500 chain.addOrReturn((PacketMsgResult)tmp); 2501 } 2502 else 2503 { 2504 throw new SQLException( 2505 "Confused. Was expecting the " 2506 + "end of result, found a " 2507 + tmp.getClass().getName()); 2508 } 2509 } 2510 if (result == null) 2511 { 2512 result = tmp; 2513 } 2514 } 2515 catch(java.io.IOException e) 2516 { 2517 throw new SQLException("Network error" + e.getMessage()); 2518 } 2519 catch (com.internetcds.jdbc.tds.TdsUnknownPacketSubType e) 2520 { 2521 throw new SQLException(e.getMessage()); 2522 } 2523 catch(com.internetcds.jdbc.tds.TdsException e) 2524 { 2525 throw new SQLException(e.getMessage()); 2526 } 2527 2528 if (!okay) 2529 { 2530 throw exception; 2531 } 2532 return result; 2533 } 2534 2535 2536 2546 synchronized public void executeProcedure( 2547 String procedureName, 2548 ParameterListItem[] formalParameterList, 2549 ParameterListItem[] actualParameterList, 2550 java.sql.Statement stmt, 2551 int timeout) 2552 throws java.sql.SQLException , com.internetcds.jdbc.tds.TdsException 2553 { 2554 2555 2578 int i; 2579 2580 try 2581 { 2582 cancelController.setQueryInProgressFlag(); 2584 2585 comm.startPacket(TdsComm.PROC); 2587 2588 if (tdsVer == Tds.TDS70) { 2589 comm.appendTdsShort((short)(procedureName.length())); 2590 comm.appendChars(procedureName); 2591 } 2592 else { 2593 byte[] nameBytes = encoder.getBytes(procedureName); 2594 comm.appendByte((byte)nameBytes.length); 2595 comm.appendBytes(nameBytes, 2596 nameBytes.length, 2597 (byte)0); 2598 } 2599 comm.appendByte((byte)0); 2600 comm.appendByte((byte)0); 2601 for(i=0; i<formalParameterList.length; i++) 2603 { 2604 byte nativeType = cvtJdbcTypeToNativeType(formalParameterList[i].type); 2605 2606 comm.appendByte((byte)0); 2607 comm.appendByte((byte)0); 2608 2609 switch(nativeType) 2610 { 2611 case SYBCHAR: 2612 case SYBVARCHAR: 2613 { 2614 String val = (String )actualParameterList[i].value; 2615 int len = val != null ? val.length() : 0; 2616 int max = formalParameterList[i].maxLength; 2617 if (formalParameterList[i].formalType.startsWith("n")) { 2621 2624 if (max > 4000) { 2625 comm.appendByte(SYBNTEXT); 2626 comm.appendTdsInt(max * 2); 2627 if (val == null) 2628 comm.appendTdsInt(0xFFFFFFFF); 2629 else { 2630 comm.appendTdsInt(len * 2); 2631 comm.appendChars(val); 2632 } 2633 } 2634 else { 2635 comm.appendByte((byte)(SYBNVARCHAR | 0x80)); 2636 comm.appendTdsShort((short)(max * 2)); 2637 if (val == null) 2638 comm.appendTdsShort((short)0xFFFF); 2639 else { 2640 comm.appendTdsShort((short)(len * 2)); 2641 comm.appendChars(val); 2642 } 2643 } 2644 2645 } else { 2646 2650 if (tdsVer != TDS70 && max > 255) { 2651 comm.appendByte((byte)SYBTEXT); 2653 sendSybImage(encoder.getBytes((String )actualParameterList[i] 2654 .value)); 2655 } else { 2656 sendSybChar(((String )actualParameterList[i].value), 2658 formalParameterList[i].maxLength); 2659 } 2660 } 2661 break; 2662 2663 } 2664 2665 case SYBINT4: 2666 case SYBINTN: 2667 { 2668 if (nativeType==SYBINTN) 2669 { 2670 comm.appendByte(nativeType); 2671 comm.appendByte((byte)4); 2673 2674 if (actualParameterList[i].value == null) 2676 { 2677 comm.appendByte((byte)0); 2678 } 2680 else 2681 { 2682 comm.appendByte((byte)4); 2683 comm.appendTdsInt(((Number )(actualParameterList[i].value)).intValue()); 2684 } 2685 } 2686 else if (actualParameterList[i].value == null) 2687 { 2688 comm.appendByte(SYBINTN); 2689 comm.appendByte((byte)4); 2690 comm.appendByte((byte)0); 2691 } 2692 else 2693 { 2694 comm.appendByte(nativeType); 2695 comm.appendTdsInt(((Number )(actualParameterList[i].value)).intValue()); 2696 } 2697 break; 2698 } 2699 case SYBFLT8: 2700 { 2701 if(actualParameterList[i].value!=null) { 2704 Number n = (Number )(actualParameterList[i].value); 2705 Double d = new Double (n.doubleValue()); 2706 comm.appendByte((byte)nativeType); 2707 comm.appendFlt8(d); 2708 } 2709 else { 2712 comm.appendByte(SYBINTN); 2713 comm.appendByte((byte)4); 2714 comm.appendByte((byte)0); 2715 } 2716 break; 2717 } 2718 case SYBDATETIMN: 2719 { 2720 comm.appendByte((byte)nativeType); 2721 2722 comm.appendByte((byte)8); 2723 if (actualParameterList[i].value == null) 2724 { 2725 comm.appendByte((byte)0); 2726 } 2727 else 2728 { 2729 Timestamp value; 2730 if (actualParameterList[i].value instanceof java.sql.Timestamp ) 2731 { 2732 value = (Timestamp)actualParameterList[i].value; 2733 } 2734 else 2735 { 2736 value = new Timestamp(((java.util.Date )actualParameterList[i].value).getTime()); 2737 } 2738 2739 2740 comm.appendByte((byte)8); 2741 2742 final int secondsPerDay = 24 * 60 * 60; 2743 final int msPerDay = secondsPerDay * 1000; 2744 final int nsPerMs = 1000 * 1000; 2745 final int epochsDifference = 25567; 2748 2749 long nanoseconds = value.getNanos(); 2750 2751 2753 long ms = ((value.getTime() + (nanoseconds / nsPerMs)) 2754 + zoneOffset); 2755 ms -= getDstOffset(ms); 2756 long msIntoCurrentDay = ms % msPerDay; 2757 2758 long daysIntoUnixEpoch = (ms-msIntoCurrentDay)/msPerDay; 2759 2760 int jiffies = (int)((msIntoCurrentDay * 300) / 1000); 2761 int daysIntoSybaseEpoch = (int)daysIntoUnixEpoch 2762 + epochsDifference; 2763 2764 comm.appendTdsInt(daysIntoSybaseEpoch); 2765 comm.appendTdsInt(jiffies); 2766 } 2767 break; 2768 } 2769 case SYBIMAGE: 2770 { 2771 comm.appendByte((byte)nativeType); 2772 2773 sendSybImage((byte[])actualParameterList[i].value); 2774 break; 2775 } 2776 case SYBTEXT: 2777 { 2778 comm.appendByte((byte)SYBTEXT); 2779 sendSybImage(encoder.getBytes((String )actualParameterList[i]. 2780 value)); 2781 break; 2782 } 2783 2786 case SYBBIT: 2787 { 2788 if (actualParameterList[i].value == null) 2789 { 2790 comm.appendByte(SYBINTN); 2791 comm.appendByte((byte)1); 2792 comm.appendByte((byte)0); 2793 } 2794 else 2795 { 2796 comm.appendByte(nativeType); 2797 if(((Byte )(actualParameterList[i].value)).intValue()==1) 2798 comm.appendByte((byte)1); 2799 else 2800 comm.appendByte((byte)0); 2801 } 2802 break; 2803 2804 2809 2810 } 2811 case SYBVOID: 2812 case SYBVARBINARY: 2813 case SYBBINARY: 2816 case SYBINT1: 2817 case SYBINT2: 2819 case SYBDATETIME4: 2820 case SYBREAL: 2821 case SYBMONEY: 2822 case SYBDATETIME: 2823 case SYBDECIMAL: 2824 case SYBNUMERIC: 2825 case SYBFLTN: 2826 case SYBMONEYN: 2827 case SYBMONEY4: 2828 default: 2829 { 2830 throw new SQLException("Not implemented for nativeType 0x" 2831 + Integer.toHexString(nativeType)); 2832 } 2833 } 2834 } 2835 comm.sendPacket(); 2838 waitForDataOrTimeout(stmt, timeout); 2839 } 2840 catch(java.io.IOException e) 2841 { 2842 throw new SQLException("Network error- " + e.getMessage()); 2843 } 2844 } 2845 2846 private void sendSybImage( 2847 byte[] value) 2848 throws java.io.IOException 2849 { 2850 int i; 2851 int length = (value==null ? 0 : value.length); 2852 2853 comm.appendTdsInt(length); 2855 2856 comm.appendTdsInt(length); 2858 2859 for(i=0; i<length; i++) 2861 { 2862 comm.appendByte(value[i]); 2863 } 2864 } 2865 2866 private void sendSybChar( 2867 String value, 2868 int maxLength) 2869 throws java.io.IOException 2870 { 2871 2872 byte[] converted; 2873 if (value == null) { 2874 converted = new byte[0]; 2875 } else { 2876 converted = encoder.getBytes(value); 2877 } 2878 2879 if (converted.length > 255 && tdsVer != TDS70) 2880 { 2881 throw new java.io.IOException ("String too long"); 2882 } 2883 2884 if (converted.length > 256) { 2888 comm.appendByte((byte) (SYBVARCHAR | 0x80)); 2889 comm.appendTdsShort((short)(maxLength)); 2890 comm.appendTdsShort((short)(converted.length)); 2891 } else { 2892 comm.appendByte(SYBVARCHAR); 2893 comm.appendByte((byte)(maxLength)); 2894 comm.appendByte((byte)(converted.length)); 2895 } 2896 2897 comm.appendBytes(converted); 2898 } 2899 2900 2903 private PacketResult processLoginAck() 2904 throws com.internetcds.jdbc.tds.TdsException, java.io.IOException 2905 { 2906 int len = getSubPacketLength(); 2907 int bytesRead = 0; 2908 2909 if (tdsVer == Tds.TDS70) 2910 { 2911 comm.skip(5); 2912 int nameLen = comm.getByte(); 2913 databaseProductName = comm.getString(nameLen); 2914 databaseProductVersion = ("" + comm.getByte() + "." 2915 + comm.getByte() + "." 2916 + ((256*comm.getByte())+ comm.getByte())); 2917 } 2918 else 2919 { 2920 comm.skip(5); 2921 short nameLen = comm.getByte(); 2922 databaseProductName = comm.getString(nameLen); 2923 comm.skip(1); 2924 databaseProductVersion = ("" + comm.getByte() + "." + comm.getByte()); 2925 comm.skip(1); 2926 } 2927 2928 if (databaseProductName.length()>1 2929 && -1 != databaseProductName.indexOf('\0')) 2930 { 2931 int last = databaseProductName.indexOf('\0'); 2932 databaseProductName = databaseProductName.substring(0, last); 2933 } 2934 2935 return new PacketResult(TDS_LOGIN_ACK_TOKEN); 2936 } 2937 2938 2939 2950 private PacketResult processProcId() 2951 throws java.io.IOException , com.internetcds.jdbc.tds.TdsException 2952 { 2953 int i; 2955 byte tmp; 2956 2957 for(i=0; i<8; i++) 2958 { 2959 tmp = comm.getByte(); 2960 } 2961 return new PacketResult(TDS_PROCID); 2962 } 2963 2974 private PacketRetStatResult processRetStat() 2975 throws java.io.IOException , com.internetcds.jdbc.tds.TdsException 2976 { 2977 return new PacketRetStatResult(comm.getTdsInt()); 2979 } 2980 2981 2987 private PacketResult processTds7Result() 2988 throws java.io.IOException , com.internetcds.jdbc.tds.TdsException 2989 { 2990 int numColumns = comm.getTdsShort(); 2991 Columns columns = new Columns(); 2992 2993 for (int colNum = 1; colNum <= numColumns; ++colNum) { 3000 3001 3007 byte flagData[] = new byte[4]; 3008 for (int i = 0; i < 4; i++) 3009 flagData[i] = comm.getByte(); 3010 boolean nullable = (flagData[2] & 0x01) > 0; 3011 boolean writeable = (flagData[2] & 0x08) > 0; 3012 boolean autoIncrement = (flagData[2] & 0x10) > 0; 3013 3014 3019 int columnType = comm.getByte() & 0xFF; 3020 if (columnType == 0xEF) 3021 columnType = SYBNCHAR; 3022 int xColType = -1; 3023 if (isLargeType(columnType)) { 3024 xColType = columnType; 3025 if (columnType != SYBNCHAR) 3026 columnType -= 128; 3027 } 3028 int colSize; 3030 if (isBlobType(columnType)) { 3031 3032 colSize = comm.getTdsInt(); 3034 3035 comm.getString(comm.getTdsShort()); 3037 } 3038 3039 else if (isFixedSizeColumn((byte)columnType)) 3041 colSize = lookupColumnSize((byte)columnType); 3042 3043 else if (isLargeType(xColType)) 3044 colSize = comm.getTdsShort(); 3045 3046 else 3047 colSize = comm.getByte(); 3048 3049 int precision = -1; 3051 int scale = -1; 3052 if (columnType == SYBDECIMAL || columnType == SYBNUMERIC) { 3053 precision = comm.getByte(); 3054 scale = comm.getByte(); 3055 } 3056 3057 3061 int colNameLen = comm.getByte(); 3062 String columnName = comm.getString(colNameLen); 3063 3064 columns.setType(colNum, columnType); 3066 columns.setName(colNum, columnName); 3067 columns.setLabel(colNum, columnName); 3068 columns.setDisplaySize(colNum, colSize); 3069 columns.setNullable(colNum, (nullable 3070 ? ResultSetMetaData.columnNullable 3071 : ResultSetMetaData.columnNoNulls)); 3072 columns.setAutoIncrement(colNum, autoIncrement); 3073 columns.setReadOnly(colNum, !writeable); 3074 if (precision != -1) 3075 columns.setPrecision(colNum, precision); 3076 if (scale != -1) 3077 columns.setScale(colNum, scale); 3078 } 3079 return new PacketColumnNamesResult(columns); 3080 3081 } 3083 3088 private static boolean isBlobType(int type) { 3089 return type == SYBTEXT || type == SYBIMAGE || type == SYBNTEXT; 3090 } 3091 3092 3095 private static boolean isLargeType(int type) { 3096 return type == SYBNCHAR || type > 128; 3097 } 3098 3099 private void waitForDataOrTimeout( 3100 java.sql.Statement stmt, 3101 int timeout) 3102 throws java.io.IOException , com.internetcds.jdbc.tds.TdsException 3103 { 3104 3105 if (timeout==0 || stmt==null) 3107 { 3108 comm.peek(); 3109 } 3110 else 3111 { 3112 TimeoutHandler t = new TimeoutHandler(stmt, timeout); 3114 3115 3116 t.start(); 3117 3118 comm.peek(); 3120 3121 t.stop(); 3123 t = null; 3124 } 3125 } 3126 3127 3128 3139 synchronized public void executeQuery( 3140 String sql, 3141 java.sql.Statement stmt, 3142 int timeout) 3143 throws java.io.IOException , java.sql.SQLException , TdsException 3144 { 3145 { 3146 cancelController.setQueryInProgressFlag(); 3147 comm.startPacket(TdsComm.QUERY); 3148 3149 if (tdsVer == Tds.TDS70) 3150 { 3151 comm.appendChars(sql); 3152 } 3153 else 3154 { 3155 byte[] sqlBytes = encoder.getBytes(sql); 3156 comm.appendBytes(sqlBytes, sqlBytes.length, (byte)0); 3157 } 3158 moreResults2=true; comm.sendPacket(); 3160 3161 waitForDataOrTimeout(stmt, timeout); 3162 } 3163 } 3164 3165 3166 3172 synchronized public void discardResultSet(Columns columnsInfo) 3173 throws java.io.IOException , com.internetcds.jdbc.tds.TdsException 3174 { 3175 while(isResultRow()) 3176 { 3177 if (columnsInfo == null) 3178 { 3179 throw new com.internetcds.jdbc.tds.TdsConfused(); 3180 } 3181 comm.skip(1); 3182 getRow(columnsInfo); 3183 } 3184 3185 if(comm.peek() == TDS_DONEINPROC) 3186 { 3187 PacketResult tmp = processSubPacket(); 3188 } 3189 3190 if (isEndOfResults()) 3195 { 3196 processSubPacket(); 3197 } 3198 3199 else if (!isResultSet()) 3201 { 3202 throw new TdsConfused("Was expecting an end of results token. " 3203 + "Found a 0x" 3204 + Integer.toHexString(comm.peek() & 0xff)); 3205 } 3206 } 3207 3208 3209 synchronized public byte peek() 3210 throws java.io.IOException , com.internetcds.jdbc.tds.TdsException 3211 { 3212 return comm.peek(); 3213 } 3215 3216 3226 synchronized public boolean isResultSet() 3227 throws com.internetcds.jdbc.tds.TdsException, java.io.IOException 3228 { 3229 byte type = comm.peek(); 3230 3231 3235 return type==TDS_COL_NAME_TOKEN || type == TDS7_RESULT_TOKEN; 3236 } 3237 3238 3248 synchronized public boolean isRetStat() 3249 throws com.internetcds.jdbc.tds.TdsException, java.io.IOException 3250 { 3251 byte type = comm.peek(); 3252 3253 return type==TDS_RET_STAT_TOKEN; 3254 } 3255 3256 3266 synchronized public boolean isResultRow() 3267 throws com.internetcds.jdbc.tds.TdsException, java.io.IOException 3268 { 3269 byte type = comm.peek(); 3270 3271 return type==TDS_ROW_TOKEN; 3272 } 3273 3274 3275 3286 synchronized public boolean isEndOfResults() 3287 throws com.internetcds.jdbc.tds.TdsException, java.io.IOException 3288 { 3289 byte type = comm.peek(); 3290 3291 return type==TDS_END_TOKEN || type==TDS_DONEPROC; 3292 } 3293 3294 3304 synchronized public boolean isDoneInProc() 3305 throws com.internetcds.jdbc.tds.TdsException, java.io.IOException 3306 { 3307 byte type = comm.peek(); 3308 3309 return type==TDS_DONEINPROC; 3310 } 3311 3312 3322 synchronized public boolean isMessagePacket() 3323 throws com.internetcds.jdbc.tds.TdsException, java.io.IOException 3324 { 3325 byte type = comm.peek(); 3326 return type==TDS_MSG_TOKEN; 3327 } 3328 3329 3339 synchronized public boolean isTextUpdate() 3340 throws com.internetcds.jdbc.tds.TdsException, java.io.IOException 3341 { 3342 byte type = comm.peek(); 3343 return type==TDS_TEXT_UPD_TOKEN; 3344 } 3345 3346 3356 synchronized public boolean isErrorPacket() 3357 throws com.internetcds.jdbc.tds.TdsException, java.io.IOException 3358 { 3359 byte type = comm.peek(); 3360 return type==TDS_ERR_TOKEN; 3361 } 3362 3373 synchronized public boolean isReturnStatus() 3374 throws com.internetcds.jdbc.tds.TdsException, java.io.IOException 3375 { 3376 byte type = comm.peek(); 3377 return type==TDS_RET_STAT_TOKEN; 3378 } 3379 3380 3391 synchronized public boolean isProcId() 3392 throws com.internetcds.jdbc.tds.TdsException, java.io.IOException 3393 { 3394 3395 byte type = comm.peek(); 3396 return type==TDS_PROCID; 3397 } 3398 3399 3404 int getTdsVer() { return tdsVer; } 3405 3406 3409 String getDatabaseProductName() 3410 { 3411 return databaseProductName; 3412 } 3413 3414 3417 String getDatabaseProductVersion() 3418 { 3419 return databaseProductVersion; 3420 } 3421} 3422 | Popular Tags |